aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib')
-rw-r--r--host/lib/CMakeLists.txt39
-rw-r--r--host/lib/build_info.cpp113
-rw-r--r--host/lib/convert/convert_item32.cpp3
-rw-r--r--host/lib/convert/gen_convert_general.py128
-rw-r--r--host/lib/device.cpp2
-rw-r--r--host/lib/device3.cpp75
-rw-r--r--host/lib/exception.cpp1
-rw-r--r--host/lib/experts/CMakeLists.txt34
-rw-r--r--host/lib/experts/expert_container.cpp531
-rw-r--r--host/lib/experts/expert_container.hpp202
-rw-r--r--host/lib/experts/expert_factory.cpp27
-rw-r--r--host/lib/experts/expert_factory.hpp337
-rw-r--r--host/lib/experts/expert_nodes.hpp475
-rw-r--r--host/lib/ic_reg_maps/CMakeLists.txt5
-rwxr-xr-xhost/lib/ic_reg_maps/gen_adf5355_regs.py166
-rw-r--r--host/lib/property_tree.cpp2
-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.cpp720
-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.cpp368
-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/CMakeLists.txt19
-rw-r--r--host/lib/transport/libusb1_base.cpp62
-rw-r--r--host/lib/transport/libusb1_base.hpp12
-rw-r--r--host/lib/transport/libusb1_control.cpp24
-rw-r--r--host/lib/transport/libusb1_zero_copy.cpp47
-rw-r--r--host/lib/transport/muxed_zero_copy_if.cpp250
-rw-r--r--host/lib/transport/nirio/lvbitx/CMakeLists.txt5
-rw-r--r--host/lib/transport/nirio/rpc/rpc_client.cpp24
-rw-r--r--host/lib/transport/super_recv_packet_handler.hpp127
-rw-r--r--host/lib/transport/super_send_packet_handler.hpp65
-rw-r--r--host/lib/transport/usb_dummy_impl.cpp12
-rw-r--r--host/lib/transport/zero_copy_recv_offload.cpp158
-rw-r--r--host/lib/types/device_addr.cpp8
-rw-r--r--host/lib/types/sensors.cpp15
-rw-r--r--host/lib/types/serial.cpp3
-rw-r--r--host/lib/uhd.rc.in4
-rw-r--r--host/lib/usrp/CMakeLists.txt7
-rw-r--r--host/lib/usrp/b100/CMakeLists.txt2
-rw-r--r--host/lib/usrp/b100/b100_impl.cpp99
-rw-r--r--host/lib/usrp/b100/b100_impl.hpp1
-rw-r--r--host/lib/usrp/b100/dboard_iface.cpp66
-rw-r--r--host/lib/usrp/b200/CMakeLists.txt3
-rw-r--r--host/lib/usrp/b200/b200_iface.cpp4
-rw-r--r--host/lib/usrp/b200/b200_impl.cpp152
-rw-r--r--host/lib/usrp/b200/b200_impl.hpp24
-rw-r--r--host/lib/usrp/b200/b200_io_impl.cpp3
-rw-r--r--host/lib/usrp/b200/b200_radio_ctrl_core.cpp347
-rw-r--r--host/lib/usrp/b200/b200_radio_ctrl_core.hpp64
-rw-r--r--host/lib/usrp/common/CMakeLists.txt4
-rw-r--r--host/lib/usrp/common/ad9361_ctrl.cpp54
-rw-r--r--host/lib/usrp/common/ad9361_ctrl.hpp14
-rw-r--r--host/lib/usrp/common/ad9361_driver/ad9361_device.cpp15
-rw-r--r--host/lib/usrp/common/ad9361_driver/ad9361_device.h4
-rw-r--r--host/lib/usrp/common/ad936x_manager.cpp40
-rw-r--r--host/lib/usrp/common/ad936x_manager.hpp5
-rw-r--r--host/lib/usrp/common/adf435x.cpp34
-rw-r--r--host/lib/usrp/common/adf435x.hpp364
-rw-r--r--host/lib/usrp/common/adf435x_common.cpp161
-rw-r--r--host/lib/usrp/common/adf435x_common.hpp63
-rw-r--r--host/lib/usrp/common/adf5355.cpp376
-rw-r--r--host/lib/usrp/common/adf5355.hpp57
-rw-r--r--host/lib/usrp/common/apply_corrections.cpp51
-rw-r--r--host/lib/usrp/common/apply_corrections.hpp16
-rw-r--r--host/lib/usrp/common/constrained_device_args.hpp283
-rw-r--r--host/lib/usrp/common/fw_comm_protocol.h102
-rw-r--r--host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp246
-rw-r--r--host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp72
-rw-r--r--host/lib/usrp/cores/CMakeLists.txt5
-rw-r--r--host/lib/usrp/cores/dma_fifo_core_3000.cpp401
-rw-r--r--host/lib/usrp/cores/dma_fifo_core_3000.hpp86
-rw-r--r--host/lib/usrp/cores/dsp_core_utils.cpp66
-rw-r--r--host/lib/usrp/cores/dsp_core_utils.hpp33
-rw-r--r--host/lib/usrp/cores/gpio_atr_3000.cpp341
-rw-r--r--host/lib/usrp/cores/gpio_atr_3000.hpp183
-rw-r--r--host/lib/usrp/cores/gpio_core_200.cpp83
-rw-r--r--host/lib/usrp/cores/gpio_core_200.hpp49
-rw-r--r--host/lib/usrp/cores/rx_dsp_core_200.cpp41
-rw-r--r--host/lib/usrp/cores/rx_dsp_core_3000.cpp101
-rw-r--r--host/lib/usrp/cores/rx_dsp_core_3000.hpp3
-rw-r--r--host/lib/usrp/cores/rx_frontend_core_200.cpp6
-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.cpp17
-rw-r--r--host/lib/usrp/cores/spi_core_3000.cpp45
-rw-r--r--host/lib/usrp/cores/spi_core_3000.hpp7
-rw-r--r--host/lib/usrp/cores/tx_dsp_core_200.cpp41
-rw-r--r--host/lib/usrp/cores/tx_dsp_core_3000.cpp49
-rw-r--r--host/lib/usrp/cores/tx_frontend_core_200.cpp4
-rw-r--r--host/lib/usrp/cores/tx_vita_core_3000.cpp59
-rw-r--r--host/lib/usrp/cores/tx_vita_core_3000.hpp14
-rw-r--r--host/lib/usrp/cores/user_settings_core_3000.cpp85
-rw-r--r--host/lib/usrp/cores/user_settings_core_3000.hpp35
-rw-r--r--host/lib/usrp/dboard/CMakeLists.txt4
-rw-r--r--host/lib/usrp/dboard/db_basic_and_lf.cpp8
-rw-r--r--host/lib/usrp/dboard/db_dbsrx.cpp10
-rw-r--r--host/lib/usrp/dboard/db_dbsrx2.cpp10
-rw-r--r--host/lib/usrp/dboard/db_e3x0.cpp4
-rw-r--r--host/lib/usrp/dboard/db_rfx.cpp50
-rw-r--r--host/lib/usrp/dboard/db_sbx_common.cpp78
-rw-r--r--host/lib/usrp/dboard/db_sbx_common.hpp25
-rw-r--r--host/lib/usrp/dboard/db_sbx_version3.cpp111
-rw-r--r--host/lib/usrp/dboard/db_sbx_version4.cpp122
-rw-r--r--host/lib/usrp/dboard/db_tvrx.cpp6
-rw-r--r--host/lib/usrp/dboard/db_tvrx2.cpp16
-rw-r--r--host/lib/usrp/dboard/db_twinrx.cpp337
-rw-r--r--host/lib/usrp/dboard/db_ubx.cpp175
-rw-r--r--host/lib/usrp/dboard/db_wbx_common.cpp14
-rw-r--r--host/lib/usrp/dboard/db_wbx_common.hpp20
-rw-r--r--host/lib/usrp/dboard/db_wbx_simple.cpp42
-rw-r--r--host/lib/usrp/dboard/db_wbx_version2.cpp138
-rw-r--r--host/lib/usrp/dboard/db_wbx_version3.cpp140
-rw-r--r--host/lib/usrp/dboard/db_wbx_version4.cpp140
-rw-r--r--host/lib/usrp/dboard/db_xcvr2450.cpp46
-rw-r--r--host/lib/usrp/dboard/twinrx/table_to_cpp.py26
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp643
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp101
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_experts.cpp637
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_experts.hpp630
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp860
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp83
-rw-r--r--host/lib/usrp/dboard/twinrx/twinrx_io.hpp523
-rw-r--r--host/lib/usrp/dboard_base.cpp2
-rw-r--r--host/lib/usrp/dboard_ctor_args.hpp8
-rw-r--r--host/lib/usrp/dboard_iface.cpp80
-rw-r--r--host/lib/usrp/dboard_manager.cpp224
-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/e100/CMakeLists.txt2
-rw-r--r--host/lib/usrp/e100/dboard_iface.cpp66
-rw-r--r--host/lib/usrp/e100/e100_impl.cpp99
-rw-r--r--host/lib/usrp/e100/e100_impl.hpp1
-rw-r--r--host/lib/usrp/e300/CMakeLists.txt2
-rw-r--r--host/lib/usrp/e300/e300_fpga_defs.hpp2
-rw-r--r--host/lib/usrp/e300/e300_impl.cpp121
-rw-r--r--host/lib/usrp/e300/e300_impl.hpp12
-rw-r--r--host/lib/usrp/e300/e300_io_impl.cpp7
-rw-r--r--host/lib/usrp/e300/e300_regs.hpp2
-rw-r--r--host/lib/usrp/e300/e300_remote_codec_ctrl.cpp3
-rw-r--r--host/lib/usrp/fe_connection.cpp67
-rw-r--r--host/lib/usrp/multi_usrp.cpp237
-rw-r--r--host/lib/usrp/n230/CMakeLists.txt37
-rw-r--r--host/lib/usrp/n230/n230_clk_pps_ctrl.cpp158
-rw-r--r--host/lib/usrp/n230/n230_clk_pps_ctrl.hpp89
-rw-r--r--host/lib/usrp/n230/n230_cores.cpp91
-rw-r--r--host/lib/usrp/n230/n230_cores.hpp71
-rw-r--r--host/lib/usrp/n230/n230_defaults.h65
-rw-r--r--host/lib/usrp/n230/n230_device_args.hpp128
-rw-r--r--host/lib/usrp/n230/n230_eeprom.h124
-rw-r--r--host/lib/usrp/n230/n230_eeprom_manager.cpp207
-rw-r--r--host/lib/usrp/n230/n230_eeprom_manager.hpp58
-rw-r--r--host/lib/usrp/n230/n230_fpga_defs.h207
-rw-r--r--host/lib/usrp/n230/n230_frontend_ctrl.cpp243
-rw-r--r--host/lib/usrp/n230/n230_frontend_ctrl.hpp76
-rw-r--r--host/lib/usrp/n230/n230_fw_defs.h137
-rw-r--r--host/lib/usrp/n230/n230_fw_host_iface.h128
-rw-r--r--host/lib/usrp/n230/n230_image_loader.cpp209
-rw-r--r--host/lib/usrp/n230/n230_impl.cpp591
-rw-r--r--host/lib/usrp/n230/n230_impl.hpp81
-rw-r--r--host/lib/usrp/n230/n230_resource_manager.cpp569
-rw-r--r--host/lib/usrp/n230/n230_resource_manager.hpp318
-rw-r--r--host/lib/usrp/n230/n230_stream_manager.cpp562
-rw-r--r--host/lib/usrp/n230/n230_stream_manager.hpp151
-rw-r--r--host/lib/usrp/n230/n230_uart.cpp131
-rw-r--r--host/lib/usrp/n230/n230_uart.hpp38
-rw-r--r--host/lib/usrp/usrp1/CMakeLists.txt2
-rw-r--r--host/lib/usrp/usrp1/dboard_iface.cpp132
-rw-r--r--host/lib/usrp/usrp1/usrp1_impl.cpp57
-rw-r--r--host/lib/usrp/usrp1/usrp1_impl.hpp1
-rw-r--r--host/lib/usrp/usrp2/CMakeLists.txt2
-rw-r--r--host/lib/usrp/usrp2/dboard_iface.cpp81
-rw-r--r--host/lib/usrp/usrp2/usrp2_impl.cpp93
-rw-r--r--host/lib/usrp/usrp2/usrp2_impl.hpp1
-rw-r--r--host/lib/usrp/usrp_c.cpp91
-rw-r--r--host/lib/usrp/x300/CMakeLists.txt4
-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.cpp184
-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_fw_ctrl.cpp35
-rw-r--r--host/lib/usrp/x300/x300_impl.cpp941
-rw-r--r--host/lib/usrp/x300/x300_impl.hpp287
-rw-r--r--host/lib/usrp/x300/x300_io_impl.cpp605
-rw-r--r--host/lib/usrp/x300/x300_radio_ctrl_impl.cpp894
-rw-r--r--host/lib/usrp/x300/x300_radio_ctrl_impl.hpp198
-rw-r--r--host/lib/usrp/x300/x300_regs.hpp116
-rw-r--r--host/lib/usrp_clock/octoclock/CMakeLists.txt2
-rw-r--r--host/lib/usrp_clock/octoclock/octoclock_impl.cpp14
-rw-r--r--host/lib/utils/log.cpp14
-rw-r--r--host/lib/utils/msg.cpp2
-rw-r--r--host/lib/utils/paths.cpp1
233 files changed, 27509 insertions, 3561 deletions
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt
index f74af1f29..0cd89953c 100644
--- a/host/lib/CMakeLists.txt
+++ b/host/lib/CMakeLists.txt
@@ -65,15 +65,47 @@ MACRO(INCLUDE_SUBDIRECTORY subdir)
ENDMACRO(INCLUDE_SUBDIRECTORY)
########################################################################
+# Register lower level components
+########################################################################
+MESSAGE(STATUS "")
+# Dependencies
+FIND_PACKAGE(USB1)
+FIND_PACKAGE(GPSD)
+LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF)
+# Devices
+LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("N230" ENABLE_N230 ON "ENABLE_LIBUHD" OFF OFF)
+LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)
+
+########################################################################
# Include subdirectories (different than add)
########################################################################
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
+########################################################################
+INCLUDE(UHDBuildInfo)
+UHD_LOAD_BUILD_INFO()
+CONFIGURE_FILE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/build_info.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp
+@ONLY)
########################################################################
# Setup UHD_VERSION_STRING for version.cpp
@@ -87,8 +119,10 @@ CONFIGURE_FILE(
# Append to the list of sources for lib uhd
########################################################################
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
@@ -106,6 +140,7 @@ ENDIF(ENABLE_C_API)
# Add DLL resource file to Windows build
########################################################################
IF(MSVC)
+ MATH(EXPR TRIMMED_VERSION_MAJOR_API "${TRIMMED_VERSION_MAJOR} * 1000 + ${TRIMMED_VERSION_API}")
SET(RC_TRIMMED_VERSION_PATCH ${TRIMMED_VERSION_PATCH})
IF(UHD_VERSION_DEVEL)
SET(RC_TRIMMED_VERSION_PATCH "999")
@@ -142,7 +177,7 @@ TARGET_LINK_LIBRARIES(uhd ${Boost_LIBRARIES} ${libuhd_libs})
SET_TARGET_PROPERTIES(uhd PROPERTIES DEFINE_SYMBOL "UHD_DLL_EXPORTS")
IF(NOT LIBUHDDEV_PKG)
SET_TARGET_PROPERTIES(uhd PROPERTIES SOVERSION "${UHD_VERSION_MAJOR}")
- SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_MINOR}")
+ SET_TARGET_PROPERTIES(uhd PROPERTIES VERSION "${UHD_VERSION_MAJOR}.${UHD_VERSION_API}")
ENDIF(NOT LIBUHDDEV_PKG)
IF(DEFINED LIBUHD_OUTPUT_NAME)
SET_TARGET_PROPERTIES(uhd PROPERTIES OUTPUT_NAME ${LIBUHD_OUTPUT_NAME})
diff --git a/host/lib/build_info.cpp b/host/lib/build_info.cpp
new file mode 100644
index 000000000..5ccfd0268
--- /dev/null
+++ b/host/lib/build_info.cpp
@@ -0,0 +1,113 @@
+//
+// Copyright 2015 National Instruments Corp.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 <config.h>
+
+#include <uhd/build_info.hpp>
+
+#include <boost/format.hpp>
+#include <boost/version.hpp>
+#include <boost/algorithm/string.hpp>
+
+#ifdef ENABLE_USB
+#include <libusb.h>
+#endif
+
+namespace uhd { namespace build_info {
+
+ const std::string boost_version() {
+ return boost::algorithm::replace_all_copy(
+ std::string(BOOST_LIB_VERSION), "_", "."
+ );
+ }
+
+ const std::string build_date() {
+ return "@UHD_BUILD_DATE@";
+ }
+
+ const std::string c_compiler() {
+ return "@UHD_C_COMPILER@";
+ }
+
+ const std::string cxx_compiler() {
+ return "@UHD_CXX_COMPILER@";
+ }
+
+#ifdef _MSC_VER
+ static const std::string define_flag = "/D ";
+#else
+ static const std::string define_flag = "-D";
+#endif
+
+ const std::string c_flags() {
+ return boost::algorithm::replace_all_copy(
+ (define_flag + std::string("@UHD_C_FLAGS@")),
+ std::string(";"), (" " + define_flag)
+ );
+ }
+
+ const std::string cxx_flags() {
+ return boost::algorithm::replace_all_copy(
+ (define_flag + std::string("@UHD_CXX_FLAGS@")),
+ std::string(";"), (" " + define_flag)
+ );
+ }
+
+ const std::string enabled_components() {
+ return boost::algorithm::replace_all_copy(
+ std::string("@_uhd_enabled_components@"),
+ std::string(";"), std::string(", ")
+ );
+ }
+
+ const std::string install_prefix() {
+ return "@CMAKE_INSTALL_PREFIX@";
+ }
+
+ const std::string libusb_version() {
+ #ifdef ENABLE_USB
+ /*
+ * Versions can only be queried from 1.0.13 onward.
+ * Depending on if the commit came from libusbx or
+ * libusb (now merged), the define might be different.
+ */
+ #ifdef LIBUSB_API_VERSION /* 1.0.18 onward */
+ int major_version = LIBUSB_API_VERSION >> 24;
+ int minor_version = (LIBUSB_API_VERSION & 0xFF0000) >> 16;
+ int micro_version = ((LIBUSB_API_VERSION & 0xFFFF) - 0x100) + 18;
+
+ return str(boost::format("%d.%d.%d")
+ % major_version % minor_version % micro_version);
+ #elif defined(LIBUSBX_API_VERSION) /* 1.0.13 - 1.0.17 */
+ switch(LIBUSBX_API_VERSION & 0xFF) {
+ case 0x00:
+ return "1.0.13";
+ case 0x01:
+ return "1.0.15";
+ case 0xFF:
+ return "1.0.14";
+ default:
+ return "1.0.16 or 1.0.17";
+ }
+ #else
+ return "< 1.0.13";
+ #endif
+ #else
+ return "N/A";
+ #endif
+ }
+}}
diff --git a/host/lib/convert/convert_item32.cpp b/host/lib/convert/convert_item32.cpp
index 57bd64860..d52b47a1a 100644
--- a/host/lib/convert/convert_item32.cpp
+++ b/host/lib/convert/convert_item32.cpp
@@ -38,7 +38,10 @@
_DECLARE_ITEM32_CONVERTER(cpu_type, sc8) \
_DECLARE_ITEM32_CONVERTER(cpu_type, sc16)
+/* Create sc16<->sc16,sc8(otw) */
DECLARE_ITEM32_CONVERTER(sc16)
+/* Create fc32<->sc16,sc8(otw) */
DECLARE_ITEM32_CONVERTER(fc32)
+/* Create fc64<->sc16,sc8(otw) */
DECLARE_ITEM32_CONVERTER(fc64)
_DECLARE_ITEM32_CONVERTER(sc8, sc8)
diff --git a/host/lib/convert/gen_convert_general.py b/host/lib/convert/gen_convert_general.py
index 4f9eeb747..5c62d51df 100644
--- a/host/lib/convert/gen_convert_general.py
+++ b/host/lib/convert/gen_convert_general.py
@@ -39,30 +39,37 @@ DECLARE_CONVERTER(item32, 1, item32, 1, PRIORITY_GENERAL) {
}
"""
-TMPL_CONV_GEN2_ITEM32 = """
-DECLARE_CONVERTER(item32, 1, sc16_item32_{end}, 1, PRIORITY_GENERAL) {{
+# Some 32-bit types converters are also defined in convert_item32.cpp to
+# take care of quirks such as I/Q ordering on the wire etc.
+TMPL_CONV_ITEM32 = """
+DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{
const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
for (size_t i = 0; i < nsamps; i++) {{
- output[i] = {to_wire}(input[i]);
+ output[i] = {to_wire_or_host}(input[i]);
}}
}}
+"""
-DECLARE_CONVERTER(sc16_item32_{end}, 1, item32, 1, PRIORITY_GENERAL) {{
+# 64-bit data types are two consecutive item32 items
+TMPL_CONV_ITEM64 = """
+DECLARE_CONVERTER({in_type}, 1, {out_type}, 1, PRIORITY_GENERAL) {{
const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
- for (size_t i = 0; i < nsamps; i++) {{
- output[i] = {to_host}(input[i]);
+ // An item64 is two item32_t's
+ for (size_t i = 0; i < nsamps * 2; i++) {{
+ output[i] = {to_wire_or_host}(input[i]);
}}
}}
"""
-TMPL_CONV_U8 = """
-DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{
- const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]);
- boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]);
+
+TMPL_CONV_U8S8 = """
+DECLARE_CONVERTER({us8}, 1, {us8}_item32_{end}, 1, PRIORITY_GENERAL) {{
+ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
+ item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
// 1) Copy all the 4-byte tuples
size_t n_words = nsamps / 4;
@@ -72,8 +79,8 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{
// 2) If nsamps was not a multiple of 4, copy the rest by hand
size_t bytes_left = nsamps % 4;
if (bytes_left) {{
- const u8_t *last_input_word = reinterpret_cast<const u8_t *>(&input[n_words]);
- u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]);
+ const {us8}_t *last_input_word = reinterpret_cast<const {us8}_t *>(&input[n_words]);
+ {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]);
for (size_t k = 0; k < bytes_left; k++) {{
last_output_word[k] = last_input_word[k];
}}
@@ -81,9 +88,9 @@ DECLARE_CONVERTER(u8, 1, u8_item32_{end}, 1, PRIORITY_GENERAL) {{
}}
}}
-DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{
- const boost::uint32_t *input = reinterpret_cast<const boost::uint32_t *>(inputs[0]);
- boost::uint32_t *output = reinterpret_cast<boost::uint32_t *>(outputs[0]);
+DECLARE_CONVERTER({us8}_item32_{end}, 1, {us8}, 1, PRIORITY_GENERAL) {{
+ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
+ item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
// 1) Copy all the 4-byte tuples
size_t n_words = nsamps / 4;
@@ -93,9 +100,9 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{
// 2) If nsamps was not a multiple of 4, copy the rest by hand
size_t bytes_left = nsamps % 4;
if (bytes_left) {{
- boost::uint32_t last_input_word = {to_host}(input[n_words]);
- const u8_t *last_input_word_ptr = reinterpret_cast<const u8_t *>(&last_input_word);
- u8_t *last_output_word = reinterpret_cast<u8_t *>(&output[n_words]);
+ item32_t last_input_word = {to_host}(input[n_words]);
+ const {us8}_t *last_input_word_ptr = reinterpret_cast<const {us8}_t *>(&last_input_word);
+ {us8}_t *last_output_word = reinterpret_cast<{us8}_t *>(&output[n_words]);
for (size_t k = 0; k < bytes_left; k++) {{
last_output_word[k] = last_input_word_ptr[k];
}}
@@ -103,6 +110,40 @@ DECLARE_CONVERTER(u8_item32_{end}, 1, u8, 1, PRIORITY_GENERAL) {{
}}
"""
+TMPL_CONV_S16 = """
+DECLARE_CONVERTER(s16, 1, s16_item32_{end}, 1, PRIORITY_GENERAL) {{
+ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
+ item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
+
+ // 1) Copy all the 4-byte tuples
+ size_t n_words = nsamps / 2;
+ for (size_t i = 0; i < n_words; i++) {{
+ output[i] = {to_wire}(input[i]);
+ }}
+ // 2) If nsamps was not a multiple of 2, copy the last one by hand
+ if (nsamps % 2) {{
+ item32_t tmp = item32_t(*reinterpret_cast<const s16_t *>(&input[n_words]));
+ output[n_words] = {to_wire}(tmp);
+ }}
+}}
+
+DECLARE_CONVERTER(s16_item32_{end}, 1, s16, 1, PRIORITY_GENERAL) {{
+ const item32_t *input = reinterpret_cast<const item32_t *>(inputs[0]);
+ item32_t *output = reinterpret_cast<item32_t *>(outputs[0]);
+
+ // 1) Copy all the 4-byte tuples
+ size_t n_words = nsamps / 2;
+ for (size_t i = 0; i < n_words; i++) {{
+ output[i] = {to_host}(input[i]);
+ }}
+ // 2) If nsamps was not a multiple of 2, copy the last one by hand
+ if (nsamps % 2) {{
+ item32_t tmp = {to_host}(input[n_words]);
+ *reinterpret_cast<s16_t *>(&output[n_words]) = s16_t(tmp);
+ }}
+}}
+"""
+
TMPL_CONV_USRP1_COMPLEX = """
DECLARE_CONVERTER(${cpu_type}, ${width}, sc16_item16_usrp1, 1, PRIORITY_GENERAL){
% for w in range(width):
@@ -164,23 +205,52 @@ if __name__ == '__main__':
file = os.path.basename(__file__)
output = parse_tmpl(TMPL_HEADER, file=file)
- #generate complex converters for all gen2 platforms
- for end, to_host, to_wire in (
- ('be', 'uhd::ntohx', 'uhd::htonx'),
- ('le', 'uhd::wtohx', 'uhd::htowx'),
- ):
- output += TMPL_CONV_GEN2_ITEM32.format(
- end=end, to_host=to_host, to_wire=to_wire
- )
- #generate raw (u8) converters:
+ ## Generate all data types that are exactly
+ ## item32 or multiples thereof:
+ for end in ('be', 'le'):
+ host_to_wire = {'be': 'uhd::htonx', 'le': 'uhd::htowx'}[end]
+ wire_to_host = {'be': 'uhd::ntohx', 'le': 'uhd::wtohx'}[end]
+ # item32 types (sc16->sc16 is a special case because it defaults
+ # to Q/I order on the wire:
+ for in_type, out_type, to_wire_or_host in (
+ ('item32', 'sc16_item32_{end}', host_to_wire),
+ ('sc16_item32_{end}', 'item32', wire_to_host),
+ ('f32', 'f32_item32_{end}', host_to_wire),
+ ('f32_item32_{end}', 'f32', wire_to_host),
+ ):
+ output += TMPL_CONV_ITEM32.format(
+ end=end, to_wire_or_host=to_wire_or_host,
+ in_type=in_type.format(end=end), out_type=out_type.format(end=end)
+ )
+ # 2xitem32 types:
+ for in_type, out_type in (
+ ('fc32', 'fc32_item32_{end}'),
+ ('fc32_item32_{end}', 'fc32'),
+ ):
+ output += TMPL_CONV_ITEM64.format(
+ end=end, to_wire_or_host=to_wire_or_host,
+ in_type=in_type.format(end=end), out_type=out_type.format(end=end)
+ )
+
+ ## Real 16-Bit:
for end, to_host, to_wire in (
('be', 'uhd::ntohx', 'uhd::htonx'),
('le', 'uhd::wtohx', 'uhd::htowx'),
):
- output += TMPL_CONV_U8.format(
- end=end, to_host=to_host, to_wire=to_wire
+ output += TMPL_CONV_S16.format(
+ end=end, to_host=to_host, to_wire=to_wire
)
+ ## Real 8-Bit Types:
+ for us8 in ('u8', 's8'):
+ for end, to_host, to_wire in (
+ ('be', 'uhd::ntohx', 'uhd::htonx'),
+ ('le', 'uhd::wtohx', 'uhd::htowx'),
+ ):
+ output += TMPL_CONV_U8S8.format(
+ us8=us8, end=end, to_host=to_host, to_wire=to_wire
+ )
+
#generate complex converters for usrp1 format (requires Cheetah)
for width in 1, 2, 4:
for cpu_type, do_scale in (
diff --git a/host/lib/device.cpp b/host/lib/device.cpp
index 3e84d5bea..ff4bbc212 100644
--- a/host/lib/device.cpp
+++ b/host/lib/device.cpp
@@ -50,7 +50,7 @@ static size_t hash_device_addr(
if(dev_addr.has_key("resource")) {
boost::hash_combine(hash, "resource");
- boost::hash_combine(hash, dev_addr["resource"]);
+ boost::hash_combine(hash, dev_addr["resource"]);
}
else {
BOOST_FOREACH(const std::string &key, uhd::sorted(dev_addr.keys())){
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/exception.cpp b/host/lib/exception.cpp
index a9e36fd15..fc87fe791 100644
--- a/host/lib/exception.cpp
+++ b/host/lib/exception.cpp
@@ -43,6 +43,7 @@ make_exception_impl("EnvironmentError", environment_error, exception)
make_exception_impl("IOError", io_error, environment_error)
make_exception_impl("OSError", os_error, environment_error)
make_exception_impl("SystemError", system_error, exception)
+make_exception_impl("SyntaxError", syntax_error, exception)
usb_error::usb_error(int code, const std::string &what):
runtime_error(str(boost::format("%s %d: %s") % "USBError" % code % what)), _code(code) {}
diff --git a/host/lib/experts/CMakeLists.txt b/host/lib/experts/CMakeLists.txt
new file mode 100644
index 000000000..db533e7fa
--- /dev/null
+++ b/host/lib/experts/CMakeLists.txt
@@ -0,0 +1,34 @@
+#
+# 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/>.
+#
+
+########################################################################
+# This file included, use CMake directory variables
+########################################################################
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+
+LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/expert_container.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/expert_factory.cpp
+)
+
+# Verbose Debug output for send/recv
+SET( UHD_EXPERT_LOGGING OFF CACHE BOOL "Enable verbose logging for experts" )
+OPTION( UHD_EXPERT_LOGGING "Enable verbose logging for experts" "" )
+IF(UHD_EXPERT_LOGGING)
+ MESSAGE(STATUS "Enabling verbose logging for experts")
+ ADD_DEFINITIONS(-DUHD_EXPERT_LOGGING)
+ENDIF()
diff --git a/host/lib/experts/expert_container.cpp b/host/lib/experts/expert_container.cpp
new file mode 100644
index 000000000..edfc2ebe3
--- /dev/null
+++ b/host/lib/experts/expert_container.cpp
@@ -0,0 +1,531 @@
+//
+// 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 "expert_container.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/utils/msg.hpp>
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread.hpp>
+#include <boost/graph/graph_traits.hpp>
+#include <boost/graph/depth_first_search.hpp>
+#include <boost/graph/topological_sort.hpp>
+#include <boost/graph/adjacency_list.hpp>
+
+#ifdef UHD_EXPERT_LOGGING
+#define EX_LOG(depth, str) _log(depth, str)
+#else
+#define EX_LOG(depth, str)
+#endif
+
+namespace uhd { namespace experts {
+
+typedef boost::adjacency_list<
+ boost::vecS, //Container used to represent the edge-list for each of the vertices.
+ boost::vecS, //container used to represent the vertex-list of the graph.
+ boost::directedS, //Directionality of graph
+ dag_vertex_t*, //Storage for each vertex
+ boost::no_property, //Storage for each edge
+ boost::no_property, //Storage for graph object
+ boost::listS //Container used to represent the edge-list for the graph.
+> expert_graph_t;
+
+typedef std::map<std::string, expert_graph_t::vertex_descriptor> vertex_map_t;
+typedef std::list<expert_graph_t::vertex_descriptor> node_queue_t;
+
+typedef boost::graph_traits<expert_graph_t>::edge_iterator edge_iter;
+typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter;
+
+class expert_container_impl : public expert_container
+{
+private: //Visitor class for cycle detection algorithm
+ struct cycle_det_visitor : public boost::dfs_visitor<>
+ {
+ cycle_det_visitor(std::vector<std::string>& back_edges):
+ _back_edges(back_edges) {}
+
+ template <class Edge, class Graph>
+ void back_edge(Edge u, const Graph& g) {
+ _back_edges.push_back(
+ g[boost::source(u,g)]->get_name() + "->" + g[boost::target(u,g)]->get_name());
+ }
+ private: std::vector<std::string>& _back_edges;
+ };
+
+public:
+ expert_container_impl(const std::string& name):
+ _name(name)
+ {
+ }
+
+ ~expert_container_impl()
+ {
+ clear();
+ }
+
+ const std::string& get_name() const
+ {
+ return _name;
+ }
+
+ void resolve_all(bool force = false)
+ {
+ boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex);
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ EX_LOG(0, str(boost::format("resolve_all(%s)") % (force?"force":"")));
+ _resolve_helper("", "", force);
+ }
+
+ void resolve_from(const std::string& node_name)
+ {
+ boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex);
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ EX_LOG(0, str(boost::format("resolve_from(%s)") % node_name));
+ _resolve_helper(node_name, "", false);
+ }
+
+ void resolve_to(const std::string& node_name)
+ {
+ boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex);
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ EX_LOG(0, str(boost::format("resolve_to(%s)") % node_name));
+ _resolve_helper("", node_name, false);
+ }
+
+ dag_vertex_t& retrieve(const std::string& name) const
+ {
+ try {
+ expert_graph_t::vertex_descriptor vertex = _lookup_vertex(name);
+ return _get_vertex(vertex);
+ } catch(std::exception&) {
+ throw uhd::lookup_error("failed to find node " + name + " in expert graph");
+ }
+ }
+
+ const dag_vertex_t& lookup(const std::string& name) const
+ {
+ return retrieve(name);
+ }
+
+ const node_retriever_t& node_retriever() const
+ {
+ return *this;
+ }
+
+ std::string to_dot() const
+ {
+ static const std::string DATA_SHAPE("ellipse");
+ static const std::string WORKER_SHAPE("box");
+
+ std::string dot_str;
+ dot_str += "digraph uhd_experts_" + _name + " {\n rankdir=LR;\n";
+ // Iterate through the vertices and print them out
+ for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag);
+ vi.first != vi.second;
+ ++vi.first
+ ) {
+ const dag_vertex_t& vertex = _get_vertex(*vi.first);
+ if (vertex.get_class() != CLASS_WORKER) {
+ dot_str += str(boost::format(" %d [label=\"%s\",shape=%s,xlabel=\"%s\"];\n") %
+ boost::uint32_t(*vi.first) % vertex.get_name() %
+ DATA_SHAPE % vertex.get_dtype());
+ } else {
+ dot_str += str(boost::format(" %d [label=\"%s\",shape=%s];\n") %
+ boost::uint32_t(*vi.first) % vertex.get_name() % WORKER_SHAPE);
+ }
+ }
+
+ // Iterate through the edges and print them out
+ for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag);
+ ei.first != ei.second;
+ ++ei.first
+ ) {
+ dot_str += str(boost::format(" %d -> %d;\n") %
+ boost::uint32_t(boost::source(*(ei.first), _expert_dag)) %
+ boost::uint32_t(boost::target(*(ei.first), _expert_dag)));
+ }
+ dot_str += "}\n";
+ return dot_str;
+ }
+
+ void debug_audit() const
+ {
+#ifdef UHD_EXPERT_LOGGING
+ EX_LOG(0, "debug_audit()");
+
+ //Test 1: Check for cycles in graph
+ std::vector<std::string> back_edges;
+ cycle_det_visitor cdet_vis(back_edges);
+ boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis));
+ if (back_edges.empty()) {
+ EX_LOG(1, "cycle check ... PASSED");
+ } else {
+ EX_LOG(1, "cycle check ... ERROR!!!");
+ BOOST_FOREACH(const std::string& e, back_edges) {
+ EX_LOG(2, "back edge: " + e);
+ }
+ }
+ back_edges.clear();
+
+ //Test 2: Check data node input and output edges
+ std::vector<std::string> data_node_issues;
+ BOOST_FOREACH(const vertex_map_t::value_type& v, _datanode_map) {
+ size_t in_count = 0, out_count = 0;
+ for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag);
+ ei.first != ei.second;
+ ++ei.first
+ ) {
+ if (boost::target(*(ei.first), _expert_dag) == v.second)
+ in_count++;
+ if (boost::source(*(ei.first), _expert_dag) == v.second)
+ out_count++;
+ }
+ bool prop_unused = false;
+ if (in_count > 1) {
+ data_node_issues.push_back(v.first + ": multiple writers (workers)");
+ } else if (in_count > 0) {
+ if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY) {
+ data_node_issues.push_back(v.first + ": multiple writers (worker and property tree)");
+ }
+ } else {
+ if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) {
+ data_node_issues.push_back(v.first + ": unreachable (will always hold initial value)");
+ } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_write_callback()) {
+ if (out_count > 0) {
+ data_node_issues.push_back(v.first + ": needs explicit resolve after write");
+ } else {
+ data_node_issues.push_back(v.first + ": unused (no readers or writers)");
+ prop_unused = true;
+ }
+ }
+ }
+ if (out_count < 1) {
+ if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) {
+ data_node_issues.push_back(v.first + ": unused (is not read by any worker)");
+ } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_read_callback()) {
+ if (not prop_unused) {
+ data_node_issues.push_back(v.first + ": needs explicit resolve to read");
+ }
+ }
+ }
+ }
+
+ if (data_node_issues.empty()) {
+ EX_LOG(1, "data node check ... PASSED");
+ } else {
+ EX_LOG(1, "data node check ... WARNING!");
+ BOOST_FOREACH(const std::string& i, data_node_issues) {
+ EX_LOG(2, i);
+ }
+ }
+ data_node_issues.clear();
+
+ //Test 3: Check worker node input and output edges
+ std::vector<std::string> worker_issues;
+ BOOST_FOREACH(const vertex_map_t::value_type& v, _worker_map) {
+ size_t in_count = 0, out_count = 0;
+ for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag);
+ ei.first != ei.second;
+ ++ei.first
+ ) {
+ if (boost::target(*(ei.first), _expert_dag) == v.second)
+ in_count++;
+ if (boost::source(*(ei.first), _expert_dag) == v.second)
+ out_count++;
+ }
+ if (in_count < 1) {
+ worker_issues.push_back(v.first + ": no inputs (will never resolve)");
+ }
+ if (out_count < 1) {
+ worker_issues.push_back(v.first + ": no outputs");
+ }
+ }
+ if (worker_issues.empty()) {
+ EX_LOG(1, "worker check ... PASSED");
+ } else {
+ EX_LOG(1, "worker check ... WARNING!");
+ BOOST_FOREACH(const std::string& i, worker_issues) {
+ EX_LOG(2, i);
+ }
+ }
+ worker_issues.clear();
+#endif
+ }
+
+ inline boost::recursive_mutex& resolve_mutex() {
+ return _resolve_mutex;
+ }
+
+protected:
+ void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ //Sanity check node pointer
+ if (data_node == NULL) {
+ throw uhd::runtime_error("NULL data node passed into expert container for registration.");
+ }
+
+ //Sanity check the data node and ensure that it is not already in this graph
+ EX_LOG(0, str(boost::format("add_data_node(%s)") % data_node->get_name()));
+ if (data_node->get_class() == CLASS_WORKER) {
+ throw uhd::runtime_error("Supplied node " + data_node->get_name() + " is not a data/property node.");
+ delete data_node;
+ }
+ if (_datanode_map.find(data_node->get_name()) != _datanode_map.end()) {
+ throw uhd::runtime_error("Data node with name " + data_node->get_name() + " already exists");
+ delete data_node;
+ }
+
+ try {
+ //Add a vertex in this graph for the data node
+ expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(data_node, _expert_dag);
+ EX_LOG(1, str(boost::format("added vertex %s") % data_node->get_name()));
+ _datanode_map.insert(vertex_map_t::value_type(data_node->get_name(), gr_node));
+
+ //Add resolve callbacks
+ if (resolve_mode == AUTO_RESOLVE_ON_WRITE or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) {
+ EX_LOG(2, str(boost::format("added write callback")));
+ data_node->set_write_callback(boost::bind(&expert_container_impl::resolve_from, this, _1));
+ }
+ if (resolve_mode == AUTO_RESOLVE_ON_READ or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) {
+ EX_LOG(2, str(boost::format("added read callback")));
+ data_node->set_read_callback(boost::bind(&expert_container_impl::resolve_to, this, _1));
+ }
+ } catch (...) {
+ clear();
+ throw uhd::assertion_error("Unknown unrecoverable error adding data node. Cleared expert container.");
+ }
+ }
+
+ void add_worker(worker_node_t* worker)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ //Sanity check node pointer
+ if (worker == NULL) {
+ throw uhd::runtime_error("NULL worker passed into expert container for registration.");
+ }
+
+ //Sanity check the data node and ensure that it is not already in this graph
+ EX_LOG(0, str(boost::format("add_worker(%s)") % worker->get_name()));
+ if (worker->get_class() != CLASS_WORKER) {
+ throw uhd::runtime_error("Supplied node " + worker->get_name() + " is not a worker node.");
+ delete worker;
+ }
+ if (_worker_map.find(worker->get_name()) != _worker_map.end()) {
+ throw uhd::runtime_error("Resolver with name " + worker->get_name() + " already exists.");
+ delete worker;
+ }
+
+ try {
+ //Add a vertex in this graph for the worker node
+ expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(worker, _expert_dag);
+ EX_LOG(1, str(boost::format("added vertex %s") % worker->get_name()));
+ _worker_map.insert(vertex_map_t::value_type(worker->get_name(), gr_node));
+
+ //For each input, add an edge from the input to this node
+ BOOST_FOREACH(const std::string& node_name, worker->get_inputs()) {
+ vertex_map_t::const_iterator node = _datanode_map.find(node_name);
+ if (node != _datanode_map.end()) {
+ boost::add_edge((*node).second, gr_node, _expert_dag);
+ EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[(*node).second]->get_name() % _expert_dag[gr_node]->get_name()));
+ } else {
+ throw uhd::runtime_error("Data node with name " + node_name + " was not found");
+ }
+ }
+
+ //For each output, add an edge from this node to the output
+ BOOST_FOREACH(const std::string& node_name, worker->get_outputs()) {
+ vertex_map_t::const_iterator node = _datanode_map.find(node_name);
+ if (node != _datanode_map.end()) {
+ boost::add_edge(gr_node, (*node).second, _expert_dag);
+ EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[gr_node]->get_name() % _expert_dag[(*node).second]->get_name()));
+ } else {
+ throw uhd::runtime_error("Data node with name " + node_name + " was not found");
+ }
+ }
+ } catch (uhd::runtime_error& ex) {
+ clear();
+ //Promote runtime_error to assertion_error
+ throw uhd::assertion_error(std::string(ex.what()) + " (Cleared expert container because error is unrecoverable).");
+ } catch (...) {
+ clear();
+ throw uhd::assertion_error("Unknown unrecoverable error adding worker. Cleared expert container.");
+ }
+ }
+
+ void clear()
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ EX_LOG(0, "clear()");
+
+ // Iterate through the vertices and release their node storage
+ typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter;
+ for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag);
+ vi.first != vi.second;
+ ++vi.first
+ ) {
+ try {
+ delete _expert_dag[*vi.first];
+ _expert_dag[*vi.first] = NULL;
+ } catch (...) {
+ //If a dag_vertex is a worker, it has a virtual dtor which
+ //can possibly throw an exception. We will not let that
+ //terminate clear() and leave things in a bad state.
+ }
+ }
+
+ //The following calls will not throw because they all contain
+ //intrinsic types.
+
+ // Release all vertices and edges in the DAG
+ _expert_dag.clear();
+
+ // Release all nodes in the map
+ _worker_map.clear();
+ _datanode_map.clear();
+ }
+
+private:
+ void _resolve_helper(std::string start, std::string stop, bool force)
+ {
+ //Sort the graph topologically. This ensures that for all dependencies, the dependant
+ //is always after all of its dependencies.
+ node_queue_t sorted_nodes;
+ try {
+ boost::topological_sort(_expert_dag, std::front_inserter(sorted_nodes));
+ } catch (boost::not_a_dag&) {
+ std::vector<std::string> back_edges;
+ cycle_det_visitor cdet_vis(back_edges);
+ boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis));
+ if (not back_edges.empty()) {
+ std::string edges;
+ BOOST_FOREACH(const std::string& e, back_edges) {
+ edges += "* " + e + "";
+ }
+ throw uhd::runtime_error("Cannot resolve expert because it has at least one cycle!\n"
+ "The following back-edges were found:" + edges);
+ }
+ }
+ if (sorted_nodes.empty()) return;
+
+ //Determine the start and stop node. If one is not explicitly specified then
+ //resolve everything
+ expert_graph_t::vertex_descriptor start_vertex = sorted_nodes.front();
+ expert_graph_t::vertex_descriptor stop_vertex = sorted_nodes.back();
+ if (not start.empty()) start_vertex = _lookup_vertex(start);
+ if (not stop.empty()) stop_vertex = _lookup_vertex(stop);
+
+ //First Pass: Resolve all nodes if they are dirty, in a topological order
+ std::list<dag_vertex_t*> resolved_workers;
+ bool start_node_encountered = false;
+ for (node_queue_t::iterator node_iter = sorted_nodes.begin();
+ node_iter != sorted_nodes.end();
+ ++node_iter
+ ) {
+ //Determine if we are at or beyond the starting node
+ if (*node_iter == start_vertex) start_node_encountered = true;
+
+ //Only resolve if the starting node has passed
+ if (start_node_encountered) {
+ dag_vertex_t& node = _get_vertex(*node_iter);
+ std::string node_val;
+ if (force or node.is_dirty()) {
+ node.resolve();
+ if (node.get_class() == CLASS_WORKER) {
+ resolved_workers.push_back(&node);
+ }
+ EX_LOG(1, str(boost::format("resolved node %s (%s) [%s]") %
+ node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string()));
+ } else {
+ EX_LOG(1, str(boost::format("skipped node %s (%s) [%s]") %
+ node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string()));
+ }
+ }
+
+ //Determine if we are beyond the stop node
+ if (*node_iter == stop_vertex) break;
+ }
+
+ //Second Pass: Mark all the workers clean. The policy is that a worker will mark all of
+ //its dependencies clean so after this step all data nodes that are not consumed by a worker
+ //will remain dirty (as they should because no one has consumed their value)
+ for (std::list<dag_vertex_t*>::iterator worker = resolved_workers.begin();
+ worker != resolved_workers.end();
+ ++worker
+ ) {
+ (*worker)->mark_clean();
+ }
+ }
+
+ expert_graph_t::vertex_descriptor _lookup_vertex(const std::string& name) const
+ {
+ expert_graph_t::vertex_descriptor vertex;
+ //Look for node in the data-node map
+ vertex_map_t::const_iterator vertex_iter = _datanode_map.find(name);
+ if (vertex_iter != _datanode_map.end()) {
+ vertex = (*vertex_iter).second;
+ } else {
+ //If not found, look in the worker-node map
+ vertex_iter = _worker_map.find(name);
+ if (vertex_iter != _worker_map.end()) {
+ vertex = (*vertex_iter).second;
+ } else {
+ throw uhd::lookup_error("Could not find node with name " + name);
+ }
+ }
+ return vertex;
+ }
+
+ dag_vertex_t& _get_vertex(expert_graph_t::vertex_descriptor desc) const {
+ //Requirement: Node must exist in expert graph
+ dag_vertex_t* vertex_ptr = _expert_dag[desc];
+ if (vertex_ptr) {
+ return *vertex_ptr;
+ } else {
+ throw uhd::assertion_error("Expert graph malformed. Found a NULL node.");
+ }
+ }
+
+ void _log(size_t depth, const std::string& str) const
+ {
+ std::string indents;
+ for (size_t i = 0; i < depth; i++) indents += "- ";
+ UHD_MSG(fastpath) << "[expert::" + _name + "] " << indents << str << std::endl;
+ }
+
+private:
+ const std::string _name;
+ expert_graph_t _expert_dag; //The primary graph data structure as an adjacency list
+ vertex_map_t _worker_map; //A map from vertex name to vertex descriptor for workers
+ vertex_map_t _datanode_map; //A map from vertex name to vertex descriptor for data nodes
+ boost::mutex _mutex;
+ boost::recursive_mutex _resolve_mutex;
+};
+
+expert_container::sptr expert_container::make(const std::string& name)
+{
+ return boost::make_shared<expert_container_impl>(name);
+}
+
+}}
diff --git a/host/lib/experts/expert_container.hpp b/host/lib/experts/expert_container.hpp
new file mode 100644
index 000000000..7e626dcc5
--- /dev/null
+++ b/host/lib/experts/expert_container.hpp
@@ -0,0 +1,202 @@
+//
+// 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_UHD_EXPERTS_EXPERT_CONTAINER_HPP
+#define INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP
+
+#include "expert_nodes.hpp"
+#include <uhd/config.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+
+namespace uhd { namespace experts {
+
+ enum auto_resolve_mode_t {
+ AUTO_RESOLVE_OFF,
+ AUTO_RESOLVE_ON_READ,
+ AUTO_RESOLVE_ON_WRITE,
+ AUTO_RESOLVE_ON_READ_WRITE
+ };
+
+ class UHD_API expert_container : private boost::noncopyable, public node_retriever_t {
+ public: //Methods
+ typedef boost::shared_ptr<expert_container> sptr;
+
+ virtual ~expert_container() {};
+
+ /*!
+ * Return the name of this container
+ */
+ virtual const std::string& get_name() const = 0;
+
+ /*!
+ * Resolves all the nodes in this expert graph.
+ *
+ * Dependency analysis is performed on the graph and nodes
+ * are resolved in a topologically sorted order to ensure
+ * that no nodes receive stale data.
+ * Nodes and their dependencies are resolved only if they are
+ * dirty i.e. their contained values have changed since the
+ * last resolve.
+ * This call requires an acyclic expert graph.
+ *
+ * \param force If true then ignore dirty state and resolve all nodes
+ * \throws uhd::runtime_error if graph cannot be resolved
+ */
+ virtual void resolve_all(bool force = false) = 0;
+
+ /*!
+ * Resolves all the nodes that depend on the specified node.
+ *
+ * Dependency analysis is performed on the graph and nodes
+ * are resolved in a topologically sorted order to ensure
+ * that no nodes receive stale data.
+ * Nodes and their dependencies are resolved only if they are
+ * dirty i.e. their contained values have changed since the
+ * last resolve.
+ * This call requires an acyclic expert graph.
+ *
+ * \param node_name Name of the node to start resolving from
+ * \throws uhd::lookup_error if node_name not in container
+ * \throws uhd::runtime_error if graph cannot be resolved
+ *
+ */
+ virtual void resolve_from(const std::string& node_name) = 0;
+
+ /*!
+ * Resolves all the specified node and all of its dependencies.
+ *
+ * Dependency analysis is performed on the graph and nodes
+ * are resolved in a topologically sorted order to ensure
+ * that no nodes receive stale data.
+ * Nodes and their dependencies are resolved only if they are
+ * dirty i.e. their contained values have changed since the
+ * last resolve.
+ * This call requires an acyclic expert graph.
+ *
+ * \param node_name Name of the node to resolve
+ * \throws uhd::lookup_error if node_name not in container
+ * \throws uhd::runtime_error if graph cannot be resolved
+ *
+ */
+ virtual void resolve_to(const std::string& node_name) = 0;
+
+ /*!
+ * Return a node retriever object for this container
+ */
+ virtual const node_retriever_t& node_retriever() const = 0;
+
+ /*!
+ * Returns a DOT (graph description language) representation
+ * of the expert graph. The output has labels for the node
+ * name, node type (data or worker) and the underlying
+ * data type for each node.
+ *
+ */
+ virtual std::string to_dot() const = 0;
+
+ /*!
+ * Runs several sanity checks on the underlying graph to
+ * flag dependency issues. Outputs of the checks are
+ * logged to the console so UHD_EXPERTS_VERBOSE_LOGGING
+ * must be enabled to see the results
+ *
+ */
+ virtual void debug_audit() const = 0;
+
+ private:
+ /*!
+ * Lookup a node with the specified name in the contained graph
+ *
+ * If the node is found, a reference to the node is returned.
+ * If the node is not found, uhd::lookup_error is thrown
+ * lookup can return a data or a worker node
+ * \implements uhd::experts::node_retriever_t
+ *
+ * \param name Name of the node to find
+ *
+ */
+ virtual const dag_vertex_t& lookup(const std::string& name) const = 0;
+ virtual dag_vertex_t& retrieve(const std::string& name) const = 0;
+
+ /*!
+ * expert_factory is a friend of expert_container and
+ * handles all operations that change the structure of
+ * the underlying dependency graph.
+ * The expert_container instance owns all data and worker
+ * nodes and is responsible for release storage on destruction.
+ * However, the expert_factory allocates storage for the
+ * node and passes them into the expert_container using the
+ * following "protected" API calls.
+ *
+ */
+ friend class expert_factory;
+
+ /*!
+ * Creates an empty instance of expert_container with the
+ * specified name.
+ *
+ * \param name Name of the container
+ */
+ static sptr make(const std::string& name);
+
+ /*!
+ * Returns a reference to the resolver mutex.
+ *
+ * The resolver mutex guarantees that external operations
+ * to data-nodes are serialized with resolves of this
+ * container.
+ *
+ */
+ virtual boost::recursive_mutex& resolve_mutex() = 0;
+
+ /*!
+ * Add a data node to the expert graph
+ *
+ * \param data_node Pointer to a fully constructed data node object
+ * \resolve_mode Auto resolve options: Choose from "disabled" and resolve on "read", "write" or "both"
+ * \throws uhd::runtime_error if node already exists or is of a wrong type (recoverable)
+ * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph)
+ *
+ */
+ virtual void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode = AUTO_RESOLVE_OFF) = 0;
+
+ /*!
+ * Add a worker node to the expert graph
+ *
+ * \param worker Pointer to a fully constructed worker object
+ * \throws uhd::runtime_error if worker already exists or is of a wrong type (recoverable)
+ * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph)
+ *
+ */
+ virtual void add_worker(worker_node_t* worker) = 0;
+
+ /*!
+ * Release all storage for this object. This will delete all contained
+ * data and worker nodes and remove all dependency relationship and
+ * resolve callbacks.
+ *
+ * The object will be restored to its newly constructed state. Will not
+ * throw.
+ */
+ virtual void clear() = 0;
+ };
+
+}}
+
+#endif /* INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP */
diff --git a/host/lib/experts/expert_factory.cpp b/host/lib/experts/expert_factory.cpp
new file mode 100644
index 000000000..f887ad4a3
--- /dev/null
+++ b/host/lib/experts/expert_factory.cpp
@@ -0,0 +1,27 @@
+//
+// 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 "expert_factory.hpp"
+
+namespace uhd { namespace experts {
+
+expert_container::sptr expert_factory::create_container(const std::string& name)
+{
+ return expert_container::make(name);
+}
+
+}}
diff --git a/host/lib/experts/expert_factory.hpp b/host/lib/experts/expert_factory.hpp
new file mode 100644
index 000000000..83369117d
--- /dev/null
+++ b/host/lib/experts/expert_factory.hpp
@@ -0,0 +1,337 @@
+//
+// 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_UHD_EXPERTS_EXPERT_FACTORY_HPP
+#define INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP
+
+#include "expert_container.hpp"
+#include <uhd/property_tree.hpp>
+#include <uhd/config.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/bind.hpp>
+#include <memory>
+
+namespace uhd { namespace experts {
+
+ /*!
+ * expert_factory is a friend of expert_container and
+ * handles all operations to create and change the structure of
+ * the an expert container.
+ * The expert_factory allocates storage for the nodes in the
+ * expert_container and passes allocated objects to the container
+ * using private APIs. The expert_container instance owns all
+ * data and workernodes and is responsible for releasing their
+ * storage on destruction.
+ *
+ */
+ class UHD_API expert_factory : public boost::noncopyable {
+ public:
+
+ /*!
+ * Creates an empty instance of expert_container with the
+ * specified name.
+ *
+ * \param name Name of the container
+ */
+ static expert_container::sptr create_container(
+ const std::string& name
+ );
+
+ /*!
+ * Add a data node to the expert graph.
+ *
+ * \param container A shared pointer to the container to add the node to
+ * \param name The name of the data node
+ * \param init_val The initial value of the data node
+ * \param mode The auto resolve mode
+ *
+ * Requirements for data_t
+ * - Must have a default constructor
+ * - Must have a copy constructor
+ * - Must have an assignment operator (=)
+ * - Must have an equality operator (==)
+ */
+ template<typename data_t>
+ inline static void add_data_node(
+ expert_container::sptr container,
+ const std::string& name,
+ const data_t& init_val,
+ const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF
+ ) {
+ container->add_data_node(new data_node_t<data_t>(name, init_val), mode);
+ }
+
+ /*!
+ * Add a expert property to a property tree AND an expert graph
+ *
+ * \param container A shared pointer to the expert container to add the node to
+ * \param subtree A shared pointer to subtree to add the property to
+ * \param path The path of the property in the subtree
+ * \param name The name of the data node in the expert graph
+ * \param init_val The initial value of the data node
+ * \param mode The auto resolve mode
+ *
+ * Requirements for data_t
+ * - Must have a default constructor
+ * - Must have a copy constructor
+ * - Must have an assignment operator (=)
+ * - Must have an equality operator (==)
+ */
+ template<typename data_t>
+ inline static property<data_t>& add_prop_node(
+ expert_container::sptr container,
+ property_tree::sptr subtree,
+ const fs_path &path,
+ const std::string& name,
+ const data_t& init_val,
+ const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF
+ ) {
+ property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE);
+ data_node_t<data_t>* node_ptr =
+ new data_node_t<data_t>(name, init_val, &container->resolve_mutex());
+ prop.set(init_val);
+ prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, node_ptr, _1));
+ prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, node_ptr));
+ container->add_data_node(node_ptr, mode);
+ return prop;
+ }
+
+ /*!
+ * Add a expert property to a property tree AND an expert graph.
+ * The property is registered with the path as the identifier for
+ * both the property subtree and the expert container
+ *
+ * \param container A shared pointer to the expert container to add the node to
+ * \param subtree A shared pointer to subtree to add the property to
+ * \param path The path of the property in the subtree
+ * \param init_val The initial value of the data node
+ * \param mode The auto resolve mode
+ *
+ */
+ template<typename data_t>
+ inline static property<data_t>& add_prop_node(
+ expert_container::sptr container,
+ property_tree::sptr subtree,
+ const fs_path &path,
+ const data_t& init_val,
+ const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF
+ ) {
+ return add_prop_node(container, subtree, path, path, init_val, mode);
+ }
+
+ /*!
+ * Add a dual expert property to a property tree AND an expert graph.
+ * A dual property is a desired and coerced value pair
+ *
+ * \param container A shared pointer to the expert container to add the node to
+ * \param subtree A shared pointer to subtree to add the property to
+ * \param path The path of the property in the subtree
+ * \param desired_name The name of the desired data node in the expert graph
+ * \param desired_name The name of the coerced data node in the expert graph
+ * \param init_val The initial value of both the data nodes
+ * \param mode The auto resolve mode
+ *
+ * Requirements for data_t
+ * - Must have a default constructor
+ * - Must have a copy constructor
+ * - Must have an assignment operator (=)
+ * - Must have an equality operator (==)
+ */
+ template<typename data_t>
+ inline static property<data_t>& add_dual_prop_node(
+ expert_container::sptr container,
+ property_tree::sptr subtree,
+ const fs_path &path,
+ const std::string& desired_name,
+ const std::string& coerced_name,
+ const data_t& init_val,
+ const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF
+ ) {
+ bool auto_resolve_desired = (mode==AUTO_RESOLVE_ON_WRITE or mode==AUTO_RESOLVE_ON_READ_WRITE);
+ bool auto_resolve_coerced = (mode==AUTO_RESOLVE_ON_READ or mode==AUTO_RESOLVE_ON_READ_WRITE);
+
+ property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE);
+ data_node_t<data_t>* desired_node_ptr =
+ new data_node_t<data_t>(desired_name, init_val, &container->resolve_mutex());
+ data_node_t<data_t>* coerced_node_ptr =
+ new data_node_t<data_t>(coerced_name, init_val, &container->resolve_mutex());
+ prop.set(init_val);
+ prop.set_coerced(init_val);
+ prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, desired_node_ptr, _1));
+ prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, coerced_node_ptr));
+
+ container->add_data_node(desired_node_ptr,
+ auto_resolve_desired ? AUTO_RESOLVE_ON_WRITE : AUTO_RESOLVE_OFF);
+ container->add_data_node(coerced_node_ptr,
+ auto_resolve_coerced ? AUTO_RESOLVE_ON_READ : AUTO_RESOLVE_OFF);
+ return prop;
+ }
+
+ /*!
+ * Add a dual expert property to a property tree AND an expert graph.
+ * A dual property is a desired and coerced value pair
+ * The property is registered with path/desired as the desired node
+ * name and path/coerced as the coerced node name
+ *
+ * \param container A shared pointer to the expert container to add the node to
+ * \param subtree A shared pointer to subtree to add the property to
+ * \param path The path of the property in the subtree
+ * \param init_val The initial value of both the data nodes
+ * \param mode The auto resolve mode
+ *
+ */
+ template<typename data_t>
+ inline static property<data_t>& add_dual_prop_node(
+ expert_container::sptr container,
+ property_tree::sptr subtree,
+ const fs_path &path,
+ const data_t& init_val,
+ const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF
+ ) {
+ return add_dual_prop_node(container, subtree, path, path + "/desired", path + "/coerced", init_val, mode);
+ }
+
+ /*!
+ * Add a worker node to the expert graph.
+ * The expert_container owns and manages storage for the worker
+ *
+ * \tparam worker_t Data type of the worker class
+ *
+ * \param container A shared pointer to the container to add the node to
+ *
+ */
+ template<typename worker_t>
+ inline static void add_worker_node(
+ expert_container::sptr container
+ ) {
+ container->add_worker(new worker_t());
+ }
+
+ /*!
+ * Add a worker node to the expert graph.
+ * The expert_container owns and manages storage for the worker
+ *
+ * \tparam worker_t Data type of the worker class
+ * \tparam arg1_t Data type of the first argument to the constructor
+ * \tparam ...
+ * \tparam argN_t Data type of the Nth argument to the constructor
+ *
+ * \param container A shared pointer to the container to add the node to
+ * \param arg1 First arg to ctor
+ * \param ...
+ * \param argN Nth arg to ctor
+ *
+ */
+ template<typename worker_t, typename arg1_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1
+ ) {
+ container->add_worker(new worker_t(arg1));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2
+ ) {
+ container->add_worker(new worker_t(arg1, arg2));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3,
+ arg4_t const & arg4
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3, arg4));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t,
+ typename arg5_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3,
+ arg4_t const & arg4,
+ arg5_t const & arg5
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t,
+ typename arg5_t, typename arg6_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3,
+ arg4_t const & arg4,
+ arg5_t const & arg5,
+ arg6_t const & arg6
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t,
+ typename arg5_t, typename arg6_t, typename arg7_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3,
+ arg4_t const & arg4,
+ arg5_t const & arg5,
+ arg6_t const & arg6,
+ arg7_t const & arg7
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7));
+ }
+
+ template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t,
+ typename arg5_t, typename arg6_t, typename arg7_t, typename arg8_t>
+ inline static void add_worker_node(
+ expert_container::sptr container,
+ arg1_t const & arg1,
+ arg2_t const & arg2,
+ arg3_t const & arg3,
+ arg4_t const & arg4,
+ arg5_t const & arg5,
+ arg6_t const & arg6,
+ arg7_t const & arg7,
+ arg7_t const & arg8
+ ) {
+ container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8));
+ }
+ };
+}}
+
+#endif /* INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP */
diff --git a/host/lib/experts/expert_nodes.hpp b/host/lib/experts/expert_nodes.hpp
new file mode 100644
index 000000000..dc5cc934b
--- /dev/null
+++ b/host/lib/experts/expert_nodes.hpp
@@ -0,0 +1,475 @@
+//
+// 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_UHD_EXPERTS_EXPERT_NODES_HPP
+#define INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP
+
+#include <uhd/config.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/utils/dirty_tracked.hpp>
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+#include <boost/thread.hpp>
+#include <boost/units/detail/utility.hpp>
+#include <memory>
+#include <list>
+
+namespace uhd { namespace experts {
+
+ enum node_class_t { CLASS_WORKER, CLASS_DATA, CLASS_PROPERTY };
+ enum node_access_t { ACCESS_READER, ACCESS_WRITER };
+ enum node_author_t { AUTHOR_NONE, AUTHOR_USER, AUTHOR_EXPERT };
+
+ /*!---------------------------------------------------------
+ * class dag_vertex_t
+ *
+ * This serves as the base class for all nodes in the expert
+ * graph. Data nodes and workers are derived from this class.
+ * ---------------------------------------------------------
+ */
+ class dag_vertex_t : private boost::noncopyable {
+ public:
+ typedef boost::function<void(std::string)> callback_func_t;
+
+ virtual ~dag_vertex_t() {}
+
+ // Getters for basic info about the node
+ inline node_class_t get_class() const {
+ return _class;
+ }
+
+ inline const std::string& get_name() const {
+ return _name;
+ }
+
+ virtual const std::string& get_dtype() const = 0;
+
+ virtual std::string to_string() const = 0;
+
+ // Graph resolution specific
+ virtual bool is_dirty() const = 0;
+ virtual void mark_clean() = 0;
+ virtual void resolve() = 0;
+
+ // External callbacks
+ virtual void set_write_callback(const callback_func_t& func) = 0;
+ virtual bool has_write_callback() const = 0;
+ virtual void clear_write_callback() = 0;
+ virtual void set_read_callback(const callback_func_t& func) = 0;
+ virtual bool has_read_callback() const = 0;
+ virtual void clear_read_callback() = 0;
+
+ protected:
+ dag_vertex_t(const node_class_t c, const std::string& n):
+ _class(c), _name(n) {}
+
+ private:
+ const node_class_t _class;
+ const std::string _name;
+ };
+
+ class data_node_printer {
+ public:
+ //Generic implementation
+ template<typename data_t>
+ static std::string print(const data_t& val) {
+ std::ostringstream os;
+ os << val;
+ return os.str();
+ }
+
+ static std::string print(const boost::uint8_t& val) {
+ std::ostringstream os;
+ os << int(val);
+ return os.str();
+ }
+ };
+
+ /*!---------------------------------------------------------
+ * class data_node_t
+ *
+ * The data node class hold a passive piece of data in the
+ * expert graph. A data node is clean if its underlying data
+ * is clean. Access to the underlying data is provided using
+ * two methods:
+ * 1. Special accessor classes (for R/W enforcement)
+ * 2. External clients (via commit and retrieve). This access
+ * is protected by the callback mutex.
+ *
+ * Requirements for data_t
+ * - Must have a default constructor
+ * - Must have a copy constructor
+ * - Must have an assignment operator (=)
+ * - Must have an equality operator (==)
+ * ---------------------------------------------------------
+ */
+ template<typename data_t>
+ class data_node_t : public dag_vertex_t {
+ public:
+ // A data_node_t instance can have a type of CLASS_DATA or CLASS_PROPERTY
+ // In general a data node is a property if it can be accessed and modified
+ // from the outside world (of experts) using read and write callbacks. We
+ // assume that if a callback mutex is passed into the data node that it will
+ // be accessed from the outside and tag the data node as a PROPERTY.
+ data_node_t(const std::string& name, boost::recursive_mutex* mutex = NULL) :
+ dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(), _author(AUTHOR_NONE) {}
+
+ data_node_t(const std::string& name, const data_t& value, boost::recursive_mutex* mutex = NULL) :
+ dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(value), _author(AUTHOR_NONE) {}
+
+ // Basic info
+ virtual const std::string& get_dtype() const {
+ static const std::string dtype(
+ boost::units::detail::demangle(typeid(data_t).name()));
+ return dtype;
+ }
+
+ virtual std::string to_string() const {
+ return data_node_printer::print(get());
+ }
+
+ inline node_author_t get_author() const {
+ return _author;
+ }
+
+ // Graph resolution specific
+ virtual bool is_dirty() const {
+ return _data.is_dirty();
+ }
+
+ virtual void mark_clean() {
+ _data.mark_clean();
+ }
+
+ void resolve() {
+ //NOP
+ }
+
+ // Data node specific setters and getters (for the framework)
+ void set(const data_t& value) {
+ _data = value;
+ _author = AUTHOR_EXPERT;
+ }
+
+ const data_t& get() const {
+ return _data;
+ }
+
+ // Data node specific setters and getters (for external entities)
+ void commit(const data_t& value) {
+ if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex");
+ boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex);
+ set(value);
+ _author = AUTHOR_USER;
+ if (is_dirty() and has_write_callback()) {
+ _wr_callback(std::string(get_name())); //Put the name on the stack before calling
+ }
+ }
+
+ const data_t retrieve() const {
+ if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex");
+ boost::lock_guard<boost::recursive_mutex> lock(*_callback_mutex);
+ if (has_read_callback()) {
+ _rd_callback(std::string(get_name()));
+ }
+ return get();
+ }
+
+ private:
+ // External callbacks
+ virtual void set_write_callback(const callback_func_t& func) {
+ _wr_callback = func;
+ }
+
+ virtual bool has_write_callback() const {
+ return not _wr_callback.empty();
+ }
+
+ virtual void clear_write_callback() {
+ _wr_callback.clear();
+ }
+
+ virtual void set_read_callback(const callback_func_t& func) {
+ _rd_callback = func;
+ }
+
+ virtual bool has_read_callback() const {
+ return not _rd_callback.empty();
+ }
+
+ virtual void clear_read_callback() {
+ _rd_callback.clear();
+ }
+
+ boost::recursive_mutex* _callback_mutex;
+ callback_func_t _rd_callback;
+ callback_func_t _wr_callback;
+ dirty_tracked<data_t> _data;
+ node_author_t _author;
+ };
+
+ /*!---------------------------------------------------------
+ * class node_retriever_t
+ *
+ * Node storage is managed by a framework class so we need
+ * and interface to find and retrieve data nodes to associate
+ * with accessors.
+ * ---------------------------------------------------------
+ */
+ class node_retriever_t {
+ public:
+ virtual ~node_retriever_t() {}
+ virtual const dag_vertex_t& lookup(const std::string& name) const = 0;
+ private:
+ friend class data_accessor_t;
+ virtual dag_vertex_t& retrieve(const std::string& name) const = 0;
+ };
+
+ /*!---------------------------------------------------------
+ * class data_accessor_t
+ *
+ * Accessors provide protected access to data nodes and help
+ * establish dependency relationships.
+ * ---------------------------------------------------------
+ */
+ class data_accessor_t {
+ public:
+ virtual ~data_accessor_t() {}
+
+ virtual bool is_reader() const = 0;
+ virtual bool is_writer() const = 0;
+ virtual dag_vertex_t& node() const = 0;
+ protected:
+ data_accessor_t(const node_retriever_t& r, const std::string& n):
+ _vertex(r.retrieve(n)) {}
+ dag_vertex_t& _vertex;
+ };
+
+ template<typename data_t>
+ class data_accessor_base : public data_accessor_t {
+ public:
+ virtual ~data_accessor_base() {}
+
+ virtual bool is_reader() const {
+ return _access == ACCESS_READER;
+ }
+
+ virtual bool is_writer() const {
+ return _access == ACCESS_WRITER;
+ }
+
+ inline bool is_dirty() const {
+ return _datanode->is_dirty();
+ }
+
+ inline node_class_t get_class() const {
+ return _datanode->get_class();
+ }
+
+ inline node_author_t get_author() const {
+ return _datanode->get_author();
+ }
+
+ protected:
+ data_accessor_base(
+ const node_retriever_t& r, const std::string& n, const node_access_t a) :
+ data_accessor_t(r, n), _datanode(NULL), _access(a)
+ {
+ _datanode = dynamic_cast< data_node_t<data_t>* >(&node());
+ if (_datanode == NULL) {
+ throw uhd::type_error("Expected data type for node " + n +
+ " was " + boost::units::detail::demangle(typeid(data_t).name()) +
+ " but got " + node().get_dtype());
+ }
+ }
+
+ data_node_t<data_t>* _datanode;
+ const node_access_t _access;
+
+ private:
+ virtual dag_vertex_t& node() const {
+ return _vertex;
+ }
+ };
+
+ /*!---------------------------------------------------------
+ * class data_reader_t
+ *
+ * Accessor to read the value of a data node and to establish
+ * a data node => worker node dependency
+ * ---------------------------------------------------------
+ */
+ template<typename data_t>
+ class data_reader_t : public data_accessor_base<data_t> {
+ public:
+ data_reader_t(const node_retriever_t& retriever, const std::string& node) :
+ data_accessor_base<data_t>(
+ retriever, node, ACCESS_READER) {}
+
+ inline const data_t& get() const {
+ return data_accessor_base<data_t>::_datanode->get();
+ }
+
+ inline operator const data_t&() const {
+ return get();
+ }
+
+ inline bool operator==(const data_t& rhs) {
+ return get() == rhs;
+ }
+
+ inline bool operator!=(const data_t& rhs) {
+ return !(get() == rhs);
+ }
+ };
+
+ /*!---------------------------------------------------------
+ * class data_reader_t
+ *
+ * Accessor to read and write the value of a data node and
+ * to establish a worker node => data node dependency
+ * ---------------------------------------------------------
+ */
+ template<typename data_t>
+ class data_writer_t : public data_accessor_base<data_t> {
+ public:
+ data_writer_t(const node_retriever_t& retriever, const std::string& node) :
+ data_accessor_base<data_t>(
+ retriever, node, ACCESS_WRITER) {}
+
+ inline const data_t& get() const {
+ return data_accessor_base<data_t>::_datanode->get();
+ }
+
+ inline operator const data_t&() const {
+ return get();
+ }
+
+ inline bool operator==(const data_t& rhs) {
+ return get() == rhs;
+ }
+
+ inline bool operator!=(const data_t& rhs) {
+ return !(get() == rhs);
+ }
+
+ inline void set(const data_t& value) {
+ data_accessor_base<data_t>::_datanode->set(value);
+ }
+
+ inline data_writer_t<data_t>& operator=(const data_t& value) {
+ set(value);
+ return *this;
+ }
+
+ inline data_writer_t<data_t>& operator=(const data_writer_t<data_t>& value) {
+ set(value.get());
+ return *this;
+ }
+};
+
+ /*!---------------------------------------------------------
+ * class worker_node_t
+ *
+ * A node class to implement a function that consumes
+ * zero or more input data nodes and emits zeroor more output
+ * data nodes. The worker can also operate on other non-expert
+ * interfaces because worker_node_t is abstract and the client
+ * is required to implement the "resolve" method in a subclass.
+ * ---------------------------------------------------------
+ */
+ class worker_node_t : public dag_vertex_t {
+ public:
+ worker_node_t(const std::string& name) :
+ dag_vertex_t(CLASS_WORKER, name) {}
+
+ // Worker node specific
+ std::list<std::string> get_inputs() const {
+ std::list<std::string> retval;
+ BOOST_FOREACH(data_accessor_t* acc, _inputs) {
+ retval.push_back(acc->node().get_name());
+ }
+ return retval;
+ }
+
+ std::list<std::string> get_outputs() const {
+ std::list<std::string> retval;
+ BOOST_FOREACH(data_accessor_t* acc, _outputs) {
+ retval.push_back(acc->node().get_name());
+ }
+ return retval;
+ }
+
+ protected:
+ // This function is used to bind data accessors
+ // to this worker. Accessors can be read/write
+ // and the binding will ensure proper dependency
+ // handling.
+ void bind_accessor(data_accessor_t& accessor) {
+ if (accessor.is_reader()) {
+ _inputs.push_back(&accessor);
+ } else if (accessor.is_writer()) {
+ _outputs.push_back(&accessor);
+ } else {
+ throw uhd::assertion_error("Invalid accessor type");
+ }
+ }
+
+ private:
+ // Graph resolution specific
+ virtual bool is_dirty() const {
+ bool inputs_dirty = false;
+ BOOST_FOREACH(data_accessor_t* acc, _inputs) {
+ inputs_dirty |= acc->node().is_dirty();
+ }
+ return inputs_dirty;
+ }
+
+ virtual void mark_clean() {
+ BOOST_FOREACH(data_accessor_t* acc, _inputs) {
+ acc->node().mark_clean();
+ }
+ }
+
+ virtual void resolve() = 0;
+
+ // Basic type info
+ virtual const std::string& get_dtype() const {
+ static const std::string dtype = "<worker>";
+ return dtype;
+ }
+
+ virtual std::string to_string() const {
+ return "<worker>";
+ }
+
+ // Workers don't have callbacks so implement stubs
+ virtual void set_write_callback(const callback_func_t&) {}
+ virtual bool has_write_callback() const { return false; }
+ virtual void clear_write_callback() {}
+ virtual void set_read_callback(const callback_func_t&) {}
+ virtual bool has_read_callback() const { return false; }
+ virtual void clear_read_callback() {}
+
+ std::list<data_accessor_t*> _inputs;
+ std::list<data_accessor_t*> _outputs;
+ };
+
+}}
+
+#endif /* INCLUDED_UHD_EXPERTS_EXPERT_NODE_HPP */
diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt
index 1de50579f..f8ccd9814 100644
--- a/host/lib/ic_reg_maps/CMakeLists.txt
+++ b/host/lib/ic_reg_maps/CMakeLists.txt
@@ -122,4 +122,9 @@ LIBUHD_PYTHON_GEN_SOURCE(
${CMAKE_CURRENT_BINARY_DIR}/lmk04816_regs.hpp
)
+LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf5355_regs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/adf5355_regs.hpp
+)
+
SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS)
diff --git a/host/lib/ic_reg_maps/gen_adf5355_regs.py b/host/lib/ic_reg_maps/gen_adf5355_regs.py
new file mode 100755
index 000000000..db7cc09a9
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_adf5355_regs.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+REGS_TMPL="""\
+########################################################################
+## address 0
+########################################################################
+int_16_bit 0[4:19] 0
+prescaler 0[20] 0 4_5, 8_9
+autocal_en 0[21] 0 disabled, enabled
+reg0_reserved0 0[22:31] 0x000
+########################################################################
+## address 1
+########################################################################
+frac1_24_bit 1[4:27] 0
+reg1_reserved0 1[28:31] 0x0
+########################################################################
+## address 2
+########################################################################
+mod2_14_bit 2[4:17] 0
+frac2_14_bit 2[18:31] 0
+########################################################################
+## address 3
+########################################################################
+phase_24_bit 3[4:27] 0
+phase_adjust 3[28] 0 disabled, enabled
+phase_resync 3[29] 0 disabled, enabled
+sd_load_reset 3[30] 0 on_reg0_update, disabled
+##reserved 3[31] 0
+########################################################################
+## address 4
+########################################################################
+counter_reset 4[4] 0 disabled, enabled
+cp_three_state 4[5] 0 disabled, enabled
+power_down 4[6] 0 disabled, enabled
+pd_polarity 4[7] 1 negative, positive
+mux_logic 4[8] 0 1_8V, 3_3V
+ref_mode 4[9] 0 single, diff
+<% current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) %>\
+charge_pump_current 4[10:13] 0 ${current_setting_enums}
+double_buff_div 4[14] 0 disabled, enabled
+r_counter_10_bit 4[15:24] 0
+reference_divide_by_2 4[25] 1 disabled, enabled
+reference_doubler 4[26] 0 disabled, enabled
+muxout 4[27:29] 1 3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved
+reg4_reserved0 4[30:31] 0
+########################################################################
+## address 5
+########################################################################
+reg5_reserved0 5[4:31] 0x0080002
+########################################################################
+## address 6
+########################################################################
+output_power 6[4:5] 0 m4dbm, m1dbm, 2dbm, 5dbm
+rf_out_a_enabled 6[6] 0 disabled, enabled
+reg6_reserved0 6[7:9] 0x0
+rf_out_b_enabled 6[10] 1 enabled, disabled
+mute_till_lock_detect 6[11] 0 mute_disabled, mute_enabled
+reg6_reserved1 6[12] 0
+cp_bleed_current 6[13:20] 0
+rf_divider_select 6[21:23] 0 div1, div2, div4, div8, div16, div32, div64
+feedback_select 6[24] 0 divided, fundamental
+reg6_reserved2 6[25:28] 0xA
+negative_bleed 6[29] 0 disabled, enabled
+gated_bleed 6[30] 0 disabled, enabled
+reg6_reserved3 6[31] 0
+########################################################################
+## address 7
+########################################################################
+ld_mode 7[4] 0 frac_n, int_n
+frac_n_ld_precision 7[5:6] 0 5ns, 6ns, 8ns, 12ns
+loss_of_lock_mode 7[7] 0 disabled, enabled
+ld_cyc_count 7[8:9] 0 1024, 2048, 4096, 8192
+reg7_reserved0 7[10:24] 0x0
+le_sync 7[25] 0 disabled, le_synced_to_refin
+reg7_reserved1 7[26:31] 0x4
+########################################################################
+## address 8
+########################################################################
+reg8_reserved0 8[4:31] 0x102D402
+########################################################################
+## address 9
+########################################################################
+synth_lock_timeout 9[4:8] 0
+auto_level_timeout 9[9:13] 0
+timeout 9[14:23] 0
+vco_band_div 9[24:31] 0
+########################################################################
+## address 10
+########################################################################
+adc_enable 10[4] 0 disabled, enabled
+adc_conversion 10[5] 0 disabled, enabled
+adc_clock_divider 10[6:13] 1
+reg10_reserved0 10[14:31] 0x300
+########################################################################
+## address 11
+########################################################################
+reg11_reserved0 11[4:31] 0x0061300
+########################################################################
+## address 12
+########################################################################
+reg12_reserved0 12[4:15] 0x041
+phase_resync_clk_div 12[16:31] 0
+"""
+
+########################################################################
+# Template for methods in the body of the struct
+########################################################################
+BODY_TMPL="""\
+enum addr_t{
+ ADDR_R0 = 0,
+ ADDR_R1 = 1,
+ ADDR_R2 = 2,
+ ADDR_R3 = 3,
+ ADDR_R4 = 4,
+ ADDR_R5 = 5,
+ ADDR_R6 = 6,
+ ADDR_R7 = 7,
+ ADDR_R8 = 8,
+ ADDR_R9 = 9,
+ ADDR_R10 = 10,
+ ADDR_R11 = 11,
+ ADDR_R12 = 12
+};
+
+boost::uint32_t get_reg(boost::uint8_t addr){
+ boost::uint32_t reg = addr & 0xF;
+ switch(addr){
+ % for addr in range(12+1):
+ case ${addr}:
+ % for reg in filter(lambda r: r.get_addr() == addr, regs):
+ reg |= (boost::uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()};
+ % endfor
+ break;
+ % endfor
+ }
+ return reg;
+}
+"""
+
+if __name__ == '__main__':
+ import common; common.generate(
+ name='adf5355_regs',
+ regs_tmpl=REGS_TMPL,
+ body_tmpl=BODY_TMPL,
+ file=__file__,
+ )
diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp
index 039f05f12..76d7bccba 100644
--- a/host/lib/property_tree.cpp
+++ b/host/lib/property_tree.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011,2014 Ettus Research LLC
+// Copyright 2011,2014-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
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..83c3a2626
--- /dev/null
+++ b/host/lib/rfnoc/ctrl_iface.cpp
@@ -0,0 +1,376 @@
+//
+// Copyright 2012-2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "ctrl_iface.hpp"
+#include "async_packet_handler.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/types/sid.hpp>
+#include <uhd/transport/chdr.hpp>
+#include <uhd/rfnoc/constants.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <queue>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace uhd::transport;
+
+static const double ACK_TIMEOUT = 2.0; //supposed to be worst case practical timeout
+static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command
+static const size_t SR_READBACK = 32;
+
+ctrl_iface::~ctrl_iface(void){
+ /* NOP */
+}
+
+class ctrl_iface_impl: public ctrl_iface
+{
+public:
+
+ ctrl_iface_impl(const bool big_endian,
+ uhd::transport::zero_copy_if::sptr ctrl_xport,
+ uhd::transport::zero_copy_if::sptr resp_xport,
+ const boost::uint32_t sid, const std::string &name
+ ) :
+ _link_type(vrt::if_packet_info_t::LINK_TYPE_CHDR),
+ _packet_type(vrt::if_packet_info_t::PACKET_TYPE_CONTEXT),
+ _bige(big_endian),
+ _ctrl_xport(ctrl_xport), _resp_xport(resp_xport),
+ _sid(sid),
+ _name(name),
+ _seq_out(0),
+ _timeout(ACK_TIMEOUT),
+ _resp_queue(128/*max response msgs*/),
+ _resp_queue_size(_resp_xport ? _resp_xport->get_num_recv_frames() : 3),
+ _rb_address(uhd::rfnoc::SR_READBACK)
+ {
+ if (resp_xport) {
+ while (resp_xport->get_recv_buff(0.0)) {} //flush
+ }
+ this->set_time(uhd::time_spec_t(0.0));
+ this->set_tick_rate(1.0); //something possible but bogus
+ }
+
+ ~ctrl_iface_impl(void)
+ {
+ _timeout = ACK_TIMEOUT; //reset timeout to something small
+ UHD_SAFE_CALL(
+ this->peek32(0);//dummy peek with the purpose of ack'ing all packets
+ _async_task.reset();//now its ok to release the task
+ )
+ }
+
+ /*******************************************************************
+ * Peek and poke 32 bit implementation
+ ******************************************************************/
+ void poke32(const wb_addr_type addr, const boost::uint32_t data)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ this->send_pkt(addr/4, data);
+ this->wait_for_ack(false);
+ }
+
+ boost::uint32_t peek32(const wb_addr_type addr)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ this->send_pkt(_rb_address, addr/8);
+ const boost::uint64_t res = this->wait_for_ack(true);
+ const boost::uint32_t lo = boost::uint32_t(res & 0xffffffff);
+ const boost::uint32_t hi = boost::uint32_t(res >> 32);
+ return ((addr/4) & 0x1)? hi : lo;
+ }
+
+ boost::uint64_t peek64(const wb_addr_type addr)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ this->send_pkt(_rb_address, addr/8);
+ return this->wait_for_ack(true);
+ }
+
+ /*******************************************************************
+ * Update methods for time
+ ******************************************************************/
+ void set_time(const uhd::time_spec_t &time)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ _time = time;
+ _use_time = _time != uhd::time_spec_t(0.0);
+ if (_use_time) _timeout = MASSIVE_TIMEOUT; //permanently sets larger timeout
+ }
+
+ uhd::time_spec_t get_time(void)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ return _time;
+ }
+
+ void set_tick_rate(const double rate)
+ {
+ boost::mutex::scoped_lock lock(_mutex);
+ _tick_rate = rate;
+ }
+
+private:
+ // This is the buffer type for response messages
+ struct resp_buff_type
+ {
+ boost::uint32_t data[8];
+ };
+
+ /*******************************************************************
+ * Primary control and interaction private methods
+ ******************************************************************/
+ inline void send_pkt(const boost::uint32_t addr, const boost::uint32_t data = 0)
+ {
+ managed_send_buffer::sptr buff = _ctrl_xport->get_send_buff(0.0);
+ if (not buff) {
+ throw uhd::runtime_error("fifo ctrl timed out getting a send buffer");
+ }
+ boost::uint32_t *pkt = buff->cast<boost::uint32_t *>();
+
+ //load packet info
+ vrt::if_packet_info_t packet_info;
+ packet_info.link_type = _link_type;
+ packet_info.packet_type = _packet_type;
+ packet_info.num_payload_words32 = 2;
+ packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t);
+ packet_info.packet_count = _seq_out;
+ packet_info.tsf = _time.to_ticks(_tick_rate);
+ packet_info.sob = false;
+ packet_info.eob = false;
+ packet_info.sid = _sid;
+ packet_info.has_sid = true;
+ packet_info.has_cid = false;
+ packet_info.has_tsi = false;
+ packet_info.has_tsf = _use_time;
+ packet_info.has_tlr = false;
+
+ //load header
+ if (_bige) vrt::if_hdr_pack_be(pkt, packet_info);
+ else vrt::if_hdr_pack_le(pkt, packet_info);
+
+ //load payload
+ pkt[packet_info.num_header_words32+0] = (_bige)? uhd::htonx(addr) : uhd::htowx(addr);
+ pkt[packet_info.num_header_words32+1] = (_bige)? uhd::htonx(data) : uhd::htowx(data);
+ //UHD_MSG(status) << boost::format("0x%08x, 0x%08x\n") % addr % data;
+ //send the buffer over the interface
+ _outstanding_seqs.push(_seq_out);
+ buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32));
+
+ _seq_out++;//inc seq for next call
+ }
+
+ UHD_INLINE boost::uint64_t wait_for_ack(const bool readback)
+ {
+ while (readback or (_outstanding_seqs.size() >= _resp_queue_size))
+ {
+ //get seq to ack from outstanding packets list
+ UHD_ASSERT_THROW(not _outstanding_seqs.empty());
+ const size_t seq_to_ack = _outstanding_seqs.front();
+ _outstanding_seqs.pop();
+
+ //parse the packet
+ vrt::if_packet_info_t packet_info;
+ resp_buff_type resp_buff;
+ memset(&resp_buff, 0x00, sizeof(resp_buff));
+ boost::uint32_t const *pkt = NULL;
+ managed_recv_buffer::sptr buff;
+
+ //get buffer from response endpoint - or die in timeout
+ if (_resp_xport)
+ {
+ buff = _resp_xport->get_recv_buff(_timeout);
+ try
+ {
+ UHD_ASSERT_THROW(bool(buff));
+ UHD_ASSERT_THROW(buff->size() > 0);
+ }
+ catch(const std::exception &ex)
+ {
+ throw uhd::io_error(str(boost::format("Block ctrl (%s) no response packet - %s") % _name % ex.what()));
+ }
+ pkt = buff->cast<const boost::uint32_t *>();
+ packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t);
+ }
+
+ //get buffer from response endpoint - or die in timeout
+ else
+ {
+ /*
+ * Couldn't get message with haste.
+ * Now check both possible queues for messages.
+ * Messages should come in on _resp_queue,
+ * but could end up in dump_queue.
+ * If we don't get a message --> Die in timeout.
+ */
+ double accum_timeout = 0.0;
+ const double short_timeout = 0.005; // == 5ms
+ while(not ((_resp_queue.pop_with_haste(resp_buff))
+ || (check_dump_queue(resp_buff))
+ || (_resp_queue.pop_with_timed_wait(resp_buff, short_timeout))
+ )){
+ /*
+ * If a message couldn't be received within a given timeout
+ * --> throw AssertionError!
+ */
+ accum_timeout += short_timeout;
+ UHD_ASSERT_THROW(accum_timeout < _timeout);
+ }
+
+ pkt = resp_buff.data;
+ packet_info.num_packet_words32 = sizeof(resp_buff)/sizeof(boost::uint32_t);
+ }
+
+ //parse the buffer
+ try
+ {
+ packet_info.link_type = _link_type;
+ if (_bige) vrt::chdr::if_hdr_unpack_be(pkt, packet_info);
+ else vrt::chdr::if_hdr_unpack_le(pkt, packet_info);
+ }
+ catch(const std::exception &ex)
+ {
+ UHD_MSG(error) << "[" << _name << "] Block ctrl bad VITA packet: " << ex.what() << std::endl;
+ if (buff){
+ UHD_MSG(status) << boost::format("%08X") % pkt[0] << std::endl;
+ UHD_MSG(status) << boost::format("%08X") % pkt[1] << std::endl;
+ UHD_MSG(status) << boost::format("%08X") % pkt[2] << std::endl;
+ UHD_MSG(status) << boost::format("%08X") % pkt[3] << std::endl;
+ }
+ else{
+ UHD_MSG(status) << "buff is NULL" << std::endl;
+ }
+ }
+
+ //check the buffer
+ try
+ {
+ UHD_ASSERT_THROW(packet_info.has_sid);
+ if (packet_info.sid != boost::uint32_t((_sid >> 16) | (_sid << 16))) {
+ throw uhd::io_error(
+ str(
+ boost::format("Expected SID: %s Received SID: %s")
+ % uhd::sid_t(_sid).reversed().to_pp_string_hex()
+ % uhd::sid_t(packet_info.sid).to_pp_string_hex()
+ )
+ );
+ }
+
+ if (packet_info.packet_count != (seq_to_ack & 0xfff)) {
+ throw uhd::io_error(
+ str(
+ boost::format("Expected packet index: %d Received index: %d")
+ % packet_info.packet_count
+ % (seq_to_ack & 0xfff)
+ )
+ );
+ }
+
+ UHD_ASSERT_THROW(packet_info.num_payload_words32 == 2);
+ //UHD_ASSERT_THROW(packet_info.packet_type == _packet_type);
+ }
+ catch(const std::exception &ex)
+ {
+ throw uhd::io_error(str(boost::format("Block ctrl (%s) packet parse error - %s") % _name % ex.what()));
+ }
+
+ //return the readback value
+ if (readback and _outstanding_seqs.empty())
+ {
+ const boost::uint64_t hi = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+0]) : uhd::wtohx(pkt[packet_info.num_header_words32+0]);
+ const boost::uint64_t lo = (_bige)? uhd::ntohx(pkt[packet_info.num_header_words32+1]) : uhd::wtohx(pkt[packet_info.num_header_words32+1]);
+ return ((hi << 32) | lo);
+ }
+ }
+
+ return 0;
+ }
+
+ /*
+ * If ctrl_core waits for a message that didn't arrive it can search for it in the dump queue.
+ * This actually happens during shutdown.
+ * handle_async_task can't access queue anymore thus it returns the corresponding message.
+ * msg_task class implements a dump_queue to store such messages.
+ * With check_dump_queue we can check if a message we are waiting for got stranded there.
+ * If a message got stuck we get it here and push it onto our own message_queue.
+ */
+ bool check_dump_queue(resp_buff_type& b) {
+ const size_t min_buff_size = 8; // Same value as in b200_io_impl->handle_async_task
+ boost::uint32_t recv_sid = (((_sid)<<16)|((_sid)>>16));
+ uhd::msg_task::msg_payload_t msg;
+ do{
+ msg = _async_task->get_msg_from_dump_queue(recv_sid);
+ }
+ while(msg.size() < min_buff_size && msg.size() != 0);
+
+ if(msg.size() >= min_buff_size) {
+ memcpy(b.data, &msg.front(), std::min(msg.size(), sizeof(b.data)));
+ return true;
+ }
+ return false;
+ }
+
+ void push_response(const boost::uint32_t *buff)
+ {
+ resp_buff_type resp_buff;
+ std::memcpy(resp_buff.data, buff, sizeof(resp_buff));
+ _resp_queue.push_with_haste(resp_buff);
+ }
+
+ void hold_task(uhd::msg_task::sptr task)
+ {
+ _async_task = task;
+ }
+
+ const vrt::if_packet_info_t::link_type_t _link_type;
+ const vrt::if_packet_info_t::packet_type_t _packet_type;
+ const bool _bige;
+ const uhd::transport::zero_copy_if::sptr _ctrl_xport;
+ const uhd::transport::zero_copy_if::sptr _resp_xport;
+ uhd::msg_task::sptr _async_task;
+ const boost::uint32_t _sid;
+ const std::string _name;
+ boost::mutex _mutex;
+ size_t _seq_out;
+ uhd::time_spec_t _time;
+ bool _use_time;
+ double _tick_rate;
+ double _timeout;
+ std::queue<size_t> _outstanding_seqs;
+ bounded_buffer<resp_buff_type> _resp_queue;
+ const size_t _resp_queue_size;
+
+ const size_t _rb_address;
+};
+
+ctrl_iface::sptr ctrl_iface::make(
+ const bool big_endian,
+ zero_copy_if::sptr ctrl_xport,
+ zero_copy_if::sptr resp_xport,
+ const boost::uint32_t sid,
+ const std::string &name
+) {
+ return sptr(new ctrl_iface_impl(
+ big_endian, ctrl_xport, resp_xport, sid, name
+ ));
+}
diff --git a/host/lib/rfnoc/ctrl_iface.hpp b/host/lib/rfnoc/ctrl_iface.hpp
new file mode 100644
index 000000000..4141b6583
--- /dev/null
+++ b/host/lib/rfnoc/ctrl_iface.hpp
@@ -0,0 +1,68 @@
+//
+// Copyright 2012-2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP
+#define INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP
+
+#include <uhd/utils/msg_task.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <uhd/transport/zero_copy.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <string>
+
+namespace uhd { namespace rfnoc {
+
+/*!
+ * Provide access to peek, poke for the radio ctrl module
+ */
+class ctrl_iface : public uhd::timed_wb_iface
+{
+public:
+ typedef boost::shared_ptr<ctrl_iface> sptr;
+
+ virtual ~ctrl_iface(void) = 0;
+
+ //! Make a new control object
+ static sptr make(
+ const bool big_endian,
+ uhd::transport::zero_copy_if::sptr ctrl_xport,
+ uhd::transport::zero_copy_if::sptr resp_xport,
+ const boost::uint32_t sid,
+ const std::string &name = "0"
+ );
+
+ //! Hold a ref to a task thats feeding push response
+ virtual void hold_task(uhd::msg_task::sptr task) = 0;
+
+ //! Push a response externall (resp_xport is NULL)
+ virtual void push_response(const boost::uint32_t *buff) = 0;
+
+ //! Set the command time that will activate
+ virtual void set_time(const uhd::time_spec_t &time) = 0;
+
+ //! Get the command time that will activate
+ virtual uhd::time_spec_t get_time(void) = 0;
+
+ //! Set the tick rate (converting time into ticks)
+ virtual void set_tick_rate(const double rate) = 0;
+};
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_CTRL_IFACE_HPP */
diff --git a/host/lib/rfnoc/ddc_block_ctrl_impl.cpp b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp
new file mode 100644
index 000000000..2aac22ca4
--- /dev/null
+++ b/host/lib/rfnoc/ddc_block_ctrl_impl.cpp
@@ -0,0 +1,281 @@
+//
+// Copyright 2016 Ettus Research
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "dsp_core_utils.hpp"
+#include <uhd/rfnoc/ddc_block_ctrl.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/convert.hpp>
+#include <uhd/types/ranges.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <cmath>
+
+using namespace uhd::rfnoc;
+
+// TODO move this to a central location
+template <class T> T ceil_log2(T num){
+ return std::ceil(std::log(num)/std::log(T(2)));
+}
+
+// TODO remove this once we have actual lambdas
+static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value)
+{
+ return tree->access<double>(prop).set(value).get();
+}
+
+static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop)
+{
+ return tree->access<double>(prop).get();
+}
+
+class ddc_block_ctrl_impl : public ddc_block_ctrl
+{
+public:
+ static const size_t NUM_HALFBANDS = 3;
+ static const size_t CIC_MAX_DECIM = 255;
+
+ UHD_RFNOC_BLOCK_CONSTRUCTOR(ddc_block_ctrl)
+ {
+ // Argument/prop tree hooks
+ for (size_t chan = 0; chan < get_input_ports().size(); chan++) {
+ double default_freq = get_arg<double>("freq", chan);
+ _tree->access<double>(get_arg_path("freq/value", chan))
+ .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_freq, this, _1, chan))
+ .set(default_freq);
+ ;
+ double default_output_rate = get_arg<double>("output_rate", chan);
+ _tree->access<double>(get_arg_path("output_rate/value", chan))
+ .set_coercer(boost::bind(&ddc_block_ctrl_impl::set_output_rate, this, _1, chan))
+ .set(default_output_rate)
+ ;
+ _tree->access<double>(get_arg_path("input_rate/value", chan))
+ .add_coerced_subscriber(boost::bind(&ddc_block_ctrl_impl::set_input_rate, this, _1, chan))
+ ;
+
+ // Legacy properties (for backward compat w/ multi_usrp)
+ const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan;
+ // Legacy properties
+ _tree->create<double>(dsp_base_path / "rate/value")
+ .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan), _1))
+ .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("output_rate/value", chan)))
+ ;
+ _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range")
+ .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_output_rates, this))
+ ;
+ _tree->create<double>(dsp_base_path / "freq/value")
+ .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1))
+ .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan)))
+ ;
+ _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range")
+ .set_publisher(boost::bind(&ddc_block_ctrl_impl::get_freq_range, this))
+ ;
+ _tree->access<uhd::time_spec_t>("time/cmd")
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan))
+ ;
+ if (_tree->exists("tick_rate")) {
+ const double tick_rate = _tree->access<double>("tick_rate").get();
+ set_command_tick_rate(tick_rate, chan);
+ _tree->access<double>("tick_rate")
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan))
+ ;
+ }
+
+ // Rate 1:1 by default
+ sr_write("N", 1, chan);
+ sr_write("M", 1, chan);
+ sr_write("CONFIG", 1, chan); // Enable clear EOB
+ }
+ } // end ctor
+ virtual ~ddc_block_ctrl_impl() {};
+
+ double get_output_scale_factor(size_t port=ANY_PORT)
+ {
+ port = port == ANY_PORT ? 0 : port;
+ if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) {
+ return SCALE_UNDEFINED;
+ }
+ return get_arg<double>("scalar_correction", port);
+ }
+
+ double get_input_samp_rate(size_t port=ANY_PORT)
+ {
+ port = port == ANY_PORT ? 0 : port;
+ if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) {
+ return RATE_UNDEFINED;
+ }
+ return get_arg<double>("input_rate", port);
+ }
+
+ double get_output_samp_rate(size_t port=ANY_PORT)
+ {
+ port = port == ANY_PORT ? 0 : port;
+ if (not (_rx_streamer_active.count(port) and _rx_streamer_active.at(port))) {
+ return RATE_UNDEFINED;
+ }
+ return get_arg<double>("output_rate", port);
+ }
+
+
+ void issue_stream_cmd(
+ const uhd::stream_cmd_t &stream_cmd_,
+ const size_t chan
+ ) {
+ UHD_RFNOC_BLOCK_TRACE() << "ddc_block_ctrl_base::issue_stream_cmd()" << std::endl;
+
+ if (list_upstream_nodes().count(chan) == 0) {
+ UHD_MSG(status) << "No upstream blocks." << std::endl;
+ return;
+ }
+
+ uhd::stream_cmd_t stream_cmd = stream_cmd_;
+ if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or
+ stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) {
+ size_t decimation = get_arg<double>("input_rate", chan) / get_arg<double>("output_rate", chan);
+ stream_cmd.num_samps *= decimation;
+ }
+
+ source_node_ctrl::sptr this_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(chan).lock());
+ if (this_upstream_block_ctrl) {
+ this_upstream_block_ctrl->issue_stream_cmd(
+ stream_cmd,
+ get_upstream_port(chan)
+ );
+ }
+ }
+
+private:
+
+ //! Set the CORDIC frequency shift the signal to \p requested_freq
+ double set_freq(const double requested_freq, const size_t chan)
+ {
+ const double input_rate = get_arg<double>("input_rate");
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq, input_rate, actual_freq, freq_word);
+ sr_write("CORDIC_FREQ", uint32_t(freq_word), chan);
+ return actual_freq;
+ }
+
+ //! Return a range of valid frequencies the CORDIC can tune to
+ uhd::meta_range_t get_freq_range(void)
+ {
+ const double input_rate = get_arg<double>("input_rate");
+ return uhd::meta_range_t(
+ -input_rate/2,
+ +input_rate/2,
+ input_rate/std::pow(2.0, 32)
+ );
+ }
+
+ // FIXME this misses a whole bunch of valid rates. Anything with CIC decim <= 255
+ // is OK.
+ uhd::meta_range_t get_output_rates(void)
+ {
+ uhd::meta_range_t range;
+ const double input_rate = get_arg<double>("input_rate");
+ for (int decim = 1024; decim > 512; decim -= 8){
+ range.push_back(uhd::range_t(input_rate/decim));
+ }
+ for (int decim = 512; decim > 256; decim -= 4){
+ range.push_back(uhd::range_t(input_rate/decim));
+ }
+ for (int decim = 256; decim > 128; decim -= 2){
+ range.push_back(uhd::range_t(input_rate/decim));
+ }
+ for (int decim = 128; decim >= 1; decim -= 1){
+ range.push_back(uhd::range_t(input_rate/decim));
+ }
+ return range;
+ }
+
+ double set_output_rate(const int requested_rate, const size_t chan)
+ {
+ const double input_rate = get_arg<double>("input_rate");
+ const size_t decim_rate = boost::math::iround(input_rate/this->get_output_rates().clip(requested_rate, true));
+ size_t decim = decim_rate;
+
+ // The FPGA knows which halfbands to enable for any given value of hb_enable.
+ uint32_t hb_enable = 0;
+ while ((decim % 2 == 0) and hb_enable < NUM_HALFBANDS) {
+ hb_enable++;
+ decim /= 2;
+ }
+ UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS);
+ UHD_ASSERT_THROW(decim <= CIC_MAX_DECIM);
+ // What we can't cover with halfbands, we do with the CIC
+ sr_write("DECIM_WORD", (hb_enable << 8) | (decim & 0xff), chan);
+
+ // Rate change = M/N
+ sr_write("N", std::pow(2.0, double(hb_enable)) * (decim & 0xff), chan);
+ sr_write("M", 1, chan);
+
+ if (decim > 1 and hb_enable == 0) {
+ UHD_MSG(warning) << boost::format(
+ "The requested decimation is odd; the user should expect passband CIC rolloff.\n"
+ "Select an even decimation to ensure that a halfband filter is enabled.\n"
+ "Decimations factorable by 4 will enable 2 halfbands, those factorable by 8 will enable 3 halfbands.\n"
+ "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n"
+ ) % decim_rate % (input_rate/1e6) % (requested_rate/1e6);
+ }
+
+ // Caclulate algorithmic gain of CIC for a given decimation.
+ // For Ettus CIC R=decim, M=1, N=4. Gain = (R * M) ^ N
+ const double rate_pow = std::pow(double(decim & 0xff), 4);
+ // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account
+ // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation).
+ // CORDIC algorithmic gain limits asymptotically around 1.647 after many iterations.
+ static const double CORDIC_GAIN = 1.648;
+ //
+ // The polar rotation of [I,Q] = [1,1] by Pi/8 also yields max magnitude of SQRT(2) (~1.4142) however
+ // input to the CORDIC thats outside the unit circle can only be sourced from a saturated RF frontend.
+ // To provide additional dynamic range head room accordingly using scale factor applied at egress from DDC would
+ // cost us small signal performance, thus we do no provide compensation gain for a saturated front end and allow
+ // the signal to clip in the H/W as needed. If we wished to avoid the signal clipping in these circumstances then adjust code to read:
+ // _scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow*1.415);
+ const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CORDIC_GAIN*rate_pow);
+ update_scalar(scaling_adjustment, chan);
+ return input_rate/decim_rate;
+ }
+
+ //! Set frequency and decimation again
+ void set_input_rate(const double /* rate */, const size_t chan)
+ {
+ const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired();
+ set_arg<double>("freq", desired_freq, chan);
+ const double desired_output_rate = _tree->access<double>(get_arg_path("output_rate", chan) / "value").get_desired();
+ set_arg<double>("output_rate", desired_output_rate, chan);
+ }
+
+ // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account
+ // gain compensation blocks already hardcoded in place in DDC (that provide simple 1/2^n gain compensation).
+ // Further more factor in OTW format which adds further gain factor to weight output samples correctly.
+ void update_scalar(const double scalar, const size_t chan)
+ {
+ const double target_scalar = (1 << 15) * scalar;
+ const int32_t actual_scalar = boost::math::iround(target_scalar);
+ // Calculate the error introduced by using integer representation for the scalar, can be corrected in host later.
+ const double scalar_correction =
+ target_scalar / actual_scalar / double(1 << 15) // Rounding error, normalized to 1.0
+ * get_arg<double>("fullscale"); // Scaling requested by host
+ set_arg<double>("scalar_correction", scalar_correction, chan);
+ // Write DDC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits.
+ sr_write("SCALE_IQ", actual_scalar, chan);
+ }
+
+};
+
+UHD_RFNOC_BLOCK_REGISTER(ddc_block_ctrl, "DDC");
diff --git a/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp
new file mode 100644
index 000000000..5f476074b
--- /dev/null
+++ b/host/lib/rfnoc/dma_fifo_block_ctrl_impl.cpp
@@ -0,0 +1,122 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/dma_fifo_block_ctrl.hpp>
+#include "dma_fifo_core_3000.hpp"
+#include "wb_iface_adapter.hpp"
+#include <uhd/convert.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread/mutex.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+//TODO (Ashish): This should come from the framework
+static const double BUS_CLK_RATE = 166.67e6;
+
+class dma_fifo_block_ctrl_impl : public dma_fifo_block_ctrl
+{
+public:
+ static const uint32_t DEFAULT_SIZE = 32*1024*1024;
+
+ UHD_RFNOC_BLOCK_CONSTRUCTOR(dma_fifo_block_ctrl)
+ {
+ _perifs.resize(get_input_ports().size());
+ for(size_t i = 0; i < _perifs.size(); i++) {
+ _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>(
+ // poke32 functor
+ boost::bind(
+ static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write),
+ this, _1, _2, i
+ ),
+ // peek32 functor
+ boost::bind(
+ static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32),
+ this,
+ _1, i
+ ),
+ // peek64 functor
+ boost::bind(
+ static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64),
+ this,
+ _1, i
+ )
+ );
+ static const uint32_t USER_SR_BASE = 128*4;
+ static const uint32_t USER_RB_BASE = 0; //Don't care
+ _perifs[i].base_addr = DEFAULT_SIZE*i;
+ _perifs[i].depth = DEFAULT_SIZE;
+ _perifs[i].core = dma_fifo_core_3000::make(_perifs[i].ctrl, USER_SR_BASE, USER_RB_BASE);
+ _perifs[i].core->resize(_perifs[i].base_addr, _perifs[i].depth);
+ UHD_MSG(status) << boost::format("[DMA FIFO] Running BIST for FIFO %d... ") % i;
+ if (_perifs[i].core->ext_bist_supported()) {
+ boost::uint32_t bisterr = _perifs[i].core->run_bist();
+ if (bisterr != 0) {
+ throw uhd::runtime_error(str(boost::format("BIST failed! (code: %d)\n") % bisterr));
+ } else {
+ double throughput = _perifs[i].core->get_bist_throughput(BUS_CLK_RATE);
+ UHD_MSG(status) << (boost::format("pass (Throughput: %.1fMB/s)") % (throughput/1e6)) << std::endl;
+ }
+ } else {
+ if (_perifs[i].core->run_bist() == 0) {
+ UHD_MSG(status) << "pass\n";
+ } else {
+ throw uhd::runtime_error("BIST failed!\n");
+ }
+ }
+ _tree->access<int>(get_arg_path("base_addr/value", i))
+ .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, _1, boost::ref(_perifs[i].depth), i))
+ .set(_perifs[i].base_addr)
+ ;
+ _tree->access<int>(get_arg_path("depth/value", i))
+ .add_coerced_subscriber(boost::bind(&dma_fifo_block_ctrl_impl::resize, this, boost::ref(_perifs[i].base_addr), _1, i))
+ .set(_perifs[i].depth)
+ ;
+ }
+ }
+
+ void resize(const uint32_t base_addr, const uint32_t depth, const size_t chan) {
+ boost::lock_guard<boost::mutex> lock(_config_mutex);
+ _perifs[chan].base_addr = base_addr;
+ _perifs[chan].depth = depth;
+ _perifs[chan].core->resize(base_addr, depth);
+ }
+
+ uint32_t get_base_addr(const size_t chan) const {
+ return _perifs[chan].base_addr;
+ }
+
+ uint32_t get_depth(const size_t chan) const {
+ return _perifs[chan].depth;
+ }
+
+private:
+ struct fifo_perifs_t
+ {
+ wb_iface::sptr ctrl;
+ dma_fifo_core_3000::sptr core;
+ uint32_t base_addr;
+ uint32_t depth;
+ };
+ std::vector<fifo_perifs_t> _perifs;
+
+ boost::mutex _config_mutex;
+};
+
+UHD_RFNOC_BLOCK_REGISTER(dma_fifo_block_ctrl, "DmaFIFO");
diff --git a/host/lib/rfnoc/duc_block_ctrl_impl.cpp b/host/lib/rfnoc/duc_block_ctrl_impl.cpp
new file mode 100644
index 000000000..0340ba0d6
--- /dev/null
+++ b/host/lib/rfnoc/duc_block_ctrl_impl.cpp
@@ -0,0 +1,268 @@
+//
+// Copyright 2016 Ettus Research
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "dsp_core_utils.hpp"
+#include <uhd/rfnoc/duc_block_ctrl.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/convert.hpp>
+#include <uhd/types/ranges.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <cmath>
+
+using namespace uhd::rfnoc;
+
+// TODO move this to a central location
+template <class T> T ceil_log2(T num){
+ return std::ceil(std::log(num)/std::log(T(2)));
+}
+
+// TODO remove this once we have actual lambdas
+static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop, double value)
+{
+ return tree->access<double>(prop).set(value).get();
+}
+
+static double lambda_forward_prop(uhd::property_tree::sptr tree, uhd::fs_path prop)
+{
+ return tree->access<double>(prop).get();
+}
+
+class duc_block_ctrl_impl : public duc_block_ctrl
+{
+public:
+ static const size_t NUM_HALFBANDS = 2;
+ static const size_t CIC_MAX_INTERP = 128;
+
+ UHD_RFNOC_BLOCK_CONSTRUCTOR(duc_block_ctrl)
+ {
+ // Argument/prop tree hooks
+ for (size_t chan = 0; chan < get_input_ports().size(); chan++) {
+ double default_freq = get_arg<double>("freq", chan);
+ _tree->access<double>(get_arg_path("freq/value", chan))
+ .set_coercer(boost::bind(&duc_block_ctrl_impl::set_freq, this, _1, chan))
+ .set(default_freq);
+ ;
+ double default_input_rate = get_arg<double>("input_rate", chan);
+ _tree->access<double>(get_arg_path("input_rate/value", chan))
+ .set_coercer(boost::bind(&duc_block_ctrl_impl::set_input_rate, this, _1, chan))
+ .set(default_input_rate)
+ ;
+ _tree->access<double>(get_arg_path("output_rate/value", chan))
+ .add_coerced_subscriber(boost::bind(&duc_block_ctrl_impl::set_output_rate, this, _1, chan))
+ ;
+
+ // Legacy properties (for backward compat w/ multi_usrp)
+ const uhd::fs_path dsp_base_path = _root_path / "legacy_api" / chan;
+ // Legacy properties
+ _tree->create<double>(dsp_base_path / "rate/value")
+ .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan), _1))
+ .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("input_rate/value", chan)))
+ ;
+ _tree->create<uhd::meta_range_t>(dsp_base_path / "rate/range")
+ .set_publisher(boost::bind(&duc_block_ctrl_impl::get_input_rates, this))
+ ;
+ _tree->create<double>(dsp_base_path / "freq/value")
+ .set_coercer(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan), _1))
+ .set_publisher(boost::bind(&lambda_forward_prop, _tree, get_arg_path("freq/value", chan)))
+ ;
+ _tree->create<uhd::meta_range_t>(dsp_base_path / "freq/range")
+ .set_publisher(boost::bind(&duc_block_ctrl_impl::get_freq_range, this))
+ ;
+ _tree->access<uhd::time_spec_t>("time/cmd")
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, chan))
+ ;
+ if (_tree->exists("tick_rate")) {
+ const double tick_rate = _tree->access<double>("tick_rate").get();
+ set_command_tick_rate(tick_rate, chan);
+ _tree->access<double>("tick_rate")
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, _1, chan))
+ ;
+ }
+
+ // Rate 1:1 by default
+ sr_write("N", 1, chan);
+ sr_write("M", 1, chan);
+ sr_write("CONFIG", 1, chan); // Enable clear EOB
+ }
+ } // end ctor
+ virtual ~duc_block_ctrl_impl() {};
+
+ double get_input_scale_factor(size_t port=ANY_PORT)
+ {
+ port = (port == ANY_PORT) ? 0 : port;
+ if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) {
+ return SCALE_UNDEFINED;
+ }
+ return get_arg<double>("scalar_correction", port);
+ }
+
+ double get_input_samp_rate(size_t port=ANY_PORT)
+ {
+ port = (port == ANY_PORT) ? 0 : port;
+ if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) {
+ return RATE_UNDEFINED;
+ }
+ return get_arg<double>("input_rate", port);
+ }
+
+ double get_output_samp_rate(size_t port=ANY_PORT)
+ {
+ port = (port == ANY_PORT) ? 0 : port;
+ if (not (_tx_streamer_active.count(port) and _tx_streamer_active.at(port))) {
+ return RATE_UNDEFINED;
+ }
+ return get_arg<double>("output_rate", port == ANY_PORT ? 0 : port);
+ }
+
+ void issue_stream_cmd(
+ const uhd::stream_cmd_t &stream_cmd_,
+ const size_t chan
+ ) {
+ UHD_RFNOC_BLOCK_TRACE() << "duc_block_ctrl_base::issue_stream_cmd()" << std::endl;
+
+ uhd::stream_cmd_t stream_cmd = stream_cmd_;
+ if (stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE or
+ stream_cmd.stream_mode == uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE) {
+ size_t interpolation = get_arg<double>("output_rate", chan) / get_arg<double>("input_rate", chan);
+ stream_cmd.num_samps *= interpolation;
+ }
+
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) {
+ source_node_ctrl::sptr this_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock());
+ this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan);
+ }
+ }
+
+private:
+
+ //! Set the CORDIC frequency shift the signal to \p requested_freq
+ double set_freq(const double requested_freq, const size_t chan)
+ {
+ const double output_rate = get_arg<double>("output_rate");
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq, output_rate, actual_freq, freq_word);
+ // Xilinx CORDIC uses a different format for the phase increment, hence the divide-by-four:
+ sr_write("CORDIC_FREQ", uint32_t(freq_word/4), chan);
+ return actual_freq;
+ }
+
+ //! Return a range of valid frequencies the CORDIC can tune to
+ uhd::meta_range_t get_freq_range(void)
+ {
+ const double output_rate = get_arg<double>("output_rate");
+ return uhd::meta_range_t(
+ -output_rate/2,
+ +output_rate/2,
+ output_rate/std::pow(2.0, 32)
+ );
+ }
+
+ uhd::meta_range_t get_input_rates(void)
+ {
+ uhd::meta_range_t range;
+ const double output_rate = get_arg<double>("output_rate");
+ for (int rate = 512; rate > 256; rate -= 4){
+ range.push_back(uhd::range_t(output_rate/rate));
+ }
+ for (int rate = 256; rate > 128; rate -= 2){
+ range.push_back(uhd::range_t(output_rate/rate));
+ }
+ for (int rate = 128; rate >= 1; rate -= 1){
+ range.push_back(uhd::range_t(output_rate/rate));
+ }
+ return range;
+ }
+
+ double set_input_rate(const int requested_rate, const size_t chan)
+ {
+ const double output_rate = get_arg<double>("output_rate", chan);
+ const size_t interp_rate = boost::math::iround(output_rate/get_input_rates().clip(requested_rate, true));
+ size_t interp = interp_rate;
+
+ uint32_t hb_enable = 0;
+ while ((interp % 2 == 0) and hb_enable < NUM_HALFBANDS) {
+ hb_enable++;
+ interp /= 2;
+ }
+ UHD_ASSERT_THROW(hb_enable <= NUM_HALFBANDS);
+ UHD_ASSERT_THROW(interp > 0 and interp <= CIC_MAX_INTERP);
+ // hacky hack: Unlike the DUC, the DUC actually simply has 2
+ // flags to enable either halfband.
+ uint32_t hb_enable_word = hb_enable;
+ if (hb_enable == 2) {
+ hb_enable_word = 3;
+ }
+ hb_enable_word <<= 8;
+ // What we can't cover with halfbands, we do with the CIC
+ sr_write("INTERP_WORD", hb_enable_word | (interp & 0xff), chan);
+
+ // Rate change = M/N
+ sr_write("N", 1, chan);
+ sr_write("M", std::pow(2.0, double(hb_enable)) * (interp & 0xff), chan);
+
+ if (interp > 1 and hb_enable == 0) {
+ UHD_MSG(warning) << boost::format(
+ "The requested interpolation is odd; the user should expect passband CIC rolloff.\n"
+ "Select an even interpolation to ensure that a halfband filter is enabled.\n"
+ "interpolation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n"
+ ) % interp_rate % (output_rate/1e6) % (requested_rate/1e6);
+ }
+
+ // Calculate algorithmic gain of CIC for a given interpolation
+ // For Ettus CIC R=interp, M=1, N=4. Gain = (R * M) ^ (N - 1)
+ const int CIC_N = 4;
+ const double rate_pow = std::pow(double(interp & 0xff), CIC_N - 1);
+
+ // Experimentally determined value to scale the output to [-1, 1]
+ // This must also encompass the CORDIC gain
+ static const double CONSTANT_GAIN = 1.1644;
+
+ const double scaling_adjustment = std::pow(2, ceil_log2(rate_pow))/(CONSTANT_GAIN*rate_pow);
+ update_scalar(scaling_adjustment, chan);
+ return output_rate/interp_rate;
+ }
+
+ //! Set frequency and interpolation again
+ void set_output_rate(const double /* rate */, const size_t chan)
+ {
+ const double desired_freq = _tree->access<double>(get_arg_path("freq", chan) / "value").get_desired();
+ set_arg<double>("freq", desired_freq, chan);
+ const double desired_input_rate = _tree->access<double>(get_arg_path("input_rate", chan) / "value").get_desired();
+ set_arg<double>("input_rate", desired_input_rate, chan);
+ }
+
+ // Calculate compensation gain values for algorithmic gain of CORDIC and CIC taking into account
+ // gain compensation blocks already hardcoded in place in DUC (that provide simple 1/2^n gain compensation).
+ // Further more factor in OTW format which adds further gain factor to weight output samples correctly.
+ void update_scalar(const double scalar, const size_t chan)
+ {
+ const double target_scalar = (1 << 15) * scalar;
+ const int32_t actual_scalar = boost::math::iround(target_scalar);
+ // Calculate the error introduced by using integer representation for the scalar
+ const double scalar_correction =
+ actual_scalar / target_scalar * (double(1 << 15) - 1.0) // Rounding error, normalized to 1.0
+ * get_arg<double>("fullscale"); // Scaling requested by host
+ set_arg<double>("scalar_correction", scalar_correction, chan);
+ // Write DUC with scaling correction for CIC and CORDIC that maximizes dynamic range in 32/16/12/8bits.
+ sr_write("SCALE_IQ", actual_scalar, chan);
+ }
+};
+
+UHD_RFNOC_BLOCK_REGISTER(duc_block_ctrl, "DUC");
+
diff --git a/host/lib/rfnoc/graph_impl.cpp b/host/lib/rfnoc/graph_impl.cpp
new file mode 100644
index 000000000..64c6f6abe
--- /dev/null
+++ b/host/lib/rfnoc/graph_impl.cpp
@@ -0,0 +1,164 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "graph_impl.hpp"
+#include <uhd/rfnoc/source_block_ctrl_base.hpp>
+#include <uhd/rfnoc/sink_block_ctrl_base.hpp>
+#include <uhd/utils/msg.hpp>
+
+using namespace uhd::rfnoc;
+
+/****************************************************************************
+ * Structors
+ ***************************************************************************/
+graph_impl::graph_impl(
+ const std::string &name,
+ boost::weak_ptr<uhd::device3> device_ptr
+ //async_msg_handler::sptr msg_handler
+) : _name(name)
+ , _device_ptr(device_ptr)
+{
+
+}
+
+
+/****************************************************************************
+ * Connection API
+ ***************************************************************************/
+void graph_impl::connect(
+ const block_id_t &src_block,
+ size_t src_block_port,
+ const block_id_t &dst_block,
+ size_t dst_block_port,
+ const size_t pkt_size_
+) {
+ device3::sptr device_ptr = _device_ptr.lock();
+ if (not device_ptr) {
+ throw uhd::runtime_error("Invalid device");
+ }
+
+ uhd::rfnoc::source_block_ctrl_base::sptr src = device_ptr->get_block_ctrl<rfnoc::source_block_ctrl_base>(src_block);
+ uhd::rfnoc::sink_block_ctrl_base::sptr dst = device_ptr->get_block_ctrl<rfnoc::sink_block_ctrl_base>(dst_block);
+
+ /********************************************************************
+ * 1. Draw the edges (logically connect the nodes)
+ ********************************************************************/
+ size_t actual_src_block_port = src->connect_downstream(
+ boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(dst),
+ src_block_port
+ );
+ if (src_block_port == uhd::rfnoc::ANY_PORT) {
+ src_block_port = actual_src_block_port;
+ } else if (src_block_port != actual_src_block_port) {
+ throw uhd::runtime_error(str(
+ boost::format("Can't connect to port %d on block %s.")
+ % src_block_port % src->unique_id()
+ ));
+ }
+ size_t actual_dst_block_port = dst->connect_upstream(
+ boost::dynamic_pointer_cast<uhd::rfnoc::node_ctrl_base>(src),
+ dst_block_port
+ );
+ if (dst_block_port == uhd::rfnoc::ANY_PORT) {
+ dst_block_port = actual_dst_block_port;
+ } else if (dst_block_port != actual_dst_block_port) {
+ throw uhd::runtime_error(str(
+ boost::format("Can't connect to port %d on block %s.")
+ % dst_block_port % dst->unique_id()
+ ));
+ }
+ src->set_downstream_port(actual_src_block_port, actual_dst_block_port);
+ dst->set_upstream_port(actual_dst_block_port, actual_src_block_port);
+ // At this point, ports are locked and no one else can simply connect
+ // into them.
+ //UHD_MSG(status)
+ //<< "[" << _name << "] Connecting "
+ //<< src_block << ":" << actual_src_block_port << " --> "
+ //<< dst_block << ":" << actual_dst_block_port << std::endl;
+
+ /********************************************************************
+ * 2. Check IO signatures match
+ ********************************************************************/
+ if (not rfnoc::stream_sig_t::is_compatible(
+ src->get_output_signature(actual_src_block_port),
+ dst->get_input_signature(actual_dst_block_port)
+ )) {
+ throw uhd::runtime_error(str(
+ boost::format("Can't connect block %s to %s: IO signature mismatch\n(%s is incompatible with %s).")
+ % src->get_block_id().get() % dst->get_block_id().get()
+ % src->get_output_signature(actual_src_block_port)
+ % dst->get_input_signature(actual_dst_block_port)
+ ));
+ }
+
+ /********************************************************************
+ * 3. Configure the source block's destination
+ ********************************************************************/
+ // Calculate SID
+ sid_t sid = dst->get_address(dst_block_port);
+ sid.set_src(src->get_address(src_block_port));
+
+ // Set SID on source block
+ src->set_destination(sid.get(), src_block_port);
+
+ /********************************************************************
+ * 4. Configure flow control
+ ********************************************************************/
+ size_t pkt_size = (pkt_size_ != 0) ? pkt_size_ : src->get_output_signature(src_block_port).packet_size;
+ if (pkt_size == 0) { // Unspecified packet rate. Assume max packet size.
+ UHD_MSG(status) << "Assuming max packet size for " << src->get_block_id() << std::endl;
+ pkt_size = uhd::rfnoc::MAX_PACKET_SIZE;
+ }
+ // FC window (in packets) depends on FIFO size... ...and packet size.
+ size_t buf_size_pkts = dst->get_fifo_size(dst_block_port) / pkt_size;
+ if (buf_size_pkts == 0) {
+ throw uhd::runtime_error(str(
+ boost::format("Input FIFO for block %s is too small (%d kiB) for packets of size %d kiB\n"
+ "coming from block %s.")
+ % dst->get_block_id().get() % (dst->get_fifo_size(dst_block_port) / 1024)
+ % (pkt_size / 1024) % src->get_block_id().get()
+ ));
+ }
+ src->configure_flow_control_out(buf_size_pkts, src_block_port);
+ // On the same crossbar, use lots of FC packets
+ size_t pkts_per_ack = std::min(
+ uhd::rfnoc::DEFAULT_FC_XBAR_PKTS_PER_ACK,
+ buf_size_pkts - 1
+ );
+ // Over the network, use less or we'd flood the transport
+ if (sid.get_src_addr() != sid.get_dst_addr()) {
+ pkts_per_ack = std::max<size_t>(buf_size_pkts / uhd::rfnoc::DEFAULT_FC_TX_RESPONSE_FREQ, 1);
+ }
+ dst->configure_flow_control_in(
+ 0, // Default to not use cycles
+ pkts_per_ack,
+ dst_block_port
+ );
+
+ /********************************************************************
+ * 5. Configure error policy
+ ********************************************************************/
+ dst->set_error_policy("next_burst");
+}
+
+void graph_impl::connect(
+ const block_id_t &src_block,
+ const block_id_t &dst_block
+) {
+ connect(src_block, ANY_PORT, dst_block, ANY_PORT);
+}
+
diff --git a/host/lib/rfnoc/graph_impl.hpp b/host/lib/rfnoc/graph_impl.hpp
new file mode 100644
index 000000000..12dbf6357
--- /dev/null
+++ b/host/lib/rfnoc/graph_impl.hpp
@@ -0,0 +1,76 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP
+#define INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP
+
+#include <uhd/rfnoc/graph.hpp>
+#include <uhd/device3.hpp>
+
+namespace uhd { namespace rfnoc {
+
+class graph_impl : public graph
+{
+public:
+ /*!
+ * \param name An optional name to describe this graph
+ * \param device_ptr Weak pointer to the originating device3
+ * \param msg_handler Pointer to the async message handler
+ */
+ graph_impl(
+ const std::string &name,
+ boost::weak_ptr<uhd::device3> device_ptr
+ //async_msg_handler::sptr msg_handler
+ );
+ virtual ~graph_impl() {};
+
+ /************************************************************************
+ * Connection API
+ ***********************************************************************/
+ void connect(
+ const block_id_t &src_block,
+ size_t src_block_port,
+ const block_id_t &dst_block,
+ size_t dst_block_port,
+ const size_t pkt_size = 0
+ );
+
+ void connect(
+ const block_id_t &src_block,
+ const block_id_t &dst_block
+ );
+
+ /************************************************************************
+ * Utilities
+ ***********************************************************************/
+ std::string get_name() const { return _name; }
+
+
+private:
+
+ //! Optional: A string to describe this graph
+ const std::string _name;
+
+ //! Reference to the generating device object
+ const boost::weak_ptr<uhd::device3> _device_ptr;
+
+};
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_GRAPH_IMPL_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/legacy_compat.cpp b/host/lib/rfnoc/legacy_compat.cpp
new file mode 100644
index 000000000..843cdea34
--- /dev/null
+++ b/host/lib/rfnoc/legacy_compat.cpp
@@ -0,0 +1,720 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "legacy_compat.hpp"
+#include <uhd/property_tree.hpp>
+#include <uhd/rfnoc/radio_ctrl.hpp>
+#include <uhd/rfnoc/ddc_block_ctrl.hpp>
+#include <uhd/rfnoc/graph.hpp>
+#include <uhd/usrp/subdev_spec.hpp>
+#include <uhd/stream.hpp>
+#include <uhd/types/stream_cmd.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/transport/chdr.hpp>
+#include <boost/make_shared.hpp>
+
+#define UHD_LEGACY_LOG() UHD_LOGV(never)
+
+using namespace uhd::rfnoc;
+using uhd::usrp::subdev_spec_t;
+using uhd::usrp::subdev_spec_pair_t;
+using uhd::stream_cmd_t;
+
+/************************************************************************
+ * Constants
+ ***********************************************************************/
+static const std::string RADIO_BLOCK_NAME = "Radio";
+static const std::string DFIFO_BLOCK_NAME = "DmaFIFO";
+static const std::string DDC_BLOCK_NAME = "DDC";
+static const std::string DUC_BLOCK_NAME = "DUC";
+static const size_t MAX_BYTES_PER_HEADER =
+ uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t);
+static const size_t BYTES_PER_SAMPLE = 4; // We currently only support sc16
+
+/************************************************************************
+ * Static helpers
+ ***********************************************************************/
+static uhd::fs_path mb_root(const size_t mboard)
+{
+ return uhd::fs_path("/mboards") / mboard;
+}
+
+size_t num_ports(const uhd::property_tree::sptr &tree, const std::string &block_name, const std::string &in_out)
+{
+ return tree->list(
+ uhd::fs_path("/mboards/0/xbar") /
+ str(boost::format("%s_0") % block_name) /
+ "ports" / in_out
+ ).size();
+}
+
+size_t calc_num_tx_chans_per_radio(
+ const uhd::property_tree::sptr &tree,
+ const size_t num_radios_per_board,
+ const bool has_ducs,
+ const bool has_dmafifo
+) {
+ const size_t num_radio_ports = num_ports(tree, RADIO_BLOCK_NAME, "in");
+ if (has_ducs) {
+ return std::min(
+ num_radio_ports,
+ num_ports(tree, DUC_BLOCK_NAME, "in")
+ );
+ }
+
+ if (not has_dmafifo) {
+ return num_radio_ports;
+ }
+
+ const size_t num_dmafifo_ports_per_radio = num_ports(tree, DFIFO_BLOCK_NAME, "in") / num_radios_per_board;
+ UHD_ASSERT_THROW(num_dmafifo_ports_per_radio);
+
+ return std::min(
+ num_radio_ports,
+ num_dmafifo_ports_per_radio
+ );
+}
+
+double lambda_const_double(const double d)
+{
+ return d;
+}
+
+uhd::meta_range_t lambda_const_meta_range(const double start, const double stop, const double step)
+{
+ return uhd::meta_range_t(start, stop, step);
+}
+/************************************************************************
+ * Class Definition
+ ***********************************************************************/
+class legacy_compat_impl : public legacy_compat
+{
+public:
+ /************************************************************************
+ * Structors and Initialization
+ ***********************************************************************/
+ legacy_compat_impl(
+ uhd::device3::sptr device,
+ const uhd::device_addr_t &args
+ ) : _device(device),
+ _tree(device->get_tree()),
+ _has_ducs(not args.has_key("skip_duc") and not device->find_blocks(DUC_BLOCK_NAME).empty()),
+ _has_ddcs(not args.has_key("skip_ddc") and not device->find_blocks(DDC_BLOCK_NAME).empty()),
+ _has_dmafifo(not args.has_key("skip_dram") and not device->find_blocks(DFIFO_BLOCK_NAME).empty()),
+ _num_mboards(_tree->list("/mboards").size()),
+ _num_radios_per_board(device->find_blocks<radio_ctrl>("0/Radio").size()), // These might throw, maybe we catch that and provide a nicer error message.
+ _num_tx_chans_per_radio(
+ calc_num_tx_chans_per_radio(_tree, _num_radios_per_board, _has_ducs, not device->find_blocks(DFIFO_BLOCK_NAME).empty())
+ ),
+ _num_rx_chans_per_radio(_has_ddcs ?
+ std::min(num_ports(_tree, RADIO_BLOCK_NAME, "out"), num_ports(_tree, DDC_BLOCK_NAME, "out"))
+ : num_ports(_tree, RADIO_BLOCK_NAME, "out")),
+ _rx_spp(get_block_ctrl<radio_ctrl>(0, RADIO_BLOCK_NAME, 0)->get_arg<int>("spp")),
+ _tx_spp(_rx_spp),
+ _rx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board)),
+ _tx_channel_map(_num_mboards, std::vector<radio_port_pair_t>(_num_radios_per_board))
+ {
+ _device->clear();
+ check_available_periphs(); // Throws if invalid configuration.
+ setup_prop_tree();
+ if (_tree->exists("/mboards/0/mtu/send")) {
+ _tx_spp = (_tree->access<size_t>("/mboards/0/mtu/send").get() - MAX_BYTES_PER_HEADER) / BYTES_PER_SAMPLE;
+ }
+ connect_blocks();
+ if (args.has_key("skip_ddc")) {
+ UHD_LEGACY_LOG() << "[legacy_compat] Skipping DDCs by user request." << std::endl;
+ } else if (not _has_ddcs) {
+ UHD_MSG(warning)
+ << "[legacy_compat] No DDCs detected. You will only be able to receive at the radio frontend rate."
+ << std::endl;
+ }
+ if (args.has_key("skip_duc")) {
+ UHD_LEGACY_LOG() << "[legacy_compat] Skipping DUCs by user request." << std::endl;
+ } else if (not _has_ducs) {
+ UHD_MSG(warning) << "[legacy_compat] No DUCs detected. You will only be able to transmit at the radio frontend rate." << std::endl;
+ }
+ if (args.has_key("skip_dram")) {
+ UHD_LEGACY_LOG() << "[legacy_compat] Skipping DRAM by user request." << std::endl;
+ } else if (not _has_dmafifo) {
+ UHD_MSG(warning) << "[legacy_compat] No DMA FIFO detected. You will only be able to transmit at slow rates." << std::endl;
+ }
+
+ for (size_t mboard = 0; mboard < _num_mboards; mboard++) {
+ for (size_t radio = 0; radio < _num_radios_per_board; radio++) {
+ _rx_channel_map[mboard][radio].radio_index = radio;
+ _tx_channel_map[mboard][radio].radio_index = radio;
+ }
+
+ const double tick_rate = _tree->access<double>(mb_root(mboard) / "tick_rate").get();
+ update_tick_rate_on_blocks(tick_rate, mboard);
+ }
+ }
+
+ /************************************************************************
+ * API Calls
+ ***********************************************************************/
+ uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan)
+ {
+ // The DSP index is the same as the radio index
+ size_t dsp_index = _rx_channel_map[mboard_idx][chan].radio_index;
+ size_t port_index = _rx_channel_map[mboard_idx][chan].port_index;
+
+ if (not _has_ddcs) {
+ return mb_root(mboard_idx) / "rx_dsps" / dsp_index / port_index;
+ }
+
+ return mb_root(mboard_idx) / "xbar" /
+ str(boost::format("%s_%d") % DDC_BLOCK_NAME % dsp_index) /
+ "legacy_api" / port_index;
+ }
+
+ uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan)
+ {
+ // The DSP index is the same as the radio index
+ size_t dsp_index = _tx_channel_map[mboard_idx][chan].radio_index;
+ size_t port_index = _tx_channel_map[mboard_idx][chan].port_index;
+
+ if (not _has_ducs) {
+ return mb_root(mboard_idx) / "tx_dsps" / dsp_index / port_index;
+ }
+
+ return mb_root(mboard_idx) / "xbar" /
+ str(boost::format("%s_%d") % DUC_BLOCK_NAME % dsp_index) /
+ "legacy_api" / port_index;
+ }
+
+ uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan)
+ {
+ size_t radio_index = _rx_channel_map[mboard_idx][chan].radio_index;
+ size_t port_index = _rx_channel_map[mboard_idx][chan].port_index;
+ return uhd::fs_path(str(
+ boost::format("/mboards/%d/xbar/%s_%d/rx_fe_corrections/%d/")
+ % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index
+ ));
+ }
+
+ uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan)
+ {
+ size_t radio_index = _tx_channel_map[mboard_idx][chan].radio_index;
+ size_t port_index = _tx_channel_map[mboard_idx][chan].port_index;
+ return uhd::fs_path(str(
+ boost::format("/mboards/%d/xbar/%s_%d/tx_fe_corrections/%d/")
+ % mboard_idx % RADIO_BLOCK_NAME % radio_index % port_index
+ ));
+ }
+
+ void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t mboard, size_t chan)
+ {
+ UHD_LEGACY_LOG() << "[legacy_compat] issue_stream_cmd() " << std::endl;
+ const size_t &radio_index = _rx_channel_map[mboard][chan].radio_index;
+ const size_t &port_index = _rx_channel_map[mboard][chan].port_index;
+ if (_has_ddcs) {
+ get_block_ctrl<ddc_block_ctrl>(mboard, DDC_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index);
+ } else {
+ get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio_index)->issue_stream_cmd(stream_cmd, port_index);
+ }
+ }
+
+ //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call
+ uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args_)
+ {
+ uhd::stream_args_t args(args_);
+ if (args.otw_format.empty()) {
+ args.otw_format = "sc16";
+ }
+ _update_stream_args_for_streaming<uhd::RX_DIRECTION>(args, _rx_channel_map);
+ UHD_LEGACY_LOG() << "[legacy_compat] rx stream args: " << args.args.to_string() << std::endl;
+ return _device->get_rx_stream(args);
+ }
+
+ //! Sets block_id<N> and block_port<N> in the streamer args, otherwise forwards the call.
+ // If spp is in the args, update the radios. If it's not set, copy the value from the radios.
+ uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args_)
+ {
+ uhd::stream_args_t args(args_);
+ if (args.otw_format.empty()) {
+ args.otw_format = "sc16";
+ }
+ _update_stream_args_for_streaming<uhd::TX_DIRECTION>(args, _tx_channel_map);
+ UHD_LEGACY_LOG() << "[legacy_compat] tx stream args: " << args.args.to_string() << std::endl;
+ return _device->get_tx_stream(args);
+ }
+
+ double get_tick_rate(const size_t mboard_idx=0)
+ {
+ return _tree->access<double>(mb_root(mboard_idx) / "tick_rate").get();
+ }
+
+ uhd::meta_range_t lambda_get_samp_rate_range(
+ const size_t mboard_idx,
+ const size_t radio_idx,
+ const size_t chan,
+ uhd::direction_t dir
+ ) {
+ radio_ctrl::sptr radio_sptr = get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx);
+ const double samp_rate = (dir == uhd::TX_DIRECTION) ?
+ radio_sptr->get_input_samp_rate(chan) :
+ radio_sptr->get_output_samp_rate(chan)
+ ;
+
+ return uhd::meta_range_t(samp_rate, samp_rate, 0.0);
+ }
+
+ void set_tick_rate(const double tick_rate, const size_t mboard_idx=0)
+ {
+ _tree->access<double>(mb_root(mboard_idx) / "tick_rate").set(tick_rate);
+ update_tick_rate_on_blocks(tick_rate, mboard_idx);
+ }
+
+private: // types
+ struct radio_port_pair_t {
+ radio_port_pair_t(const size_t radio=0, const size_t port=0) : radio_index(radio), port_index(port) {}
+ size_t radio_index;
+ size_t port_index;
+ };
+ //! Map: _rx_channel_map[mboard_idx][chan_idx] => (Radio, Port)
+ // Container is not a std::map because we need to guarantee contiguous
+ // ports and correct order anyway.
+ typedef std::vector< std::vector<radio_port_pair_t> > chan_map_t;
+
+private: // methods
+ /************************************************************************
+ * Private helpers
+ ***********************************************************************/
+ std::string get_slot_name(const size_t radio_index)
+ {
+ return (radio_index == 0) ? "A" : "B";
+ }
+
+ size_t get_radio_index(const std::string slot_name)
+ {
+ return (slot_name == "A") ? 0 : 1;
+ }
+
+ template <typename block_type>
+ inline typename block_type::sptr get_block_ctrl(const size_t mboard_idx, const std::string &name, const size_t block_count)
+ {
+ block_id_t block_id(mboard_idx, name, block_count);
+ return _device->get_block_ctrl<block_type>(block_id);
+ }
+
+ template <uhd::direction_t dir>
+ void _update_stream_args_for_streaming(
+ uhd::stream_args_t &args,
+ chan_map_t &chan_map
+ ) {
+ // If the user provides spp, that value is always applied. If it's
+ // different from what we thought it was, we need to update the blocks.
+ // If it's not provided, we provide our own spp value.
+ const size_t args_spp = args.args.cast<size_t>("spp", 0);
+ if (dir == uhd::RX_DIRECTION) {
+ if (args.args.has_key("spp") and args_spp != _rx_spp) {
+ for (size_t mboard = 0; mboard < _num_mboards; mboard++) {
+ for (size_t radio = 0; radio < _num_radios_per_board; radio++) {
+ get_block_ctrl<radio_ctrl>(mboard, RADIO_BLOCK_NAME, radio)->set_arg<int>("spp", args_spp);
+ }
+ }
+ _rx_spp = args_spp;
+ // TODO: Update flow control on the blocks
+ } else {
+ args.args["spp"] = str(boost::format("%d") % _rx_spp);
+ }
+ } else {
+ if (args.args.has_key("spp") and args_spp != _tx_spp) {
+ _tx_spp = args_spp;
+ // TODO: Update flow control on the blocks
+ } else {
+ args.args["spp"] = str(boost::format("%d") % _tx_spp);
+ }
+ }
+
+ if (args.channels.empty()) {
+ args.channels = std::vector<size_t>(1, 0);
+ }
+ for (size_t i = 0; i < args.channels.size(); i++) {
+ const size_t stream_arg_chan_idx = args.channels[i];
+ // Determine which mboard, and on that mboard, which channel this is:
+ size_t mboard_idx = 0;
+ size_t this_mboard_chan_idx = stream_arg_chan_idx;
+ while (this_mboard_chan_idx >= chan_map[mboard_idx].size()) {
+ mboard_idx++;
+ this_mboard_chan_idx -= chan_map[mboard_idx].size();
+ }
+ if (mboard_idx >= chan_map.size()) {
+ throw uhd::index_error(str(
+ boost::format("[legacy_compat]: %s channel %u out of range for given frontend configuration.")
+ % (dir == uhd::TX_DIRECTION ? "TX" : "RX")
+ % stream_arg_chan_idx
+ ));
+ }
+ // Map that mboard and channel to a block:
+ const size_t radio_index = chan_map[mboard_idx][this_mboard_chan_idx].radio_index;
+ size_t port_index = chan_map[mboard_idx][this_mboard_chan_idx].port_index;
+ const std::string block_name = _get_streamer_block_id_and_port<dir>(mboard_idx, radio_index, port_index);
+ args.args[str(boost::format("block_id%d") % stream_arg_chan_idx)] = block_name;
+ args.args[str(boost::format("block_port%d") % stream_arg_chan_idx)] = str(boost::format("%d") % port_index);
+ }
+ }
+
+ template <uhd::direction_t dir>
+ std::string _get_streamer_block_id_and_port(
+ const size_t mboard_idx,
+ const size_t radio_index,
+ size_t &port_index
+ ) {
+ if (dir == uhd::TX_DIRECTION) {
+ if (_has_dmafifo) {
+ port_index = radio_index;
+ return block_id_t(mboard_idx, DFIFO_BLOCK_NAME, 0).to_string();
+ } else {
+ if (_has_ducs) {
+ return block_id_t(mboard_idx, DUC_BLOCK_NAME, radio_index).to_string();
+ } else {
+ return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string();
+ }
+ }
+ } else {
+ if (_has_ddcs) {
+ return block_id_t(mboard_idx, DDC_BLOCK_NAME, radio_index).to_string();
+ } else {
+ return block_id_t(mboard_idx, RADIO_BLOCK_NAME, radio_index).to_string();
+ }
+ }
+ }
+
+ /************************************************************************
+ * Initialization
+ ***********************************************************************/
+ /*! Check this device has all the required peripherals.
+ *
+ * Check rules:
+ * - Every mboard needs the same number of radios.
+ * - For every radio block, there must be DDC and a DUC block,
+ * with matching number of ports.
+ *
+ * \throw uhd::runtime_error if any of these checks fail.
+ */
+ void check_available_periphs()
+ {
+ if (_num_radios_per_board == 0) {
+ throw uhd::runtime_error("For legacy APIs, all devices require at least one radio.");
+ }
+ block_id_t radio_block_id(0, RADIO_BLOCK_NAME);
+ block_id_t duc_block_id(0, DUC_BLOCK_NAME);
+ block_id_t ddc_block_id(0, DDC_BLOCK_NAME);
+ block_id_t fifo_block_id(0, DFIFO_BLOCK_NAME, 0);
+ for (size_t i = 0; i < _num_mboards; i++) {
+ radio_block_id.set_device_no(i);
+ duc_block_id.set_device_no(i);
+ ddc_block_id.set_device_no(i);
+ fifo_block_id.set_device_no(i);
+ for (size_t k = 0; k < _num_radios_per_board; k++) {
+ radio_block_id.set_block_count(k);
+ duc_block_id.set_block_count(k);
+ ddc_block_id.set_block_count(k);
+ // Only one FIFO per crossbar, so don't set block count for that block
+ if (not _device->has_block(radio_block_id)
+ or (_has_ducs and not _device->has_block(duc_block_id))
+ or (_has_ddcs and not _device->has_block(ddc_block_id))
+ or (_has_dmafifo and not _device->has_block(fifo_block_id))
+ ) {
+ throw uhd::runtime_error("For legacy APIs, all devices require the same number of radios, DDCs and DUCs.");
+ }
+
+ const size_t this_spp = get_block_ctrl<radio_ctrl>(i, RADIO_BLOCK_NAME, k)->get_arg<int>("spp");
+ if (this_spp != _rx_spp) {
+ throw uhd::runtime_error(str(
+ boost::format("[legacy compat] Radios have differing spp values: %s has %d, others have %d")
+ % radio_block_id.to_string() % this_spp % _rx_spp
+ ));
+ }
+ }
+ }
+ }
+
+ /*! Initialize properties in property tree to match legacy mode
+ */
+ void setup_prop_tree()
+ {
+ for (size_t mboard_idx = 0; mboard_idx < _num_mboards; mboard_idx++) {
+ uhd::fs_path root = mb_root(mboard_idx);
+ // Subdev specs
+ if (_tree->exists(root / "tx_subdev_spec")) {
+ _tree->access<subdev_spec_t>(root / "tx_subdev_spec")
+ .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION))
+ .update()
+ .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION));
+ } else {
+ _tree->create<subdev_spec_t>(root / "tx_subdev_spec")
+ .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::TX_DIRECTION))
+ .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::TX_DIRECTION));
+ }
+
+ if (_tree->exists(root / "rx_subdev_spec")) {
+ _tree->access<subdev_spec_t>(root / "rx_subdev_spec")
+ .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION))
+ .update()
+ .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION));
+ } else {
+ _tree->create<subdev_spec_t>(root / "rx_subdev_spec")
+ .add_coerced_subscriber(boost::bind(&legacy_compat_impl::set_subdev_spec, this, _1, mboard_idx, uhd::RX_DIRECTION))
+ .set_publisher(boost::bind(&legacy_compat_impl::get_subdev_spec, this, mboard_idx, uhd::RX_DIRECTION));
+ }
+
+ if (not _has_ddcs) {
+ for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) {
+ for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) {
+ const uhd::fs_path rx_dsp_base_path(mb_root(mboard_idx) / "rx_dsps" / radio_idx / chan);
+ _tree->create<double>(rx_dsp_base_path / "rate/value")
+ .set(0.0)
+ .set_publisher(
+ boost::bind(
+ &radio_ctrl::get_output_samp_rate,
+ get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx),
+ chan
+ )
+ )
+ ;
+ _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "rate/range")
+ .set_publisher(
+ boost::bind(
+ &legacy_compat_impl::lambda_get_samp_rate_range,
+ this,
+ mboard_idx, radio_idx, chan,
+ uhd::RX_DIRECTION
+ )
+ )
+ ;
+ _tree->create<double>(rx_dsp_base_path / "freq/value")
+ .set_publisher(boost::bind(&lambda_const_double, 0.0))
+ ;
+ _tree->create<uhd::meta_range_t>(rx_dsp_base_path / "freq/range")
+ .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0))
+ ;
+ }
+ }
+ }
+ if (not _has_ducs) {
+ for (size_t radio_idx = 0; radio_idx < _num_radios_per_board; radio_idx++) {
+ for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) {
+ const uhd::fs_path tx_dsp_base_path(mb_root(mboard_idx) / "tx_dsps" / radio_idx / chan);
+ _tree->create<double>(tx_dsp_base_path / "rate/value")
+ .set(0.0)
+ .set_publisher(
+ boost::bind(
+ &radio_ctrl::get_output_samp_rate,
+ get_block_ctrl<radio_ctrl>(mboard_idx, RADIO_BLOCK_NAME, radio_idx),
+ chan
+ )
+ )
+ ;
+ _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "rate/range")
+ .set_publisher(
+ boost::bind(
+ &legacy_compat_impl::lambda_get_samp_rate_range,
+ this,
+ mboard_idx, radio_idx, chan,
+ uhd::TX_DIRECTION
+ )
+ )
+ ;
+ _tree->create<double>(tx_dsp_base_path / "freq/value")
+ .set_publisher(boost::bind(&lambda_const_double, 0.0))
+ ;
+ _tree->create<uhd::meta_range_t>(tx_dsp_base_path / "freq/range")
+ .set_publisher(boost::bind(&lambda_const_meta_range, 0.0, 0.0, 0.0))
+ ;
+ }
+ }
+ }
+ }
+ }
+
+ /*! Default block connections.
+ *
+ * Tx connections:
+ *
+ * [Host] => DMA FIFO => DUC => Radio
+ *
+ * Note: There is only one DMA FIFO per crossbar, with twice the number of ports.
+ *
+ * Rx connections:
+ *
+ * Radio => DDC => [Host]
+ *
+ * Streamers are *not* generated here.
+ */
+ void connect_blocks()
+ {
+ _graph = _device->create_graph("legacy");
+ const size_t rx_bpp = _rx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER;
+ const size_t tx_bpp = _tx_spp * BYTES_PER_SAMPLE + MAX_BYTES_PER_HEADER;
+ for (size_t mboard = 0; mboard < _num_mboards; mboard++) {
+ for (size_t radio = 0; radio < _num_radios_per_board; radio++) {
+ // Tx Channels
+ for (size_t chan = 0; chan < _num_tx_chans_per_radio; chan++) {
+ if (_has_ducs) {
+ _graph->connect(
+ block_id_t(mboard, DUC_BLOCK_NAME, radio), chan,
+ block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan,
+ tx_bpp
+ );
+ if (_has_dmafifo) {
+ // We have DMA FIFO *and* DUCs
+ _graph->connect(
+ block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio,
+ block_id_t(mboard, DUC_BLOCK_NAME, radio), chan,
+ tx_bpp
+ );
+ }
+ } else if (_has_dmafifo) {
+ // We have DMA FIFO, *no* DUCs
+ _graph->connect(
+ block_id_t(mboard, DFIFO_BLOCK_NAME, 0), radio,
+ block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan,
+ tx_bpp
+ );
+ }
+ }
+ // Rx Channels
+ for (size_t chan = 0; chan < _num_rx_chans_per_radio; chan++) {
+ if (_has_ddcs) {
+ _graph->connect(
+ block_id_t(mboard, RADIO_BLOCK_NAME, radio), chan,
+ block_id_t(mboard, DDC_BLOCK_NAME, radio), chan,
+ rx_bpp
+ );
+ }
+ }
+ }
+ }
+ }
+
+
+ /************************************************************************
+ * Subdev translation
+ ***********************************************************************/
+ /*! Subdev -> (Radio, Port)
+ *
+ * Example: Device is X300, subdev spec is 'A:0 B:0', we have 2 radios.
+ * Then we map to ((0, 0), (1, 0)). I.e., zero-th port on radio 0 and
+ * radio 1, respectively.
+ */
+ void set_subdev_spec(const subdev_spec_t &spec, const size_t mboard, const uhd::direction_t dir)
+ {
+ UHD_ASSERT_THROW(mboard < _num_mboards);
+ chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map;
+ std::vector<radio_port_pair_t> new_mapping(spec.size());
+ for (size_t i = 0; i < spec.size(); i++) {
+ const size_t new_radio_index = get_radio_index(spec[i].db_name);
+ radio_ctrl::sptr radio = get_block_ctrl<radio_ctrl>(mboard, "Radio", new_radio_index);
+ size_t new_port_index = radio->get_chan_from_dboard_fe(spec[i].sd_name, dir);
+ if (new_port_index >= radio->get_input_ports().size()) {
+ new_port_index = radio->get_input_ports().at(0);
+ }
+ radio_port_pair_t new_radio_port_pair(new_radio_index, new_port_index);
+ new_mapping[i] = new_radio_port_pair;
+ }
+ chan_map[mboard] = new_mapping;
+ }
+
+ subdev_spec_t get_subdev_spec(const size_t mboard, const uhd::direction_t dir)
+ {
+ UHD_ASSERT_THROW(mboard < _num_mboards);
+ subdev_spec_t subdev_spec;
+ chan_map_t &chan_map = (dir == uhd::TX_DIRECTION) ? _tx_channel_map : _rx_channel_map;
+ for (size_t chan_idx = 0; chan_idx < chan_map[mboard].size(); chan_idx++) {
+ const size_t radio_index = chan_map[mboard][chan_idx].radio_index;
+ const size_t port_index = chan_map[mboard][chan_idx].port_index;
+ const std::string new_db_name = get_slot_name(radio_index);
+ const std::string new_sd_name =
+ get_block_ctrl<radio_ctrl>(mboard, "Radio", radio_index)->get_dboard_fe_from_chan(port_index, dir);
+ subdev_spec_pair_t new_pair(new_db_name, new_sd_name);
+ subdev_spec.push_back(new_pair);
+ }
+
+ return subdev_spec;
+ }
+
+ void update_tick_rate_on_blocks(const double tick_rate, const size_t mboard_idx)
+ {
+ block_id_t radio_block_id(mboard_idx, RADIO_BLOCK_NAME);
+ block_id_t duc_block_id(mboard_idx, DUC_BLOCK_NAME);
+ block_id_t ddc_block_id(mboard_idx, DDC_BLOCK_NAME);
+
+ for (size_t radio = 0; radio < _num_radios_per_board; radio++) {
+ radio_block_id.set_block_count(radio);
+ duc_block_id.set_block_count(radio);
+ ddc_block_id.set_block_count(radio);
+ radio_ctrl::sptr radio_sptr = _device->get_block_ctrl<radio_ctrl>(radio_block_id);
+ radio_sptr->set_rate(tick_rate);
+ for (size_t chan = 0; chan < _num_rx_chans_per_radio and _has_ddcs; chan++) {
+ const double radio_output_rate = radio_sptr->get_output_samp_rate(chan);
+ _device->get_block_ctrl(ddc_block_id)->set_arg<double>("input_rate", radio_output_rate, chan);
+ }
+ for (size_t chan = 0; chan < _num_tx_chans_per_radio and _has_ducs; chan++) {
+ const double radio_input_rate = radio_sptr->get_input_samp_rate(chan);
+ _device->get_block_ctrl(duc_block_id)->set_arg<double>("output_rate", radio_input_rate, chan);
+ }
+ }
+ }
+
+private: // attributes
+ uhd::device3::sptr _device;
+ uhd::property_tree::sptr _tree;
+
+ const bool _has_ducs;
+ const bool _has_ddcs;
+ const bool _has_dmafifo;
+ const size_t _num_mboards;
+ const size_t _num_radios_per_board;
+ const size_t _num_tx_chans_per_radio;
+ const size_t _num_rx_chans_per_radio;
+ size_t _rx_spp;
+ size_t _tx_spp;
+
+ chan_map_t _rx_channel_map;
+ chan_map_t _tx_channel_map;
+
+ graph::sptr _graph;
+};
+
+legacy_compat::sptr legacy_compat::make(
+ uhd::device3::sptr device,
+ const uhd::device_addr_t &args
+) {
+ UHD_ASSERT_THROW(bool(device));
+ static std::map<void *, boost::weak_ptr<legacy_compat> > legacy_cache;
+
+ if (legacy_cache.count(device.get())) {
+ legacy_compat::sptr legacy_compat_copy = legacy_cache.at(device.get()).lock();
+ if (not bool(legacy_compat_copy)) {
+ throw uhd::runtime_error("Reference to existing legacy compat object expired prematurely!");
+ }
+
+ UHD_LEGACY_LOG() << "[legacy_compat] Using existing legacy compat object for this device." << std::endl;
+ return legacy_compat_copy;
+ }
+
+ legacy_compat::sptr new_legacy_compat = boost::make_shared<legacy_compat_impl>(device, args);
+ legacy_cache[device.get()] = new_legacy_compat;
+ return new_legacy_compat;
+}
+
diff --git a/host/lib/rfnoc/legacy_compat.hpp b/host/lib/rfnoc/legacy_compat.hpp
new file mode 100644
index 000000000..29be1bdc2
--- /dev/null
+++ b/host/lib/rfnoc/legacy_compat.hpp
@@ -0,0 +1,55 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_RFNOC_LEGACY_COMPAT_HPP
+#define INCLUDED_RFNOC_LEGACY_COMPAT_HPP
+
+#include <uhd/device3.hpp>
+#include <uhd/stream.hpp>
+
+namespace uhd { namespace rfnoc {
+
+ /*! Legacy compatibility layer class.
+ */
+ class legacy_compat
+ {
+ public:
+ typedef boost::shared_ptr<legacy_compat> sptr;
+
+ virtual uhd::fs_path rx_dsp_root(const size_t mboard_idx, const size_t chan) = 0;
+
+ virtual uhd::fs_path tx_dsp_root(const size_t mboard_idx, const size_t chan) = 0;
+
+ virtual uhd::fs_path rx_fe_root(const size_t mboard_idx, const size_t chan) = 0;
+
+ virtual uhd::fs_path tx_fe_root(const size_t mboard_idx, const size_t chan) = 0;
+
+ virtual void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, size_t mboard, size_t chan) = 0;
+
+ virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args) = 0;
+
+ virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args) = 0;
+
+ static sptr make(
+ uhd::device3::sptr device,
+ const uhd::device_addr_t &args
+ );
+ };
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_RFNOC_LEGACY_COMPAT_HPP */
diff --git a/host/lib/rfnoc/nocscript/CMakeLists.txt b/host/lib/rfnoc/nocscript/CMakeLists.txt
new file mode 100644
index 000000000..77fcc101c
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/CMakeLists.txt
@@ -0,0 +1,37 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
+LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/basic_functions.hpp
+)
+
+IF(ENABLE_MANUAL)
+ LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_basic_funcs.py
+ ${CMAKE_BINARY_DIR}/docs/nocscript_functions.dox
+ )
+ENDIF(ENABLE_MANUAL)
+
+LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/expression.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/function_table.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/parser.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/block_iface.cpp
+)
diff --git a/host/lib/rfnoc/nocscript/block_iface.cpp b/host/lib/rfnoc/nocscript/block_iface.cpp
new file mode 100644
index 000000000..2034d3438
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/block_iface.cpp
@@ -0,0 +1,255 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "block_iface.hpp"
+#include "function_table.hpp"
+#include <uhd/exception.hpp>
+#include <uhd/utils/msg.hpp>
+#include <boost/assign.hpp>
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#define UHD_NOCSCRIPT_LOG() UHD_LOGV(never)
+
+using namespace uhd::rfnoc;
+using namespace uhd::rfnoc::nocscript;
+
+block_iface::block_iface(block_ctrl_base *block_ptr)
+ : _block_ptr(block_ptr)
+{
+ function_table::sptr ft = function_table::make();
+
+ // Add the SR_WRITE() function
+ expression_function::argtype_list_type sr_write_args = boost::assign::list_of
+ (expression::TYPE_STRING)
+ (expression::TYPE_INT)
+ ;
+ ft->register_function(
+ "SR_WRITE",
+ boost::bind(&block_iface::_nocscript__sr_write, this, _1),
+ expression::TYPE_BOOL,
+ sr_write_args
+ );
+
+ // Add read access to arguments ($foo)
+ expression_function::argtype_list_type arg_set_args_wo_port = boost::assign::list_of
+ (expression::TYPE_STRING)
+ (expression::TYPE_INT)
+ ;
+ expression_function::argtype_list_type arg_set_args_w_port = boost::assign::list_of
+ (expression::TYPE_STRING)
+ (expression::TYPE_INT)
+ (expression::TYPE_INT)
+ ;
+#define REGISTER_ARG_SETTER(noctype, setter_func) \
+ arg_set_args_wo_port[1] = expression::noctype; \
+ arg_set_args_w_port[1] = expression::noctype; \
+ ft->register_function( \
+ "SET_ARG", \
+ boost::bind(&block_iface::setter_func, this, _1), \
+ expression::TYPE_BOOL, \
+ arg_set_args_wo_port \
+ ); \
+ ft->register_function( \
+ "SET_ARG", \
+ boost::bind(&block_iface::setter_func, this, _1), \
+ expression::TYPE_BOOL, \
+ arg_set_args_w_port \
+ );
+ REGISTER_ARG_SETTER(TYPE_INT, _nocscript__arg_set_int);
+ REGISTER_ARG_SETTER(TYPE_STRING, _nocscript__arg_set_string);
+ REGISTER_ARG_SETTER(TYPE_DOUBLE, _nocscript__arg_set_double);
+ REGISTER_ARG_SETTER(TYPE_INT_VECTOR, _nocscript__arg_set_intvec);
+
+
+ // Add read/write access to local variables
+ expression_function::argtype_list_type set_var_args = boost::assign::list_of
+ (expression::TYPE_STRING)
+ (expression::TYPE_INT)
+ ;
+ const expression_function::argtype_list_type get_var_args = boost::assign::list_of
+ (expression::TYPE_STRING)
+ ;
+#define REGISTER_VAR_ACCESS(noctype, typestr) \
+ set_var_args[1] = expression::noctype; \
+ ft->register_function( \
+ "SET_VAR", \
+ boost::bind(&block_iface::_nocscript__var_set, this, _1), \
+ expression::TYPE_BOOL, \
+ set_var_args \
+ ); \
+ ft->register_function( \
+ "GET_"#typestr, \
+ boost::bind(&block_iface::_nocscript__var_get, this, _1), \
+ expression::noctype, \
+ get_var_args \
+ );
+ REGISTER_VAR_ACCESS(TYPE_INT, INT);
+ REGISTER_VAR_ACCESS(TYPE_STRING, STRING);
+ REGISTER_VAR_ACCESS(TYPE_DOUBLE, DOUBLE);
+ REGISTER_VAR_ACCESS(TYPE_INT_VECTOR, INT_VECTOR);
+
+ // Create the parser
+ _parser = parser::make(
+ ft,
+ boost::bind(&block_iface::_nocscript__arg_get_type, this, _1),
+ boost::bind(&block_iface::_nocscript__arg_get_val, this, _1)
+ );
+}
+
+
+void block_iface::run_and_check(const std::string &code, const std::string &error_message)
+{
+ boost::mutex::scoped_lock local_interpreter_lock(_lil_mutex);
+
+ UHD_NOCSCRIPT_LOG() << "[NocScript] Executing and asserting code: " << code << std::endl;
+ expression::sptr e = _parser->create_expr_tree(code);
+ expression_literal result = e->eval();
+ if (not result.to_bool()) {
+ if (error_message.empty()) {
+ throw uhd::runtime_error(str(
+ boost::format("[NocScript] Code returned false: %s")
+ % code
+ ));
+ } else {
+ throw uhd::runtime_error(str(
+ boost::format("[NocScript] Error: %s")
+ % error_message
+ ));
+ }
+ }
+
+ _vars.clear(); // We go out of scope, and so do NocScript variables
+}
+
+
+expression_literal block_iface::_nocscript__sr_write(expression_container::expr_list_type args)
+{
+ const std::string reg_name = args[0]->eval().get_string();
+ const boost::uint32_t reg_val = boost::uint32_t(args[1]->eval().get_int());
+ bool result = true;
+ try {
+ UHD_NOCSCRIPT_LOG() << "[NocScript] Executing SR_WRITE() " << std::endl;
+ _block_ptr->sr_write(reg_name, reg_val);
+ } catch (const uhd::exception &e) {
+ UHD_MSG(error) << boost::format("[NocScript] Error while executing SR_WRITE(%s, 0x%X):\n%s")
+ % reg_name % reg_val % e.what()
+ << std::endl;
+ result = false;
+ }
+
+ return expression_literal(result);
+}
+
+expression::type_t block_iface::_nocscript__arg_get_type(const std::string &varname)
+{
+ const std::string var_type = _block_ptr->get_arg_type(varname);
+ if (var_type == "int") {
+ return expression::TYPE_INT;
+ } else if (var_type == "string") {
+ return expression::TYPE_STRING;
+ } else if (var_type == "double") {
+ return expression::TYPE_DOUBLE;
+ } else if (var_type == "int_vector") {
+ UHD_THROW_INVALID_CODE_PATH(); // TODO
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+expression_literal block_iface::_nocscript__arg_get_val(const std::string &varname)
+{
+ const std::string var_type = _block_ptr->get_arg_type(varname);
+ if (var_type == "int") {
+ return expression_literal(_block_ptr->get_arg<int>(varname));
+ } else if (var_type == "string") {
+ return expression_literal(_block_ptr->get_arg<std::string>(varname));
+ } else if (var_type == "double") {
+ return expression_literal(_block_ptr->get_arg<double>(varname));
+ } else if (var_type == "int_vector") {
+ UHD_THROW_INVALID_CODE_PATH(); // TODO
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+expression_literal block_iface::_nocscript__arg_set_int(const expression_container::expr_list_type &args)
+{
+ const std::string var_name = args[0]->eval().get_string();
+ const int val = args[1]->eval().get_int();
+ size_t port = 0;
+ if (args.size() == 3) {
+ port = size_t(args[2]->eval().get_int());
+ }
+ UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl;
+ _block_ptr->set_arg<int>(var_name, val, port);
+ return expression_literal(true);
+}
+
+expression_literal block_iface::_nocscript__arg_set_string(const expression_container::expr_list_type &args)
+{
+ const std::string var_name = args[0]->eval().get_string();
+ const std::string val = args[1]->eval().get_string();
+ size_t port = 0;
+ if (args.size() == 3) {
+ port = size_t(args[2]->eval().get_int());
+ }
+ UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl;
+ _block_ptr->set_arg<std::string>(var_name, val, port);
+ return expression_literal(true);
+}
+
+expression_literal block_iface::_nocscript__arg_set_double(const expression_container::expr_list_type &args)
+{
+ const std::string var_name = args[0]->eval().get_string();
+ const double val = args[1]->eval().get_double();
+ size_t port = 0;
+ if (args.size() == 3) {
+ port = size_t(args[2]->eval().get_int());
+ }
+ UHD_NOCSCRIPT_LOG() << "[NocScript] Setting $" << var_name << std::endl;
+ _block_ptr->set_arg<double>(var_name, val, port);
+ return expression_literal(true);
+}
+
+expression_literal block_iface::_nocscript__arg_set_intvec(const expression_container::expr_list_type &)
+{
+ UHD_THROW_INVALID_CODE_PATH();
+}
+
+block_iface::sptr block_iface::make(uhd::rfnoc::block_ctrl_base* block_ptr)
+{
+ return sptr(new block_iface(block_ptr));
+}
+
+expression_literal block_iface::_nocscript__var_get(const expression_container::expr_list_type &args)
+{
+ expression_literal expr = _vars[args[0]->eval().get_string()];
+ //std::cout << "[NocScript] Getting var " << args[0]->eval().get_string() << " == " << expr << std::endl;
+ //std::cout << "[NocScript] Type " << expr.infer_type() << std::endl;
+ //return _vars[args[0]->eval().get_string()];
+ return expr;
+}
+
+expression_literal block_iface::_nocscript__var_set(const expression_container::expr_list_type &args)
+{
+ _vars[args[0]->eval().get_string()] = args[1]->eval();
+ //std::cout << "[NocScript] Set var " << args[0]->eval().get_string() << " to " << _vars[args[0]->eval().get_string()] << std::endl;
+ //std::cout << "[NocScript] Type " << _vars[args[0]->eval().get_string()].infer_type() << std::endl;
+ return expression_literal(true);
+}
+
diff --git a/host/lib/rfnoc/nocscript/block_iface.hpp b/host/lib/rfnoc/nocscript/block_iface.hpp
new file mode 100644
index 000000000..6354409f2
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/block_iface.hpp
@@ -0,0 +1,94 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "expression.hpp"
+#include "parser.hpp"
+#include <uhd/rfnoc/block_ctrl_base.hpp>
+#include <boost/thread/mutex.hpp>
+
+#ifndef INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP
+#define INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+
+/*! NocScript / Block interface class.
+ *
+ * This class only exists as a member of an rfnoc::block_ctrl_base class.
+ * It should never be instantiated anywhere else. It is used to execute
+ * NocScript function calls that require access to the original block
+ * controller class.
+ */
+class block_iface {
+
+ public:
+ typedef boost::shared_ptr<block_iface> sptr;
+
+ static sptr make(uhd::rfnoc::block_ctrl_base* block_ptr);
+
+ block_iface(uhd::rfnoc::block_ctrl_base* block_ptr);
+
+ /*! Execute \p code and make sure it returns 'true'.
+ *
+ * \param code Must be a valid NocScript expression that returns a boolean value.
+ * If it returns false, this is interpreted as failure.
+ * \param error_message If the expression fails, this error message is printed.
+ * \throws uhd::runtime_error if the expression returns false.
+ * \throws uhd::syntax_error if the expression is invalid.
+ */
+ void run_and_check(const std::string &code, const std::string &error_message="");
+
+ private:
+ //! For the local interpreter lock (lil)
+ boost::mutex _lil_mutex;
+
+ //! Wrapper for block_ctrl_base::sr_write, so we can call it from within NocScript
+ expression_literal _nocscript__sr_write(expression_container::expr_list_type);
+
+ //! Argument type getter that can be used within NocScript
+ expression::type_t _nocscript__arg_get_type(const std::string &argname);
+
+ //! Argument value getter that can be used within NocScript
+ expression_literal _nocscript__arg_get_val(const std::string &argname);
+
+ //! Argument value setters:
+ expression_literal _nocscript__arg_set_int(const expression_container::expr_list_type &);
+ expression_literal _nocscript__arg_set_string(const expression_container::expr_list_type &);
+ expression_literal _nocscript__arg_set_double(const expression_container::expr_list_type &);
+ expression_literal _nocscript__arg_set_intvec(const expression_container::expr_list_type &);
+
+ //! Variable value getter
+ expression_literal _nocscript__var_get(const expression_container::expr_list_type &);
+
+ //! Variable value setter
+ expression_literal _nocscript__var_set(const expression_container::expr_list_type &);
+
+ //! Raw pointer to the block class. Note that since block_iface may
+ // only live as a member of a block_ctrl_base, we don't really need
+ // the reference counting.
+ uhd::rfnoc::block_ctrl_base* _block_ptr;
+
+ //! Pointer to the parser object
+ parser::sptr _parser;
+
+ //! Container for scoped variables
+ std::map<std::string, expression_literal> _vars;
+};
+
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_NOCSCRIPT_BLOCK_IFACE_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/nocscript/expression.cpp b/host/lib/rfnoc/nocscript/expression.cpp
new file mode 100644
index 000000000..38d6e2128
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/expression.cpp
@@ -0,0 +1,413 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "expression.hpp"
+#include "function_table.hpp"
+#include <uhd/utils/cast.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/assign.hpp>
+#include <boost/algorithm/string.hpp>
+
+using namespace uhd::rfnoc::nocscript;
+
+std::map<expression::type_t, std::string> expression::type_repr = boost::assign::map_list_of
+ (TYPE_INT, "INT")
+ (TYPE_DOUBLE, "DOUBLE")
+ (TYPE_STRING, "STRING")
+ (TYPE_BOOL, "BOOL")
+ (TYPE_INT_VECTOR, "INT_VECTOR")
+;
+
+/********************************************************************
+ * Literal expressions (constants)
+ *******************************************************************/
+expression_literal::expression_literal(
+ const std::string token_val,
+ expression::type_t type
+) : _bool_val(false)
+ , _int_val(0)
+ , _double_val(0.0)
+ , _val(token_val)
+ , _type(type)
+{
+ switch (_type) {
+ case expression::TYPE_STRING:
+ // Remove the leading and trailing quotes:
+ _val = _val.substr(1, _val.size()-2);
+ break;
+
+ case expression::TYPE_INT:
+ if (_val.substr(0, 2) == "0x") {
+ _int_val = uhd::cast::hexstr_cast<int>(_val);
+ } else {
+ _int_val = boost::lexical_cast<int>(_val);
+ }
+ break;
+
+ case expression::TYPE_DOUBLE:
+ _double_val = boost::lexical_cast<double>(_val);
+ break;
+
+ case expression::TYPE_BOOL:
+ if (boost::to_upper_copy(_val) == "TRUE") {
+ _bool_val = true;
+ } else {
+ // lexical cast to bool is too picky
+ _bool_val = bool(boost::lexical_cast<int>(_val));
+ }
+ break;
+
+ case expression::TYPE_INT_VECTOR:
+ {
+ std::string str_vec = _val.substr(1, _val.size()-2);
+ std::vector<std::string> subtoken_list;
+ boost::split(subtoken_list, str_vec, boost::is_any_of(", "), boost::token_compress_on);
+ BOOST_FOREACH(const std::string &t, subtoken_list) {
+ _int_vector_val.push_back(boost::lexical_cast<int>(t));
+ }
+ break;
+ }
+
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+expression_literal::expression_literal(bool b)
+ : _bool_val(b)
+ , _int_val(0)
+ , _double_val(0.0)
+ , _val("")
+ , _type(expression::TYPE_BOOL)
+{
+ // nop
+}
+
+expression_literal::expression_literal(int i)
+ : _bool_val(false)
+ , _int_val(i)
+ , _double_val(0.0)
+ , _val("")
+ , _type(expression::TYPE_INT)
+{
+ // nop
+}
+
+expression_literal::expression_literal(double d)
+ : _bool_val(false)
+ , _int_val(0)
+ , _double_val(d)
+ , _val("")
+ , _type(expression::TYPE_DOUBLE)
+{
+ // nop
+}
+
+expression_literal::expression_literal(const std::string &s)
+ : _bool_val(false)
+ , _int_val(0)
+ , _double_val(0.0)
+ , _val(s)
+ , _type(expression::TYPE_STRING)
+{
+ // nop
+}
+
+expression_literal::expression_literal(const std::vector<int> v)
+ : _bool_val(false)
+ , _int_val(0)
+ , _double_val(0.0)
+ , _int_vector_val(v)
+ , _val("")
+ , _type(expression::TYPE_INT_VECTOR)
+{
+ // nop
+}
+
+bool expression_literal::to_bool() const
+{
+ switch (_type) {
+ case TYPE_INT:
+ return bool(boost::lexical_cast<int>(_val));
+ case TYPE_STRING:
+ return not _val.empty();
+ case TYPE_DOUBLE:
+ return bool(boost::lexical_cast<double>(_val));
+ case TYPE_BOOL:
+ return _bool_val;
+ case TYPE_INT_VECTOR:
+ return not _int_vector_val.empty();
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+int expression_literal::get_int() const
+{
+ if (_type != TYPE_INT) {
+ throw uhd::type_error("Cannot call get_int() on non-int value.");
+ }
+
+ return _int_val;
+}
+
+double expression_literal::get_double() const
+{
+ if (_type != TYPE_DOUBLE) {
+ throw uhd::type_error("Cannot call get_double() on non-double value.");
+ }
+
+ return _double_val;
+}
+
+std::string expression_literal::get_string() const
+{
+ if (_type != TYPE_STRING) {
+ throw uhd::type_error("Cannot call get_string() on non-string value.");
+ }
+
+ return _val;
+}
+
+bool expression_literal::get_bool() const
+{
+ if (_type != TYPE_BOOL) {
+ throw uhd::type_error("Cannot call get_bool() on non-boolean value.");
+ }
+
+ return _bool_val;
+}
+
+std::vector<int> expression_literal::get_int_vector() const
+{
+ if (_type != TYPE_INT_VECTOR) {
+ throw uhd::type_error("Cannot call get_bool() on non-boolean value.");
+ }
+
+ return _int_vector_val;
+}
+
+std::string expression_literal::repr() const
+{
+ switch (_type) {
+ case TYPE_INT:
+ return boost::lexical_cast<std::string>(_int_val);
+ case TYPE_STRING:
+ return _val;
+ case TYPE_DOUBLE:
+ return boost::lexical_cast<std::string>(_double_val);
+ case TYPE_BOOL:
+ return _bool_val ? "TRUE" : "FALSE";
+ case TYPE_INT_VECTOR:
+ {
+ std::stringstream sstr;
+ sstr << "[";
+ for (size_t i = 0; i < _int_vector_val.size(); i++) {
+ if (i > 0) {
+ sstr << ", ";
+ }
+ sstr << _int_vector_val[i];
+ }
+ sstr << "]";
+ return sstr.str();
+ }
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+bool expression_literal::operator==(const expression_literal &rhs) const
+{
+ if (rhs.infer_type() != _type) {
+ return false;
+ }
+
+ switch (_type) {
+ case TYPE_INT:
+ return get_int() == rhs.get_int();
+ case TYPE_STRING:
+ return get_string() == rhs.get_string();
+ case TYPE_DOUBLE:
+ return get_double() == rhs.get_double();
+ case TYPE_BOOL:
+ return get_bool() == rhs.get_bool();
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+/********************************************************************
+ * Containers
+ *******************************************************************/
+expression_container::sptr expression_container::make()
+{
+ return sptr(new expression_container);
+}
+
+expression::type_t expression_container::infer_type() const
+{
+ if (_combiner == COMBINE_OR or _combiner == COMBINE_AND) {
+ return TYPE_BOOL;
+ }
+
+ if (_sub_exprs.empty()) {
+ return TYPE_BOOL;
+ }
+
+ return _sub_exprs.back()->infer_type();
+}
+
+void expression_container::add(expression::sptr new_expr)
+{
+ _sub_exprs.push_back(new_expr);
+}
+
+bool expression_container::empty() const
+{
+ return _sub_exprs.empty();
+}
+
+void expression_container::set_combiner_safe(const combiner_type c)
+{
+ if (_combiner == COMBINE_NOTSET) {
+ _combiner = c;
+ return;
+ }
+
+ throw uhd::syntax_error("Attempting to override combiner type");
+}
+
+expression_literal expression_container::eval()
+{
+ if (_sub_exprs.empty()) {
+ return expression_literal(true);
+ }
+
+ expression_literal ret_val;
+ BOOST_FOREACH(const expression::sptr &sub_expr, _sub_exprs) {
+ ret_val = sub_expr->eval();
+ if (_combiner == COMBINE_AND and ret_val.to_bool() == false) {
+ return ret_val;
+ }
+ if (_combiner == COMBINE_OR and ret_val.to_bool() == true) {
+ return ret_val;
+ }
+ // For ALL, we return the last one, so just overwrite it
+ }
+ return ret_val;
+}
+
+/********************************************************************
+ * Functions
+ *******************************************************************/
+std::string expression_function::to_string(const std::string &name, const argtype_list_type &types)
+{
+ std::string s = name;
+ int arg_count = 0;
+ BOOST_FOREACH(const expression::type_t type, types) {
+ if (arg_count == 0) {
+ s += "(";
+ } else {
+ s += ", ";
+ }
+ s += type_repr[type];
+ arg_count++;
+ }
+ s += ")";
+
+ return s;
+}
+
+expression_function::expression_function(
+ const std::string &name,
+ const function_table::sptr func_table
+) : _name(name)
+ , _func_table(func_table)
+{
+ _combiner = COMBINE_ALL;
+ if (not _func_table->function_exists(_name)) {
+ throw uhd::syntax_error(str(
+ boost::format("Unknown function: %s")
+ % _name
+ ));
+ }
+}
+
+void expression_function::add(expression::sptr new_expr)
+{
+ expression_container::add(new_expr);
+ _arg_types.push_back(new_expr->infer_type());
+}
+
+expression::type_t expression_function::infer_type() const
+{
+ return _func_table->get_type(_name, _arg_types);
+}
+
+expression_literal expression_function::eval()
+{
+ return _func_table->eval(_name, _arg_types, _sub_exprs);
+}
+
+
+std::string expression_function::repr() const
+{
+ return to_string(_name, _arg_types);
+}
+
+expression_function::sptr expression_function::make(
+ const std::string &name,
+ const function_table::sptr func_table
+) {
+ return sptr(new expression_function(name, func_table));
+}
+
+/********************************************************************
+ * Variables
+ *******************************************************************/
+expression_variable::expression_variable(
+ const std::string &token_val,
+ type_getter_type type_getter,
+ value_getter_type value_getter
+) : _type_getter(type_getter)
+ , _value_getter(value_getter)
+{
+ // We can assume this is true because otherwise, it's not a valid token:
+ UHD_ASSERT_THROW(not token_val.empty() and token_val[0] == '$');
+
+ _varname = token_val.substr(1);
+}
+
+expression::type_t expression_variable::infer_type() const
+{
+ return _type_getter(_varname);
+}
+
+expression_literal expression_variable::eval()
+{
+ return _value_getter(_varname);
+}
+
+expression_variable::sptr expression_variable::make(
+ const std::string &token_val,
+ type_getter_type type_getter,
+ value_getter_type value_getter
+) {
+ return sptr(new expression_variable(token_val, type_getter, value_getter));
+}
+
diff --git a/host/lib/rfnoc/nocscript/expression.hpp b/host/lib/rfnoc/nocscript/expression.hpp
new file mode 100644
index 000000000..83fc5bcbc
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/expression.hpp
@@ -0,0 +1,380 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/exception.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/function.hpp>
+#include <boost/make_shared.hpp>
+#include <vector>
+#include <map>
+
+#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP
+#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+
+// Forward declaration for expression::eval()
+class expression_literal;
+
+/*! Virtual base class for Noc-Script expressions.
+ */
+class expression
+{
+ public:
+ typedef boost::shared_ptr<expression> sptr;
+
+ //! All the possible return types for expressions within Noc-Script
+ enum type_t {
+ TYPE_INT,
+ TYPE_DOUBLE,
+ TYPE_STRING,
+ TYPE_BOOL,
+ TYPE_INT_VECTOR
+ };
+
+ // TODO make this a const and fix the [] usage
+ static std::map<type_t, std::string> type_repr;
+
+ //! Returns the type of this expression without evaluating it
+ virtual type_t infer_type() const = 0;
+
+ //! Evaluate current expression and return its return value
+ virtual expression_literal eval() = 0;
+};
+
+/*! Literal (constant) expression class
+ *
+ * A literal is any value that is literally given in the NoC-Script
+ * source code, such as '5', '"FOO"', or '2.3'.
+ */
+class expression_literal : public expression
+{
+ public:
+ typedef boost::shared_ptr<expression_literal> sptr;
+
+ template <typename expr_type>
+ static sptr make(expr_type x) { return boost::make_shared<expression_literal>(x); };
+
+ /*! Generate the literal expression from its token string representation.
+ * This includes markup, e.g. a string would still have the quotes, and
+ * a hex value would still have leading 0x.
+ */
+ expression_literal(
+ const std::string token_val,
+ expression::type_t type
+ );
+
+ //! Create a boolean literal expression from a C++ bool.
+ expression_literal(bool b=false);
+ //! Create an integer literal expression from a C++ int.
+ expression_literal(int i);
+ //! Create a double literal expression from a C++ double.
+ expression_literal(double d);
+ //! Create a string literal expression from a C++ string.
+ expression_literal(const std::string &s);
+ //! Create an int vector literal expression from a C++ vector<int>.
+ expression_literal(std::vector<int> v);
+
+ expression::type_t infer_type() const
+ {
+ return _type;
+ }
+
+ //! Literals aren't evaluated as such, so the evaluation
+ // simply returns a copy of itself.
+ expression_literal eval()
+ {
+ return *this; // TODO make sure this is copy
+ }
+
+ /*! A 'type cast' to bool. Cast rules are similar to most
+ * scripting languages:
+ * - Integers and doubles are false if zero, true otherwise
+ * - Strings are false if empty, true otherwise
+ * - Vectors are false if empty, true otherwise
+ */
+ bool to_bool() const;
+
+ /*! Convenience function to typecast to C++ int
+ *
+ * Note that the current type must be TYPE_INT.
+ *
+ * \return C++ int representation of current literal
+ * \throws uhd::type_error if type didn't match
+ */
+ int get_int() const;
+
+ /*! Convenience function to typecast to C++ double
+ *
+ * Note that the current type must be TYPE_DOUBLE.
+ *
+ * \return C++ double representation of current literal
+ * \throws uhd::type_error if type didn't match
+ */
+ double get_double() const;
+
+ /*! Convenience function to typecast to C++ std::string.
+ *
+ * Note that the current type must be TYPE_STRING.
+ *
+ * \return String representation of current literal.
+ * \throws uhd::type_error if type didn't match.
+ */
+ std::string get_string() const;
+
+ /*! Convenience function to typecast to C++ int vector.
+ *
+ * Note that the current type must be TYPE_INT_VECTOR.
+ *
+ * \return String representation of current literal.
+ * \throws uhd::type_error if type didn't match.
+ */
+ std::vector<int> get_int_vector() const;
+
+ /*! Convenience function to typecast to C++ bool.
+ *
+ * Note that the current type must be TYPE_BOOL.
+ * See also expression_literal::to_bool() for a type-cast
+ * style function.
+ *
+ * \return bool representation of current literal.
+ * \throws uhd::type_error if type didn't match.
+ */
+ bool get_bool() const;
+
+ //! String representation
+ std::string repr() const;
+
+ bool operator==(const expression_literal &rhs) const;
+
+ private:
+ //! For TYPE_BOOL
+ bool _bool_val;
+
+ //! For TYPE_INT
+ int _int_val;
+
+ //! For TYPE_DOUBLE
+ double _double_val;
+
+ //! For TYPE_INT_VECTOR
+ std::vector<int> _int_vector_val;
+
+ //! Store the token value
+ std::string _val;
+
+ //! Current expression type
+ expression::type_t _type;
+};
+
+UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal &l)
+{
+ out << l.repr();
+ return out;
+}
+
+UHD_INLINE std::ostream& operator<< (std::ostream& out, const expression_literal::sptr &l)
+{
+ out << l->repr();
+ return out;
+}
+
+/*! Contains multiple (sub-)expressions.
+ */
+class expression_container : public expression
+{
+ public:
+ typedef boost::shared_ptr<expression_container> sptr;
+ typedef std::vector<expression::sptr> expr_list_type;
+
+ //! Return an sptr to an empty container
+ static sptr make();
+
+ //! List of valid combination types (see expression_container::eval()).
+ enum combiner_type {
+ COMBINE_ALL,
+ COMBINE_AND,
+ COMBINE_OR,
+ COMBINE_NOTSET
+ };
+
+ //! Create an empty container
+ expression_container() : _combiner(COMBINE_NOTSET) {};
+
+ /*! Type-deduction rules for containers are:
+ * - If the combination type is COMBINE_ALL or COMBINE_AND,
+ * return value must be TYPE_BOOL
+ * - In all other cases, we return the last expression return
+ * value, and hence its type is relevant
+ */
+ expression::type_t infer_type() const;
+
+ /*! Add another expression container to this container.
+ */
+ virtual void add(expression::sptr new_expr);
+
+ virtual bool empty() const;
+
+ void set_combiner_safe(const combiner_type c);
+
+ void set_combiner(const combiner_type c) { _combiner = c; };
+
+ combiner_type get_combiner() const { return _combiner; };
+
+ /*! Evaluate a container by evaluating its sub-expressions.
+ *
+ * If a container contains multiple sub-expressions, the rules
+ * for evaluating them depend on the combiner_type:
+ * - COMBINE_ALL: Run all the sub-expressions and return the last
+ * expression's return value
+ * - COMBINE_AND: Run sub-expressions, in order, until one of them
+ * returns false. Following expressions are not evaluated (like
+ * most C++ compilers).
+ * - COMBINE_OR: Run sub-expressions, in order, until one of them
+ * returns true. Following expressions are not evaluated.
+ *
+ * In the special case where no sub-expressions are contained, always
+ * returns true.
+ */
+ virtual expression_literal eval();
+
+ protected:
+ //! Store all the sub-expressions, in order
+ expr_list_type _sub_exprs;
+ combiner_type _combiner;
+};
+
+// Forward declaration:
+class function_table;
+/*! A function call is a special type of container.
+ *
+ * All arguments are sub-expressions. The combiner type is
+ * always COMBINE_ALL in this case (changing the combiner type
+ * does not affect anything).
+ *
+ * The actual function maps to a C++ function available through
+ * a uhd::rfnoc::nocscript::function_table object.
+ *
+ * The recommended to use this is:
+ * 1. Create a function object giving its name (e.g. ADD)
+ * 2. Use the add() method to add all the function arguments
+ * in the right order (left to right).
+ * 3. Once step 2 is complete, the function object can be used.
+ * Call infer_type() to get the return value, if required.
+ * 4. Calling eval() will call into the function table. The
+ * argument expressions are evaluated, if so required, inside
+ * the function (lazy evalulation). Functions do not need
+ * to evaluate arguments.
+ */
+class expression_function : public expression_container
+{
+ public:
+ typedef boost::shared_ptr<expression_function> sptr;
+ typedef std::vector<expression::type_t> argtype_list_type;
+
+ //! Return an sptr to a function object without args
+ static sptr make(
+ const std::string &name,
+ const boost::shared_ptr<function_table> func_table
+ );
+
+ static std::string to_string(const std::string &name, const argtype_list_type &types);
+
+ expression_function(
+ const std::string &name,
+ const boost::shared_ptr<function_table> func_table
+ );
+
+ //! Add an argument expression
+ virtual void add(expression::sptr new_expr);
+
+ /*! Looks up the function type in the function table.
+ *
+ * Note that this will only work after all arguments have been
+ * added, as they are also used to look up a function's type in the
+ * function table.
+ */
+ expression::type_t infer_type() const;
+
+ /*! Evaluate all arguments, then the function itself.
+ */
+ expression_literal eval();
+
+ //! String representation
+ std::string repr() const;
+
+ private:
+ std::string _name;
+ const boost::shared_ptr<function_table> _func_table;
+ std::vector<expression::type_t> _arg_types;
+};
+
+
+/*! Variable expression
+ *
+ * Variables are like literals, only their type and value aren't known
+ * at parse-time. Instead, we provide a function object to look up
+ * variable's types and value.
+ */
+class expression_variable : public expression
+{
+ public:
+ typedef boost::shared_ptr<expression_variable> sptr;
+ typedef boost::function<expression::type_t(const std::string &)> type_getter_type;
+ typedef boost::function<expression_literal(const std::string &)> value_getter_type;
+
+ static sptr make(
+ const std::string &token_val,
+ type_getter_type type_getter,
+ value_getter_type value_getter
+ );
+
+ /*! Create a variable object from its token value
+ * (e.g. '$spp', i.e. including the '$' symbol). The variable
+ * does not have to exist at this point.
+ */
+ expression_variable(
+ const std::string &token_val,
+ type_getter_type type_getter,
+ value_getter_type value_getter
+ );
+
+ /*! Looks up the variable type in the variable table.
+ *
+ * \throws Depending on \p type_getter, this may throw when the variable does not exist.
+ * Recommended behaviour is to throw uhd::syntax_error.
+ */
+ expression::type_t infer_type() const;
+
+ /*! Look up a variable's value in the variable table.
+ *
+ * \throws Depending on \p value_getter, this may throw when the variable does not exist.
+ * Recommended behaviour is to throw uhd::syntax_error.
+ */
+ expression_literal eval();
+
+ private:
+ std::string _varname;
+ type_getter_type _type_getter;
+ value_getter_type _value_getter;
+};
+
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_EXPR_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/nocscript/function_table.cpp b/host/lib/rfnoc/nocscript/function_table.cpp
new file mode 100644
index 000000000..bebceb8dc
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/function_table.cpp
@@ -0,0 +1,113 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "function_table.hpp"
+#include "basic_functions.hpp"
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <map>
+
+using namespace uhd::rfnoc::nocscript;
+
+class function_table_impl : public function_table
+{
+ public:
+ struct function_info {
+ expression::type_t return_type;
+ function_ptr function;
+
+ function_info() {};
+ function_info(const expression::type_t return_type_, const function_ptr &function_)
+ : return_type(return_type_), function(function_)
+ {};
+ };
+ // Should be an unordered_map... sigh, we'll get to C++11 someday.
+ typedef std::map<std::string, std::map<expression_function::argtype_list_type, function_info> > table_type;
+
+ /************************************************************************
+ * Structors
+ ***********************************************************************/
+ function_table_impl()
+ {
+ _REGISTER_ALL_FUNCS();
+ }
+
+ ~function_table_impl() {};
+
+
+ /************************************************************************
+ * Interface implementation
+ ***********************************************************************/
+ bool function_exists(const std::string &name) const {
+ return bool(_table.count(name));
+ }
+
+ bool function_exists(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const {
+ table_type::const_iterator it = _table.find(name);
+ return (it != _table.end()) and bool(it->second.count(arg_types));
+ }
+
+ expression::type_t get_type(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const {
+ table_type::const_iterator it = _table.find(name);
+ if (it == _table.end() or (it->second.find(arg_types) == it->second.end())) {
+ throw uhd::syntax_error(str(
+ boost::format("Unable to retrieve return value for function %s")
+ % expression_function::to_string(name, arg_types)
+ ));
+ }
+ return it->second.find(arg_types)->second.return_type;
+ }
+
+ expression_literal eval(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types,
+ expression_container::expr_list_type &arguments
+ ) {
+ if (not function_exists(name, arg_types)) {
+ throw uhd::syntax_error(str(
+ boost::format("Cannot eval() function %s, not a known signature")
+ % expression_function::to_string(name, arg_types)
+ ));
+ }
+
+ return _table[name][arg_types].function(arguments);
+ }
+
+ void register_function(
+ const std::string &name,
+ const function_table::function_ptr &ptr,
+ const expression::type_t return_type,
+ const expression_function::argtype_list_type &sig
+ ) {
+ _table[name][sig] = function_info(return_type, ptr);
+ }
+
+ private:
+ table_type _table;
+};
+
+function_table::sptr function_table::make()
+{
+ return sptr(new function_table_impl());
+}
diff --git a/host/lib/rfnoc/nocscript/function_table.hpp b/host/lib/rfnoc/nocscript/function_table.hpp
new file mode 100644
index 000000000..6c715308e
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/function_table.hpp
@@ -0,0 +1,93 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "expression.hpp"
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+#include <vector>
+
+#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP
+#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+
+class function_table
+{
+ public:
+ typedef boost::shared_ptr<function_table> sptr;
+ typedef boost::function<expression_literal(expression_container::expr_list_type&)> function_ptr;
+
+ static sptr make();
+ virtual ~function_table() {};
+
+ /*! Check if any function with a given name exists
+ *
+ * \returns True, if any function with name \p name is registered.
+ */
+ virtual bool function_exists(const std::string &name) const = 0;
+
+ /*! Check if a function with a given name and list of argument types exists
+ *
+ * \returns True, if such a function is registered.
+ */
+ virtual bool function_exists(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const = 0;
+
+ /*! Get the return type of a function with given name and argument type list
+ *
+ * \returns The function's return type
+ * \throws uhd::syntax_error if no such function is registered
+ */
+ virtual expression::type_t get_type(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const = 0;
+
+ /*! Calls the function \p name with the argument list \p arguments
+ *
+ * \param arg_types A list of types for each argument
+ * \param arguments An expression list of the arguments
+ * \returns The return value of the called function
+ * \throws uhd::syntax_error if no such function is found
+ */
+ virtual expression_literal eval(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types,
+ expression_container::expr_list_type &arguments
+ ) = 0;
+
+ /*! Register a new function
+ *
+ * \param name Name of the function (e.g. 'ADD')
+ * \param ptr Function object
+ * \param return_type The function's return value
+ * \param sig The function signature (list of argument types)
+ */
+ virtual void register_function(
+ const std::string &name,
+ const function_ptr &ptr,
+ const expression::type_t return_type,
+ const expression_function::argtype_list_type &sig
+ ) = 0;
+};
+
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_FUNCTABLE_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/nocscript/gen_basic_funcs.py b/host/lib/rfnoc/nocscript/gen_basic_funcs.py
new file mode 100755
index 000000000..702b3e884
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/gen_basic_funcs.py
@@ -0,0 +1,474 @@
+#!/usr/bin/env python
+"""
+Generate the function list for the basic NocScript functions
+"""
+
+import re
+import os
+import sys
+from mako.template import Template
+
+#############################################################################
+# This is the interesting part: Add new functions in here
+#
+# Notes:
+# - Lines starting with # are considered comments, and will be removed from
+# the output
+# - C++ comments will be copied onto the generated file if inside functions
+# - Docstrings start with //! and are required
+# - Function signature is RETURN_TYPE NAME(ARG_TYPE1, ARG_TYPE2, ...)
+# - Function body is valid C++
+# - If your function requires special includes, put them in INCLUDE_LIST
+# - End of functions is delimited by s/^}/, so take care with the indents!
+# - Use these substitutions:
+# - ${RETURN}(...): Create a valid return value
+# - ${args[n]}: Access the n-th argument
+#
+INCLUDE_LIST = """
+#include <boost/math/special_functions/round.hpp>
+#include <boost/thread/thread.hpp>
+"""
+FUNCTION_LIST = """
+CATEGORY: Math Functions
+//! Returns x + y
+INT ADD(INT, INT)
+{
+ ${RETURN}(${args[0]} + ${args[1]});
+}
+
+//! Returns x + y
+DOUBLE ADD(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} + ${args[1]});
+}
+
+//! Returns x * y
+DOUBLE MULT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} * ${args[1]});
+}
+
+//! Returns x * y
+INT MULT(INT, INT)
+{
+ ${RETURN}(${args[0]} * ${args[1]});
+}
+
+//! Returns x / y
+DOUBLE DIV(DOUBLE, DOUBLE)
+{
+ ${RETURN}(${args[0]} / ${args[1]});
+}
+
+//! Returns true if x <= y (Less or Equal)
+BOOL LE(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} <= ${args[1]}));
+}
+
+//! Returns true if x <= y (Less or Equal)
+BOOL LE(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} <= ${args[1]}));
+}
+
+//! Returns true if x >= y (Greater or Equal)
+BOOL GE(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} >= ${args[1]}));
+}
+
+//! Returns true if x >= y (Greater or Equal)
+BOOL GE(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} >= ${args[1]}));
+}
+
+//! Returns true if x < y (Less Than)
+BOOL LT(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} < ${args[1]}));
+}
+
+//! Returns true if x > y (Greater Than)
+BOOL GT(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} > ${args[1]}));
+}
+
+//! Returns true if x < y (Less Than)
+BOOL LT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} < ${args[1]}));
+}
+
+//! Returns true if x > y (Greater Than)
+BOOL GT(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} > ${args[1]}));
+}
+
+//! Round x and return it as an integer
+INT IROUND(DOUBLE)
+{
+ ${RETURN}(int(boost::math::iround(${args[0]})));
+}
+
+//! Returns true if x is a power of 2
+BOOL IS_PWR_OF_2(INT)
+{
+ if (${args[0]} < 0) return ${FALSE};
+ int i = ${args[0]};
+ while ( (i & 1) == 0 and (i > 1) ) {
+ i >>= 1;
+ }
+ ${RETURN}(bool(i == 1));
+}
+
+//! Returns floor(log2(x)).
+INT LOG2(INT)
+{
+ if (${args[0]} < 0) {
+ throw uhd::runtime_error(str(
+ boost::format("In NocScript function ${func_name}: Cannot calculate log2() of negative number.")
+ ));
+ }
+
+ int power_value = ${args[0]};
+ int log2_value = 0;
+ while ( (power_value & 1) == 0 and (power_value > 1) ) {
+ power_value >>= 1;
+ log2_value++;
+ }
+ ${RETURN}(log2_value);
+}
+
+//! Returns x % y
+INT MODULO(INT, INT)
+{
+ ${RETURN}(${args[0]} % ${args[1]});
+}
+
+//! Returns true if x == y
+BOOL EQUAL(INT, INT)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+//! Returns true if x == y
+BOOL EQUAL(DOUBLE, DOUBLE)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+//! Returns true if x == y
+BOOL EQUAL(STRING, STRING)
+{
+ ${RETURN}(bool(${args[0]} == ${args[1]}));
+}
+
+CATEGORY: Bitwise Operations
+//! Returns x >> y
+INT SHIFT_RIGHT(INT, INT)
+{
+ ${RETURN}(${args[0]} >> ${args[1]});
+}
+
+//! Returns x << y
+INT SHIFT_LEFT(INT, INT)
+{
+ ${RETURN}(${args[0]} << ${args[1]});
+}
+
+//! Returns x & y
+INT BITWISE_AND(INT, INT)
+{
+ ${RETURN}(${args[0]} & ${args[1]});
+}
+
+//! Returns x | y
+INT BITWISE_OR(INT, INT)
+{
+ ${RETURN}(${args[0]} | ${args[1]});
+}
+
+//! Returns x ^ y
+INT BITWISE_XOR(INT, INT)
+{
+ ${RETURN}(${args[0]} ^ ${args[1]});
+}
+
+CATEGORY: Boolean Logic
+//! Returns x xor y.
+BOOL XOR(BOOL, BOOL)
+{
+ ${RETURN}(${args[0]} xor ${args[1]});
+}
+
+//! Returns !x
+BOOL NOT(BOOL)
+{
+ ${RETURN}(not ${args[0]});
+}
+
+//! Always returns true
+BOOL TRUE()
+{
+ return ${TRUE};
+}
+
+//! Always returns false
+BOOL FALSE()
+{
+ return ${FALSE};
+}
+
+CATEGORY: Conditional Execution
+//! Executes x, if true, execute y. Returns true if x is true.
+BOOL IF(BOOL, BOOL)
+{
+ if (${args[0]}) {
+ ${args[1]};
+ ${RETURN}(true);
+ }
+ ${RETURN}(false);
+}
+
+//! Executes x, if true, execute y, otherwise, execute z. Returns true if x is true.
+BOOL IF_ELSE(BOOL, BOOL, BOOL)
+{
+ if (${args[0]}) {
+ ${args[1]};
+ ${RETURN}(true);
+ } else {
+ ${args[2]};
+ }
+ ${RETURN}(false);
+}
+
+CATEGORY: Execution Control
+//! Sleep for x seconds. Fractions are allowed. Millisecond accuracy.
+BOOL SLEEP(DOUBLE)
+{
+ int ms = ${args[0]} / 1000;
+ boost::this_thread::sleep(boost::posix_time::milliseconds(ms));
+ ${RETURN}(true);
+}
+"""
+# End of interesting part. The rest will take this and turn into a C++
+# header file.
+#############################################################################
+
+HEADER = """<% import time %>//
+///////////////////////////////////////////////////////////////////////
+// This file was generated by ${file} on ${time.strftime("%c")}
+///////////////////////////////////////////////////////////////////////
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+/****************************************************************************
+ * This file is autogenerated! Any manual changes in here will be
+ * overwritten by calling nocscript_gen_basic_funcs.py!
+ ***************************************************************************/
+
+#include "expression.hpp"
+#include "function_table.hpp"
+#include <uhd/exception.hpp>
+#include <boost/format.hpp>
+#include <boost/assign/list_of.hpp>
+${INCLUDE_LIST}
+
+#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP
+#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+"""
+
+# Not a Mako template:
+FOOTER="""
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_BASICFUNCS_HPP */
+"""
+
+# Not a Mako template:
+FUNC_TEMPLATE = """
+expression_literal {NAME}(expression_container::expr_list_type &{ARGS})
+{BODY}
+"""
+
+REGISTER_MACRO_TEMPLATE = """#define _REGISTER_ALL_FUNCS()${registry}
+"""
+
+REGISTER_COMMANDS_TEMPLATE = """
+ % if len(arglist):
+ expression_function::argtype_list_type ${func_name}_args = boost::assign::list_of
+ % for this_type in arglist:
+ (expression::TYPE_${this_type})
+ % endfor
+ ;
+ % else:
+ expression_function::argtype_list_type ${func_name}_args;
+ % endif
+ register_function(
+ "${name}",
+ boost::bind(&${func_name}, _1),
+ expression::TYPE_${retval},
+ ${func_name}_args
+ );"""
+
+DOXY_TEMPLATE = """/*! \page page_nocscript_funcs NocScript Function Reference
+% for cat, func_by_name in func_list_tree.iteritems():
+- ${cat}
+% for func_name, func_info_list in func_by_name.iteritems():
+ - ${func_name}: ${func_info_list[0]['docstring']}
+% for func_info in func_info_list:
+ - ${func_info['arglist']} -> ${func_info['retval']}
+% endfor
+% endfor
+% endfor
+
+*/
+"""
+
+def parse_tmpl(_tmpl_text, **kwargs):
+ return Template(_tmpl_text).render(**kwargs)
+
+def make_cxx_func_name(func_dict):
+ """
+ Creates a unique C++ function name from a function description
+ """
+ return "{name}__{retval}__{arglist}".format(
+ name=func_dict['name'],
+ retval=func_dict['retval'],
+ arglist="_".join(func_dict['arglist'])
+ )
+
+def make_cxx_func_body(func_dict):
+ """
+ Formats the function body properly
+ """
+ type_lookup_methods = {
+ 'INT': 'get_int',
+ 'DOUBLE': 'get_double',
+ 'BOOL': 'get_bool',
+ 'STRING': 'get_string',
+ }
+ args_lookup = []
+ for idx, arg_type in enumerate(func_dict['arglist']):
+ args_lookup.append("args[{idx}]->eval().{getter}()".format(idx=idx, getter=type_lookup_methods[arg_type]))
+ return parse_tmpl(
+ func_dict['body'],
+ args=args_lookup,
+ FALSE='expression_literal(false)',
+ TRUE='expression_literal(true)',
+ RETURN='return expression_literal',
+ **func_dict
+ )
+
+def prep_function_list():
+ """
+ - Remove all comments
+ - Split the function list into individual functions
+ - Split the functions into return value, name, argument list and body
+ """
+ comment_remove_re = re.compile(r'^\s*#.*$', flags=re.MULTILINE)
+ func_list_wo_comments = comment_remove_re.sub('', FUNCTION_LIST)
+ func_splitter_re = re.compile(r'(?<=^})\s*$', flags=re.MULTILINE)
+ func_list_split = func_splitter_re.split(func_list_wo_comments)
+ func_list_split = [x.strip() for x in func_list_split if len(x.strip())]
+ func_list = []
+ last_category = ''
+ for func in func_list_split:
+ split_regex = r'(^CATEGORY: (?P<cat>[^\n]*)\s*)?' \
+ r'//!(?P<docstring>[^\n]*)\s*' + \
+ r'(?P<retval>[A-Z][A-Z0-9_]*)\s+' + \
+ r'(?P<funcname>[A-Z][A-Z0-9_]*)\s*\((?P<arglist>[^\)]*)\)\s*' + \
+ r'(?P<funcbody>^{.*)'
+ split_re = re.compile(split_regex, flags=re.MULTILINE|re.DOTALL)
+ mo = split_re.match(func)
+ if mo.group('cat'):
+ last_category = mo.group('cat').strip()
+ func_dict = {
+ 'docstring': mo.group('docstring').strip(),
+ 'name': mo.group('funcname'),
+ 'retval': mo.group('retval'),
+ 'arglist': [x.strip() for x in mo.group('arglist').split(',') if len(x.strip())],
+ 'body': mo.group('funcbody'),
+ 'category': last_category,
+ }
+ func_dict['func_name'] = make_cxx_func_name(func_dict)
+ func_list.append(func_dict)
+ return func_list
+
+def write_function_header(output_filename):
+ """
+ Create the .hpp file that defines all the NocScript functions in C++.
+ """
+ func_list = prep_function_list()
+ # Step 1: Write the prototypes
+ func_prototypes = ''
+ registry_commands = ''
+ for func in func_list:
+ func_prototypes += FUNC_TEMPLATE.format(
+ NAME=func['func_name'],
+ BODY=make_cxx_func_body(func),
+ ARGS="args" if len(func['arglist']) else ""
+ )
+ registry_commands += parse_tmpl(
+ REGISTER_COMMANDS_TEMPLATE,
+ **func
+ )
+ # Step 2: Write the registry process
+ register_func = parse_tmpl(REGISTER_MACRO_TEMPLATE, registry=registry_commands)
+ register_func = register_func.replace('\n', ' \\\n')
+
+ # Final step: Join parts and write to file
+ full_file = "\n".join((
+ parse_tmpl(HEADER, file = os.path.basename(__file__), INCLUDE_LIST=INCLUDE_LIST),
+ func_prototypes,
+ register_func,
+ FOOTER,
+ ))
+ open(output_filename, 'w').write(full_file)
+
+def write_manual_file(output_filename):
+ """
+ Write the Doxygen file for the NocScript functions.
+ """
+ func_list = prep_function_list()
+ func_list_tree = {}
+ for func in func_list:
+ if not func_list_tree.has_key(func['category']):
+ func_list_tree[func['category']] = {}
+ if not func_list_tree[func['category']].has_key(func['name']):
+ func_list_tree[func['category']][func['name']] = []
+ func_list_tree[func['category']][func['name']].append(func)
+ open(output_filename, 'w').write(parse_tmpl(DOXY_TEMPLATE, func_list_tree=func_list_tree))
+
+
+def main():
+ if len(sys.argv) < 2:
+ print("No output file specified!")
+ exit(1)
+ outfile = sys.argv[1]
+ if os.path.splitext(outfile)[1] == '.dox':
+ write_manual_file(outfile)
+ else:
+ write_function_header(outfile)
+
+if __name__ == "__main__":
+ main()
diff --git a/host/lib/rfnoc/nocscript/parser.cpp b/host/lib/rfnoc/nocscript/parser.cpp
new file mode 100644
index 000000000..bb7ed6cdf
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/parser.cpp
@@ -0,0 +1,363 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "parser.hpp"
+#include <uhd/utils/cast.hpp>
+#include <boost/spirit/include/lex_lexertl.hpp>
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <boost/assign.hpp>
+#include <boost/make_shared.hpp>
+#include <sstream>
+#include <stack>
+
+using namespace uhd::rfnoc::nocscript;
+namespace lex = boost::spirit::lex;
+
+class parser_impl : public parser
+{
+ public:
+ /******************************************************************
+ * Structors TODO make them protected
+ *****************************************************************/
+ parser_impl(
+ function_table::sptr ftable,
+ expression_variable::type_getter_type var_type_getter,
+ expression_variable::value_getter_type var_value_getter
+ ) : _ftable(ftable)
+ , _var_type_getter(var_type_getter)
+ , _var_value_getter(var_value_getter)
+ {
+ // nop
+ }
+
+ ~parser_impl() {};
+
+
+ /******************************************************************
+ * Parsing
+ *****************************************************************/
+ //! List of parser tokens
+ enum token_ids
+ {
+ ID_WHITESPACE = lex::min_token_id + 42,
+ ID_KEYWORD,
+ ID_ARG_SEP,
+ ID_PARENS_OPEN,
+ ID_PARENS_CLOSE,
+ ID_VARIABLE,
+ ID_LITERAL_DOUBLE,
+ ID_LITERAL_INT,
+ ID_LITERAL_HEX,
+ ID_LITERAL_STR,
+ ID_LITERAL_VECTOR_INT
+ };
+
+ //! The Lexer object used for NocScript
+ template <typename Lexer>
+ struct ns_lexer : lex::lexer<Lexer>
+ {
+ ns_lexer()
+ {
+ this->self.add
+ ("\\s+", ID_WHITESPACE)
+ (",", ID_ARG_SEP)
+ ("[A-Z][A-Z0-9_]*", ID_KEYWORD)
+ ("\\(", ID_PARENS_OPEN)
+ ("\\)", ID_PARENS_CLOSE)
+ ("\\$[a-z][a-z0-9_]*", ID_VARIABLE)
+ ("-?\\d+\\.\\d+", ID_LITERAL_DOUBLE)
+ ("-?\\d+", ID_LITERAL_INT)
+ ("0x[0-9A-F]+", ID_LITERAL_HEX)
+ ("\\\"[^\\\"]*\\\"", ID_LITERAL_STR)
+ ("'[^']*'", ID_LITERAL_STR) // both work
+ ("\\[[0-9]\\]", ID_LITERAL_VECTOR_INT)
+ ;
+ }
+ };
+
+ private:
+ struct grammar_props
+ {
+ function_table::sptr ftable;
+ expression_variable::type_getter_type var_type_getter;
+ expression_variable::value_getter_type var_value_getter;
+
+ //! Store the last keyword
+ std::string function_name;
+ std::string error;
+ std::stack<expression_container::sptr> expr_stack;
+
+ grammar_props(
+ function_table::sptr ftable_,
+ expression_variable::type_getter_type var_type_getter_,
+ expression_variable::value_getter_type var_value_getter_
+ ) : ftable(ftable_), var_type_getter(var_type_getter_), var_value_getter(var_value_getter_),
+ function_name("")
+ {
+ UHD_ASSERT_THROW(expr_stack.empty());
+ // Push an empty container to the stack to hold the result
+ expr_stack.push(expression_container::make());
+ }
+
+ expression::sptr get_result()
+ {
+ UHD_ASSERT_THROW(expr_stack.size() == 1);
+ return expr_stack.top();
+ }
+ };
+
+ //! This isn't strictly a grammar, as it also includes semantic
+ // actions etc. I'm not going to spend ages thinking of a better
+ // name at this point.
+ struct grammar
+ {
+ // Implementation detail specific to boost::bind (see Boost::Spirit
+ // examples)
+ typedef bool result_type;
+
+ static const int VALID_COMMA = 0x1;
+ static const int VALID_PARENS_OPEN = 0x2;
+ static const int VALID_PARENS_CLOSE = 0x4;
+ static const int VALID_EXPRESSION = 0x8 + 0x02;
+ static const int VALID_OPERATOR = 0x10;
+
+ // !This function operator gets called for each of the matched tokens.
+ template <typename Token>
+ bool operator()(Token const& t, grammar_props &P, int &next_valid_state) const
+ {
+ //! This is totally not how Boost::Spirit is meant to be used,
+ // as there's token types etc. But for now let's just convert
+ // every token to a string, and then handle it as such.
+ std::stringstream sstr;
+ sstr << t.value();
+ std::string val = sstr.str();
+ //std::cout << "VAL: " << val << std::endl;
+ //std::cout << "Next valid states:\n"
+ //<< boost::format("VALID_COMMA [%s]\n") % ((next_valid_state & 0x1) ? "x" : " ")
+ //<< boost::format("VALID_PARENS_OPEN [%s]\n") % ((next_valid_state & 0x2) ? "x" : " ")
+ //<< boost::format("VALID_PARENS_CLOSE [%s]\n") % ((next_valid_state & 0x4) ? "x" : " ")
+ //<< boost::format("VALID_EXPRESSION [%s]\n") % ((next_valid_state & (0x8 + 0x02)) ? "x" : " ")
+ //<< boost::format("VALID_OPERATOR [%s]\n") % ((next_valid_state & 0x10) ? "x" : " ")
+ //<< std::endl;
+
+ switch (t.id()) {
+
+ case ID_WHITESPACE:
+ // Ignore
+ break;
+
+ case ID_KEYWORD:
+ // Ambiguous, could be an operator (AND, OR) or a function name (ADD, MULT...).
+ // So first, check which it is:
+ if (val == "AND" or val == "OR") {
+ if (not (next_valid_state & VALID_OPERATOR)) {
+ P.error = str(boost::format("Unexpected operator: %s") % val);
+ return false;
+ }
+ next_valid_state = VALID_EXPRESSION;
+ try {
+ if (val == "AND") {
+ P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_AND);
+ } else if (val == "OR") {
+ P.expr_stack.top()->set_combiner_safe(expression_container::COMBINE_OR);
+ }
+ } catch (const uhd::syntax_error &e) {
+ P.error = str(boost::format("Operator %s is mixing operator types within this container.") % val);
+ }
+ // Right now, we can't have multiple operator types within a container.
+ // We might be able to change that, if there's enough demand. Either
+ // we keep track of multiple operators, or we open a new container.
+ // In the latter case, we'd need a way of keeping track of those containers,
+ // so it's a bit tricky.
+ break;
+ }
+ // If it's not a keyword, it has to be a function, so check the
+ // function table:
+ if (not (next_valid_state & VALID_EXPRESSION)) {
+ P.error = str(boost::format("Unexpected expression: %s") % val);
+ return false;
+ }
+ if (not P.ftable->function_exists(val)) {
+ P.error = str(boost::format("Unknown function: %s") % val);
+ return false;
+ }
+ P.function_name = val;
+ next_valid_state = VALID_PARENS_OPEN;
+ break;
+
+ // Every () creates a new container, either a raw container or
+ // a function.
+ case ID_PARENS_OPEN:
+ if (not (next_valid_state & VALID_PARENS_OPEN)) {
+ P.error = str(boost::format("Unexpected parentheses."));
+ return false;
+ }
+ if (not P.function_name.empty()) {
+ // We've already checked the function name exists
+ P.expr_stack.push(expression_function::make(P.function_name, P.ftable));
+ P.function_name.clear();
+ } else {
+ P.expr_stack.push(expression_container::make());
+ }
+ // Push another empty container to hold the first element/argument
+ // in this container:
+ P.expr_stack.push(expression_container::make());
+ next_valid_state = VALID_EXPRESSION | VALID_PARENS_CLOSE;
+ break;
+
+ case ID_PARENS_CLOSE:
+ {
+ if (not (next_valid_state & VALID_PARENS_CLOSE)) {
+ P.error = str(boost::format("Unexpected parentheses."));
+ return false;
+ }
+ if (P.expr_stack.size() < 2) {
+ P.error = str(boost::format("Unbalanced closing parentheses."));
+ return false;
+ }
+ // First pop the last expression inside the parentheses,
+ // if it's not empty, add it to the top container (this also avoids
+ // adding arguments to functions if none were provided):
+ expression_container::sptr c = P.expr_stack.top();
+ P.expr_stack.pop();
+ if (not c->empty()) {
+ P.expr_stack.top()->add(c);
+ }
+ // At the end of (), either a function or container is complete,
+ // so pop that and add it to its top container:
+ expression_container::sptr c2 = P.expr_stack.top();
+ P.expr_stack.pop();
+ P.expr_stack.top()->add(c2);
+ next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE;
+ }
+ break;
+
+ case ID_ARG_SEP:
+ {
+ if (not (next_valid_state & VALID_COMMA)) {
+ P.error = str(boost::format("Unexpected comma."));
+ return false;
+ }
+ next_valid_state = VALID_EXPRESSION;
+ // If stack size is 1, we're on the base container, which means we
+ // simply string stuff.
+ if (P.expr_stack.size() == 1) {
+ break;
+ }
+ // Otherwise, a ',' always means we add the previous expression to
+ // the current container:
+ expression_container::sptr c = P.expr_stack.top();
+ P.expr_stack.pop();
+ P.expr_stack.top()->add(c);
+ // It also means another expression is following, so create another
+ // empty container for that:
+ P.expr_stack.push(expression_container::make());
+ }
+ break;
+
+ // All the atomic expressions just get added to the current container:
+
+ case ID_VARIABLE:
+ {
+ if (not (next_valid_state & VALID_EXPRESSION)) {
+ P.error = str(boost::format("Unexpected expression."));
+ return false;
+ }
+ expression_variable::sptr v = expression_variable::make(val, P.var_type_getter, P.var_value_getter);
+ P.expr_stack.top()->add(v);
+ next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE;
+ }
+ break;
+
+ default:
+ // If we get here, we assume it's a literal expression
+ {
+ if (not (next_valid_state & VALID_EXPRESSION)) {
+ P.error = str(boost::format("Unexpected expression."));
+ return false;
+ }
+ expression::type_t token_type;
+ switch (t.id()) { // A map lookup would be more elegant, but we'd need a nicer C++ for that
+ case ID_LITERAL_DOUBLE: token_type = expression::TYPE_DOUBLE; break;
+ case ID_LITERAL_INT: token_type = expression::TYPE_INT; break;
+ case ID_LITERAL_HEX: token_type = expression::TYPE_INT; break;
+ case ID_LITERAL_STR: token_type = expression::TYPE_STRING; break;
+ case ID_LITERAL_VECTOR_INT: token_type = expression::TYPE_INT_VECTOR; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ P.expr_stack.top()->add(boost::make_shared<expression_literal>(val, token_type));
+ next_valid_state = VALID_OPERATOR | VALID_COMMA | VALID_PARENS_CLOSE;
+ break;
+ }
+
+ } // end switch
+ return true;
+ }
+ };
+
+ public:
+ expression::sptr create_expr_tree(const std::string &code)
+ {
+ // Create empty stack and keyword states
+ grammar_props P(_ftable, _var_type_getter, _var_value_getter);
+ int next_valid_state = grammar::VALID_EXPRESSION;
+
+ // Create a lexer instance
+ ns_lexer<lex::lexertl::lexer<> > lexer_functor;
+
+ // Tokenize the string
+ char const* first = code.c_str();
+ char const* last = &first[code.size()];
+ bool r = lex::tokenize(
+ first, last, // Iterators
+ lexer_functor, // Lexer
+ boost::bind(grammar(), _1, boost::ref(P), boost::ref(next_valid_state)) // Function object
+ );
+
+ // Check the parsing worked:
+ if (not r or P.expr_stack.size() != 1) {
+ std::string rest(first, last);
+ throw uhd::syntax_error(str(
+ boost::format("Parsing stopped at: %s\nError message: %s")
+ % rest % P.error
+ ));
+ }
+
+ // Clear stack and return result
+ return P.get_result();
+ }
+
+ private:
+
+ function_table::sptr _ftable;
+ expression_variable::type_getter_type _var_type_getter;
+ expression_variable::value_getter_type _var_value_getter;
+};
+
+parser::sptr parser::make(
+ function_table::sptr ftable,
+ expression_variable::type_getter_type var_type_getter,
+ expression_variable::value_getter_type var_value_getter
+) {
+ return sptr(new parser_impl(
+ ftable,
+ var_type_getter,
+ var_value_getter
+ ));
+}
+
diff --git a/host/lib/rfnoc/nocscript/parser.hpp b/host/lib/rfnoc/nocscript/parser.hpp
new file mode 100644
index 000000000..32fecd2c0
--- /dev/null
+++ b/host/lib/rfnoc/nocscript/parser.hpp
@@ -0,0 +1,50 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "expression.hpp"
+#include "function_table.hpp"
+#include <boost/shared_ptr.hpp>
+
+#ifndef INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP
+#define INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP
+
+namespace uhd { namespace rfnoc { namespace nocscript {
+
+class parser
+{
+ public:
+ typedef boost::shared_ptr<parser> sptr;
+
+ static sptr make(
+ function_table::sptr ftable,
+ expression_variable::type_getter_type var_type_getter,
+ expression_variable::value_getter_type var_value_getter
+ );
+
+ /*! The main parsing call: Turn a string of code into an expression tree.
+ *
+ * Evaluating the returned object will execute the code.
+ *
+ * \throws uhd::syntax_error if \p code contains syntax errors
+ */
+ virtual expression::sptr create_expr_tree(const std::string &code) = 0;
+};
+
+}}} /* namespace uhd::rfnoc::nocscript */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_NOCSCRIPT_PARSER_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/node_ctrl_base.cpp b/host/lib/rfnoc/node_ctrl_base.cpp
new file mode 100644
index 000000000..6e19d276a
--- /dev/null
+++ b/host/lib/rfnoc/node_ctrl_base.cpp
@@ -0,0 +1,103 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/node_ctrl_base.hpp>
+#include <uhd/utils/msg.hpp>
+
+using namespace uhd::rfnoc;
+
+std::string node_ctrl_base::unique_id() const
+{
+ // Most instantiations will override this, so we don't need anything
+ // more elegant here.
+ return str(boost::format("%08X") % size_t(this));
+}
+
+void node_ctrl_base::clear()
+{
+ UHD_RFNOC_BLOCK_TRACE() << "node_ctrl_base::clear() " << std::endl;
+ // Reset connections:
+ _upstream_nodes.clear();
+ _downstream_nodes.clear();
+}
+
+void node_ctrl_base::_register_downstream_node(
+ node_ctrl_base::sptr,
+ size_t
+) {
+ throw uhd::runtime_error("Attempting to register a downstream block on a non-source node.");
+}
+
+void node_ctrl_base::_register_upstream_node(
+ node_ctrl_base::sptr,
+ size_t
+) {
+ throw uhd::runtime_error("Attempting to register an upstream block on a non-sink node.");
+}
+
+void node_ctrl_base::set_downstream_port(
+ const size_t this_port,
+ const size_t remote_port
+) {
+ if (not _downstream_nodes.count(this_port) and remote_port != ANY_PORT) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Cannot set remote downstream port: Port %d not connected.")
+ % unique_id() % this_port
+ ));
+ }
+ _downstream_ports[this_port] = remote_port;
+}
+
+size_t node_ctrl_base::get_downstream_port(const size_t this_port)
+{
+ if (not _downstream_ports.count(this_port)
+ or not _downstream_nodes.count(this_port)
+ or _downstream_ports[this_port] == ANY_PORT) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Cannot retrieve remote downstream port: Port %d not connected.")
+ % unique_id() % this_port
+ ));
+ }
+ return _downstream_ports[this_port];
+}
+
+void node_ctrl_base::set_upstream_port(
+ const size_t this_port,
+ const size_t remote_port
+) {
+ if (not _upstream_nodes.count(this_port) and remote_port != ANY_PORT) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Cannot set remote upstream port: Port %d not connected.")
+ % unique_id() % this_port
+ ));
+ }
+ _upstream_ports[this_port] = remote_port;
+}
+
+size_t node_ctrl_base::get_upstream_port(const size_t this_port)
+{
+ if (not _upstream_ports.count(this_port)
+ or not _upstream_nodes.count(this_port)
+ or _upstream_ports[this_port] == ANY_PORT) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Cannot retrieve remote upstream port: Port %d not connected.")
+ % unique_id() % this_port
+ ));
+ }
+ return _upstream_ports[this_port];
+}
+
diff --git a/host/lib/rfnoc/radio_ctrl_impl.cpp b/host/lib/rfnoc/radio_ctrl_impl.cpp
new file mode 100644
index 000000000..1cc7a2472
--- /dev/null
+++ b/host/lib/rfnoc/radio_ctrl_impl.cpp
@@ -0,0 +1,368 @@
+//
+// Copyright 2014-2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "wb_iface_adapter.hpp"
+#include <boost/foreach.hpp>
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <uhd/convert.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/direction.hpp>
+#include "radio_ctrl_impl.hpp"
+#include "../../transport/super_recv_packet_handler.hpp"
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+static const size_t BYTES_PER_SAMPLE = 4;
+
+/****************************************************************************
+ * Structors and init
+ ***************************************************************************/
+// Note: block_ctrl_base must be called before this, but has to be called by
+// the derived class because of virtual inheritance
+radio_ctrl_impl::radio_ctrl_impl() :
+ _tick_rate(rfnoc::rate_node_ctrl::RATE_UNDEFINED)
+{
+ _num_rx_channels = get_output_ports().size();
+ _num_tx_channels = get_input_ports().size();
+ _continuous_streaming = std::vector<bool>(2, false);
+
+ for (size_t i = 0; i < _num_rx_channels; i++) {
+ _rx_streamer_active[i] = false;
+ }
+ for (size_t i = 0; i < _num_tx_channels; i++) {
+ _tx_streamer_active[i] = false;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Setup peripherals
+ /////////////////////////////////////////////////////////////////////////
+ for (size_t i = 0; i < _get_num_radios(); i++) {
+ _register_loopback_self_test(i);
+ _perifs[i].ctrl = boost::make_shared<wb_iface_adapter>(
+ // poke32 functor
+ boost::bind(
+ static_cast< void (block_ctrl_base::*)(const boost::uint32_t, const boost::uint32_t, const size_t) >(&block_ctrl_base::sr_write),
+ this, _1, _2, i
+ ),
+ // peek32 functor
+ boost::bind(
+ static_cast< boost::uint32_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read32),
+ this,
+ _1, i
+ ),
+ // peek64 functor
+ boost::bind(
+ static_cast< boost::uint64_t (block_ctrl_base::*)(const boost::uint32_t, const size_t) >(&block_ctrl_base::user_reg_read64),
+ this,
+ _1, i
+ ),
+ // get_time functor
+ boost::bind(
+ static_cast< time_spec_t (block_ctrl_base::*)(const size_t) >(&block_ctrl_base::get_command_time),
+ this, i
+ ),
+ // set_time functor
+ boost::bind(
+ static_cast< void (block_ctrl_base::*)(const time_spec_t&, const size_t) >(&block_ctrl_base::set_command_time),
+ this,
+ _1, i
+ )
+ );
+
+ // FIXME there's currently no way to set the underflow policy
+
+ if (i == 0) {
+ time_core_3000::readback_bases_type time64_rb_bases;
+ time64_rb_bases.rb_now = regs::RB_TIME_NOW;
+ time64_rb_bases.rb_pps = regs::RB_TIME_PPS;
+ _time64 = time_core_3000::make(_perifs[i].ctrl, regs::sr_addr(regs::TIME), time64_rb_bases);
+ this->set_time_now(0.0);
+ }
+
+ //Reset the RX control engine
+ sr_write(regs::RX_CTRL_HALT, 1, i);
+ }
+
+ ////////////////////////////////////////////////////////////////////
+ // Register the time keeper
+ ////////////////////////////////////////////////////////////////////
+ if (not _tree->exists(fs_path("time") / "now")) {
+ _tree->create<time_spec_t>(fs_path("time") / "now")
+ .set_publisher(boost::bind(&radio_ctrl_impl::get_time_now, this))
+ ;
+ }
+ if (not _tree->exists(fs_path("time") / "pps")) {
+ _tree->create<time_spec_t>(fs_path("time") / "pps")
+ .set_publisher(boost::bind(&radio_ctrl_impl::get_time_last_pps, this))
+ ;
+ }
+ if (not _tree->exists(fs_path("time") / "cmd")) {
+ _tree->create<time_spec_t>(fs_path("time") / "cmd");
+ }
+ _tree->access<time_spec_t>(fs_path("time") / "now")
+ .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_now, this, _1))
+ ;
+ _tree->access<time_spec_t>(fs_path("time") / "pps")
+ .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::set_time_next_pps, this, _1))
+ ;
+ for (size_t i = 0; i < _get_num_radios(); i++) {
+ _tree->access<time_spec_t>("time/cmd")
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_tick_rate, this, boost::ref(_tick_rate), i))
+ .add_coerced_subscriber(boost::bind(&block_ctrl_base::set_command_time, this, _1, i))
+ ;
+ }
+ // spp gets created in the XML file
+ _tree->access<int>(get_arg_path("spp") / "value")
+ .add_coerced_subscriber(boost::bind(&radio_ctrl_impl::_update_spp, this, _1))
+ .update()
+ ;
+}
+
+void radio_ctrl_impl::_register_loopback_self_test(size_t chan)
+{
+ UHD_MSG(status) << "[RFNoC Radio] Performing register loopback test... " << std::flush;
+ size_t hash = size_t(time(NULL));
+ for (size_t i = 0; i < 100; i++)
+ {
+ boost::hash_combine(hash, i);
+ sr_write(regs::TEST, boost::uint32_t(hash), chan);
+ boost::uint32_t result = user_reg_read32(regs::RB_TEST, chan);
+ if (result != boost::uint32_t(hash)) {
+ UHD_MSG(status) << "fail" << std::endl;
+ UHD_MSG(status) << boost::format("expected: %x result: %x") % boost::uint32_t(hash) % result << std::endl;
+ return; // exit on any failure
+ }
+ }
+ UHD_MSG(status) << "pass" << std::endl;
+}
+
+/****************************************************************************
+ * API calls
+ ***************************************************************************/
+double radio_ctrl_impl::set_rate(double rate)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ _tick_rate = rate;
+ _time64->set_tick_rate(_tick_rate);
+ _time64->self_test();
+ return _tick_rate;
+}
+
+void radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan)
+{
+ _tx_antenna[chan] = ant;
+}
+
+void radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan)
+{
+ _rx_antenna[chan] = ant;
+}
+
+double radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan)
+{
+ return _tx_freq[chan] = freq;
+}
+
+double radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan)
+{
+ return _rx_freq[chan] = freq;
+}
+
+double radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan)
+{
+ return _tx_gain[chan] = gain;
+}
+
+double radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan)
+{
+ return _rx_gain[chan] = gain;
+}
+
+void radio_ctrl_impl::set_time_sync(const uhd::time_spec_t &time)
+{
+ _time64->set_time_sync(time);
+}
+
+double radio_ctrl_impl::get_rate() const
+{
+ return _tick_rate;
+}
+
+std::string radio_ctrl_impl::get_tx_antenna(const size_t chan) /* const */
+{
+ return _tx_antenna[chan];
+}
+
+std::string radio_ctrl_impl::get_rx_antenna(const size_t chan) /* const */
+{
+ return _rx_antenna[chan];
+}
+
+double radio_ctrl_impl::get_tx_frequency(const size_t chan) /* const */
+{
+ return _tx_freq[chan];
+}
+
+double radio_ctrl_impl::get_rx_frequency(const size_t chan) /* const */
+{
+ return _rx_freq[chan];
+}
+
+double radio_ctrl_impl::get_tx_gain(const size_t chan) /* const */
+{
+ return _tx_gain[chan];
+}
+
+double radio_ctrl_impl::get_rx_gain(const size_t chan) /* const */
+{
+ return _rx_gain[chan];
+}
+
+/***********************************************************************
+ * RX Streamer-related methods (from source_block_ctrl_base)
+ **********************************************************************/
+//! Pass stream commands to the radio
+void radio_ctrl_impl::issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t chan)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() " << chan << " " << char(stream_cmd.stream_mode) << std::endl;
+ if (not _is_streamer_active(uhd::RX_DIRECTION, chan)) {
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::issue_stream_cmd() called on inactive channel. Skipping." << std::endl;
+ return;
+ }
+ UHD_ASSERT_THROW(stream_cmd.num_samps <= 0x0fffffff);
+ _continuous_streaming[chan] = (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
+
+ //setup the mode to instruction flags
+ typedef boost::tuple<bool, bool, bool, bool> inst_t;
+ static const uhd::dict<stream_cmd_t::stream_mode_t, inst_t> mode_to_inst = boost::assign::map_list_of
+ //reload, chain, samps, stop
+ (stream_cmd_t::STREAM_MODE_START_CONTINUOUS, inst_t(true, true, false, false))
+ (stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, inst_t(false, false, false, true))
+ (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE, inst_t(false, false, true, false))
+ (stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE, inst_t(false, true, true, false))
+ ;
+
+ //setup the instruction flag values
+ bool inst_reload, inst_chain, inst_samps, inst_stop;
+ boost::tie(inst_reload, inst_chain, inst_samps, inst_stop) = mode_to_inst[stream_cmd.stream_mode];
+
+ //calculate the word from flags and length
+ boost::uint32_t cmd_word = 0;
+ cmd_word |= boost::uint32_t((stream_cmd.stream_now)? 1 : 0) << 31;
+ cmd_word |= boost::uint32_t((inst_chain)? 1 : 0) << 30;
+ cmd_word |= boost::uint32_t((inst_reload)? 1 : 0) << 29;
+ cmd_word |= boost::uint32_t((inst_stop)? 1 : 0) << 28;
+ cmd_word |= (inst_samps)? stream_cmd.num_samps : ((inst_stop)? 0 : 1);
+
+ //issue the stream command
+ const boost::uint64_t ticks = (stream_cmd.stream_now)? 0 : stream_cmd.time_spec.to_ticks(get_rate());
+ sr_write(regs::RX_CTRL_CMD, cmd_word, chan);
+ sr_write(regs::RX_CTRL_TIME_HI, boost::uint32_t(ticks >> 32), chan);
+ sr_write(regs::RX_CTRL_TIME_LO, boost::uint32_t(ticks >> 0), chan); //latches the command
+}
+
+std::vector<size_t> radio_ctrl_impl::get_active_rx_ports()
+{
+ std::vector<size_t> active_rx_ports;
+ typedef std::map<size_t, bool> map_t;
+ BOOST_FOREACH(map_t::value_type &m, _rx_streamer_active) {
+ if (m.second) {
+ active_rx_ports.push_back(m.first);
+ }
+ }
+ return active_rx_ports;
+}
+
+/***********************************************************************
+ * Radio controls (radio_ctrl specific)
+ **********************************************************************/
+void radio_ctrl_impl::set_rx_streamer(bool active, const size_t port)
+{
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_rx_streamer() " << port << " -> " << active << std::endl;
+ if (port > _num_rx_channels) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Can't (un)register RX streamer on port %d (invalid port)")
+ % unique_id() % port
+ ));
+ }
+ _rx_streamer_active[port] = active;
+ if (not check_radio_config()) {
+ throw std::runtime_error(str(
+ boost::format("[%s]: Invalid radio configuration.")
+ % unique_id()
+ ));
+ }
+}
+
+void radio_ctrl_impl::set_tx_streamer(bool active, const size_t port)
+{
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::set_tx_streamer() " << port << " -> " << active << std::endl;
+ if (port > _num_tx_channels) {
+ throw uhd::value_error(str(
+ boost::format("[%s] Can't (un)register TX streamer on port %d (invalid port)")
+ % unique_id() % port
+ ));
+ }
+ _tx_streamer_active[port] = active;
+ if (not check_radio_config()) {
+ throw std::runtime_error(str(
+ boost::format("[%s]: Invalid radio configuration.")
+ % unique_id()
+ ));
+ }
+}
+
+// Subscribers to block args:
+// TODO move to nocscript
+void radio_ctrl_impl::_update_spp(int spp)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Requested spp: " << spp << std::endl;
+ if (spp == 0) {
+ spp = DEFAULT_PACKET_SIZE / BYTES_PER_SAMPLE;
+ }
+ UHD_RFNOC_BLOCK_TRACE() << "radio_ctrl_impl::_update_spp(): Setting spp to: " << spp << std::endl;
+ for (size_t i = 0; i < _num_rx_channels; i++) {
+ sr_write(regs::RX_CTRL_MAXLEN, uint32_t(spp), i);
+ }
+}
+
+void radio_ctrl_impl::set_time_now(const time_spec_t &time_spec)
+{
+ _time64->set_time_now(time_spec);
+}
+
+void radio_ctrl_impl::set_time_next_pps(const time_spec_t &time_spec)
+{
+ _time64->set_time_next_pps(time_spec);
+}
+
+
+time_spec_t radio_ctrl_impl::get_time_now()
+{
+ return _time64->get_time_now();
+}
+
+time_spec_t radio_ctrl_impl::get_time_last_pps()
+{
+ return _time64->get_time_last_pps();
+}
+
diff --git a/host/lib/rfnoc/radio_ctrl_impl.hpp b/host/lib/rfnoc/radio_ctrl_impl.hpp
new file mode 100644
index 000000000..4224ec5c4
--- /dev/null
+++ b/host/lib/rfnoc/radio_ctrl_impl.hpp
@@ -0,0 +1,209 @@
+//
+// Copyright 2014-2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP
+#define INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP
+
+#include "rx_vita_core_3000.hpp"
+#include "tx_vita_core_3000.hpp"
+#include "time_core_3000.hpp"
+#include "gpio_atr_3000.hpp"
+#include <uhd/rfnoc/radio_ctrl.hpp>
+#include <uhd/types/direction.hpp>
+#include <boost/thread.hpp>
+
+//! Shorthand for radio block constructor
+#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(CLASS_NAME) \
+ CLASS_NAME##_impl(const make_args_t &make_args);
+
+#define UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(CLASS_NAME) \
+ CLASS_NAME##_impl::CLASS_NAME##_impl( \
+ const make_args_t &make_args \
+ ) : block_ctrl_base(make_args), radio_ctrl_impl()
+
+namespace uhd {
+ namespace rfnoc {
+
+/*! \brief Provide access to a radio.
+ *
+ */
+class radio_ctrl_impl : public radio_ctrl
+{
+public:
+ /************************************************************************
+ * Structors
+ ***********************************************************************/
+ radio_ctrl_impl();
+ virtual ~radio_ctrl_impl() {};
+
+ /************************************************************************
+ * Public Radio API calls
+ ***********************************************************************/
+ virtual double set_rate(double rate);
+ virtual void set_tx_antenna(const std::string &ant, const size_t chan);
+ virtual void set_rx_antenna(const std::string &ant, const size_t chan);
+ virtual double set_tx_frequency(const double freq, const size_t chan);
+ virtual double set_rx_frequency(const double freq, const size_t chan);
+ virtual double set_tx_gain(const double gain, const size_t chan);
+ virtual double set_rx_gain(const double gain, const size_t chan);
+ virtual void set_time_sync(const uhd::time_spec_t &time);
+
+ virtual double get_rate() const;
+ virtual std::string get_tx_antenna(const size_t chan) /* const */;
+ virtual std::string get_rx_antenna(const size_t chan) /* const */;
+ virtual double get_tx_frequency(const size_t) /* const */;
+ virtual double get_rx_frequency(const size_t) /* const */;
+ virtual double get_tx_gain(const size_t) /* const */;
+ virtual double get_rx_gain(const size_t) /* const */;
+
+ void set_time_now(const time_spec_t &time_spec);
+ void set_time_next_pps(const time_spec_t &time_spec);
+ time_spec_t get_time_now();
+ time_spec_t get_time_last_pps();
+
+ /***********************************************************************
+ * Block control API calls
+ **********************************************************************/
+ void set_rx_streamer(bool active, const size_t port);
+ void set_tx_streamer(bool active, const size_t port);
+
+ void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t port);
+
+ virtual double get_input_samp_rate(size_t /* port */) { return get_rate(); }
+ virtual double get_output_samp_rate(size_t /* port */) { return get_rate(); }
+ double _get_tick_rate() { return get_rate(); }
+
+ std::vector<size_t> get_active_rx_ports();
+ bool in_continuous_streaming_mode(const size_t chan) { return _continuous_streaming.at(chan); }
+ void rx_ctrl_clear_cmds(const size_t port) { sr_write(regs::RX_CTRL_CLEAR_CMDS, 0, port); }
+
+protected: // TODO see what's protected and what's private
+ void _register_loopback_self_test(size_t chan);
+
+ /***********************************************************************
+ * Registers
+ **********************************************************************/
+ struct regs {
+ static inline boost::uint32_t sr_addr(const boost::uint32_t offset)
+ {
+ return offset * 4;
+ }
+
+ static const uint32_t BASE = 128;
+
+ // defined in radio_core_regs.vh
+ static const uint32_t TIME = 128; // time hi - 128, time lo - 129, ctrl - 130
+ static const uint32_t CLEAR_CMDS = 131; // Any write to this reg clears the command FIFO
+ static const uint32_t LOOPBACK = 132;
+ static const uint32_t TEST = 133;
+ static const uint32_t CODEC_IDLE = 134;
+ static const uint32_t TX_CTRL_ERROR_POLICY = 144;
+ static const uint32_t RX_CTRL_CMD = 152;
+ static const uint32_t RX_CTRL_TIME_HI = 153;
+ static const uint32_t RX_CTRL_TIME_LO = 154;
+ static const uint32_t RX_CTRL_HALT = 155;
+ static const uint32_t RX_CTRL_MAXLEN = 156;
+ static const uint32_t RX_CTRL_CLEAR_CMDS = 157;
+ static const uint32_t MISC_OUTS = 160;
+ static const uint32_t DACSYNC = 161;
+ static const uint32_t SPI = 168;
+ static const uint32_t LEDS = 176;
+ static const uint32_t FP_GPIO = 184;
+ static const uint32_t GPIO = 192;
+ // NOTE: Upper 32 registers (224-255) are reserved for the output settings bus for use with
+ // device specific front end control
+
+ // frontend control: needs rethinking TODO
+ //static const uint32_t TX_FRONT = BASE + 96;
+ //static const uint32_t RX_FRONT = BASE + 112;
+ //static const uint32_t READBACK = BASE + 127;
+
+ static const uint32_t RB_TIME_NOW = 0;
+ static const uint32_t RB_TIME_PPS = 1;
+ static const uint32_t RB_TEST = 2;
+ static const uint32_t RB_CODEC_READBACK = 3;
+ static const uint32_t RB_RADIO_NUM = 4;
+ static const uint32_t RB_MISC_IO = 16;
+ static const uint32_t RB_SPI = 17;
+ static const uint32_t RB_LEDS = 18;
+ static const uint32_t RB_DB_GPIO = 19;
+ static const uint32_t RB_FP_GPIO = 20;
+ };
+
+ /***********************************************************************
+ * Block control API calls
+ **********************************************************************/
+ void _update_spp(int spp);
+
+ inline size_t _get_num_radios() const {
+ return std::max(_num_rx_channels, _num_tx_channels);
+ }
+
+ inline timed_wb_iface::sptr _get_ctrl(size_t radio_num) const {
+ return _perifs.at(radio_num).ctrl;
+ }
+
+ inline bool _is_streamer_active(uhd::direction_t dir, const size_t chan) const {
+ switch (dir) {
+ case uhd::TX_DIRECTION:
+ return _tx_streamer_active.at(chan);
+ case uhd::RX_DIRECTION:
+ return _rx_streamer_active.at(chan);
+ case uhd::DX_DIRECTION:
+ return _rx_streamer_active.at(chan) and _tx_streamer_active.at(chan);
+ default:
+ return false;
+ }
+ }
+
+ virtual bool check_radio_config() { return true; };
+
+ //! There is always only one time core per radio
+ time_core_3000::sptr _time64;
+
+ boost::mutex _mutex;
+
+private:
+ /************************************************************************
+ * Peripherals
+ ***********************************************************************/
+ //! Stores pointers to all streaming-related radio cores
+ struct radio_perifs_t
+ {
+ timed_wb_iface::sptr ctrl;
+ };
+ std::map<size_t, radio_perifs_t> _perifs;
+
+ size_t _num_tx_channels;
+ size_t _num_rx_channels;
+
+ // Cached values
+ double _tick_rate;
+ std::map<size_t, std::string> _tx_antenna;
+ std::map<size_t, std::string> _rx_antenna;
+ std::map<size_t, double> _tx_freq;
+ std::map<size_t, double> _rx_freq;
+ std::map<size_t, double> _tx_gain;
+ std::map<size_t, double> _rx_gain;
+
+ std::vector<bool> _continuous_streaming;
+}; /* class radio_ctrl_impl */
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_RADIO_CTRL_IMPL_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/rate_node_ctrl.cpp b/host/lib/rfnoc/rate_node_ctrl.cpp
new file mode 100644
index 000000000..5e3117e23
--- /dev/null
+++ b/host/lib/rfnoc/rate_node_ctrl.cpp
@@ -0,0 +1,67 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/rate_node_ctrl.hpp>
+#include <boost/bind.hpp>
+
+using namespace uhd::rfnoc;
+
+const double rate_node_ctrl::RATE_UNDEFINED = -1.0;
+
+static double _get_input_samp_rate(rate_node_ctrl::sptr node, size_t port)
+{
+ return node->get_input_samp_rate(port);
+}
+
+static double _get_output_samp_rate(rate_node_ctrl::sptr node, size_t port)
+{
+ return node->get_output_samp_rate(port);
+}
+
+
+// FIXME add recursion limiters (i.e. list of explored nodes)
+double rate_node_ctrl::get_input_samp_rate(
+ size_t /* port */
+) {
+ try {
+ return find_downstream_unique_property<rate_node_ctrl, double>(
+ boost::bind(_get_input_samp_rate, _1, _2),
+ RATE_UNDEFINED
+ );
+ } catch (const uhd::runtime_error &ex) {
+ throw uhd::runtime_error(str(
+ boost::format("Multiple sampling rates downstream of %s: %s.")
+ % unique_id() % ex.what()
+ ));
+ }
+}
+
+double rate_node_ctrl::get_output_samp_rate(
+ size_t /* port */
+) {
+ try {
+ return find_upstream_unique_property<rate_node_ctrl, double>(
+ boost::bind(_get_output_samp_rate, _1, _2),
+ RATE_UNDEFINED
+ );
+ } catch (const uhd::runtime_error &ex) {
+ throw uhd::runtime_error(str(
+ boost::format("Multiple sampling rates upstream of %s: %s.")
+ % unique_id() % ex.what()
+ ));
+ }
+}
diff --git a/host/lib/rfnoc/rx_stream_terminator.cpp b/host/lib/rfnoc/rx_stream_terminator.cpp
new file mode 100644
index 000000000..b2a2d5a64
--- /dev/null
+++ b/host/lib/rfnoc/rx_stream_terminator.cpp
@@ -0,0 +1,131 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "rx_stream_terminator.hpp"
+#include "radio_ctrl_impl.hpp"
+#include "../transport/super_recv_packet_handler.hpp"
+#include <uhd/utils/msg.hpp>
+#include <uhd/rfnoc/source_node_ctrl.hpp>
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+
+using namespace uhd::rfnoc;
+
+size_t rx_stream_terminator::_count = 0;
+
+rx_stream_terminator::rx_stream_terminator() :
+ _term_index(_count),
+ _samp_rate(rate_node_ctrl::RATE_UNDEFINED),
+ _tick_rate(tick_node_ctrl::RATE_UNDEFINED)
+{
+ _count++;
+}
+
+std::string rx_stream_terminator::unique_id() const
+{
+ return str(boost::format("RX Terminator %d") % _term_index);
+}
+
+void rx_stream_terminator::set_tx_streamer(bool, const size_t)
+{
+ /* nop */
+}
+
+void rx_stream_terminator::set_rx_streamer(bool active, const size_t)
+{
+ // TODO this is identical to source_node_ctrl::set_rx_streamer() -> factor out
+ UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::set_rx_streamer() " << active << std::endl;
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) {
+ source_node_ctrl::sptr curr_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock());
+ if (curr_upstream_block_ctrl) {
+ curr_upstream_block_ctrl->set_rx_streamer(
+ active,
+ get_upstream_port(upstream_node.first)
+ );
+ }
+ }
+}
+
+void rx_stream_terminator::handle_overrun(boost::weak_ptr<uhd::rx_streamer> streamer, const size_t)
+{
+ std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> > upstream_radio_nodes =
+ find_upstream_node<uhd::rfnoc::radio_ctrl_impl>();
+ const size_t n_radios = upstream_radio_nodes.size();
+ if (n_radios == 0) {
+ return;
+ }
+
+ UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::handle_overrun()" << std::endl;
+ boost::shared_ptr<uhd::transport::sph::recv_packet_streamer> my_streamer =
+ boost::dynamic_pointer_cast<uhd::transport::sph::recv_packet_streamer>(streamer.lock());
+ if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense.
+
+ bool in_continuous_streaming_mode = true;
+ int num_channels = 0;
+ BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) {
+ num_channels += node->get_active_rx_ports().size();
+ BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) {
+ in_continuous_streaming_mode = in_continuous_streaming_mode && node->in_continuous_streaming_mode(port);
+ }
+ }
+ if (num_channels == 0) {
+ return;
+ }
+
+ if (num_channels == 1 and in_continuous_streaming_mode) {
+ std::vector<size_t> active_rx_ports = upstream_radio_nodes[0]->get_active_rx_ports();
+ if (active_rx_ports.empty()) {
+ return;
+ }
+ const size_t port = active_rx_ports[0];
+ upstream_radio_nodes[0]->issue_stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS, port);
+ return;
+ }
+
+ /////////////////////////////////////////////////////////////
+ // MIMO overflow recovery time
+ /////////////////////////////////////////////////////////////
+ BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) {
+ BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) {
+ // check all the ports on all the radios
+ node->rx_ctrl_clear_cmds(port);
+ node->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS, port);
+ }
+ }
+ //flush transports
+ my_streamer->flush_all(0.001); // TODO flushing will probably have to go away.
+ //restart streaming on all channels
+ if (in_continuous_streaming_mode) {
+ stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS);
+ stream_cmd.stream_now = false;
+ stream_cmd.time_spec = upstream_radio_nodes[0]->get_time_now() + time_spec_t(0.05);
+
+ BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl_impl> &node, upstream_radio_nodes) {
+ BOOST_FOREACH(const size_t port, node->get_active_rx_ports()) {
+ node->issue_stream_cmd(stream_cmd, port);
+ }
+ }
+ }
+}
+
+rx_stream_terminator::~rx_stream_terminator()
+{
+ UHD_RFNOC_BLOCK_TRACE() << "rx_stream_terminator::~rx_stream_terminator() " << std::endl;
+ set_rx_streamer(false, 0);
+}
+
diff --git a/host/lib/rfnoc/rx_stream_terminator.hpp b/host/lib/rfnoc/rx_stream_terminator.hpp
new file mode 100644
index 000000000..5159cd34a
--- /dev/null
+++ b/host/lib/rfnoc/rx_stream_terminator.hpp
@@ -0,0 +1,86 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP
+#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP
+
+#include <uhd/rfnoc/sink_node_ctrl.hpp>
+#include <uhd/rfnoc/rate_node_ctrl.hpp>
+#include <uhd/rfnoc/tick_node_ctrl.hpp>
+#include <uhd/rfnoc/scalar_node_ctrl.hpp>
+#include <uhd/rfnoc/terminator_node_ctrl.hpp>
+#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros
+
+namespace uhd {
+ namespace rfnoc {
+
+/*! \brief Terminator node for Rx streamers.
+ *
+ * This node is only used by rx_streamers. It terminates the flow graph
+ * inside the streamer and does not have a counterpart on the FPGA.
+ */
+class rx_stream_terminator :
+ public sink_node_ctrl,
+ public rate_node_ctrl,
+ public tick_node_ctrl,
+ public scalar_node_ctrl,
+ public terminator_node_ctrl
+{
+public:
+ UHD_RFNOC_BLOCK_OBJECT(rx_stream_terminator)
+
+ static sptr make()
+ {
+ return sptr(new rx_stream_terminator);
+ }
+
+ // If this is called, then by a send terminator at the other end
+ // of a flow graph.
+ double get_input_samp_rate(size_t) { return _samp_rate; };
+
+ // Same for the scaling factor
+ double get_input_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; };
+
+ std::string unique_id() const;
+
+ void set_rx_streamer(bool active, const size_t port);
+
+ void set_tx_streamer(bool active, const size_t port);
+
+ virtual ~rx_stream_terminator();
+
+ void handle_overrun(boost::weak_ptr<uhd::rx_streamer>, const size_t);
+
+protected:
+ rx_stream_terminator();
+
+ virtual double _get_tick_rate() { return _tick_rate; };
+
+private:
+ //! Every terminator has a unique index
+ const size_t _term_index;
+ static size_t _count;
+
+ double _samp_rate;
+ double _tick_rate;
+
+}; /* class rx_stream_terminator */
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_RECV_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/scalar_node_ctrl.cpp b/host/lib/rfnoc/scalar_node_ctrl.cpp
new file mode 100644
index 000000000..56cc4dcf2
--- /dev/null
+++ b/host/lib/rfnoc/scalar_node_ctrl.cpp
@@ -0,0 +1,66 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/scalar_node_ctrl.hpp>
+#include <boost/bind.hpp>
+
+using namespace uhd::rfnoc;
+
+const double scalar_node_ctrl::SCALE_UNDEFINED = -1.0;
+
+static double _get_input_factor(scalar_node_ctrl::sptr node, size_t port)
+{
+ return node->get_input_scale_factor(port);
+}
+
+static double _get_output_factor(scalar_node_ctrl::sptr node, size_t port)
+{
+ return node->get_output_scale_factor(port);
+}
+
+// FIXME add recursion limiters (i.e. list of explored nodes)
+double scalar_node_ctrl::get_input_scale_factor(
+ size_t /* port */
+) {
+ try {
+ return find_downstream_unique_property<scalar_node_ctrl, double>(
+ boost::bind(_get_input_factor, _1, _2),
+ SCALE_UNDEFINED
+ );
+ } catch (const uhd::runtime_error &ex) {
+ throw uhd::runtime_error(str(
+ boost::format("Multiple scaling factors rates downstream of %s: %s.")
+ % unique_id() % ex.what()
+ ));
+ }
+}
+
+double scalar_node_ctrl::get_output_scale_factor(
+ size_t /* port */
+) {
+ try {
+ return find_upstream_unique_property<scalar_node_ctrl, double>(
+ boost::bind(_get_output_factor, _1, _2),
+ SCALE_UNDEFINED
+ );
+ } catch (const uhd::runtime_error &ex) {
+ throw uhd::runtime_error(str(
+ boost::format("Multiple scaling factors rates upstream of %s: %s.")
+ % unique_id() % ex.what()
+ ));
+ }
+}
diff --git a/host/lib/rfnoc/sink_block_ctrl_base.cpp b/host/lib/rfnoc/sink_block_ctrl_base.cpp
new file mode 100644
index 000000000..56755a269
--- /dev/null
+++ b/host/lib/rfnoc/sink_block_ctrl_base.cpp
@@ -0,0 +1,111 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "utils.hpp"
+#include <uhd/rfnoc/sink_block_ctrl_base.hpp>
+#include <uhd/rfnoc/constants.hpp>
+#include <uhd/utils/msg.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+
+/***********************************************************************
+ * Stream signatures
+ **********************************************************************/
+stream_sig_t sink_block_ctrl_base::get_input_signature(size_t block_port) const
+{
+ if (not _tree->exists(_root_path / "ports" / "in" / block_port)) {
+ throw uhd::runtime_error(str(
+ boost::format("Invalid port number %d for block %s")
+ % block_port % unique_id()
+ ));
+ }
+
+ return _resolve_port_def(
+ _tree->access<blockdef::port_t>(_root_path / "ports" / "in" / block_port).get()
+ );
+}
+
+std::vector<size_t> sink_block_ctrl_base::get_input_ports() const
+{
+ std::vector<size_t> input_ports;
+ input_ports.reserve(_tree->list(_root_path / "ports" / "in").size());
+ BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "in")) {
+ input_ports.push_back(boost::lexical_cast<size_t>(port));
+ }
+ return input_ports;
+}
+
+/***********************************************************************
+ * FPGA Configuration
+ **********************************************************************/
+size_t sink_block_ctrl_base::get_fifo_size(size_t block_port) const {
+ if (_tree->exists(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port))) {
+ return _tree->access<size_t>(_root_path / "input_buffer_size" / str(boost::format("%d") % block_port)).get();
+ }
+ return 0;
+}
+
+void sink_block_ctrl_base::configure_flow_control_in(
+ size_t cycles,
+ size_t packets,
+ size_t block_port
+) {
+ UHD_RFNOC_BLOCK_TRACE() << boost::format("sink_block_ctrl_base::configure_flow_control_in(cycles=%d, packets=%d)") % cycles % packets << std::endl;
+ boost::uint32_t cycles_word = 0;
+ if (cycles) {
+ cycles_word = (1<<31) | cycles;
+ }
+ sr_write(SR_FLOW_CTRL_CYCS_PER_ACK, cycles_word, block_port);
+
+ boost::uint32_t packets_word = 0;
+ if (packets) {
+ packets_word = (1<<31) | packets;
+ }
+ sr_write(SR_FLOW_CTRL_PKTS_PER_ACK, packets_word, block_port);
+}
+
+void sink_block_ctrl_base::set_error_policy(
+ const std::string &policy
+) {
+ if (policy == "next_packet")
+ {
+ sr_write(SR_ERROR_POLICY, (1 << 1) | 1);
+ }
+ else if (policy == "next_burst")
+ {
+ sr_write(SR_ERROR_POLICY, (1 << 2) | 1);
+ }
+ else if (policy == "wait")
+ {
+ sr_write(SR_ERROR_POLICY, 1);
+ }
+ else throw uhd::value_error("Block input cannot handle requested error policy: " + policy);
+}
+
+/***********************************************************************
+ * Hooks
+ **********************************************************************/
+size_t sink_block_ctrl_base::_request_input_port(
+ const size_t suggested_port,
+ const uhd::device_addr_t &
+) const {
+ const std::set<size_t> valid_input_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "in"));
+ return utils::node_map_find_first_free(_upstream_nodes, suggested_port, valid_input_ports);
+}
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/sink_node_ctrl.cpp b/host/lib/rfnoc/sink_node_ctrl.cpp
new file mode 100644
index 000000000..8398641fd
--- /dev/null
+++ b/host/lib/rfnoc/sink_node_ctrl.cpp
@@ -0,0 +1,92 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "utils.hpp"
+#include <uhd/utils/msg.hpp>
+#include <uhd/rfnoc/sink_node_ctrl.hpp>
+#include <uhd/rfnoc/source_node_ctrl.hpp>
+
+using namespace uhd::rfnoc;
+
+size_t sink_node_ctrl::connect_upstream(
+ node_ctrl_base::sptr upstream_node,
+ size_t port,
+ const uhd::device_addr_t &args
+) {
+ boost::mutex::scoped_lock lock(_input_mutex);
+ port = _request_input_port(port, args);
+ _register_upstream_node(upstream_node, port);
+ return port;
+}
+
+void sink_node_ctrl::set_tx_streamer(bool active, const size_t port)
+{
+ UHD_RFNOC_BLOCK_TRACE() << "sink_node_ctrl::set_tx_streamer() " << active << " " << port << std::endl;
+
+ /* Enable all downstream connections:
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, list_downstream_nodes()) {
+ sptr curr_downstream_block_ctrl =
+ boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock());
+ if (curr_downstream_block_ctrl) {
+ curr_downstream_block_ctrl->set_tx_streamer(
+ active,
+ get_downstream_port(downstream_node.first)
+ );
+ }
+ }
+ */
+
+ // Only enable 1:1
+ if (list_downstream_nodes().count(port)) {
+ sink_node_ctrl::sptr this_downstream_block_ctrl =
+ boost::dynamic_pointer_cast<sink_node_ctrl>(list_downstream_nodes().at(port).lock());
+ if (this_downstream_block_ctrl) {
+ this_downstream_block_ctrl->set_tx_streamer(
+ active,
+ get_downstream_port(port)
+ );
+ }
+ }
+
+ _tx_streamer_active[port] = active;
+}
+
+size_t sink_node_ctrl::_request_input_port(
+ const size_t suggested_port,
+ const uhd::device_addr_t &
+) const {
+ return utils::node_map_find_first_free(_upstream_nodes, suggested_port);
+}
+
+void sink_node_ctrl::_register_upstream_node(
+ node_ctrl_base::sptr upstream_node,
+ size_t port
+) {
+ // Do all the checks:
+ if (port == ANY_PORT) {
+ throw uhd::type_error("Invalid input port number.");
+ }
+ if (_upstream_nodes.count(port) and not _upstream_nodes[port].expired()) {
+ throw uhd::runtime_error(str(boost::format("On node %s, input port %d is already connected.") % unique_id() % port));
+ }
+ if (not boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node)) {
+ throw uhd::type_error("Attempting to register a non-source block as upstream.");
+ }
+ // Alles klar, Herr Kommissar :)
+
+ _upstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(upstream_node);
+}
diff --git a/host/lib/rfnoc/source_block_ctrl_base.cpp b/host/lib/rfnoc/source_block_ctrl_base.cpp
new file mode 100644
index 000000000..72845ad64
--- /dev/null
+++ b/host/lib/rfnoc/source_block_ctrl_base.cpp
@@ -0,0 +1,137 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "utils.hpp"
+#include <uhd/rfnoc/source_block_ctrl_base.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/rfnoc/constants.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+/***********************************************************************
+ * Streaming operations
+ **********************************************************************/
+void source_block_ctrl_base::issue_stream_cmd(
+ const uhd::stream_cmd_t &stream_cmd,
+ const size_t chan
+) {
+ UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::issue_stream_cmd()" << std::endl;
+ if (_upstream_nodes.empty()) {
+ UHD_MSG(warning) << "issue_stream_cmd() not implemented for " << get_block_id() << std::endl;
+ return;
+ }
+
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, _upstream_nodes) {
+ source_node_ctrl::sptr this_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock());
+ this_upstream_block_ctrl->issue_stream_cmd(stream_cmd, chan);
+ }
+}
+
+/***********************************************************************
+ * Stream signatures
+ **********************************************************************/
+stream_sig_t source_block_ctrl_base::get_output_signature(size_t block_port) const
+{
+ if (not _tree->exists(_root_path / "ports" / "out" / block_port)) {
+ throw uhd::runtime_error(str(
+ boost::format("Invalid port number %d for block %s")
+ % block_port % unique_id()
+ ));
+ }
+
+ return _resolve_port_def(
+ _tree->access<blockdef::port_t>(_root_path / "ports" / "out" / block_port).get()
+ );
+}
+
+std::vector<size_t> source_block_ctrl_base::get_output_ports() const
+{
+ std::vector<size_t> output_ports;
+ output_ports.reserve(_tree->list(_root_path / "ports" / "out").size());
+ BOOST_FOREACH(const std::string port, _tree->list(_root_path / "ports" / "out")) {
+ output_ports.push_back(boost::lexical_cast<size_t>(port));
+ }
+ return output_ports;
+}
+
+/***********************************************************************
+ * FPGA Configuration
+ **********************************************************************/
+void source_block_ctrl_base::set_destination(
+ boost::uint32_t next_address,
+ size_t output_block_port
+) {
+ UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::set_destination() " << uhd::sid_t(next_address) << std::endl;
+ sid_t new_sid(next_address);
+ new_sid.set_src(get_address(output_block_port));
+ UHD_RFNOC_BLOCK_TRACE() << " Setting SID: " << new_sid << std::endl << " ";
+ sr_write(SR_NEXT_DST_SID, (1<<16) | next_address, output_block_port);
+}
+
+void source_block_ctrl_base::configure_flow_control_out(
+ size_t buf_size_pkts,
+ size_t block_port,
+ UHD_UNUSED(const uhd::sid_t &sid)
+) {
+ UHD_RFNOC_BLOCK_TRACE() << "source_block_ctrl_base::configure_flow_control_out() buf_size_pkts==" << buf_size_pkts << std::endl;
+ if (buf_size_pkts < 2) {
+ throw uhd::runtime_error(str(
+ boost::format("Invalid window size %d for block %s. Window size must at least be 2.")
+ % buf_size_pkts % unique_id()
+ ));
+ }
+
+ //Disable the window and let all upstream data flush out
+ //We need to do this every time the window is changed because
+ //a) We don't know what state the flow-control module was left in
+ // in the previous run (it should still be enabled)
+ //b) Changing the window size where data is buffered upstream may
+ // result in stale packets entering the stream.
+ sr_write(SR_FLOW_CTRL_WINDOW_EN, 0, block_port);
+
+ //Wait for data to flush out.
+ //In the FPGA we are guaranteed that all buffered packets are more-or-less consecutive.
+ //1ms@200MHz = 200,000 cycles of "flush time".
+ //200k cycles = 200k * 8 bytes (64 bits) = 1.6MB of data that can be flushed.
+ //Typically in the FPGA we have buffering in the order of kilobytes so waiting for 1MB
+ //to flush is more than enough time.
+ //TODO: Enhancement. We should get feedback from the FPGA about when the source_flow_control
+ // module is done flushing.
+ boost::this_thread::sleep(boost::posix_time::milliseconds(1));
+
+ //Resize the FC window.
+ //Precondition: No data can be buffered upstream.
+ sr_write(SR_FLOW_CTRL_WINDOW_SIZE, buf_size_pkts, block_port);
+
+ //Enable the FC window.
+ //Precondition: The window size must be set.
+ sr_write(SR_FLOW_CTRL_WINDOW_EN, (buf_size_pkts != 0), block_port);
+}
+
+/***********************************************************************
+ * Hooks
+ **********************************************************************/
+size_t source_block_ctrl_base::_request_output_port(
+ const size_t suggested_port,
+ const uhd::device_addr_t &
+) const {
+ const std::set<size_t> valid_output_ports = utils::str_list_to_set<size_t>(_tree->list(_root_path / "ports" / "out"));
+ return utils::node_map_find_first_free(_downstream_nodes, suggested_port, valid_output_ports);
+}
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/source_node_ctrl.cpp b/host/lib/rfnoc/source_node_ctrl.cpp
new file mode 100644
index 000000000..c97c72354
--- /dev/null
+++ b/host/lib/rfnoc/source_node_ctrl.cpp
@@ -0,0 +1,96 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "utils.hpp"
+#include <uhd/utils/msg.hpp>
+#include <uhd/rfnoc/source_node_ctrl.hpp>
+#include <uhd/rfnoc/sink_node_ctrl.hpp>
+
+using namespace uhd::rfnoc;
+
+size_t source_node_ctrl::connect_downstream(
+ node_ctrl_base::sptr downstream_node,
+ size_t port,
+ const uhd::device_addr_t &args
+) {
+ boost::mutex::scoped_lock lock(_output_mutex);
+ port = _request_output_port(port, args);
+ _register_downstream_node(downstream_node, port);
+ return port;
+}
+
+void source_node_ctrl::set_rx_streamer(bool active, const size_t port)
+{
+ UHD_RFNOC_BLOCK_TRACE() << "source_node_ctrl::set_rx_streamer() " << port << " -> " << active << std::endl;
+
+ /* This will enable all upstream blocks:
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t upstream_node, list_upstream_nodes()) {
+ sptr curr_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(upstream_node.second.lock());
+ if (curr_upstream_block_ctrl) {
+ curr_upstream_block_ctrl->set_rx_streamer(
+ active,
+ get_upstream_port(upstream_node.first)
+ );
+ }
+ }
+ */
+
+ // This only enables 1:1 (if output 1 is enabled, enable what's connected to input 1)
+ if (list_upstream_nodes().count(port)) {
+ source_node_ctrl::sptr this_upstream_block_ctrl =
+ boost::dynamic_pointer_cast<source_node_ctrl>(list_upstream_nodes().at(port).lock());
+ if (this_upstream_block_ctrl) {
+ this_upstream_block_ctrl->set_rx_streamer(
+ active,
+ get_upstream_port(port)
+ );
+ }
+ }
+
+ _rx_streamer_active[port] = active;
+}
+
+size_t source_node_ctrl::_request_output_port(
+ const size_t suggested_port,
+ const uhd::device_addr_t &
+) const {
+ return utils::node_map_find_first_free(_downstream_nodes, suggested_port);
+}
+
+void source_node_ctrl::_register_downstream_node(
+ node_ctrl_base::sptr downstream_node,
+ size_t port
+) {
+ // Do all the checks:
+ if (port == ANY_PORT) {
+ throw uhd::type_error(str(
+ boost::format("[%s] Invalid output port number (ANY).")
+ % unique_id()
+ ));
+ }
+ if (_downstream_nodes.count(port) and not _downstream_nodes[port].expired()) {
+ throw uhd::runtime_error(str(boost::format("On node %s, output port %d is already connected.") % unique_id() % port));
+ }
+ if (not boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node)) {
+ throw uhd::type_error("Attempting to register a non-sink block as downstream.");
+ }
+ // Alles klar, Herr Kommissar :)
+
+ _downstream_nodes[port] = boost::weak_ptr<node_ctrl_base>(downstream_node);
+}
+
diff --git a/host/lib/rfnoc/stream_sig.cpp b/host/lib/rfnoc/stream_sig.cpp
new file mode 100644
index 000000000..3d953bcb2
--- /dev/null
+++ b/host/lib/rfnoc/stream_sig.cpp
@@ -0,0 +1,85 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/stream_sig.hpp>
+#include <uhd/convert.hpp>
+#include <boost/format.hpp>
+
+using namespace uhd::rfnoc;
+
+stream_sig_t::stream_sig_t() :
+ item_type(""),
+ vlen(0),
+ packet_size(0),
+ is_bursty(false)
+{
+ // nop
+}
+
+std::string stream_sig_t::to_string()
+{
+ return str(
+ boost::format(
+ "%s,vlen=%d,packet_size=%d"
+ ) % item_type % vlen % packet_size
+ );
+}
+
+std::string stream_sig_t::to_pp_string()
+{
+ return str(
+ boost::format(
+ "Data type: %s | Vector Length: %d | Packet size: %d"
+ ) % item_type % vlen % packet_size
+ );
+}
+
+size_t stream_sig_t::get_bytes_per_item() const
+{
+ if (item_type == "") {
+ return 0;
+ }
+
+ return uhd::convert::get_bytes_per_item(item_type);
+}
+
+bool stream_sig_t::is_compatible(const stream_sig_t &output_sig, const stream_sig_t &input_sig)
+{
+ /// Item types:
+ if (not (input_sig.item_type.empty() or output_sig.item_type.empty())
+ and input_sig.item_type != output_sig.item_type) {
+ return false;
+ }
+
+ /// Vector lengths
+ if (output_sig.vlen and input_sig.vlen) {
+ if (input_sig.vlen != output_sig.vlen) {
+ return false;
+ }
+ }
+
+ /// Packet sizes
+ if (output_sig.packet_size and input_sig.packet_size) {
+ if (input_sig.packet_size != output_sig.packet_size) {
+ return false;
+ }
+ }
+
+ // You may pass
+ return true;
+}
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/tick_node_ctrl.cpp b/host/lib/rfnoc/tick_node_ctrl.cpp
new file mode 100644
index 000000000..fa5c7b6a1
--- /dev/null
+++ b/host/lib/rfnoc/tick_node_ctrl.cpp
@@ -0,0 +1,75 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/rfnoc/tick_node_ctrl.hpp>
+
+using namespace uhd::rfnoc;
+
+const double tick_node_ctrl::RATE_UNDEFINED = 0;
+
+double tick_node_ctrl::get_tick_rate(
+ const std::set< node_ctrl_base::sptr > &_explored_nodes
+) {
+ // First, see if we've implemented _get_tick_rate()
+ {
+ double my_tick_rate = _get_tick_rate();
+ if (my_tick_rate != RATE_UNDEFINED) {
+ return my_tick_rate;
+ }
+ }
+
+ // If not, we ask all our neighbours for the tick rate.
+ // This will fail if we get different values.
+ std::set< node_ctrl_base::sptr > explored_nodes(_explored_nodes);
+ explored_nodes.insert(shared_from_this());
+ // Here, we need all up- and downstream nodes
+ std::vector< sptr > neighbouring_tick_nodes = find_downstream_node<tick_node_ctrl>();
+ {
+ std::vector< sptr > upstream_neighbouring_tick_nodes = find_upstream_node<tick_node_ctrl>();
+ neighbouring_tick_nodes.insert(
+ neighbouring_tick_nodes.end(),
+ upstream_neighbouring_tick_nodes.begin(),
+ upstream_neighbouring_tick_nodes.end()
+ );
+ } // neighbouring_tick_nodes is now initialized
+ double ret_val = RATE_UNDEFINED;
+ BOOST_FOREACH(const sptr &node, neighbouring_tick_nodes) {
+ if (_explored_nodes.count(node)) {
+ continue;
+ }
+ double tick_rate = node->get_tick_rate(explored_nodes);
+ if (tick_rate == RATE_UNDEFINED) {
+ continue;
+ }
+ if (ret_val == RATE_UNDEFINED) {
+ ret_val = tick_rate;
+ // TODO: Remember name of this node so we can make the throw message more descriptive.
+ continue;
+ }
+ if (tick_rate != ret_val) {
+ throw uhd::runtime_error(
+ str(
+ // TODO add node names
+ boost::format("Conflicting tick rates: One neighbouring block specifies %d MHz, another %d MHz.")
+ % tick_rate % ret_val
+ )
+ );
+ }
+ }
+ return ret_val;
+}
+
diff --git a/host/lib/rfnoc/tx_stream_terminator.cpp b/host/lib/rfnoc/tx_stream_terminator.cpp
new file mode 100644
index 000000000..2746fc4d8
--- /dev/null
+++ b/host/lib/rfnoc/tx_stream_terminator.cpp
@@ -0,0 +1,65 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "tx_stream_terminator.hpp"
+#include <boost/format.hpp>
+#include <uhd/rfnoc/sink_node_ctrl.hpp>
+
+using namespace uhd::rfnoc;
+
+size_t tx_stream_terminator::_count = 0;
+
+tx_stream_terminator::tx_stream_terminator() :
+ _term_index(_count),
+ _samp_rate(rate_node_ctrl::RATE_UNDEFINED),
+ _tick_rate(tick_node_ctrl::RATE_UNDEFINED)
+{
+ _count++;
+}
+
+std::string tx_stream_terminator::unique_id() const
+{
+ return str(boost::format("TX Terminator %d") % _term_index);
+}
+
+void tx_stream_terminator::set_rx_streamer(bool, const size_t)
+{
+ /* nop */
+}
+
+void tx_stream_terminator::set_tx_streamer(bool active, const size_t /* port */)
+{
+ // TODO this is identical to sink_node_ctrl::set_tx_streamer() -> factor out
+ UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::set_tx_streamer() " << active << std::endl;
+ BOOST_FOREACH(const node_ctrl_base::node_map_pair_t downstream_node, _downstream_nodes) {
+ sink_node_ctrl::sptr curr_downstream_block_ctrl =
+ boost::dynamic_pointer_cast<sink_node_ctrl>(downstream_node.second.lock());
+ if (curr_downstream_block_ctrl) {
+ curr_downstream_block_ctrl->set_tx_streamer(
+ active,
+ get_downstream_port(downstream_node.first)
+ );
+ }
+ }
+}
+
+tx_stream_terminator::~tx_stream_terminator()
+{
+ UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::~tx_stream_terminator() " << std::endl;
+ set_tx_streamer(false, 0);
+}
+
diff --git a/host/lib/rfnoc/tx_stream_terminator.hpp b/host/lib/rfnoc/tx_stream_terminator.hpp
new file mode 100644
index 000000000..169d7cd6a
--- /dev/null
+++ b/host/lib/rfnoc/tx_stream_terminator.hpp
@@ -0,0 +1,90 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP
+#define INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP
+
+#include <uhd/rfnoc/source_node_ctrl.hpp>
+#include <uhd/rfnoc/rate_node_ctrl.hpp>
+#include <uhd/rfnoc/tick_node_ctrl.hpp>
+#include <uhd/rfnoc/scalar_node_ctrl.hpp>
+#include <uhd/rfnoc/terminator_node_ctrl.hpp>
+#include <uhd/rfnoc/block_ctrl_base.hpp> // For the block macros
+#include <uhd/utils/msg.hpp>
+
+namespace uhd {
+ namespace rfnoc {
+
+/*! \brief Terminator node for Tx streamers.
+ *
+ * This node is only used by tx_streamers. It terminates the flow graph
+ * inside the streamer and does not have a counterpart on the FPGA.
+ */
+class tx_stream_terminator :
+ public source_node_ctrl,
+ public rate_node_ctrl,
+ public tick_node_ctrl,
+ public scalar_node_ctrl,
+ public terminator_node_ctrl
+{
+public:
+ UHD_RFNOC_BLOCK_OBJECT(tx_stream_terminator)
+
+ static sptr make()
+ {
+ return sptr(new tx_stream_terminator);
+ }
+
+ void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t)
+ {
+ UHD_RFNOC_BLOCK_TRACE() << "tx_stream_terminator::issue_stream_cmd()" << std::endl;
+ }
+
+ // If this is called, then by a send terminator at the other end
+ // of a flow graph.
+ double get_output_samp_rate(size_t) { return _samp_rate; };
+
+ // Same for the scaling factor
+ double get_output_scale_factor(size_t) { return scalar_node_ctrl::SCALE_UNDEFINED; };
+
+ std::string unique_id() const;
+
+ void set_rx_streamer(bool active, const size_t port);
+
+ void set_tx_streamer(bool active, const size_t port);
+
+ virtual ~tx_stream_terminator();
+
+protected:
+ tx_stream_terminator();
+
+ virtual double _get_tick_rate() { return _tick_rate; };
+
+private:
+ //! Every terminator has a unique index
+ const size_t _term_index;
+ static size_t _count;
+
+ double _samp_rate;
+ double _tick_rate;
+
+}; /* class tx_stream_terminator */
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_TERMINATOR_SEND_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/utils.hpp b/host/lib/rfnoc/utils.hpp
new file mode 100644
index 000000000..ecd3d7cfb
--- /dev/null
+++ b/host/lib/rfnoc/utils.hpp
@@ -0,0 +1,77 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_UTILS_HPP
+#define INCLUDED_LIBUHD_RFNOC_UTILS_HPP
+
+#include <uhd/rfnoc/node_ctrl_base.hpp>
+#include <boost/lexical_cast.hpp>
+#include <set>
+
+namespace uhd { namespace rfnoc { namespace utils {
+
+ /*! If \p suggested_port equals ANY_PORT, return the first available
+ * port number on \p nodes. Otherwise, return \p suggested_port.
+ *
+ * If \p allowed_ports is given, another condition is that the port
+ * number must be listed in here.
+ * If \p allowed_ports is not specified or empty, the assumption is
+ * that all ports are valid.
+ *
+ * On failure, ANY_PORT is returned.
+ */
+ static size_t node_map_find_first_free(
+ node_ctrl_base::node_map_t nodes,
+ const size_t suggested_port,
+ const std::set<size_t> allowed_ports=std::set<size_t>()
+ ) {
+ size_t port = suggested_port;
+ if (port == ANY_PORT) {
+ if (allowed_ports.empty()) {
+ port = 0;
+ while (nodes.count(port) and (port != ANY_PORT)) {
+ port++;
+ }
+ } else {
+ BOOST_FOREACH(const size_t allowed_port, allowed_ports) {
+ if (not nodes.count(port)) {
+ return allowed_port;
+ }
+ return ANY_PORT;
+ }
+ }
+ } else {
+ if (not (allowed_ports.empty() or allowed_ports.count(port))) {
+ return ANY_PORT;
+ }
+ }
+ return port;
+ }
+
+ template <typename T>
+ static std::set<T> str_list_to_set(const std::vector<std::string> &list) {
+ std::set<T> return_set;
+ BOOST_FOREACH(const std::string &S, list) {
+ return_set.insert(boost::lexical_cast<T>(S));
+ }
+ return return_set;
+ }
+
+}}}; /* namespace uhd::rfnoc::utils */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_UTILS_HPP */
+// vim: sw=4 et:
diff --git a/host/lib/rfnoc/wb_iface_adapter.cpp b/host/lib/rfnoc/wb_iface_adapter.cpp
new file mode 100644
index 000000000..6688fe86b
--- /dev/null
+++ b/host/lib/rfnoc/wb_iface_adapter.cpp
@@ -0,0 +1,71 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "wb_iface_adapter.hpp"
+
+using namespace uhd::rfnoc;
+
+wb_iface_adapter::wb_iface_adapter(
+ const poke32_type &poke32_functor_,
+ const peek32_type &peek32_functor_,
+ const peek64_type &peek64_functor_,
+ const gettime_type &gettime_functor_,
+ const settime_type &settime_functor_
+) : poke32_functor(poke32_functor_)
+ , peek32_functor(peek32_functor_)
+ , peek64_functor(peek64_functor_)
+ , gettime_functor(gettime_functor_)
+ , settime_functor(settime_functor_)
+{
+ // nop
+}
+
+wb_iface_adapter::wb_iface_adapter(
+ const poke32_type &poke32_functor_,
+ const peek32_type &peek32_functor_,
+ const peek64_type &peek64_functor_
+) : poke32_functor(poke32_functor_)
+ , peek32_functor(peek32_functor_)
+ , peek64_functor(peek64_functor_)
+{
+ // nop
+}
+
+void wb_iface_adapter::poke32(const wb_addr_type addr, const boost::uint32_t data)
+{
+ poke32_functor(addr / 4, data); // FIXME remove the requirement for /4
+}
+
+boost::uint32_t wb_iface_adapter::peek32(const wb_addr_type addr)
+{
+ return peek32_functor(addr);
+}
+
+boost::uint64_t wb_iface_adapter::peek64(const wb_addr_type addr)
+{
+ return peek64_functor(addr);
+}
+
+uhd::time_spec_t wb_iface_adapter::get_time(void)
+{
+ return gettime_functor();
+}
+
+void wb_iface_adapter::set_time(const uhd::time_spec_t& t)
+{
+ settime_functor(t);
+}
diff --git a/host/lib/rfnoc/wb_iface_adapter.hpp b/host/lib/rfnoc/wb_iface_adapter.hpp
new file mode 100644
index 000000000..04623d203
--- /dev/null
+++ b/host/lib/rfnoc/wb_iface_adapter.hpp
@@ -0,0 +1,70 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP
+#define INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP
+
+#include <uhd/config.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <boost/function.hpp>
+
+namespace uhd {
+ namespace rfnoc {
+
+class UHD_API wb_iface_adapter : public uhd::timed_wb_iface
+{
+public:
+ typedef boost::shared_ptr<wb_iface_adapter> sptr;
+ typedef boost::function<void(wb_addr_type, boost::uint32_t)> poke32_type;
+ typedef boost::function<boost::uint32_t(wb_addr_type)> peek32_type;
+ typedef boost::function<boost::uint64_t(wb_addr_type)> peek64_type;
+ typedef boost::function<time_spec_t(void)> gettime_type;
+ typedef boost::function<void(const time_spec_t&)> settime_type;
+
+ wb_iface_adapter(
+ const poke32_type &,
+ const peek32_type &,
+ const peek64_type &,
+ const gettime_type &,
+ const settime_type &
+ );
+
+ wb_iface_adapter(
+ const poke32_type &,
+ const peek32_type &,
+ const peek64_type &
+ );
+
+ virtual ~wb_iface_adapter(void) {};
+
+ virtual void poke32(const wb_addr_type addr, const boost::uint32_t data);
+ virtual boost::uint32_t peek32(const wb_addr_type addr);
+ virtual boost::uint64_t peek64(const wb_addr_type addr);
+ virtual time_spec_t get_time(void);
+ virtual void set_time(const time_spec_t& t);
+
+private:
+ const poke32_type poke32_functor;
+ const peek32_type peek32_functor;
+ const peek64_type peek64_functor;
+ const gettime_type gettime_functor;
+ const settime_type settime_functor;
+};
+
+}} // namespace uhd::rfnoc
+
+#endif /* INCLUDED_RFNOC_WB_IFACE_ADAPTER_HPP */
diff --git a/host/lib/rfnoc/xports.hpp b/host/lib/rfnoc/xports.hpp
new file mode 100644
index 000000000..7872f2e1b
--- /dev/null
+++ b/host/lib/rfnoc/xports.hpp
@@ -0,0 +1,36 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/types/sid.hpp>
+#include <uhd/transport/zero_copy.hpp>
+
+namespace uhd {
+
+ /*! Holds all necessary items for a bidirectional link
+ */
+ struct both_xports_t
+ {
+ uhd::transport::zero_copy_if::sptr recv;
+ uhd::transport::zero_copy_if::sptr send;
+ size_t recv_buff_size;
+ size_t send_buff_size;
+ uhd::sid_t send_sid;
+ uhd::sid_t recv_sid;
+ };
+
+};
+
diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt
index 6abc399b4..44c8d59af 100644
--- a/host/lib/transport/CMakeLists.txt
+++ b/host/lib/transport/CMakeLists.txt
@@ -22,17 +22,15 @@
########################################################################
# Include subdirectories (different than add)
########################################################################
-INCLUDE_SUBDIRECTORY(nirio)
+IF(ENABLE_X300)
+ INCLUDE_SUBDIRECTORY(nirio)
+ENDIF(ENABLE_X300)
########################################################################
# Setup libusb
########################################################################
-MESSAGE(STATUS "")
-FIND_PACKAGE(USB1)
-
-LIBUHD_REGISTER_COMPONENT("USB" ENABLE_USB ON "ENABLE_LIBUHD;LIBUSB_FOUND" OFF OFF)
-
IF(ENABLE_USB)
+ MESSAGE(STATUS "")
MESSAGE(STATUS "USB support enabled via libusb.")
INCLUDE_DIRECTORIES(${LIBUSB_INCLUDE_DIRS})
LIBUHD_APPEND_LIBS(${LIBUSB_LIBRARIES})
@@ -124,14 +122,21 @@ LIBUHD_PYTHON_GEN_SOURCE(
)
LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/zero_copy_recv_offload.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tcp_zero_copy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/buffer_pool.cpp
${CMAKE_CURRENT_SOURCE_DIR}/if_addrs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/udp_simple.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/chdr.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/muxed_zero_copy_if.cpp
)
+IF(ENABLE_X300)
+ LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/nirio_zero_copy.cpp
+ )
+ENDIF(ENABLE_X300)
+
# Verbose Debug output for send/recv
SET( UHD_TXRX_DEBUG_PRINTS OFF CACHE BOOL "Use verbose debug output for send/recv" )
OPTION( UHD_TXRX_DEBUG_PRINTS "Use verbose debug output for send/recv" "" )
diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp
index f92117a9e..7b9e11da9 100644
--- a/host/lib/transport/libusb1_base.cpp
+++ b/host/lib/transport/libusb1_base.cpp
@@ -47,10 +47,7 @@ public:
task_handler = task::make(boost::bind(&libusb_session_impl::libusb_event_handler_task, this, _context));
}
- ~libusb_session_impl(void){
- task_handler.reset();
- libusb_exit(_context);
- }
+ virtual ~libusb_session_impl(void);
libusb_context *get_context(void) const{
return _context;
@@ -86,6 +83,11 @@ private:
}
};
+libusb_session_impl::~libusb_session_impl(void){
+ task_handler.reset();
+ libusb_exit(_context);
+}
+
libusb::session::sptr libusb::session::get_global_session(void){
static boost::weak_ptr<session> global_session;
@@ -121,9 +123,7 @@ public:
_dev = dev;
}
- ~libusb_device_impl(void){
- libusb_unref_device(this->get());
- }
+ virtual ~libusb_device_impl(void);
libusb_device *get(void) const{
return _dev;
@@ -134,6 +134,10 @@ private:
libusb_device *_dev;
};
+libusb_device_impl::~libusb_device_impl(void){
+ libusb_unref_device(this->get());
+}
+
/***********************************************************************
* libusb device list
**********************************************************************/
@@ -160,6 +164,8 @@ public:
libusb_free_device_list(dev_list, false/*dont unref*/);
}
+ virtual ~libusb_device_list_impl(void);
+
size_t size(void) const{
return _devs.size();
}
@@ -172,6 +178,10 @@ private:
std::vector<libusb::device::sptr> _devs;
};
+libusb_device_list_impl::~libusb_device_list_impl(void){
+ /* NOP */
+}
+
libusb::device_list::sptr libusb::device_list::make(void){
return sptr(new libusb_device_list_impl());
}
@@ -190,6 +200,8 @@ public:
UHD_ASSERT_THROW(libusb_get_device_descriptor(_dev->get(), &_desc) == 0);
}
+ virtual ~libusb_device_descriptor_impl(void);
+
const libusb_device_descriptor &get(void) const{
return _desc;
}
@@ -207,12 +219,12 @@ public:
);
unsigned char buff[512];
- ssize_t ret = libusb_get_string_descriptor_ascii(
- handle->get(), off, buff, sizeof(buff)
+ int ret = libusb_get_string_descriptor_ascii(
+ handle->get(), off, buff, int(sizeof(buff))
);
if (ret < 0) return ""; //on error, just return empty string
- std::string string_descriptor((char *)buff, ret);
+ std::string string_descriptor((char *)buff, size_t(ret));
byte_vector_t string_vec(string_descriptor.begin(), string_descriptor.end());
std::string out;
BOOST_FOREACH(boost::uint8_t byte, string_vec){
@@ -227,6 +239,10 @@ private:
libusb_device_descriptor _desc;
};
+libusb_device_descriptor_impl::~libusb_device_descriptor_impl(void){
+ /* NOP */
+}
+
libusb::device_descriptor::sptr libusb::device_descriptor::make(device::sptr dev){
return sptr(new libusb_device_descriptor_impl(dev));
}
@@ -245,13 +261,7 @@ public:
UHD_ASSERT_THROW(libusb_open(_dev->get(), &_handle) == 0);
}
- ~libusb_device_handle_impl(void){
- //release all claimed interfaces
- for (size_t i = 0; i < _claimed.size(); i++){
- libusb_release_interface(this->get(), _claimed[i]);
- }
- libusb_close(_handle);
- }
+ virtual ~libusb_device_handle_impl(void);
libusb_device_handle *get(void) const{
return _handle;
@@ -283,6 +293,14 @@ private:
std::vector<int> _claimed;
};
+libusb_device_handle_impl::~libusb_device_handle_impl(void){
+ //release all claimed interfaces
+ for (size_t i = 0; i < _claimed.size(); i++){
+ libusb_release_interface(this->get(), _claimed[i]);
+ }
+ libusb_close(_handle);
+}
+
libusb::device_handle::sptr libusb::device_handle::get_cached_handle(device::sptr dev){
static uhd::dict<libusb_device *, boost::weak_ptr<device_handle> > handles;
@@ -327,6 +345,8 @@ public:
_dev = dev;
}
+ virtual ~libusb_special_handle_impl(void);
+
libusb::device::sptr get_device(void) const{
return _dev;
}
@@ -361,6 +381,10 @@ private:
libusb::device::sptr _dev; //always keep a reference to device
};
+libusb_special_handle_impl::~libusb_special_handle_impl(void){
+ /* NOP */
+}
+
libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){
return sptr(new libusb_special_handle_impl(dev));
}
@@ -368,6 +392,10 @@ libusb::special_handle::sptr libusb::special_handle::make(device::sptr dev){
/***********************************************************************
* list device handles implementations
**********************************************************************/
+usb_device_handle::~usb_device_handle(void) {
+ /* NOP */
+}
+
std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list(
boost::uint16_t vid, boost::uint16_t pid
){
diff --git a/host/lib/transport/libusb1_base.hpp b/host/lib/transport/libusb1_base.hpp
index 2ff1291d9..6d104bc75 100644
--- a/host/lib/transport/libusb1_base.hpp
+++ b/host/lib/transport/libusb1_base.hpp
@@ -67,7 +67,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<session> sptr;
- virtual ~session(void) = 0;
+ virtual ~session(void);
/*!
* Level 0: no messages ever printed by the library (default)
@@ -92,7 +92,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<device> sptr;
- virtual ~device(void) = 0;
+ virtual ~device(void);
//! get the underlying device pointer
virtual libusb_device *get(void) const = 0;
@@ -106,7 +106,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<device_list> sptr;
- virtual ~device_list(void) = 0;
+ virtual ~device_list(void);
//! make a new device list
static sptr make(void);
@@ -125,7 +125,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<device_descriptor> sptr;
- virtual ~device_descriptor(void) = 0;
+ virtual ~device_descriptor(void);
//! make a new descriptor from a device reference
static sptr make(device::sptr);
@@ -143,7 +143,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<device_handle> sptr;
- virtual ~device_handle(void) = 0;
+ virtual ~device_handle(void);
//! get a cached handle or make a new one given the device
static sptr get_cached_handle(device::sptr);
@@ -172,7 +172,7 @@ namespace libusb {
public:
typedef boost::shared_ptr<special_handle> sptr;
- virtual ~special_handle(void) = 0;
+ virtual ~special_handle(void);
//! make a new special handle from device
static sptr make(device::sptr);
diff --git a/host/lib/transport/libusb1_control.cpp b/host/lib/transport/libusb1_control.cpp
index 00c113163..a18f657d9 100644
--- a/host/lib/transport/libusb1_control.cpp
+++ b/host/lib/transport/libusb1_control.cpp
@@ -30,19 +30,21 @@ usb_control::~usb_control(void){
**********************************************************************/
class libusb_control_impl : public usb_control {
public:
- libusb_control_impl(libusb::device_handle::sptr handle, const size_t interface):
+ libusb_control_impl(libusb::device_handle::sptr handle, const int interface):
_handle(handle)
{
_handle->claim_interface(interface);
}
- ssize_t submit(boost::uint8_t request_type,
- boost::uint8_t request,
- boost::uint16_t value,
- boost::uint16_t index,
- unsigned char *buff,
- boost::uint16_t length,
- boost::int32_t libusb_timeout = 0
+ virtual ~libusb_control_impl(void);
+
+ int submit(boost::uint8_t request_type,
+ boost::uint8_t request,
+ boost::uint16_t value,
+ boost::uint16_t index,
+ unsigned char *buff,
+ boost::uint16_t length,
+ boost::uint32_t libusb_timeout = 0
){
boost::mutex::scoped_lock lock(_mutex);
return libusb_control_transfer(_handle->get(),
@@ -60,10 +62,14 @@ private:
boost::mutex _mutex;
};
+libusb_control_impl::~libusb_control_impl(void) {
+ /* NOP */
+}
+
/***********************************************************************
* USB control public make functions
**********************************************************************/
-usb_control::sptr usb_control::make(usb_device_handle::sptr handle, const size_t interface){
+usb_control::sptr usb_control::make(usb_device_handle::sptr handle, const int interface){
return sptr(new libusb_control_impl(libusb::device_handle::get_cached_handle(
boost::static_pointer_cast<libusb::special_handle>(handle)->get_device()
), interface));
diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp
index 53e345009..c32b96b63 100644
--- a/host/lib/transport/libusb1_zero_copy.cpp
+++ b/host/lib/transport/libusb1_zero_copy.cpp
@@ -125,19 +125,21 @@ public:
_ctx(libusb::session::get_global_session()->get_context()),
_lut(lut), _frame_size(frame_size) { /* NOP */ }
+ virtual ~libusb_zero_copy_mb(void);
+
void release(void){
_release_cb(this);
}
UHD_INLINE void submit(void)
{
- _lut->length = (_is_recv)? _frame_size : size(); //always set length
+ _lut->length = int((_is_recv)? _frame_size : size()); //always set length
#ifdef UHD_TXRX_DEBUG_PRINTS
result.start_time = boost::get_system_time().time_of_day().total_microseconds();
result.buff_num = num();
result.is_recv = _is_recv;
#endif
- const int ret = libusb_submit_transfer(_lut);
+ int ret = libusb_submit_transfer(_lut);
if (ret != LIBUSB_SUCCESS)
throw uhd::usb_error(ret, str(boost::format(
"usb %s submit failed: %s") % _name % libusb_error_name(ret)));
@@ -152,7 +154,7 @@ public:
throw uhd::io_error(str(boost::format("usb %s transfer status: %d")
% _name % libusb_error_name(result.status)));
result.completed = 0;
- return make(reinterpret_cast<buffer_type *>(this), _lut->buffer, (_is_recv)? result.actual_length : _frame_size);
+ return make(reinterpret_cast<buffer_type *>(this), _lut->buffer, (_is_recv)? size_t(result.actual_length) : _frame_size);
}
return typename buffer_type::sptr();
}
@@ -189,6 +191,10 @@ private:
const size_t _frame_size;
};
+libusb_zero_copy_mb::~libusb_zero_copy_mb(void) {
+ /* NOP */
+}
+
/***********************************************************************
* USB zero_copy device class
**********************************************************************/
@@ -197,7 +203,7 @@ class libusb_zero_copy_single
public:
libusb_zero_copy_single(
libusb::device_handle::sptr handle,
- const size_t interface, const size_t endpoint,
+ const int interface, const unsigned char endpoint,
const size_t num_frames, const size_t frame_size
):
_handle(handle),
@@ -221,7 +227,7 @@ public:
_handle->get(), // dev_handle
endpoint, // endpoint
static_cast<unsigned char *>(buff),
- sizeof(buff),
+ int(sizeof(buff)),
&transfered, //bytes xfered
10 //timeout ms
);
@@ -243,7 +249,7 @@ public:
_handle->get(), // dev_handle
endpoint, // endpoint
static_cast<unsigned char *>(_buffer_pool->at(i)), // buffer
- this->get_frame_size(), // length
+ int(this->get_frame_size()), // length
libusb_transfer_cb_fn(&libusb_async_cb), // callback
static_cast<void *>(&_mb_pool.back()->result), // user_data
0 // timeout (ms)
@@ -372,10 +378,10 @@ struct libusb_zero_copy_impl : usb_zero_copy
{
libusb_zero_copy_impl(
libusb::device_handle::sptr handle,
- const size_t recv_interface,
- const size_t recv_endpoint,
- const size_t send_interface,
- const size_t send_endpoint,
+ const int recv_interface,
+ const unsigned char recv_endpoint,
+ const int send_interface,
+ const unsigned char send_endpoint,
const device_addr_t &hints
){
_recv_impl.reset(new libusb_zero_copy_single(
@@ -388,6 +394,8 @@ struct libusb_zero_copy_impl : usb_zero_copy
size_t(hints.cast<double>("send_frame_size", DEFAULT_XFER_SIZE))));
}
+ virtual ~libusb_zero_copy_impl(void);
+
managed_recv_buffer::sptr get_recv_buff(double timeout)
{
boost::mutex::scoped_lock l(_recv_mutex);
@@ -410,15 +418,26 @@ struct libusb_zero_copy_impl : usb_zero_copy
boost::mutex _recv_mutex, _send_mutex;
};
+libusb_zero_copy_impl::~libusb_zero_copy_impl(void) {
+ /* NOP */
+}
+
+/***********************************************************************
+ * USB zero_copy destructor
+ **********************************************************************/
+usb_zero_copy::~usb_zero_copy(void) {
+ /* NOP */
+}
+
/***********************************************************************
* USB zero_copy make functions
**********************************************************************/
usb_zero_copy::sptr usb_zero_copy::make(
usb_device_handle::sptr handle,
- const size_t recv_interface,
- const size_t recv_endpoint,
- const size_t send_interface,
- const size_t send_endpoint,
+ const int recv_interface,
+ const unsigned char recv_endpoint,
+ const int send_interface,
+ const unsigned char send_endpoint,
const device_addr_t &hints
){
libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle(
diff --git a/host/lib/transport/muxed_zero_copy_if.cpp b/host/lib/transport/muxed_zero_copy_if.cpp
new file mode 100644
index 000000000..996db3c98
--- /dev/null
+++ b/host/lib/transport/muxed_zero_copy_if.cpp
@@ -0,0 +1,250 @@
+//
+// 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/transport/muxed_zero_copy_if.hpp>
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/locks.hpp>
+#include <map>
+
+using namespace uhd;
+using namespace uhd::transport;
+
+class muxed_zero_copy_if_impl : public muxed_zero_copy_if,
+ public boost::enable_shared_from_this<muxed_zero_copy_if_impl>
+{
+public:
+ typedef boost::shared_ptr<muxed_zero_copy_if_impl> sptr;
+
+ muxed_zero_copy_if_impl(
+ zero_copy_if::sptr base_xport,
+ stream_classifier_fn classify_fn,
+ size_t max_streams
+ ):
+ _base_xport(base_xport), _classify(classify_fn),
+ _max_num_streams(max_streams), _num_dropped_frames(0)
+ {
+ //Create the receive thread to poll the underlying transport
+ //and classify packets into queues
+ _recv_thread = boost::thread(
+ boost::bind(&muxed_zero_copy_if_impl::_update_queues, this));
+ }
+
+ virtual ~muxed_zero_copy_if_impl()
+ {
+ UHD_SAFE_CALL(
+ //Interrupt buffer updater loop
+ _recv_thread.interrupt();
+ //Wait for loop to finish
+ //No timeout on join. The recv loop is guaranteed
+ //to terminate in a reasonable amount of time because
+ //there are no timed blocks on the underlying.
+ _recv_thread.join();
+ //Flush base transport
+ while (_base_xport->get_recv_buff(0.0001)) /*NOP*/;
+ //Release child streams
+ //Note that this will not delete or flush the child streams
+ //until the owners of the streams have released the respective
+ //shared pointers. This ensures that packets are not dropped.
+ _streams.clear();
+ );
+ }
+
+ virtual zero_copy_if::sptr make_stream(const uint32_t stream_num)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (_streams.size() >= _max_num_streams) {
+ throw uhd::runtime_error("muxed_zero_copy_if: stream capacity exceeded. cannot create more streams.");
+ }
+ stream_impl::sptr stream = boost::make_shared<stream_impl>(this->shared_from_this(), stream_num);
+ _streams[stream_num] = stream;
+ return stream;
+ }
+
+ virtual size_t get_num_dropped_frames() const
+ {
+ return _num_dropped_frames;
+ }
+
+ void remove_stream(const uint32_t stream_num)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _streams.erase(stream_num);
+ }
+
+private:
+ class stream_impl : public zero_copy_if
+ {
+ public:
+ typedef boost::shared_ptr<stream_impl> sptr;
+ typedef boost::weak_ptr<stream_impl> wptr;
+
+ stream_impl(muxed_zero_copy_if_impl::sptr muxed_xport, const uint32_t stream_num):
+ _stream_num(stream_num), _muxed_xport(muxed_xport),
+ _buff_queue(muxed_xport->base_xport()->get_num_recv_frames())
+ {
+ }
+
+ ~stream_impl(void)
+ {
+ //First remove the stream from muxed transport
+ //so no more frames are pushed in
+ _muxed_xport->remove_stream(_stream_num);
+ //Flush the transport
+ managed_recv_buffer::sptr buff;
+ while (_buff_queue.pop_with_haste(buff)) {
+ //NOP
+ }
+ }
+
+ size_t get_num_recv_frames(void) const {
+ return _muxed_xport->base_xport()->get_num_recv_frames();
+ }
+
+ size_t get_recv_frame_size(void) const {
+ return _muxed_xport->base_xport()->get_recv_frame_size();
+ }
+
+ managed_recv_buffer::sptr get_recv_buff(double timeout) {
+ managed_recv_buffer::sptr buff;
+ if (_buff_queue.pop_with_timed_wait(buff, timeout)) {
+ return buff;
+ } else {
+ return managed_recv_buffer::sptr();
+ }
+
+ }
+
+ void push_recv_buff(managed_recv_buffer::sptr buff) {
+ _buff_queue.push_with_wait(buff);
+ }
+
+ size_t get_num_send_frames(void) const {
+ return _muxed_xport->base_xport()->get_num_send_frames();
+ }
+
+ size_t get_send_frame_size(void) const {
+ return _muxed_xport->base_xport()->get_send_frame_size();
+ }
+
+ managed_send_buffer::sptr get_send_buff(double timeout)
+ {
+ return _muxed_xport->base_xport()->get_send_buff(timeout);
+ }
+
+ private:
+ const uint32_t _stream_num;
+ muxed_zero_copy_if_impl::sptr _muxed_xport;
+ bounded_buffer<managed_recv_buffer::sptr> _buff_queue;
+ };
+
+ inline zero_copy_if::sptr& base_xport() { return _base_xport; }
+
+ void _update_queues()
+ {
+ //Run forever:
+ // - Pull packets from the base transport
+ // - Classify them
+ // - Push them to the appropriate receive queue
+ while (true) {
+ { //Uninterruptable block of code
+ boost::this_thread::disable_interruption interrupt_disabler;
+ if (not _process_next_buffer()) {
+ //Be a good citizen and yield if no packet is processed
+ static const size_t MIN_DUR = 1;
+ boost::this_thread::sleep_for(boost::chrono::nanoseconds(MIN_DUR));
+ //We call sleep(MIN_DUR) above instead of yield() to ensure that we
+ //relinquish the current scheduler time slot.
+ //yield() is a hint to the scheduler to end the time
+ //slice early and schedule in another thread that is ready to run.
+ //However in most situations, there will be no other thread and
+ //this thread will continue to run which will rail a CPU core.
+ //We call sleep(MIN_DUR=1) instead which will sleep for a minimum time.
+ //Ideally we would like to use boost::chrono::.*seconds::min() but that
+ //is bound to 0, which causes the sleep_for call to be a no-op and
+ //thus useless to actually force a sleep.
+
+ //****************************************************************
+ //NOTE: This behavior makes this transport a poor choice for
+ // low latency communication.
+ //****************************************************************
+ }
+ }
+ //Check if the master thread has requested a shutdown
+ if (boost::this_thread::interruption_requested()) break;
+ }
+ }
+
+ bool _process_next_buffer()
+ {
+ managed_recv_buffer::sptr buff = _base_xport->get_recv_buff(0.0);
+ if (buff) {
+ stream_impl::sptr stream;
+ try {
+ const uint32_t stream_num = _classify(buff->cast<void*>(), _base_xport->get_recv_frame_size());
+ {
+ //Hold the stream mutex long enough to pull a bounded buffer
+ //and lock it (increment its ref count).
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ stream_map_t::iterator str_iter = _streams.find(stream_num);
+ if (str_iter != _streams.end()) {
+ stream = (*str_iter).second.lock();
+ }
+ }
+ } catch (std::exception&) {
+ //If _classify throws we simply drop the frame
+ }
+ //Once a bounded buffer is acquired, we can rely on its
+ //thread safety to serialize with the consumer.
+ if (stream.get()) {
+ stream->push_recv_buff(buff);
+ } else {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _num_dropped_frames++;
+ }
+ //We processed a packet, and there could be more coming
+ //Don't yield in the next iteration.
+ return true;
+ } else {
+ //The base transport is idle. Return false to let the
+ //thread yield.
+ return false;
+ }
+ }
+
+ typedef std::map<uint32_t, stream_impl::wptr> stream_map_t;
+
+ zero_copy_if::sptr _base_xport;
+ stream_classifier_fn _classify;
+ stream_map_t _streams;
+ const size_t _max_num_streams;
+ size_t _num_dropped_frames;
+ boost::thread _recv_thread;
+ boost::mutex _mutex;
+};
+
+muxed_zero_copy_if::sptr muxed_zero_copy_if::make(
+ zero_copy_if::sptr base_xport,
+ muxed_zero_copy_if::stream_classifier_fn classify_fn,
+ size_t max_streams
+) {
+ return boost::make_shared<muxed_zero_copy_if_impl>(base_xport, classify_fn, max_streams);
+}
diff --git a/host/lib/transport/nirio/lvbitx/CMakeLists.txt b/host/lib/transport/nirio/lvbitx/CMakeLists.txt
index b9a2a9f15..5741a12f8 100644
--- a/host/lib/transport/nirio/lvbitx/CMakeLists.txt
+++ b/host/lib/transport/nirio/lvbitx/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Ettus Research LLC
+# Copyright 2013,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
@@ -30,8 +30,8 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile)
SET(IMAGES_PATH_OPT --uhd-images-path=${UHD_IMAGES_DIR})
ADD_CUSTOM_COMMAND(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/process-lvbitx.py
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.hpp
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.cpp
@@ -41,6 +41,7 @@ MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile)
)
#make libuhd depend on the output file
+ LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp)
LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp)
ENDMACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM)
diff --git a/host/lib/transport/nirio/rpc/rpc_client.cpp b/host/lib/transport/nirio/rpc/rpc_client.cpp
index bbaf9f235..3d62b57ae 100644
--- a/host/lib/transport/nirio/rpc/rpc_client.cpp
+++ b/host/lib/transport/nirio/rpc/rpc_client.cpp
@@ -1,5 +1,5 @@
///
-// Copyright 2013 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
@@ -55,22 +55,7 @@ rpc_client::rpc_client (
tcp::resolver::query::flags query_flags(tcp::resolver::query::passive);
tcp::resolver::query query(tcp::v4(), server, port, query_flags);
tcp::resolver::iterator iterator = resolver.resolve(query);
-
- #if BOOST_VERSION < 104700
- // default constructor creates end iterator
- tcp::resolver::iterator end;
-
- boost::system::error_code error = boost::asio::error::host_not_found;
- while (error && iterator != end)
- {
- _socket.close();
- _socket.connect(*iterator++, error);
- }
- if (error)
- throw boost::system::system_error(error);
- #else
- boost::asio::connect(_socket, iterator);
- #endif
+ boost::asio::connect(_socket, iterator);
UHD_LOG << "rpc_client connected to server." << std::endl;
@@ -109,11 +94,6 @@ rpc_client::rpc_client (
} catch (boost::exception&) {
UHD_LOG << "rpc_client connection request cancelled/aborted." << std::endl;
_exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category());
-#if BOOST_VERSION < 104700
- } catch (std::exception& e) {
- UHD_LOG << "rpc_client connection error: " << e.what() << std::endl;
- _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category());
-#endif
}
}
diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp
index 8bfa1973a..5ca1da687 100644
--- a/host/lib/transport/super_recv_packet_handler.hpp
+++ b/host/lib/transport/super_recv_packet_handler.hpp
@@ -24,18 +24,19 @@
#include <uhd/stream.hpp>
#include <uhd/utils/msg.hpp>
#include <uhd/utils/tasks.hpp>
-#include <uhd/utils/atomic.hpp>
#include <uhd/utils/byteswap.hpp>
#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>
#include <boost/format.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
-#include <boost/thread/barrier.hpp>
#include <iostream>
#include <vector>
@@ -92,22 +93,15 @@ public:
}
~recv_packet_handler(void){
- _task_barrier.interrupt();
- _task_handlers.clear();
+ /* NOP */
}
//! Resize the number of transport channels
void resize(const size_t size){
if (this->size() == size) return;
- _task_handlers.clear();
_props.resize(size);
//re-initialize all buffers infos by re-creating the vector
_buffers_infos = std::vector<buffers_info_type>(4, buffers_info_type(size));
- _task_barrier.resize(size);
- _task_handlers.resize(size);
- for (size_t i = 1/*skip 0*/; i < size; i++){
- _task_handlers[i] = task::make(boost::bind(&recv_packet_handler::converter_thread_task, this, i));
- };
}
//! Get the channel width of this handler
@@ -121,13 +115,42 @@ 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?
* \param threshold number of packets per channel
*/
void set_alignment_failure_threshold(const size_t threshold){
- _alignment_faulure_threshold = threshold*this->size();
+ _alignment_failure_threshold = threshold*this->size();
}
//! Set the rate of ticks per second
@@ -203,6 +226,13 @@ public:
//! Overload call to issue stream commands
void issue_stream_cmd(const stream_cmd_t &stream_cmd)
{
+ // 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++)
{
if (_props[i].issue_stream_cmd) _props[i].issue_stream_cmd(stream_cmd);
@@ -234,7 +264,7 @@ public:
buffs, nsamps_per_buff, metadata, timeout
);
- if (one_packet){
+ if (one_packet or metadata.end_of_burst){
#ifdef UHD_TXRX_DEBUG_PRINTS
dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet);
#endif
@@ -242,7 +272,9 @@ public:
}
//first recv had an error code set, return immediately
- if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) return accum_num_samps;
+ if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) {
+ return accum_num_samps;
+ }
//loop until buffer is filled or error code
while(accum_num_samps < nsamps_per_buff){
@@ -256,10 +288,16 @@ public:
_queue_error_for_next_call = true;
break;
}
+
accum_num_samps += num_samps;
+
+ //return immediately if end of burst
+ if (_queue_metadata.end_of_burst) {
+ break;
+ }
}
#ifdef UHD_TXRX_DEBUG_PRINTS
- dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet);
+ dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet);
#endif
return accum_num_samps;
}
@@ -269,7 +307,7 @@ private:
size_t _header_offset_words32;
double _tick_rate, _samp_rate;
bool _queue_error_for_next_call;
- size_t _alignment_faulure_threshold;
+ size_t _alignment_failure_threshold;
rx_metadata_t _queue_metadata;
struct xport_chan_props_type{
xport_chan_props_type(void):
@@ -283,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;
@@ -355,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.
@@ -421,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
@@ -442,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;
@@ -462,9 +513,6 @@ private:
curr_buffer_info.reset();
}
}
- get_prev_buffer_info().reset();
- get_curr_buffer_info().reset();
- get_next_buffer_info().reset();
}
/*******************************************************************
@@ -562,20 +610,27 @@ 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:
std::swap(curr_info, next_info); //save progress from curr -> next
+ if(_props[index].handle_flowctrl) {
+ _props[index].handle_flowctrl(next_info[index].ifpi.packet_count);
+ }
curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_TIMEOUT;
return;
@@ -593,7 +648,7 @@ private:
}
//too many iterations: detect alignment failure
- if (iterations++ > _alignment_faulure_threshold){
+ if (iterations++ > _alignment_failure_threshold){
UHD_MSG(error) << boost::format(
"The receive packet handler failed to time-align packets.\n"
"%u received packets were processed by the handler.\n"
@@ -657,7 +712,9 @@ private:
_convert_bytes_to_copy = bytes_to_copy;
//perform N channels of conversion
- converter_thread_task(0);
+ for (size_t i = 0; i < this->size(); i++) {
+ convert_to_out_buff(i);
+ }
//update the copy buffer's availability
info.data_bytes_to_copy -= bytes_to_copy;
@@ -670,15 +727,15 @@ private:
return nsamps_to_copy_per_io_buff;
}
- /*******************************************************************
- * Perform one thread's work of the conversion task.
- * The entry and exit use a dual synchronization barrier,
- * to wait for data to become ready and block until completion.
- ******************************************************************/
- UHD_INLINE void converter_thread_task(const size_t index)
+ /*! Run the conversion from the internal buffers to the user's output
+ * buffer.
+ *
+ * - Calls the converter
+ * - Releases internal data buffers
+ * - Updates read/write pointers
+ */
+ inline void convert_to_out_buff(const size_t index)
{
- _task_barrier.wait();
-
//shortcut references to local data structures
buffers_info_type &buff_info = get_curr_buffer_info();
per_buffer_info_type &info = buff_info[index];
@@ -702,13 +759,9 @@ private:
if (buff_info.data_bytes_to_copy == _convert_bytes_to_copy){
info.buff.reset(); //effectively a release
}
-
- if (index == 0) _task_barrier.wait_others();
}
//! Shared variables for the worker threads
- reusable_barrier _task_barrier;
- std::vector<task::sptr> _task_handlers;
size_t _convert_nsamps;
const rx_streamer::buffs_type *_convert_buffs;
size_t _convert_buffer_offset_bytes;
diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp
index c2810842e..5e81fb442 100644
--- a/host/lib/transport/super_send_packet_handler.hpp
+++ b/host/lib/transport/super_send_packet_handler.hpp
@@ -24,11 +24,14 @@
#include <uhd/stream.hpp>
#include <uhd/utils/msg.hpp>
#include <uhd/utils/tasks.hpp>
-#include <uhd/utils/atomic.hpp>
#include <uhd/utils/byteswap.hpp>
#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>
#include <boost/function.hpp>
@@ -74,22 +77,15 @@ public:
}
~send_packet_handler(void){
- _task_barrier.interrupt();
- _task_handlers.clear();
+ /* NOP */
}
//! Resize the number of transport channels
void resize(const size_t size){
if (this->size() == size) return;
- _task_handlers.clear();
_props.resize(size);
static const boost::uint64_t zero = 0;
_zero_buffs.resize(size, &zero);
- _task_barrier.resize(size);
- _task_handlers.resize(size);
- for (size_t i = 1/*skip 0*/; i < size; i++){
- _task_handlers[i] = task::make(boost::bind(&send_packet_handler::converter_thread_task, this, i));
- };
}
//! Get the channel width of this handler
@@ -109,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;
@@ -303,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):
@@ -377,21 +400,23 @@ private:
_convert_if_packet_info = &if_packet_info;
//perform N channels of conversion
- converter_thread_task(0);
+ for (size_t i = 0; i < this->size(); i++) {
+ convert_to_in_buff(i);
+ }
_next_packet_seq++; //increment sequence after commits
return nsamps_per_buff;
}
- /*******************************************************************
- * Perform one thread's work of the conversion task.
- * The entry and exit use a dual synchronization barrier,
- * to wait for data to become ready and block until completion.
- ******************************************************************/
- UHD_INLINE void converter_thread_task(const size_t index)
+ /*! Run the conversion from the internal buffers to the user's input
+ * buffer.
+ *
+ * - Calls the converter
+ * - Releases internal data buffers
+ * - Updates read/write pointers
+ */
+ UHD_INLINE void convert_to_in_buff(const size_t index)
{
- _task_barrier.wait();
-
//shortcut references to local data structures
managed_send_buffer::sptr &buff = _props[index].buff;
vrt::if_packet_info_t if_packet_info = *_convert_if_packet_info;
@@ -419,13 +444,9 @@ private:
const size_t num_vita_words32 = _header_offset_words32+if_packet_info.num_packet_words32;
buff->commit(num_vita_words32*sizeof(boost::uint32_t));
buff.reset(); //effectively a release
-
- if (index == 0) _task_barrier.wait_others();
}
//! Shared variables for the worker threads
- reusable_barrier _task_barrier;
- std::vector<task::sptr> _task_handlers;
size_t _convert_nsamps;
const tx_streamer::buffs_type *_convert_buffs;
size_t _convert_buffer_offset_bytes;
diff --git a/host/lib/transport/usb_dummy_impl.cpp b/host/lib/transport/usb_dummy_impl.cpp
index ce8c306e4..b53b6f590 100644
--- a/host/lib/transport/usb_dummy_impl.cpp
+++ b/host/lib/transport/usb_dummy_impl.cpp
@@ -31,12 +31,20 @@ std::vector<usb_device_handle::sptr> usb_device_handle::get_device_list(boost::u
return std::vector<usb_device_handle::sptr>(); //empty list
}
-usb_control::sptr usb_control::make(usb_device_handle::sptr, const size_t){
+usb_control::sptr usb_control::make(
+ usb_device_handle::sptr,
+ const int
+) {
throw uhd::not_implemented_error("no usb support -> usb_control::make not implemented");
}
usb_zero_copy::sptr usb_zero_copy::make(
- usb_device_handle::sptr, const size_t, const size_t, const size_t, const size_t, const device_addr_t &
+ usb_device_handle::sptr,
+ const int,
+ const unsigned char,
+ const int,
+ const unsigned char,
+ const device_addr_t &
){
throw uhd::not_implemented_error("no usb support -> usb_zero_copy::make not implemented");
}
diff --git a/host/lib/transport/zero_copy_recv_offload.cpp b/host/lib/transport/zero_copy_recv_offload.cpp
new file mode 100644
index 000000000..e8b013abc
--- /dev/null
+++ b/host/lib/transport/zero_copy_recv_offload.cpp
@@ -0,0 +1,158 @@
+//
+// 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 <uhd/transport/zero_copy_recv_offload.hpp>
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/transport/buffer_pool.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <boost/format.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/bind.hpp>
+
+using namespace uhd;
+using namespace uhd::transport;
+
+typedef bounded_buffer<managed_recv_buffer::sptr> bounded_buffer_t;
+
+/***********************************************************************
+ * Zero copy offload transport:
+ * An intermediate transport that utilizes threading to free
+ * the main thread from any receive work.
+ **********************************************************************/
+class zero_copy_recv_offload_impl : public zero_copy_recv_offload {
+public:
+ typedef boost::shared_ptr<zero_copy_recv_offload_impl> sptr;
+
+ zero_copy_recv_offload_impl(zero_copy_if::sptr transport,
+ const double timeout) :
+ _transport(transport), _timeout(timeout),
+ _inbox(transport->get_num_recv_frames()),
+ _recv_done(false)
+ {
+ UHD_LOG << "Created threaded transport" << std::endl;
+
+ // Create the receive and send threads to offload
+ // the system calls onto other threads
+ _recv_thread = boost::thread(
+ boost::bind(&zero_copy_recv_offload_impl::enqueue_recv, this)
+ );
+ }
+
+ // Receive thread flags
+ void set_recv_done()
+ {
+ boost::lock_guard<boost::mutex> guard(_recv_mutex);
+ _recv_done = true;
+ }
+
+ bool is_recv_done()
+ {
+ boost::lock_guard<boost::mutex> guard(_recv_mutex);
+ return _recv_done;
+ }
+
+ ~zero_copy_recv_offload_impl()
+ {
+ // Signal the threads we're finished
+ set_recv_done();
+
+ // Wait for them to join
+ UHD_SAFE_CALL(
+ _recv_thread.join();
+ )
+ }
+
+ // The receive thread function is responsible for
+ // pulling pointers to managed receiver buffers quickly
+ void enqueue_recv()
+ {
+ while (not is_recv_done()) {
+ managed_recv_buffer::sptr buff = _transport->get_recv_buff(_timeout);
+ if (not buff) continue;
+ _inbox.push_with_timed_wait(buff, _timeout);
+ }
+ }
+
+ /*******************************************************************
+ * Receive implementation:
+ * Pop the receive buffer pointer from the underlying transport
+ ******************************************************************/
+ managed_recv_buffer::sptr get_recv_buff(double timeout)
+ {
+ managed_recv_buffer::sptr ptr;
+ _inbox.pop_with_timed_wait(ptr, timeout);
+ return ptr;
+ }
+
+ size_t get_num_recv_frames() const
+ {
+ return _transport->get_num_recv_frames();
+ }
+
+ size_t get_recv_frame_size() const
+ {
+ return _transport->get_recv_frame_size();
+ }
+
+ /*******************************************************************
+ * Send implementation:
+ * Pass the send buffer pointer from the underlying transport
+ ******************************************************************/
+ managed_send_buffer::sptr get_send_buff(double timeout)
+ {
+ return _transport->get_send_buff(timeout);
+ }
+
+ size_t get_num_send_frames() const
+ {
+ return _transport->get_num_send_frames();
+ }
+
+ size_t get_send_frame_size() const
+ {
+ return _transport->get_send_frame_size();
+ }
+
+private:
+ // The linked transport
+ zero_copy_if::sptr _transport;
+
+ const double _timeout;
+
+ // Shared buffers
+ bounded_buffer_t _inbox;
+
+ // Threading
+ bool _recv_done;
+ boost::thread _recv_thread;
+ boost::mutex _recv_mutex;
+};
+
+zero_copy_recv_offload::sptr zero_copy_recv_offload::make(
+ zero_copy_if::sptr transport,
+ const double timeout)
+{
+ zero_copy_recv_offload_impl::sptr zero_copy_recv_offload(
+ new zero_copy_recv_offload_impl(transport, timeout)
+ );
+
+ return zero_copy_recv_offload;
+}
diff --git a/host/lib/types/device_addr.cpp b/host/lib/types/device_addr.cpp
index 1554c3e4e..747f61b8d 100644
--- a/host/lib/types/device_addr.cpp
+++ b/host/lib/types/device_addr.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011 Ettus Research LLC
+// Copyright 2011,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
@@ -26,8 +26,8 @@
using namespace uhd;
-static const std::string arg_delim = ",";
-static const std::string pair_delim = "=";
+static const char* arg_delim = ",";
+static const char* pair_delim = "=";
static std::string trim(const std::string &in){
return boost::algorithm::trim_copy(in);
@@ -35,7 +35,7 @@ static std::string trim(const std::string &in){
#define tokenizer(inp, sep) \
boost::tokenizer<boost::char_separator<char> > \
- (inp, boost::char_separator<char>(sep.c_str()))
+ (inp, boost::char_separator<char>(sep))
device_addr_t::device_addr_t(const std::string &args){
BOOST_FOREACH(const std::string &pair, tokenizer(args, arg_delim)){
diff --git a/host/lib/types/sensors.cpp b/host/lib/types/sensors.cpp
index 52a63d14c..0406e35d4 100644
--- a/host/lib/types/sensors.cpp
+++ b/host/lib/types/sensors.cpp
@@ -69,6 +69,12 @@ sensor_value_t::sensor_value_t(
/* NOP */
}
+sensor_value_t::sensor_value_t(const sensor_value_t& source)
+{
+ *this = source;
+}
+
+
std::string sensor_value_t::to_pp_string(void) const{
switch(type){
case BOOLEAN:
@@ -92,3 +98,12 @@ signed sensor_value_t::to_int(void) const{
double sensor_value_t::to_real(void) const{
return boost::lexical_cast<double>(value);
}
+
+sensor_value_t& sensor_value_t::operator=(const sensor_value_t& rhs)
+{
+ this->name = rhs.name;
+ this->value = rhs.value;
+ this->unit = rhs.unit;
+ this->type = rhs.type;
+ return *this;
+}
diff --git a/host/lib/types/serial.cpp b/host/lib/types/serial.cpp
index 9b8336dd8..52961691c 100644
--- a/host/lib/types/serial.cpp
+++ b/host/lib/types/serial.cpp
@@ -40,7 +40,8 @@ spi_config_t::spi_config_t(edge_t edge):
mosi_edge(edge),
miso_edge(edge)
{
- /* NOP */
+ // By default don't use a custom clock speed for the transaction
+ use_custom_divider = false;
}
void i2c_iface::write_eeprom(
diff --git a/host/lib/uhd.rc.in b/host/lib/uhd.rc.in
index 051511327..24177a00a 100644
--- a/host/lib/uhd.rc.in
+++ b/host/lib/uhd.rc.in
@@ -1,8 +1,8 @@
#include <windows.h>
VS_VERSION_INFO VERSIONINFO
- FILEVERSION @TRIMMED_VERSION_MAJOR@,@TRIMMED_VERSION_MINOR@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@
- PRODUCTVERSION @TRIMMED_VERSION_MAJOR@,@TRIMMED_VERSION_MINOR@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@
+ FILEVERSION @TRIMMED_VERSION_MAJOR_API@,@TRIMMED_VERSION_ABI@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@
+ PRODUCTVERSION @TRIMMED_VERSION_MAJOR_API@,@TRIMMED_VERSION_ABI@,@RC_TRIMMED_VERSION_PATCH@,@UHD_GIT_COUNT@
FILEFLAGSMASK 0x3fL
#ifndef NDEBUG
FILEFLAGS 0x0L
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt
index 5c9592970..f769417d9 100644
--- a/host/lib/usrp/CMakeLists.txt
+++ b/host/lib/usrp/CMakeLists.txt
@@ -18,8 +18,6 @@
########################################################################
# This file included, use CMake directory variables
########################################################################
-find_package(GPSD)
-
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
LIBUHD_APPEND_SOURCES(
@@ -32,6 +30,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp
${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/fe_connection.cpp
)
IF(ENABLE_C_API)
@@ -43,8 +42,6 @@ IF(ENABLE_C_API)
)
ENDIF(ENABLE_C_API)
-LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF)
-
IF(ENABLE_GPSD)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.cpp
@@ -55,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)
@@ -62,3 +60,4 @@ INCLUDE_SUBDIRECTORY(e100)
INCLUDE_SUBDIRECTORY(e300)
INCLUDE_SUBDIRECTORY(x300)
INCLUDE_SUBDIRECTORY(b200)
+INCLUDE_SUBDIRECTORY(n230)
diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt
index 1558cd974..66129458c 100644
--- a/host/lib/usrp/b100/CMakeLists.txt
+++ b/host/lib/usrp/b100/CMakeLists.txt
@@ -22,8 +22,6 @@
########################################################################
# Conditionally configure the B100 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
-
IF(ENABLE_B100)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp
diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp
index c4279913c..eec9f0e9a 100644
--- a/host/lib/usrp/b100/b100_impl.cpp
+++ b/host/lib/usrp/b100/b100_impl.cpp
@@ -281,7 +281,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
_tree->create<std::string>(mb_path / "name").set("B100");
_tree->create<std::string>(mb_path / "codename").set("B-Hundo");
_tree->create<std::string>(mb_path / "load_eeprom")
- .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1));
////////////////////////////////////////////////////////////////////
// setup the mboard eeprom
@@ -289,20 +289,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, B100_EEPROM_MAP_KEY);
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(mb_eeprom)
- .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_mb_eeprom, this, _1));
////////////////////////////////////////////////////////////////////
// create clock control objects
////////////////////////////////////////////////////////////////////
//^^^ clock created up top, just reg props here... ^^^
_tree->create<double>(mb_path / "tick_rate")
- .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl))
- .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1))
- .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1));
+ .set_publisher(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl))
+ .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1))
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_tick_rate, this, _1));
- //subscribe the command time while we are at it
+ //add_coerced_subscriber the command time while we are at it
_tree->create<time_spec_t>(mb_path / "time/cmd")
- .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1));
////////////////////////////////////////////////////////////////////
// create codec control objects
@@ -313,20 +313,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
_tree->create<std::string>(rx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range);
_tree->create<double>(rx_codec_path / "gains/pga/value")
- .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1))
+ .set_coercer(boost::bind(&b100_impl::update_rx_codec_gain, this, _1))
.set(0.0);
_tree->create<std::string>(tx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range);
_tree->create<double>(tx_codec_path / "gains/pga/value")
- .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1))
- .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl))
+ .add_coerced_subscriber(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1))
+ .set_publisher(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl))
.set(0.0);
////////////////////////////////////////////////////////////////////
// and do the misc mboard sensors
////////////////////////////////////////////////////////////////////
_tree->create<sensor_value_t>(mb_path / "sensors/ref_locked")
- .publish(boost::bind(&b100_impl::get_ref_locked, this));
+ .set_publisher(boost::bind(&b100_impl::get_ref_locked, this));
////////////////////////////////////////////////////////////////////
// create frontend control objects
@@ -335,27 +335,27 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
_tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE));
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
- .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
- .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1));
const fs_path rx_fe_path = mb_path / "rx_frontends" / "A";
const fs_path tx_fe_path = mb_path / "tx_frontends" / "A";
_tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1))
+ .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
- .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1))
.set(true);
_tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1))
+ .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
////////////////////////////////////////////////////////////////////
@@ -374,20 +374,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
_rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1));
fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
_tree->create<meta_range_t>(rx_dsp_path / "rate/range")
- .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno]));
_tree->create<double>(rx_dsp_path / "rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1))
- .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1))
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1));
_tree->create<double>(rx_dsp_path / "freq/value")
- .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1));
_tree->create<meta_range_t>(rx_dsp_path / "freq/range")
- .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno]));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1));
}
////////////////////////////////////////////////////////////////////
@@ -398,17 +398,17 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
);
_tx_dsp->set_link_rate(B100_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1));
+ .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range")
- .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp));
_tree->create<double>(mb_path / "tx_dsps/0/rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1))
- .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1))
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1));
_tree->create<double>(mb_path / "tx_dsps/0/freq/value")
- .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range")
- .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp));
////////////////////////////////////////////////////////////////////
// create time control objects
@@ -422,21 +422,21 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
_fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases
);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1));
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time/now")
- .publish(boost::bind(&time64_core_200::get_time_now, _time64))
- .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time/pps")
- .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64))
- .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1));
//setup time source props
_tree->create<std::string>(mb_path / "time_source/value")
- .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1));
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1));
_tree->create<std::vector<std::string> >(mb_path / "time_source/options")
- .publish(boost::bind(&time64_core_200::get_time_sources, _time64));
+ .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64));
//setup reference source props
_tree->create<std::string>(mb_path / "clock_source/value")
- .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::update_clock_source, this, _1));
static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto");
_tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources);
@@ -445,7 +445,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
_user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS));
_tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs")
- .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1));
+ .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1));
////////////////////////////////////////////////////////////////////
// create dboard control objects
@@ -463,32 +463,31 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
//create the properties and register subscribers
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")
.set(rx_db_eeprom)
- .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom")
.set(tx_db_eeprom)
- .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom")
.set(gdb_eeprom)
- .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1));
//create a new dboard interface and manager
- _dboard_iface = make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl);
- _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface);
_dboard_manager = dboard_manager::make(
rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id,
- _dboard_iface, _tree->subtree(mb_path / "dboards/A")
+ make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl),
+ _tree->subtree(mb_path / "dboards/A")
);
//bind frontend corrections to the dboard freq props
const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){
_tree->access<double>(db_tx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1));
}
const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){
_tree->access<double>(db_rx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1));
+ .add_coerced_subscriber(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1));
}
//initialize io handling
@@ -503,8 +502,8 @@ b100_impl::b100_impl(const device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
this->update_rates();
- _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter
- .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1));
+ _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter
+ .add_coerced_subscriber(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1));
//reset cordic rates and their properties to zero
BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){
diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp
index 5a8f70d73..7f37030d2 100644
--- a/host/lib/usrp/b100/b100_impl.hpp
+++ b/host/lib/usrp/b100/b100_impl.hpp
@@ -126,7 +126,6 @@ private:
//dboard stuff
uhd::usrp::dboard_manager::sptr _dboard_manager;
- uhd::usrp::dboard_iface::sptr _dboard_iface;
bool _ignore_cal_file;
std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers;
diff --git a/host/lib/usrp/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp
index 325efeec1..9829f3f09 100644
--- a/host/lib/usrp/b100/dboard_iface.cpp
+++ b/host/lib/usrp/b100/dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011 Ettus Research LLC
+// Copyright 2011,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
@@ -66,12 +66,16 @@ public:
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, boost::uint16_t);
- void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t);
- void _set_gpio_ddr(unit_t, boost::uint16_t);
- void _set_gpio_out(unit_t, boost::uint16_t);
- void set_gpio_debug(unit_t, int);
- boost::uint16_t read_gpio(unit_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);
@@ -97,6 +101,7 @@ public:
double get_clock_rate(unit_t);
void set_clock_enabled(unit_t, bool);
double get_codec_rate(unit_t);
+ void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn);
private:
timed_wb_iface::sptr _wb_iface;
@@ -127,6 +132,7 @@ void b100_dboard_iface::set_clock_rate(unit_t unit, double rate){
switch(unit){
case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate);
case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate);
+ case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return;
}
}
@@ -142,14 +148,15 @@ double b100_dboard_iface::get_clock_rate(unit_t unit){
switch(unit){
case UNIT_RX: return _clock->get_rx_clock_rate();
case UNIT_TX: return _clock->get_tx_clock_rate();
+ default: UHD_THROW_INVALID_CODE_PATH();
}
- UHD_THROW_INVALID_CODE_PATH();
}
void b100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){
switch(unit){
case UNIT_RX: return _clock->enable_rx_dboard_clock(enb);
case UNIT_TX: return _clock->enable_tx_dboard_clock(enb);
+ case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return;
}
}
@@ -160,28 +167,40 @@ double b100_dboard_iface::get_codec_rate(unit_t){
/***********************************************************************
* GPIO
**********************************************************************/
-void b100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){
- return _gpio->set_pin_ctrl(unit, value);
+void b100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_ddr(unit, value);
+boost::uint32_t b100_dboard_iface::get_pin_ctrl(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit));
}
-void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_out(unit, value);
+void b100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){
- return _gpio->read_gpio(unit);
+boost::uint32_t b100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){
+ return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg));
+}
+
+void b100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
+}
+
+boost::uint32_t b100_dboard_iface::get_gpio_ddr(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit));
}
-void b100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){
- return _gpio->set_atr_reg(unit, atr, value);
+void b100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void b100_dboard_iface::set_gpio_debug(unit_t, int){
- throw uhd::not_implemented_error("no set_gpio_debug implemented");
+boost::uint32_t b100_dboard_iface::get_gpio_out(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit));
+}
+
+boost::uint32_t b100_dboard_iface::read_gpio(unit_t unit){
+ return _gpio->read_gpio(unit);
}
/***********************************************************************
@@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){
switch(unit){
case dboard_iface::UNIT_TX: return B100_SPI_SS_TX_DB;
case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
- UHD_THROW_INVALID_CODE_PATH();
}
void b100_dboard_iface::write_spi(
@@ -268,3 +287,8 @@ uhd::time_spec_t b100_dboard_iface::get_command_time(void)
{
return _wb_iface->get_time();
}
+
+void b100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&)
+{
+ throw uhd::not_implemented_error("fe connection configuration support not implemented");
+}
diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt
index 76710dc65..d953acb19 100644
--- a/host/lib/usrp/b200/CMakeLists.txt
+++ b/host/lib/usrp/b200/CMakeLists.txt
@@ -22,8 +22,6 @@
########################################################################
# Conditionally configure the B200 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
-
IF(ENABLE_B200)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/b200_image_loader.cpp
@@ -32,5 +30,6 @@ IF(ENABLE_B200)
${CMAKE_CURRENT_SOURCE_DIR}/b200_io_impl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/b200_uart.cpp
${CMAKE_CURRENT_SOURCE_DIR}/b200_cores.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/b200_radio_ctrl_core.cpp
)
ENDIF(ENABLE_B200)
diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp
index 207c418fc..218f8fd0e 100644
--- a/host/lib/usrp/b200/b200_iface.cpp
+++ b/host/lib/usrp/b200/b200_iface.cpp
@@ -142,7 +142,7 @@ public:
boost::uint16_t index,
unsigned char *buff,
boost::uint16_t length,
- boost::int32_t timeout = 0) {
+ boost::uint32_t timeout = 0) {
return _usb_ctrl->submit(VRT_VENDOR_OUT, // bmReqeustType
request, // bRequest
value, // wValue
@@ -157,7 +157,7 @@ public:
boost::uint16_t index,
unsigned char *buff,
boost::uint16_t length,
- boost::int32_t timeout = 0) {
+ boost::uint32_t timeout = 0) {
return _usb_ctrl->submit(VRT_VENDOR_IN, // bmReqeustType
request, // bRequest
value, // wValue
diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp
index d7663c68e..9526ae2d1 100644
--- a/host/lib/usrp/b200/b200_impl.cpp
+++ b/host/lib/usrp/b200/b200_impl.cpp
@@ -42,6 +42,7 @@
using namespace uhd;
using namespace uhd::usrp;
+using namespace uhd::usrp::gpio_atr;
using namespace uhd::transport;
static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000);
@@ -364,7 +365,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
const mboard_eeprom_t mb_eeprom(*_iface, "B200");
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(mb_eeprom)
- .subscribe(boost::bind(&b200_impl::set_mb_eeprom, this, _1));
+ .add_coerced_subscriber(boost::bind(&b200_impl::set_mb_eeprom, this, _1));
////////////////////////////////////////////////////////////////////
// Identify the device type
@@ -465,7 +466,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
////////////////////////////////////////////////////////////////////
// Local control endpoint
////////////////////////////////////////////////////////////////////
- _local_ctrl = radio_ctrl_core_3000::make(false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, B200_LOCAL_CTRL_SID);
+ _local_ctrl = b200_radio_ctrl_core::make(false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, B200_LOCAL_CTRL_SID);
_local_ctrl->hold_task(_async_task);
_async_task_data->local_ctrl = _local_ctrl; //weak
this->check_fpga_compat();
@@ -502,7 +503,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
BOOST_FOREACH(const std::string &name, _gps->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name));
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name));
}
}
else
@@ -579,9 +580,9 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
// create clock control objects
////////////////////////////////////////////////////////////////////
_tree->create<double>(mb_path / "tick_rate")
- .coerce(boost::bind(&b200_impl::set_tick_rate, this, _1))
- .publish(boost::bind(&b200_impl::get_tick_rate, this))
- .subscribe(boost::bind(&b200_impl::update_tick_rate, this, _1));
+ .set_coercer(boost::bind(&b200_impl::set_tick_rate, this, _1))
+ .set_publisher(boost::bind(&b200_impl::get_tick_rate, this))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_tick_rate, this, _1));
_tree->create<time_spec_t>(mb_path / "time" / "cmd");
_tree->create<bool>(mb_path / "auto_tick_rate").set(false);
@@ -589,7 +590,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
// and do the misc mboard sensors
////////////////////////////////////////////////////////////////////
_tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked")
- .publish(boost::bind(&b200_impl::get_ref_locked, this));
+ .set_publisher(boost::bind(&b200_impl::get_ref_locked, this));
////////////////////////////////////////////////////////////////////
// create frontend mapping
@@ -598,13 +599,13 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
_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")
- .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1))
+ .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1))
.set(subdev_spec_t())
- .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
- .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1))
+ .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1))
.set(subdev_spec_t())
- .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1));
////////////////////////////////////////////////////////////////////
// setup radio control
@@ -619,27 +620,31 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
for (size_t i = 0; i < _radio_perifs.size(); i++)
this->setup_radio(i);
-
//now test each radio module's connection to the codec interface
BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs)
{
- _codec_mgr->loopback_self_test(perif.ctrl, TOREG(SR_CODEC_IDLE), RB64_CODEC_READBACK);
+ _codec_mgr->loopback_self_test(
+ boost::bind(
+ &b200_radio_ctrl_core::poke32, perif.ctrl, TOREG(SR_CODEC_IDLE), _1
+ ),
+ boost::bind(&b200_radio_ctrl_core::peek64, perif.ctrl, RB64_CODEC_READBACK)
+ );
}
//register time now and pps onto available radio cores
_tree->create<time_spec_t>(mb_path / "time" / "now")
- .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64))
- .subscribe(boost::bind(&b200_impl::set_time, this, _1))
+ .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64))
+ .add_coerced_subscriber(boost::bind(&b200_impl::set_time, this, _1))
.set(0.0);
//re-sync the times when the tick rate changes
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&b200_impl::sync_times, this));
+ .add_coerced_subscriber(boost::bind(&b200_impl::sync_times, this));
_tree->create<time_spec_t>(mb_path / "time" / "pps")
- .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64));
+ .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64));
BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs)
{
_tree->access<time_spec_t>(mb_path / "time" / "pps")
- .subscribe(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1));
+ .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1));
}
//setup time source props
@@ -649,8 +654,8 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
_tree->create<std::vector<std::string> >(mb_path / "time_source" / "options")
.set(time_sources);
_tree->create<std::string>(mb_path / "time_source" / "value")
- .coerce(boost::bind(&check_option_valid, "time source", time_sources, _1))
- .subscribe(boost::bind(&b200_impl::update_time_source, this, _1));
+ .set_coercer(boost::bind(&check_option_valid, "time source", time_sources, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_time_source, this, _1));
//setup reference source props
static const std::vector<std::string> clock_sources = (_gpsdo_capable) ?
boost::assign::list_of("internal")("external")("gpsdo") :
@@ -658,21 +663,21 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
_tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options")
.set(clock_sources);
_tree->create<std::string>(mb_path / "clock_source" / "value")
- .coerce(boost::bind(&check_option_valid, "clock source", clock_sources, _1))
- .subscribe(boost::bind(&b200_impl::update_clock_source, this, _1));
+ .set_coercer(boost::bind(&check_option_valid, "clock source", clock_sources, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_clock_source, this, _1));
////////////////////////////////////////////////////////////////////
// front panel gpio
////////////////////////////////////////////////////////////////////
- _radio_perifs[0].fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO);
+ _radio_perifs[0].fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), 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)
- .subscribe(boost::bind(&b200_impl::set_fp_gpio, this, _radio_perifs[0].fp_gpio, attr.first, _1));
+ .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, _radio_perifs[0].fp_gpio, attr.first, _1));
}
_tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK")
- .publish(boost::bind(&b200_impl::get_fp_gpio, this, _radio_perifs[0].fp_gpio));
+ .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio));
////////////////////////////////////////////////////////////////////
// dboard eeproms but not really
@@ -685,10 +690,14 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
////////////////////////////////////////////////////////////////////
// do some post-init tasks
////////////////////////////////////////////////////////////////////
-
- //init the clock rate to something reasonable
- double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE);
+ // Init the clock rate and the auto mcr appropriately
+ if (not device_addr.has_key("master_clock_rate")) {
+ UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl;
+ }
+ // We can automatically choose a master clock rate, but not if the user specifies one
+ const double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE);
_tree->access<double>(mb_path / "tick_rate").set(default_tick_rate);
+ _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate"));
//subdev spec contains full width of selections
subdev_spec_t rx_spec, tx_spec;
@@ -712,12 +721,6 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s
_radio_perifs[i].ddc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_DECIM);
_radio_perifs[i].duc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_INTERP);
}
- // We can automatically choose a master clock rate, but not if the user specifies one
- _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate"));
- if (not device_addr.has_key("master_clock_rate")) {
- UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl;
- }
-
}
b200_impl::~b200_impl(void)
@@ -753,7 +756,7 @@ void b200_impl::setup_radio(const size_t dspno)
////////////////////////////////////////////////////////////////////
// radio control
////////////////////////////////////////////////////////////////////
- perif.ctrl = radio_ctrl_core_3000::make(
+ perif.ctrl = b200_radio_ctrl_core::make(
false/*lilE*/,
_ctrl_transport,
zero_copy_if::sptr()/*null*/,
@@ -761,22 +764,23 @@ void b200_impl::setup_radio(const size_t dspno)
perif.ctrl->hold_task(_async_task);
_async_task_data->radio_ctrl[dspno] = perif.ctrl; //weak
_tree->access<time_spec_t>(mb_path / "time" / "cmd")
- .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&b200_radio_ctrl_core::set_time, perif.ctrl, _1));
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&b200_radio_ctrl_core::set_tick_rate, perif.ctrl, _1));
this->register_loopback_self_test(perif.ctrl);
////////////////////////////////////////////////////////////////////
// Set up peripherals
////////////////////////////////////////////////////////////////////
- perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR));
+ perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, TOREG(SR_ATR));
+ perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF);
// create rx dsp control objects
perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL));
perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP), true /*is_b200?*/);
perif.ddc->set_link_rate(10e9/8); //whatever
- perif.ddc->set_mux("IQ", false, dspno == 1 ? true : false, dspno == 1 ? true : false);
+ perif.ddc->set_mux(usrp::fe_connection_t(dspno == 1 ? "IbQb" : "IQ"));
perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
- perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL));
+ perif.deframer = tx_vita_core_3000::make_no_radio_buff(perif.ctrl, TOREG(SR_TX_CTRL));
perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP));
perif.duc->set_link_rate(10e9/8); //whatever
perif.duc->set_freq(tx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
@@ -796,15 +800,15 @@ void b200_impl::setup_radio(const size_t dspno)
perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path));
_tree->create<bool>(rx_dsp_path / "rate" / "set").set(false);
_tree->access<double>(rx_dsp_path / "rate" / "value")
- .coerce(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1))
- .subscribe(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1))
- .subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1))
+ .set_coercer(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1))
;
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1));
+ .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1));
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
- .subscribe(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path))
+ .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path))
;
////////////////////////////////////////////////////////////////////
@@ -814,13 +818,12 @@ void b200_impl::setup_radio(const size_t dspno)
perif.duc->populate_subtree(_tree->subtree(tx_dsp_path));
_tree->create<bool>(tx_dsp_path / "rate" / "set").set(false);
_tree->access<double>(tx_dsp_path / "rate" / "value")
- .coerce(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1))
- .subscribe(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1))
- .subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1))
+ .set_coercer(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1))
;
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1))
- .subscribe(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path))
;
////////////////////////////////////////////////////////////////////
@@ -840,17 +843,17 @@ void b200_impl::setup_radio(const size_t dspno)
// Now connect all the b200_impl-specific items
_tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked")
- .publish(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION))
+ .set_publisher(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION))
;
_tree->access<double>(rf_fe_path / "freq" / "value")
- .subscribe(boost::bind(&b200_impl::update_bandsel, this, key, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_bandsel, this, key, _1))
;
if (dir == RX_DIRECTION)
{
static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2");
_tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants);
_tree->create<std::string>(rf_fe_path / "antenna" / "value")
- .subscribe(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1))
.set("RX2")
;
@@ -986,27 +989,6 @@ void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom)
mb_eeprom.commit(*_iface, "B200");
}
-
-boost::uint32_t b200_impl::get_fp_gpio(gpio_core_200::sptr gpio)
-{
- return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX));
-}
-
-void b200_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value)
-{
- switch (attr)
- {
- case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value);
- case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value);
- case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value);
- case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value);
- case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value);
- case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value);
- case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value);
- default: UHD_THROW_INVALID_CODE_PATH();
- }
-}
-
/***********************************************************************
* Reference time and clock
**********************************************************************/
@@ -1189,11 +1171,11 @@ void b200_impl::update_atrs(void)
if (enb_rx and enb_tx) fd = STATE_FDX1_TXRX;
if (enb_rx and not enb_tx) fd = rxonly;
if (not enb_rx and enb_tx) fd = txonly;
- gpio_core_200_32wo::sptr atr = perif.atr;
- atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF);
- atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly);
- atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly);
- atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd);
+ gpio_atr_3000::sptr atr = perif.atr;
+ atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF);
+ atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly);
+ atr->set_atr_reg(ATR_REG_TX_ONLY, txonly);
+ atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd);
}
if (_radio_perifs.size() > _fe2 and _radio_perifs[_fe2].atr)
{
@@ -1207,11 +1189,11 @@ void b200_impl::update_atrs(void)
if (enb_rx and enb_tx) fd = STATE_FDX2_TXRX;
if (enb_rx and not enb_tx) fd = rxonly;
if (not enb_rx and enb_tx) fd = txonly;
- gpio_core_200_32wo::sptr atr = perif.atr;
- atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF);
- atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly);
- atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly);
- atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd);
+ gpio_atr_3000::sptr atr = perif.atr;
+ atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF);
+ atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly);
+ atr->set_atr_reg(ATR_REG_TX_ONLY, txonly);
+ atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd);
}
}
diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp
index 6c36e883d..3ca76fce6 100644
--- a/host/lib/usrp/b200/b200_impl.hpp
+++ b/host/lib/usrp/b200/b200_impl.hpp
@@ -27,8 +27,8 @@
#include "rx_vita_core_3000.hpp"
#include "tx_vita_core_3000.hpp"
#include "time_core_3000.hpp"
-#include "gpio_core_200.hpp"
-#include "radio_ctrl_core_3000.hpp"
+#include "gpio_atr_3000.hpp"
+#include "b200_radio_ctrl_core.hpp"
#include "rx_dsp_core_3000.hpp"
#include "tx_dsp_core_3000.hpp"
#include <uhd/device.hpp>
@@ -49,8 +49,8 @@
#include "recv_packet_demuxer_3000.hpp"
static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 8;
static const boost::uint8_t B200_FW_COMPAT_NUM_MINOR = 0;
-static const boost::uint16_t B200_FPGA_COMPAT_NUM = 13;
-static const boost::uint16_t B205_FPGA_COMPAT_NUM = 4;
+static const boost::uint16_t B200_FPGA_COMPAT_NUM = 14;
+static const boost::uint16_t B205_FPGA_COMPAT_NUM = 5;
static const double B200_BUS_CLOCK_RATE = 100e6;
static const boost::uint32_t B200_GPSDO_ST_NONE = 0x83;
static const size_t B200_MAX_RATE_USB2 = 53248000; // bytes/s
@@ -131,7 +131,7 @@ private:
//controllers
b200_iface::sptr _iface;
- radio_ctrl_core_3000::sptr _local_ctrl;
+ b200_radio_ctrl_core::sptr _local_ctrl;
uhd::usrp::ad9361_ctrl::sptr _codec_ctrl;
uhd::usrp::ad936x_manager::sptr _codec_mgr;
b200_local_spi_core::sptr _spi_iface;
@@ -154,8 +154,8 @@ private:
struct AsyncTaskData
{
boost::shared_ptr<async_md_type> async_md;
- boost::weak_ptr<radio_ctrl_core_3000> local_ctrl;
- boost::weak_ptr<radio_ctrl_core_3000> radio_ctrl[2];
+ boost::weak_ptr<b200_radio_ctrl_core> local_ctrl;
+ boost::weak_ptr<b200_radio_ctrl_core> radio_ctrl[2];
b200_uart::sptr gpsdo_uart;
};
boost::shared_ptr<AsyncTaskData> _async_task_data;
@@ -179,9 +179,9 @@ private:
//perifs in the radio core
struct radio_perifs_t
{
- radio_ctrl_core_3000::sptr ctrl;
- gpio_core_200_32wo::sptr atr;
- gpio_core_200::sptr fp_gpio;
+ b200_radio_ctrl_core::sptr ctrl;
+ uhd::usrp::gpio_atr::gpio_atr_3000::sptr atr;
+ uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio;
time_core_3000::sptr time64;
rx_vita_core_3000::sptr framer;
rx_dsp_core_3000::sptr ddc;
@@ -224,14 +224,10 @@ private:
enum time_source_t {GPSDO=0,EXTERNAL=1,INTERNAL=2,NONE=3,UNKNOWN=4} _time_source;
void update_gpio_state(void);
- void reset_codec_dcm(void);
void update_enables(void);
void update_atrs(void);
- boost::uint32_t get_fp_gpio(gpio_core_200::sptr);
- void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t);
-
double _tick_rate;
double get_tick_rate(void){return _tick_rate;}
double set_tick_rate(const double rate);
diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp
index d0d769504..d186ec907 100644
--- a/host/lib/usrp/b200/b200_io_impl.cpp
+++ b/host/lib/usrp/b200/b200_io_impl.cpp
@@ -158,7 +158,6 @@ void b200_impl::update_tick_rate(const double new_tick_rate)
boost::shared_ptr<sph::send_packet_streamer> my_streamer =
boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock());
if (my_streamer) my_streamer->set_tick_rate(new_tick_rate);
- perif.deframer->set_tick_rate(new_tick_rate);
}
}
@@ -319,7 +318,7 @@ boost::optional<uhd::msg_task::msg_type_t> b200_impl::handle_async_task(
case B200_RESP1_MSG_SID:
case B200_LOCAL_RESP_SID:
{
- radio_ctrl_core_3000::sptr ctrl;
+ b200_radio_ctrl_core::sptr ctrl;
if (sid == B200_RESP0_MSG_SID) ctrl = data->radio_ctrl[0].lock();
if (sid == B200_RESP1_MSG_SID) ctrl = data->radio_ctrl[1].lock();
if (sid == B200_LOCAL_RESP_SID) ctrl = data->local_ctrl.lock();
diff --git a/host/lib/usrp/b200/b200_radio_ctrl_core.cpp b/host/lib/usrp/b200/b200_radio_ctrl_core.cpp
new file mode 100644
index 000000000..b6d8f95df
--- /dev/null
+++ b/host/lib/usrp/b200/b200_radio_ctrl_core.cpp
@@ -0,0 +1,347 @@
+//
+// Copyright 2012-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 "b200_radio_ctrl_core.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/transport/vrt_if_packet.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::usrp;
+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;
+
+b200_radio_ctrl_core::~b200_radio_ctrl_core(void){
+ /* NOP */
+}
+
+class b200_radio_ctrl_core_impl: public b200_radio_ctrl_core
+{
+public:
+
+ b200_radio_ctrl_core_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() : 15)
+ {
+ 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
+ }
+
+ ~b200_radio_ctrl_core_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(SR_READBACK, 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(SR_READBACK, 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::if_hdr_unpack_be(pkt, packet_info);
+ else vrt::if_hdr_unpack_le(pkt, packet_info);
+ }
+ catch(const std::exception &ex)
+ {
+ UHD_MSG(error) << "Radio ctrl bad VITA packet: " << ex.what() << std::endl;
+ if (buff){
+ UHD_VAR(buff->size());
+ }
+ else{
+ UHD_MSG(status) << "buff is NULL" << std::endl;
+ }
+ UHD_MSG(status) << std::hex << pkt[0] << std::dec << std::endl;
+ UHD_MSG(status) << std::hex << pkt[1] << std::dec << std::endl;
+ UHD_MSG(status) << std::hex << pkt[2] << std::dec << std::endl;
+ UHD_MSG(status) << std::hex << pkt[3] << std::dec << std::endl;
+ }
+
+ //check the buffer
+ try
+ {
+ UHD_ASSERT_THROW(packet_info.has_sid);
+ UHD_ASSERT_THROW(packet_info.sid == boost::uint32_t((_sid >> 16) | (_sid << 16)));
+ UHD_ASSERT_THROW(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 radio_ctrl_cores 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;
+};
+
+b200_radio_ctrl_core::sptr b200_radio_ctrl_core::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 b200_radio_ctrl_core_impl(big_endian, ctrl_xport, resp_xport,
+ sid, name));
+}
diff --git a/host/lib/usrp/b200/b200_radio_ctrl_core.hpp b/host/lib/usrp/b200/b200_radio_ctrl_core.hpp
new file mode 100644
index 000000000..51f7e3301
--- /dev/null
+++ b/host/lib/usrp/b200/b200_radio_ctrl_core.hpp
@@ -0,0 +1,64 @@
+//
+// Copyright 2012-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_USRP_B200_RADIO_CTRL_HPP
+#define INCLUDED_LIBUHD_USRP_B200_RADIO_CTRL_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>
+
+/*!
+ * Provide access to peek, poke for the radio ctrl module
+ */
+class b200_radio_ctrl_core : public uhd::timed_wb_iface
+{
+public:
+ typedef boost::shared_ptr<b200_radio_ctrl_core> sptr;
+
+ virtual ~b200_radio_ctrl_core(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;
+};
+
+#endif /* INCLUDED_LIBUHD_USRP_B200_RADIO_CTRL_HPP */
diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt
index 9dabc4e0b..9f4cf09b5 100644
--- a/host/lib/usrp/common/CMakeLists.txt
+++ b/host/lib/usrp/common/CMakeLists.txt
@@ -29,7 +29,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver")
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/adf5355.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp
@@ -37,4 +38,5 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp
)
diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp
index 54f0fcdbf..654311424 100644
--- a/host/lib/usrp/common/ad9361_ctrl.cpp
+++ b/host/lib/usrp/common/ad9361_ctrl.cpp
@@ -93,18 +93,30 @@ class ad9361_ctrl_impl : public ad9361_ctrl
{
public:
ad9361_ctrl_impl(ad9361_params::sptr client_settings, ad9361_io::sptr io_iface):
- _device(client_settings, io_iface)
+ _device(client_settings, io_iface), _safe_spi(io_iface), _timed_spi(io_iface)
{
_device.initialize();
}
+ void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num)
+ {
+ _timed_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num);
+ _use_timed_spi();
+ }
+
+ void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num)
+ {
+ _safe_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num);
+ }
+
double set_gain(const std::string &which, const double value)
{
boost::lock_guard<boost::mutex> lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
- return _device.set_gain(direction, chain, value);
+ double return_val = _device.set_gain(direction, chain, value);
+ return return_val;
}
void set_agc(const std::string &which, bool enable)
@@ -112,7 +124,7 @@ public:
boost::lock_guard<boost::mutex> lock(_mutex);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
- _device.set_agc(chain, enable);
+ _device.set_agc(chain, enable);
}
void set_agc_mode(const std::string &which, const std::string &mode)
@@ -133,6 +145,9 @@ public:
{
boost::lock_guard<boost::mutex> lock(_mutex);
+ // Changing clock rate will disrupt AD9361's sample clock
+ _use_safe_spi();
+
//clip to known bounds
const meta_range_t clock_rate_range = ad9361_ctrl::get_clock_rate_range();
const double clipped_rate = clock_rate_range.clip(rate);
@@ -144,7 +159,11 @@ public:
) % (rate/1e6) % (clipped_rate/1e6) << std::endl;
}
- return _device.set_clock_rate(clipped_rate);
+ double return_rate = _device.set_clock_rate(clipped_rate);
+
+ _use_timed_spi();
+
+ return return_rate;
}
//! set which RX and TX chains/antennas are active
@@ -152,7 +171,11 @@ public:
{
boost::lock_guard<boost::mutex> lock(_mutex);
+ // If both RX chains are disabled then the AD9361's sample clock is disabled
+ _use_safe_spi();
_device.set_active_chains(tx1, tx2, rx1, rx2);
+ _use_timed_spi();
+
}
//! tune the given frontend, return the exact value
@@ -166,7 +189,8 @@ public:
const double value = ad9361_ctrl::get_rf_freq_range().clip(clipped_freq);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
- return _device.tune(direction, value);
+ double return_val = _device.tune(direction, value);
+ return return_val;
}
//! get the current frequency for the given frontend
@@ -283,16 +307,28 @@ private:
return ad9361_device_t::CHAIN_1;
}
- ad9361_device_t _device;
- boost::mutex _mutex;
+ void _use_safe_spi() {
+ _device.set_io_iface(_safe_spi);
+ }
+
+ void _use_timed_spi() {
+ _device.set_io_iface(_timed_spi);
+ }
+
+ ad9361_device_t _device;
+ ad9361_io::sptr _safe_spi; // SPI core that uses an always available clock
+ ad9361_io::sptr _timed_spi; // SPI core that has a dependency on the AD9361's sample clock (i.e. radio clk)
+ boost::mutex _mutex;
};
//----------------------------------------------------------------------
// Make an instance of the AD9361 Control interface
//----------------------------------------------------------------------
ad9361_ctrl::sptr ad9361_ctrl::make_spi(
- ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num)
-{
+ ad9361_params::sptr client_settings,
+ uhd::spi_iface::sptr spi_iface,
+ boost::uint32_t slave_num
+) {
boost::shared_ptr<ad9361_io_spi> spi_io_iface = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num);
return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface));
}
diff --git a/host/lib/usrp/common/ad9361_ctrl.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp
index 5c438ee9c..5770c3ec4 100644
--- a/host/lib/usrp/common/ad9361_ctrl.hpp
+++ b/host/lib/usrp/common/ad9361_ctrl.hpp
@@ -56,7 +56,13 @@ public:
//! make a new codec control object
static sptr make_spi(
- ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num);
+ ad9361_params::sptr client_settings,
+ uhd::spi_iface::sptr spi_iface,
+ boost::uint32_t slave_num
+ );
+
+ virtual void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0;
+ virtual void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0;
//! Get a list of gain names for RX or TX
static std::vector<std::string> get_gain_names(const std::string &/*which*/)
@@ -89,8 +95,10 @@ public:
//! get the clock rate range for the frontend
static uhd::meta_range_t get_clock_rate_range(void)
{
- //return uhd::meta_range_t(220e3, 61.44e6);
- return uhd::meta_range_t(5e6, ad9361_device_t::AD9361_MAX_CLOCK_RATE); //5 MHz DCM low end
+ return uhd::meta_range_t(
+ ad9361_device_t::AD9361_MIN_CLOCK_RATE,
+ ad9361_device_t::AD9361_MAX_CLOCK_RATE
+ );
}
//! set the filter bandwidth for the frontend's analog low pass
diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
index 0a8a61575..095017bb6 100644
--- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
+++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp
@@ -91,6 +91,7 @@ int get_num_taps(int max_num_taps) {
}
const double ad9361_device_t::AD9361_MAX_GAIN = 89.75;
+const double ad9361_device_t::AD9361_MIN_CLOCK_RATE = 220e3;
const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6;
const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6;
// Max bandwdith is due to filter rolloff in analog filter stage
@@ -770,7 +771,7 @@ void ad9361_device_t::_calibrate_rf_dc_offset()
size_t count = 0;
_io_iface->poke8(0x016, 0x02);
while (_io_iface->peek8(0x016) & 0x02) {
- if (count > 100) {
+ if (count > 200) {
throw uhd::runtime_error("[ad9361_device_t] RF DC Offset Calibration Failure");
break;
}
@@ -821,7 +822,7 @@ void ad9361_device_t::_calibrate_rx_quadrature()
size_t count = 0;
_io_iface->poke8(0x016, 0x20);
while (_io_iface->peek8(0x016) & 0x20) {
- if (count > 100) {
+ if (count > 1000) {
throw uhd::runtime_error("[ad9361_device_t] Rx Quadrature Calibration Failure");
break;
}
@@ -1564,6 +1565,12 @@ void ad9361_device_t::initialize()
_io_iface->poke8(0x000, 0x00);
boost::this_thread::sleep(boost::posix_time::milliseconds(20));
+ /* Check device ID to make sure iface works */
+ boost::uint32_t device_id = (_io_iface->peek8(0x037) & 0x8);
+ if (device_id != 0x8) {
+ throw uhd::runtime_error(str(boost::format("[ad9361_device_t::initialize] Device ID readback failure. Expected: 0x8, Received: 0x%x") % device_id));
+ }
+
/* There is not a WAT big enough for this. */
_io_iface->poke8(0x3df, 0x01);
@@ -1773,6 +1780,10 @@ void ad9361_device_t::initialize()
_io_iface->poke8(0x014, 0x21);
}
+void ad9361_device_t::set_io_iface(ad9361_io::sptr io_iface)
+{
+ _io_iface = io_iface;
+}
/* This function sets the RX / TX rate between AD9361 and the FPGA, and
* thus determines the interpolation / decimation required in the FPGA to
diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h
index 66bc2e8b9..d0e8a7e39 100644
--- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h
+++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h
@@ -75,6 +75,9 @@ public:
/* Initialize the AD9361 codec. */
void initialize();
+ /* Set SPI interface */
+ void set_io_iface(ad9361_io::sptr io_iface);
+
/* This function sets the RX / TX rate between AD9361 and the FPGA, and
* thus determines the interpolation / decimation required in the FPGA to
* achieve the user's requested rate.
@@ -157,6 +160,7 @@ public:
//Constants
static const double AD9361_MAX_GAIN;
static const double AD9361_MAX_CLOCK_RATE;
+ static const double AD9361_MIN_CLOCK_RATE;
static const double AD9361_CAL_VALID_WINDOW;
static const double AD9361_RECOMMENDED_MAX_BANDWIDTH;
static const double DEFAULT_RX_FREQ;
diff --git a/host/lib/usrp/common/ad936x_manager.cpp b/host/lib/usrp/common/ad936x_manager.cpp
index de8c4c7ab..e7af411fa 100644
--- a/host/lib/usrp/common/ad936x_manager.cpp
+++ b/host/lib/usrp/common/ad936x_manager.cpp
@@ -93,14 +93,12 @@ class ad936x_manager_impl : public ad936x_manager
// worst case conditions to stress the interface.
//
void loopback_self_test(
- wb_iface::sptr iface,
- wb_iface::wb_addr_type codec_idle_addr,
- wb_iface::wb_addr_type codec_readback_addr
+ boost::function<void(uint32_t)> poker_functor,
+ boost::function<uint64_t()> peeker_functor
) {
// Put AD936x in loopback mode
_codec_ctrl->data_port_loopback(true);
UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush;
- UHD_ASSERT_THROW(bool(iface));
size_t hash = size_t(time(NULL));
// Allow some time for AD936x to enter loopback mode.
@@ -118,10 +116,10 @@ class ad936x_manager_impl : public ad936x_manager
const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0;
// Write test word to codec_idle idle register (on TX side)
- iface->poke32(codec_idle_addr, word32);
+ poker_functor(word32);
// Read back values - TX is lower 32-bits and RX is upper 32-bits
- const boost::uint64_t rb_word64 = iface->peek64(codec_readback_addr);
+ const boost::uint64_t rb_word64 = peeker_functor();
const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32);
const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff);
@@ -136,7 +134,7 @@ class ad936x_manager_impl : public ad936x_manager
UHD_MSG(status) << "pass" << std::endl;
// Zero out the idle data.
- iface->poke32(codec_idle_addr, 0);
+ poker_functor(0);
// Take AD936x out of loopback mode
_codec_ctrl->data_port_loopback(false);
@@ -211,11 +209,11 @@ class ad936x_manager_impl : public ad936x_manager
// Sensors
subtree->create<sensor_value_t>("sensors/temp")
- .publish(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl))
+ .set_publisher(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl))
;
if (dir == RX_DIRECTION) {
subtree->create<sensor_value_t>("sensors/rssi")
- .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key))
+ .set_publisher(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key))
;
}
@@ -226,7 +224,7 @@ class ad936x_manager_impl : public ad936x_manager
.set(ad9361_ctrl::get_gain_range(key));
subtree->create<double>(uhd::fs_path("gains") / name / "value")
.set(ad936x_manager::DEFAULT_GAIN)
- .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1))
+ .set_coercer(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1))
;
}
@@ -238,19 +236,19 @@ class ad936x_manager_impl : public ad936x_manager
// Analog Bandwidths
subtree->create<double>("bandwidth/value")
.set(ad936x_manager::DEFAULT_BANDWIDTH)
- .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1))
+ .set_coercer(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1))
;
subtree->create<meta_range_t>("bandwidth/range")
- .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key))
+ .set_publisher(boost::bind(&ad9361_ctrl::get_bw_filter_range, key))
;
// LO Tuning
subtree->create<meta_range_t>("freq/range")
- .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range))
+ .set_publisher(boost::bind(&ad9361_ctrl::get_rf_freq_range))
;
subtree->create<double>("freq/value")
- .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key))
- .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1))
+ .set_publisher(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key))
+ .set_coercer(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1))
;
// Frontend corrections
@@ -258,21 +256,21 @@ class ad936x_manager_impl : public ad936x_manager
{
subtree->create<bool>("dc_offset/enable" )
.set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET)
- .subscribe(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1))
+ .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1))
;
subtree->create<bool>("iq_balance/enable" )
.set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE)
- .subscribe(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1))
+ .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1))
;
// AGC setup
const std::list<std::string> mode_strings = boost::assign::list_of("slow")("fast");
subtree->create<bool>("gain/agc/enable")
.set(DEFAULT_AGC_ENABLE)
- .subscribe(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1))
+ .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1))
;
subtree->create<std::string>("gain/agc/mode/value")
- .subscribe(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front())
+ .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front())
;
subtree->create< std::list<std::string> >("gain/agc/mode/options")
.set(mode_strings)
@@ -282,8 +280,8 @@ class ad936x_manager_impl : public ad936x_manager
// Frontend filters
BOOST_FOREACH(const std::string &filter_name, _codec_ctrl->get_filter_names(key)) {
subtree->create<filter_info_base::sptr>(uhd::fs_path("filters") / filter_name / "value" )
- .publish(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name))
- .subscribe(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1));
+ .set_publisher(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name))
+ .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1));
}
}
diff --git a/host/lib/usrp/common/ad936x_manager.hpp b/host/lib/usrp/common/ad936x_manager.hpp
index 9b4a351c6..c456715e3 100644
--- a/host/lib/usrp/common/ad936x_manager.hpp
+++ b/host/lib/usrp/common/ad936x_manager.hpp
@@ -80,9 +80,8 @@ public:
* \throws a uhd::runtime_error if the loopback value didn't match.
*/
virtual void loopback_self_test(
- wb_iface::sptr iface,
- wb_iface::wb_addr_type codec_idle_addr,
- wb_iface::wb_addr_type codec_readback_addr
+ boost::function<void(uint32_t)> poker_functor,
+ boost::function<uint64_t()> peeker_functor
) = 0;
/*! Determine a tick rate that will work with a given sampling rate
diff --git a/host/lib/usrp/common/adf435x.cpp b/host/lib/usrp/common/adf435x.cpp
new file mode 100644
index 000000000..f1ba6ad05
--- /dev/null
+++ b/host/lib/usrp/common/adf435x.cpp
@@ -0,0 +1,34 @@
+//
+// Copyright 2013-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 "adf435x.hpp"
+
+using namespace uhd;
+
+adf435x_iface::~adf435x_iface()
+{
+}
+
+adf435x_iface::sptr adf435x_iface::make_adf4350(write_fn_t write)
+{
+ return sptr(new adf435x_impl<adf4350_regs_t>(write));
+}
+
+adf435x_iface::sptr adf435x_iface::make_adf4351(write_fn_t write)
+{
+ return sptr(new adf435x_impl<adf4351_regs_t>(write));
+}
diff --git a/host/lib/usrp/common/adf435x.hpp b/host/lib/usrp/common/adf435x.hpp
new file mode 100644
index 000000000..d08c6b9dd
--- /dev/null
+++ b/host/lib/usrp/common/adf435x.hpp
@@ -0,0 +1,364 @@
+//
+// 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_ADF435X_HPP
+#define INCLUDED_ADF435X_HPP
+
+#include <uhd/exception.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/utils/log.hpp>
+#include <boost/function.hpp>
+#include <boost/thread.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <vector>
+#include "adf4350_regs.hpp"
+#include "adf4351_regs.hpp"
+
+class adf435x_iface
+{
+public:
+ typedef boost::shared_ptr<adf435x_iface> sptr;
+ typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t;
+
+ static sptr make_adf4350(write_fn_t write);
+ static sptr make_adf4351(write_fn_t write);
+
+ virtual ~adf435x_iface() = 0;
+
+ enum output_t { RF_OUTPUT_A, RF_OUTPUT_B };
+
+ enum prescaler_t { PRESCALER_4_5, PRESCALER_8_9 };
+
+ enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED };
+
+ enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM };
+
+ enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD };
+
+ virtual void set_reference_freq(double fref) = 0;
+
+ virtual void set_prescaler(prescaler_t prescaler) = 0;
+
+ virtual void set_feedback_select(feedback_sel_t fb_sel) = 0;
+
+ virtual void set_output_power(output_power_t power) = 0;
+
+ virtual void set_output_enable(output_t output, bool enable) = 0;
+
+ virtual void set_muxout_mode(muxout_t mode) = 0;
+
+ virtual uhd::range_t get_int_range() = 0;
+
+ virtual double set_frequency(double target_freq, bool int_n_mode, bool flush = false) = 0;
+
+ virtual void commit(void) = 0;
+};
+
+template <typename adf435x_regs_t>
+class adf435x_impl : public adf435x_iface
+{
+public:
+ adf435x_impl(write_fn_t write_fn) :
+ _write_fn(write_fn),
+ _regs(),
+ _fb_after_divider(false),
+ _reference_freq(0.0),
+ _N_min(-1)
+ {}
+
+ virtual ~adf435x_impl() {};
+
+ void set_reference_freq(double fref)
+ {
+ _reference_freq = fref;
+ }
+
+ void set_feedback_select(feedback_sel_t fb_sel)
+ {
+ _fb_after_divider = (fb_sel == FB_SEL_DIVIDED);
+ }
+
+ void set_prescaler(prescaler_t prescaler)
+ {
+ if (prescaler == PRESCALER_8_9) {
+ _regs.prescaler = adf435x_regs_t::PRESCALER_8_9;
+ _N_min = 75;
+ } else {
+ _regs.prescaler = adf435x_regs_t::PRESCALER_4_5;
+ _N_min = 23;
+ }
+ }
+
+ void set_output_power(output_power_t power)
+ {
+ switch (power) {
+ case OUTPUT_POWER_M4DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M4DBM; break;
+ case OUTPUT_POWER_M1DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M1DBM; break;
+ case OUTPUT_POWER_2DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_2DBM; break;
+ case OUTPUT_POWER_5DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_5DBM; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ void set_output_enable(output_t output, bool enable)
+ {
+ switch (output) {
+ case RF_OUTPUT_A: _regs.rf_output_enable = enable ? adf435x_regs_t::RF_OUTPUT_ENABLE_ENABLED:
+ adf435x_regs_t::RF_OUTPUT_ENABLE_DISABLED;
+ break;
+ case RF_OUTPUT_B: _regs.aux_output_enable = enable ? adf435x_regs_t::AUX_OUTPUT_ENABLE_ENABLED:
+ adf435x_regs_t::AUX_OUTPUT_ENABLE_DISABLED;
+ break;
+ }
+ }
+
+ void set_muxout_mode(muxout_t mode)
+ {
+ switch (mode) {
+ case MUXOUT_3STATE: _regs.muxout = adf435x_regs_t::MUXOUT_3STATE; break;
+ case MUXOUT_DVDD: _regs.muxout = adf435x_regs_t::MUXOUT_DVDD; break;
+ case MUXOUT_DGND: _regs.muxout = adf435x_regs_t::MUXOUT_DGND; break;
+ case MUXOUT_RDIV: _regs.muxout = adf435x_regs_t::MUXOUT_RDIV; break;
+ case MUXOUT_NDIV: _regs.muxout = adf435x_regs_t::MUXOUT_NDIV; break;
+ case MUXOUT_ALD: _regs.muxout = adf435x_regs_t::MUXOUT_ANALOG_LD; break;
+ case MUXOUT_DLD: _regs.muxout = adf435x_regs_t::MUXOUT_DLD; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ uhd::range_t get_int_range()
+ {
+ if (_N_min < 0) throw uhd::runtime_error("set_prescaler must be called before get_int_range");
+ return uhd::range_t(_N_min, 4095);
+ }
+
+ double set_frequency(double target_freq, bool int_n_mode, bool flush = false)
+ {
+ static const double REF_DOUBLER_THRESH_FREQ = 12.5e6;
+ static const double PFD_FREQ_MAX = 25.0e6;
+ static const double BAND_SEL_FREQ_MAX = 100e3;
+ static const double VCO_FREQ_MIN = 2.2e9;
+ static const double VCO_FREQ_MAX = 4.4e9;
+
+ //Default invalid value for actual_freq
+ double actual_freq = 0;
+
+ uhd::range_t rf_divider_range = _get_rfdiv_range();
+ uhd::range_t int_range = get_int_range();
+
+ double pfd_freq = 0;
+ boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0;
+ boost::uint16_t RFdiv = static_cast<boost::uint16_t>(rf_divider_range.start());
+ bool D = false, T = false;
+
+ //Reference doubler for 50% duty cycle
+ D = (_reference_freq <= REF_DOUBLER_THRESH_FREQ);
+
+ //increase RF divider until acceptable VCO frequency
+ double vco_freq = target_freq;
+ while (vco_freq < VCO_FREQ_MIN && RFdiv < static_cast<boost::uint16_t>(rf_divider_range.stop())) {
+ vco_freq *= 2;
+ RFdiv *= 2;
+ }
+
+ /*
+ * The goal here is to loop though possible R dividers,
+ * band select clock dividers, N (int) dividers, and FRAC
+ * (frac) dividers.
+ *
+ * Calculate the N and F dividers for each set of values.
+ * The loop exits when it meets all of the constraints.
+ * The resulting loop values are loaded into the registers.
+ *
+ * from pg.21
+ *
+ * f_pfd = f_ref*(1+D)/(R*(1+T))
+ * f_vco = (N + (FRAC/MOD))*f_pfd
+ * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD
+ * f_actual = f_vco/RFdiv)
+ */
+ double feedback_freq = _fb_after_divider ? target_freq : vco_freq;
+
+ for(R = 1; R <= 1023; R+=1){
+ //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T)
+ pfd_freq = _reference_freq*(D?2:1)/(R*(T?2:1));
+
+ //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth)
+ if (pfd_freq > PFD_FREQ_MAX) continue;
+
+ //First, ignore fractional part of tuning
+ N = boost::uint16_t(std::floor(feedback_freq/pfd_freq));
+
+ //keep N > minimum int divider requirement
+ if (N < static_cast<boost::uint16_t>(int_range.start())) continue;
+
+ for(BS=1; BS <= 255; BS+=1){
+ //keep the band select frequency at or below band_sel_freq_max
+ //constraint on band select clock
+ if (pfd_freq/BS > BAND_SEL_FREQ_MAX) continue;
+ goto done_loop;
+ }
+ } done_loop:
+
+ //Fractional-N calculation
+ MOD = 4095; //max fractional accuracy
+ FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD));
+ if (int_n_mode) {
+ if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target
+ N++;
+ }
+ FRAC = 0;
+ }
+
+ //Reference divide-by-2 for 50% duty cycle
+ // if R even, move one divide by 2 to to regs.reference_divide_by_2
+ if(R % 2 == 0) {
+ T = true;
+ R /= 2;
+ }
+
+ //Typical phase resync time documented in data sheet pg.24
+ static const double PHASE_RESYNC_TIME = 400e-6;
+
+ //If feedback after divider, then compensation for the divider is pulled into the INT value
+ int rf_div_compensation = _fb_after_divider ? 1 : RFdiv;
+
+ //Compute the actual frequency in terms of _reference_freq, N, FRAC, MOD, D, R and T.
+ actual_freq = (
+ double((N + (double(FRAC)/double(MOD))) *
+ (_reference_freq*(D?2:1)/(R*(T?2:1))))
+ ) / rf_div_compensation;
+
+ _regs.frac_12_bit = FRAC;
+ _regs.int_16_bit = N;
+ _regs.mod_12_bit = MOD;
+ _regs.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD)));
+ _regs.feedback_select = _fb_after_divider ?
+ adf435x_regs_t::FEEDBACK_SELECT_DIVIDED :
+ adf435x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
+ _regs.clock_div_mode = _fb_after_divider ?
+ adf435x_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE :
+ adf435x_regs_t::CLOCK_DIV_MODE_FAST_LOCK;
+ _regs.r_counter_10_bit = R;
+ _regs.reference_divide_by_2 = T ?
+ adf435x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
+ adf435x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ _regs.reference_doubler = D ?
+ adf435x_regs_t::REFERENCE_DOUBLER_ENABLED :
+ adf435x_regs_t::REFERENCE_DOUBLER_DISABLED;
+ _regs.band_select_clock_div = boost::uint8_t(BS);
+ _regs.rf_divider_select = static_cast<typename adf435x_regs_t::rf_divider_select_t>(_get_rfdiv_setting(RFdiv));
+ _regs.ldf = int_n_mode ?
+ adf435x_regs_t::LDF_INT_N :
+ adf435x_regs_t::LDF_FRAC_N;
+
+ std::string tuning_str = (int_n_mode) ? "Integer-N" : "Fractional";
+ UHD_LOGV(often)
+ << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f"
+ ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl
+ << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f"
+ ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (_reference_freq/1e6) << std::endl
+ << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl
+ << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
+ ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl;
+
+ UHD_ASSERT_THROW((_regs.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
+ UHD_ASSERT_THROW((_regs.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
+ UHD_ASSERT_THROW((_regs.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
+ UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0);
+
+ UHD_ASSERT_THROW(vco_freq >= VCO_FREQ_MIN and vco_freq <= VCO_FREQ_MAX);
+ UHD_ASSERT_THROW(RFdiv >= static_cast<boost::uint16_t>(rf_divider_range.start()));
+ UHD_ASSERT_THROW(RFdiv <= static_cast<boost::uint16_t>(rf_divider_range.stop()));
+ UHD_ASSERT_THROW(_regs.int_16_bit >= static_cast<boost::uint16_t>(int_range.start()));
+ UHD_ASSERT_THROW(_regs.int_16_bit <= static_cast<boost::uint16_t>(int_range.stop()));
+
+ if (flush) commit();
+ return actual_freq;
+ }
+
+ void commit()
+ {
+ //reset counters
+ _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_ENABLED;
+ std::vector<boost::uint32_t> regs;
+ regs.push_back(_regs.get_reg(boost::uint32_t(2)));
+ _write_fn(regs);
+ _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_DISABLED;
+
+ //write the registers
+ //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
+ regs.clear();
+ for (int addr = 5; addr >= 0; addr--) {
+ regs.push_back(_regs.get_reg(boost::uint32_t(addr)));
+ }
+ _write_fn(regs);
+ }
+
+protected:
+ uhd::range_t _get_rfdiv_range();
+ int _get_rfdiv_setting(boost::uint16_t div);
+
+ write_fn_t _write_fn;
+ adf435x_regs_t _regs;
+ double _fb_after_divider;
+ double _reference_freq;
+ int _N_min;
+};
+
+template <>
+inline uhd::range_t adf435x_impl<adf4350_regs_t>::_get_rfdiv_range()
+{
+ return uhd::range_t(1, 16);
+}
+
+template <>
+inline uhd::range_t adf435x_impl<adf4351_regs_t>::_get_rfdiv_range()
+{
+ return uhd::range_t(1, 64);
+}
+
+template <>
+inline int adf435x_impl<adf4350_regs_t>::_get_rfdiv_setting(boost::uint16_t div)
+{
+ switch (div) {
+ case 1: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV1);
+ case 2: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV2);
+ case 4: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV4);
+ case 8: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV8);
+ case 16: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV16);
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+template <>
+inline int adf435x_impl<adf4351_regs_t>::_get_rfdiv_setting(boost::uint16_t div)
+{
+ switch (div) {
+ case 1: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV1);
+ case 2: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV2);
+ case 4: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV4);
+ case 8: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV8);
+ case 16: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV16);
+ case 32: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV32);
+ case 64: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV64);
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
+#endif // INCLUDED_ADF435X_HPP
diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp
deleted file mode 100644
index 474a1c932..000000000
--- a/host/lib/usrp/common/adf435x_common.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-// Copyright 2013-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 "adf435x_common.hpp"
-
-#include <boost/math/special_functions/round.hpp>
-#include <uhd/types/tune_request.hpp>
-#include <uhd/utils/log.hpp>
-#include <cmath>
-
-
-using namespace uhd;
-
-/***********************************************************************
- * ADF 4350/4351 Tuning Utility
- **********************************************************************/
-adf435x_tuning_settings tune_adf435x_synth(
- const double target_freq,
- const double ref_freq,
- const adf435x_tuning_constraints& constraints,
- double& actual_freq)
-{
- //Default invalid value for actual_freq
- actual_freq = 0;
-
- double pfd_freq = 0;
- boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0;
- boost::uint16_t RFdiv = static_cast<boost::uint16_t>(constraints.rf_divider_range.start());
- bool D = false, T = false;
-
- //Reference doubler for 50% duty cycle
- //If ref_freq < 12.5MHz enable the reference doubler
- D = (ref_freq <= constraints.ref_doubler_threshold);
-
- static const double MIN_VCO_FREQ = 2.2e9;
- static const double MAX_VCO_FREQ = 4.4e9;
-
- //increase RF divider until acceptable VCO frequency
- double vco_freq = target_freq;
- while (vco_freq < MIN_VCO_FREQ && RFdiv < static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())) {
- vco_freq *= 2;
- RFdiv *= 2;
- }
-
- /*
- * The goal here is to loop though possible R dividers,
- * band select clock dividers, N (int) dividers, and FRAC
- * (frac) dividers.
- *
- * Calculate the N and F dividers for each set of values.
- * The loop exits when it meets all of the constraints.
- * The resulting loop values are loaded into the registers.
- *
- * from pg.21
- *
- * f_pfd = f_ref*(1+D)/(R*(1+T))
- * f_vco = (N + (FRAC/MOD))*f_pfd
- * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD
- * f_actual = f_vco/RFdiv)
- */
- double feedback_freq = constraints.feedback_after_divider ? target_freq : vco_freq;
-
- for(R = 1; R <= 1023; R+=1){
- //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T)
- pfd_freq = ref_freq*(D?2:1)/(R*(T?2:1));
-
- //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth)
- if (pfd_freq > constraints.pfd_freq_max) continue;
-
- //First, ignore fractional part of tuning
- N = boost::uint16_t(std::floor(feedback_freq/pfd_freq));
-
- //keep N > minimum int divider requirement
- if (N < static_cast<boost::uint16_t>(constraints.int_range.start())) continue;
-
- for(BS=1; BS <= 255; BS+=1){
- //keep the band select frequency at or below band_sel_freq_max
- //constraint on band select clock
- if (pfd_freq/BS > constraints.band_sel_freq_max) continue;
- goto done_loop;
- }
- } done_loop:
-
- //Fractional-N calculation
- MOD = 4095; //max fractional accuracy
- FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD));
- if (constraints.force_frac0) {
- if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target
- N++;
- }
- FRAC = 0;
- }
-
- //Reference divide-by-2 for 50% duty cycle
- // if R even, move one divide by 2 to to regs.reference_divide_by_2
- if(R % 2 == 0) {
- T = true;
- R /= 2;
- }
-
- //Typical phase resync time documented in data sheet pg.24
- static const double PHASE_RESYNC_TIME = 400e-6;
-
- //If feedback after divider, then compensation for the divider is pulled into the INT value
- int rf_div_compensation = constraints.feedback_after_divider ? 1 : RFdiv;
-
- //Compute the actual frequency in terms of ref_freq, N, FRAC, MOD, D, R and T.
- actual_freq = (
- double((N + (double(FRAC)/double(MOD))) *
- (ref_freq*(D?2:1)/(R*(T?2:1))))
- ) / rf_div_compensation;
-
- //load the settings
- adf435x_tuning_settings settings;
- settings.frac_12_bit = FRAC;
- settings.int_16_bit = N;
- settings.mod_12_bit = MOD;
- settings.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD)));
- settings.r_counter_10_bit = R;
- settings.r_divide_by_2_en = T;
- settings.r_doubler_en = D;
- settings.band_select_clock_div = boost::uint8_t(BS);
- settings.rf_divider = RFdiv;
-
- std::string tuning_str = (constraints.force_frac0) ? "Integer-N" : "Fractional";
- UHD_LOGV(often)
- << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f"
- ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl
- << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f"
- ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << std::endl
- << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl
- << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d"
- ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl;
-
- UHD_ASSERT_THROW((settings.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
- UHD_ASSERT_THROW((settings.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
- UHD_ASSERT_THROW((settings.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0);
- UHD_ASSERT_THROW((settings.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0);
-
- UHD_ASSERT_THROW(vco_freq >= MIN_VCO_FREQ and vco_freq <= MAX_VCO_FREQ);
- UHD_ASSERT_THROW(settings.rf_divider >= static_cast<boost::uint16_t>(constraints.rf_divider_range.start()));
- UHD_ASSERT_THROW(settings.rf_divider <= static_cast<boost::uint16_t>(constraints.rf_divider_range.stop()));
- UHD_ASSERT_THROW(settings.int_16_bit >= static_cast<boost::uint16_t>(constraints.int_range.start()));
- UHD_ASSERT_THROW(settings.int_16_bit <= static_cast<boost::uint16_t>(constraints.int_range.stop()));
-
- return settings;
-}
diff --git a/host/lib/usrp/common/adf435x_common.hpp b/host/lib/usrp/common/adf435x_common.hpp
deleted file mode 100644
index 617b9d97f..000000000
--- a/host/lib/usrp/common/adf435x_common.hpp
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// 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_ADF435X_COMMON_HPP
-#define INCLUDED_ADF435X_COMMON_HPP
-
-#include <boost/cstdint.hpp>
-#include <uhd/property_tree.hpp>
-#include <uhd/types/ranges.hpp>
-
-//Common IO Pins
-#define ADF435X_CE (1 << 3)
-#define ADF435X_PDBRF (1 << 2)
-#define ADF435X_MUXOUT (1 << 1) // INPUT!!!
-#define LOCKDET_MASK (1 << 0) // INPUT!!!
-
-#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control
-#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control
-
-struct adf435x_tuning_constraints {
- bool force_frac0;
- bool feedback_after_divider;
- double ref_doubler_threshold;
- double pfd_freq_max;
- double band_sel_freq_max;
- uhd::range_t rf_divider_range;
- uhd::range_t int_range;
-};
-
-struct adf435x_tuning_settings {
- boost::uint16_t frac_12_bit;
- boost::uint16_t int_16_bit;
- boost::uint16_t mod_12_bit;
- boost::uint16_t r_counter_10_bit;
- bool r_doubler_en;
- bool r_divide_by_2_en;
- boost::uint16_t clock_divider_12_bit;
- boost::uint8_t band_select_clock_div;
- boost::uint16_t rf_divider;
-};
-
-adf435x_tuning_settings tune_adf435x_synth(
- const double target_freq,
- const double ref_freq,
- const adf435x_tuning_constraints& constraints,
- double& actual_freq
-);
-
-#endif /* INCLUDED_ADF435X_COMMON_HPP */
diff --git a/host/lib/usrp/common/adf5355.cpp b/host/lib/usrp/common/adf5355.cpp
new file mode 100644
index 000000000..bb0906724
--- /dev/null
+++ b/host/lib/usrp/common/adf5355.cpp
@@ -0,0 +1,376 @@
+//
+// Copyright 2013-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 "adf5355.hpp"
+#include "adf5355_regs.hpp"
+#include <uhd/utils/math.hpp>
+#include <boost/math/common_factor_rt.hpp> //gcd
+#include <boost/thread.hpp>
+
+using namespace uhd;
+
+template<typename data_t>
+data_t clamp(data_t val, data_t min, data_t max) {
+ return (val < min) ? min : ((val > max) ? max : val);
+}
+
+template<typename data_t>
+double todbl(data_t val) {
+ return static_cast<double>(val);
+}
+
+static const double ADF5355_DOUBLER_MAX_REF_FREQ = 60e6;
+static const double ADF5355_MAX_FREQ_PFD = 125e6;
+static const double ADF5355_PRESCALER_THRESH = 7e9;
+
+static const double ADF5355_MIN_VCO_FREQ = 3.4e9;
+static const double ADF5355_MAX_VCO_FREQ = 6.8e9;
+static const double ADF5355_MAX_OUT_FREQ = 6.8e9;
+static const double ADF5355_MIN_OUT_FREQ = (3.4e9 / 64);
+static const double ADF5355_MAX_OUTB_FREQ = (6.8e9 * 2);
+static const double ADF5355_MIN_OUTB_FREQ = (3.4e9 * 2);
+
+static const double ADF5355_PHASE_RESYNC_TIME = 400e-6;
+
+static const boost::uint32_t ADF5355_MOD1 = 16777216;
+static const boost::uint32_t ADF5355_MAX_MOD2 = 16384;
+static const boost::uint16_t ADF5355_MIN_INT_PRESCALER_89 = 75;
+
+class adf5355_impl : public adf5355_iface
+{
+public:
+ adf5355_impl(write_fn_t write_fn) :
+ _write_fn(write_fn),
+ _regs(),
+ _rewrite_regs(true),
+ _wait_time_us(0),
+ _ref_freq(0.0),
+ _pfd_freq(0.0),
+ _fb_after_divider(false)
+ {
+ _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED;
+ _regs.cp_three_state = adf5355_regs_t::CP_THREE_STATE_DISABLED;
+ _regs.power_down = adf5355_regs_t::POWER_DOWN_DISABLED;
+ _regs.pd_polarity = adf5355_regs_t::PD_POLARITY_POSITIVE;
+ _regs.mux_logic = adf5355_regs_t::MUX_LOGIC_3_3V;
+ _regs.ref_mode = adf5355_regs_t::REF_MODE_SINGLE;
+ _regs.muxout = adf5355_regs_t::MUXOUT_DLD;
+ _regs.double_buff_div = adf5355_regs_t::DOUBLE_BUFF_DIV_DISABLED;
+
+ _regs.rf_out_a_enabled = adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED;
+ _regs.rf_out_b_enabled = adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED;
+ _regs.mute_till_lock_detect = adf5355_regs_t::MUTE_TILL_LOCK_DETECT_MUTE_DISABLED;
+ _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N;
+ _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS;
+ _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024;
+ _regs.le_sync = adf5355_regs_t::LE_SYNC_LE_SYNCED_TO_REFIN;
+ _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED;
+ _regs.reference_divide_by_2 = adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ _regs.reference_doubler = adf5355_regs_t::REFERENCE_DOUBLER_DISABLED;
+
+ _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED;
+ _regs.prescaler = adf5355_regs_t::PRESCALER_4_5;
+ _regs.charge_pump_current = adf5355_regs_t::CHARGE_PUMP_CURRENT_0_94MA;
+
+ _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED;
+ _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED;
+ _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
+ _regs.output_power = adf5355_regs_t::OUTPUT_POWER_5DBM;
+ _regs.cp_bleed_current = 2;
+ _regs.r_counter_10_bit = 8;
+
+
+ _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024;
+ _regs.loss_of_lock_mode = adf5355_regs_t::LOSS_OF_LOCK_MODE_DISABLED;
+ _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS;
+ _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N;
+
+ _regs.vco_band_div = 3;
+ _regs.timeout = 11;
+ _regs.auto_level_timeout = 30;
+ _regs.synth_lock_timeout = 12;
+
+ _regs.adc_clock_divider = 16;
+ _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED;
+ _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED;
+
+ _regs.phase_resync_clk_div = 0;
+ }
+
+ ~adf5355_impl()
+ {
+ _regs.power_down = adf5355_regs_t::POWER_DOWN_ENABLED;
+ commit();
+ }
+
+ void set_feedback_select(feedback_sel_t fb_sel)
+ {
+ _fb_after_divider = (fb_sel == FB_SEL_DIVIDED);
+
+ if (_fb_after_divider) {
+ _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_DIVIDED;
+ } else {
+ _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
+ }
+ }
+
+ void set_reference_freq(double fref, bool force = false)
+ {
+ //Skip the body if the reference frequency does not change
+ if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force))
+ return;
+
+ _ref_freq = fref;
+
+ //-----------------------------------------------------------
+ //Set reference settings
+
+ //Reference doubler for 50% duty cycle
+ bool doubler_en = (_ref_freq <= ADF5355_DOUBLER_MAX_REF_FREQ);
+
+ /* Calculate and maximize PFD frequency */
+ // TODO Target PFD should be configurable
+ /* TwinRX requires PFD of 6.25 MHz or less */
+ const double TWINRX_PFD_FREQ = 6.25e6;
+ _pfd_freq = TWINRX_PFD_FREQ;
+
+ int ref_div_factor = 16;
+
+ //Reference divide-by-2 for 50% duty cycle
+ // if R even, move one divide by 2 to to regs.reference_divide_by_2
+ bool div2_en = (ref_div_factor % 2 == 0);
+ if (div2_en) {
+ ref_div_factor /= 2;
+ }
+
+ _regs.reference_divide_by_2 = div2_en ?
+ adf5355_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
+ adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
+ _regs.reference_doubler = doubler_en ?
+ adf5355_regs_t::REFERENCE_DOUBLER_ENABLED :
+ adf5355_regs_t::REFERENCE_DOUBLER_DISABLED;
+ _regs.r_counter_10_bit = ref_div_factor;
+ UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0);
+
+ //-----------------------------------------------------------
+ //Set timeouts (code from ADI driver)
+ _regs.timeout = clamp<boost::uint16_t>(
+ static_cast<boost::uint16_t>(ceil(_pfd_freq / (20e3 * 30))), 1, 1023);
+ UHD_ASSERT_THROW((_regs.timeout & ((boost::uint16_t)~0x3FF)) == 0);
+ _regs.synth_lock_timeout =
+ static_cast<boost::uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout)));
+ UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((boost::uint16_t)~0x1F)) == 0);
+ _regs.auto_level_timeout =
+ static_cast<boost::uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout)));
+
+ //-----------------------------------------------------------
+ //Set VCO band divider
+ _regs.vco_band_div =
+ static_cast<boost::uint8_t>(ceil(_pfd_freq / 2.4e6));
+
+ //-----------------------------------------------------------
+ //Set ADC delay (code from ADI driver)
+ _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED;
+ _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED;
+ _regs.adc_clock_divider = clamp<boost::uint8_t>(
+ static_cast<boost::uint8_t>(ceil(((_pfd_freq / 100e3) - 2) / 4)), 1, 255);
+ _wait_time_us = static_cast<boost::uint32_t>(
+ ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2))));
+
+ //-----------------------------------------------------------
+ //Phase resync
+ _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; // Disabled during development
+ _regs.phase_adjust = adf5355_regs_t::PHASE_ADJUST_DISABLED;
+ _regs.sd_load_reset = adf5355_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE;
+ _regs.phase_resync_clk_div = static_cast<boost::uint16_t>(
+ floor(ADF5355_PHASE_RESYNC_TIME * _pfd_freq));
+
+ _rewrite_regs = true;
+ }
+
+ void set_output_power(output_power_t power)
+ {
+ adf5355_regs_t::output_power_t setting;
+ switch (power) {
+ case OUTPUT_POWER_M4DBM: setting = adf5355_regs_t::OUTPUT_POWER_M4DBM; break;
+ case OUTPUT_POWER_M1DBM: setting = adf5355_regs_t::OUTPUT_POWER_M1DBM; break;
+ case OUTPUT_POWER_2DBM: setting = adf5355_regs_t::OUTPUT_POWER_2DBM; break;
+ case OUTPUT_POWER_5DBM: setting = adf5355_regs_t::OUTPUT_POWER_5DBM; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ if (_regs.output_power != setting) _rewrite_regs = true;
+ _regs.output_power = setting;
+ }
+
+ void set_output_enable(output_t output, bool enable) {
+
+ switch (output) {
+ case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED :
+ adf5355_regs_t::RF_OUT_A_ENABLED_DISABLED;
+ break;
+ case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf5355_regs_t::RF_OUT_B_ENABLED_ENABLED :
+ adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED;
+ break;
+ }
+ }
+
+ void set_muxout_mode(muxout_t mode)
+ {
+ switch (mode) {
+ case MUXOUT_3STATE: _regs.muxout = adf5355_regs_t::MUXOUT_3STATE; break;
+ case MUXOUT_DVDD: _regs.muxout = adf5355_regs_t::MUXOUT_DVDD; break;
+ case MUXOUT_DGND: _regs.muxout = adf5355_regs_t::MUXOUT_DGND; break;
+ case MUXOUT_RDIV: _regs.muxout = adf5355_regs_t::MUXOUT_RDIV; break;
+ case MUXOUT_NDIV: _regs.muxout = adf5355_regs_t::MUXOUT_NDIV; break;
+ case MUXOUT_ALD: _regs.muxout = adf5355_regs_t::MUXOUT_ANALOG_LD; break;
+ case MUXOUT_DLD: _regs.muxout = adf5355_regs_t::MUXOUT_DLD; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+ double set_frequency(double target_freq, double freq_resolution, bool flush = false)
+ {
+ if (target_freq > ADF5355_MAX_OUT_FREQ or target_freq < ADF5355_MIN_OUT_FREQ) {
+ throw uhd::runtime_error("requested frequency out of range.");
+ }
+ if ((boost::uint32_t) freq_resolution == 0) {
+ throw uhd::runtime_error("requested resolution cannot be less than 1.");
+ }
+
+ /* Calculate target VCOout frequency */
+ //Increase RF divider until acceptable VCO frequency
+ double target_vco_freq = target_freq;
+ boost::uint32_t rf_divider = 1;
+ while (target_vco_freq < ADF5355_MIN_VCO_FREQ && rf_divider < 64) {
+ target_vco_freq *= 2;
+ rf_divider *= 2;
+ }
+
+ switch (rf_divider) {
+ case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break;
+ case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break;
+ case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break;
+ case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break;
+ case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break;
+ case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break;
+ case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ //Compute fractional PLL params
+ double prescaler_input_freq = target_vco_freq;
+ if (_fb_after_divider) {
+ prescaler_input_freq /= rf_divider;
+ }
+
+ double N = prescaler_input_freq / _pfd_freq;
+ boost::uint16_t INT = static_cast<boost::uint16_t>(floor(N));
+ boost::uint32_t FRAC1 = static_cast<boost::uint32_t>(floor((N - INT) * ADF5355_MOD1));
+ double residue = ADF5355_MOD1 * (N - (INT + FRAC1 / ADF5355_MOD1));
+
+ double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution));
+ boost::uint16_t MOD2 = static_cast<boost::uint16_t>(floor(_pfd_freq / gcd));
+
+ if (MOD2 > ADF5355_MAX_MOD2) {
+ MOD2 = ADF5355_MAX_MOD2;
+ }
+ boost::uint16_t FRAC2 = ceil(residue * MOD2);
+
+ double coerced_vco_freq = _pfd_freq * (
+ todbl(INT) + (
+ (todbl(FRAC1) +
+ (todbl(FRAC2) / todbl(MOD2)))
+ / todbl(ADF5355_MOD1)
+ )
+ );
+
+ double coerced_out_freq = coerced_vco_freq / rf_divider;
+
+ /* Update registers */
+ _regs.int_16_bit = INT;
+ _regs.frac1_24_bit = FRAC1;
+ _regs.frac2_14_bit = FRAC2;
+ _regs.mod2_14_bit = MOD2;
+ _regs.phase_24_bit = 0;
+
+/*
+ if (_regs.int_16_bit >= ADF5355_MIN_INT_PRESCALER_89) {
+ _regs.prescaler = adf5355_regs_t::PRESCALER_8_9;
+ } else {
+ _regs.prescaler = adf5355_regs_t::PRESCALER_4_5;
+ }
+
+ // ADI: Tests have shown that the optimal bleed set is the following:
+ // 4/N < IBLEED/ICP < 10/N */
+/*
+ boost::uint32_t cp_curr_ua =
+ (static_cast<boost::uint32_t>(_regs.charge_pump_current) + 1) * 315;
+ _regs.cp_bleed_current = clamp<boost::uint8_t>(
+ ceil((todbl(400)*cp_curr_ua) / (_regs.int_16_bit*375)), 1, 255);
+ _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED;
+ _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED;
+*/
+
+ if (flush) commit();
+ return coerced_out_freq;
+ }
+
+ void commit()
+ {
+ if (_rewrite_regs) {
+ //For a full state sync write registers in reverse order 12 - 0
+ addr_vtr_t regs;
+ for (int addr = 12; addr >= 0; addr--) {
+ regs.push_back(_regs.get_reg(boost::uint32_t(addr)));
+ }
+ _write_fn(regs);
+ _rewrite_regs = false;
+
+ } else {
+ //Frequency update sequence from data sheet
+ static const size_t ONE_REG = 1;
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6)));
+ _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED;
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4)));
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2)));
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1)));
+ _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED;
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0)));
+ _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED;
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4)));
+ boost::this_thread::sleep(boost::posix_time::microsec(_wait_time_us));
+ _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED;
+ _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0)));
+ }
+ }
+
+private: //Members
+ typedef std::vector<boost::uint32_t> addr_vtr_t;
+
+ write_fn_t _write_fn;
+ adf5355_regs_t _regs;
+ bool _rewrite_regs;
+ boost::uint32_t _wait_time_us;
+ double _ref_freq;
+ double _pfd_freq;
+ double _fb_after_divider;
+};
+
+adf5355_iface::sptr adf5355_iface::make(write_fn_t write)
+{
+ return sptr(new adf5355_impl(write));
+}
diff --git a/host/lib/usrp/common/adf5355.hpp b/host/lib/usrp/common/adf5355.hpp
new file mode 100644
index 000000000..fb262cc9f
--- /dev/null
+++ b/host/lib/usrp/common/adf5355.hpp
@@ -0,0 +1,57 @@
+//
+// 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_ADF5355_HPP
+#define INCLUDED_ADF5355_HPP
+
+#include <boost/function.hpp>
+#include <vector>
+
+class adf5355_iface
+{
+public:
+ typedef boost::shared_ptr<adf5355_iface> sptr;
+ typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t;
+
+ static sptr make(write_fn_t write);
+
+ virtual ~adf5355_iface() {}
+
+ enum output_t { RF_OUTPUT_A, RF_OUTPUT_B };
+
+ enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED };
+
+ enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM };
+
+ enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD };
+
+ virtual void set_reference_freq(double fref, bool force = false) = 0;
+
+ virtual void set_feedback_select(feedback_sel_t fb_sel) = 0;
+
+ virtual void set_output_power(output_power_t power) = 0;
+
+ virtual void set_output_enable(output_t output, bool enable) = 0;
+
+ virtual void set_muxout_mode(muxout_t mode) = 0;
+
+ virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0;
+
+ virtual void commit(void) = 0;
+};
+
+#endif // INCLUDED_ADF5355_HPP
diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp
index 3d33e7d11..272e0e093 100644
--- a/host/lib/usrp/common/apply_corrections.cpp
+++ b/host/lib/usrp/common/apply_corrections.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011 Ettus Research LLC
+// Copyright 2011-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
@@ -144,6 +144,34 @@ static void apply_fe_corrections(
/***********************************************************************
* Wrapper routines with nice try/catch + print
**********************************************************************/
+void uhd::usrp::apply_tx_fe_corrections( //overloading to work according to rfnoc tree struct
+ property_tree::sptr sub_tree, //starts at mboards/x
+ const uhd::fs_path db_path,
+ const uhd::fs_path tx_fe_corr_path,
+ const double lo_freq //actual lo freq
+){
+ boost::mutex::scoped_lock l(corrections_mutex);
+ try{
+ apply_fe_corrections(
+ sub_tree,
+ db_path + "/tx_eeprom",
+ tx_fe_corr_path + "/iq_balance/value",
+ "tx_iq_cal_v0.2_",
+ lo_freq
+ );
+ apply_fe_corrections(
+ sub_tree,
+ db_path + "/tx_eeprom",
+ tx_fe_corr_path + "/dc_offset/value",
+ "tx_dc_cal_v0.2_",
+ lo_freq
+ );
+ }
+ catch(const std::exception &e){
+ UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl;
+ }
+}
+
void uhd::usrp::apply_tx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
@@ -171,6 +199,27 @@ void uhd::usrp::apply_tx_fe_corrections(
}
}
+void uhd::usrp::apply_rx_fe_corrections( //overloading to work according to rfnoc tree struct
+ property_tree::sptr sub_tree, //starts at mboards/x
+ const uhd::fs_path db_path,
+ const uhd::fs_path rx_fe_corr_path,
+ const double lo_freq //actual lo freq
+){
+ boost::mutex::scoped_lock l(corrections_mutex);
+ try{
+ apply_fe_corrections(
+ sub_tree,
+ db_path + "/rx_eeprom",
+ rx_fe_corr_path + "/iq_balance/value",
+ "rx_iq_cal_v0.2_",
+ lo_freq
+ );
+ }
+ catch(const std::exception &e){
+ UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl;
+ }
+}
+
void uhd::usrp::apply_rx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
diff --git a/host/lib/usrp/common/apply_corrections.hpp b/host/lib/usrp/common/apply_corrections.hpp
index c516862d1..0ab5377f3 100644
--- a/host/lib/usrp/common/apply_corrections.hpp
+++ b/host/lib/usrp/common/apply_corrections.hpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011 Ettus Research LLC
+// Copyright 2011-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
@@ -26,16 +26,28 @@ namespace uhd{ namespace usrp{
void apply_tx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
- const std::string &slot, //name of dboard slot
+ const fs_path db_path,
+ const fs_path tx_fe_corr_path,
const double tx_lo_freq //actual lo freq
);
+ void apply_tx_fe_corrections(
+ property_tree::sptr sub_tree, //starts at mboards/x
+ const std::string &slot, //name of dboard slot
+ const double tx_lo_freq //actual lo freq
+ );
void apply_rx_fe_corrections(
property_tree::sptr sub_tree, //starts at mboards/x
const std::string &slot, //name of dboard slot
const double rx_lo_freq //actual lo freq
);
+ void apply_rx_fe_corrections(
+ property_tree::sptr sub_tree, //starts at mboards/x
+ const fs_path db_path,
+ const fs_path rx_fe_corr_path,
+ const double rx_lo_freq //actual lo freq
+ );
}} //namespace uhd::usrp
#endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */
diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp
new file mode 100644
index 000000000..1bfd1df00
--- /dev/null
+++ b/host/lib/usrp/common/constrained_device_args.hpp
@@ -0,0 +1,283 @@
+//
+// 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_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP
+#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP
+
+#include <uhd/types/device_addr.hpp>
+#include <uhd/exception.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/assign/list_of.hpp>
+#include <vector>
+#include <string>
+
+namespace uhd {
+namespace usrp {
+
+ /*!
+ * constrained_device_args_t provides a base and utilities to
+ * map key=value pairs passed in through the device creation
+ * args interface (device_addr_t).
+ *
+ * Inherit from this class to create typed device specific
+ * arguments and use the base class methods to handle parsing
+ * the device_addr or any key=value string to populate the args
+ *
+ * This file contains a library of different types of args the
+ * the user can pass in. The library can be extended to support
+ * non-intrinsic types by the client.
+ *
+ */
+ class constrained_device_args_t {
+ public: //Types
+
+ /*!
+ * Base argument type. All other arguments inherit from this.
+ */
+ class generic_arg {
+ public:
+ generic_arg(const std::string& key): _key(key) {}
+ inline const std::string& key() const { return _key; }
+ inline virtual std::string to_string() const = 0;
+ private:
+ std::string _key;
+ };
+
+ /*!
+ * String argument type. Can be case sensitive or insensitive
+ */
+ template<bool case_sensitive>
+ class str_arg : public generic_arg {
+ public:
+ str_arg(const std::string& name, const std::string& default_value) :
+ generic_arg(name) { set(default_value); }
+
+ inline void set(const std::string& value) {
+ _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value);
+ }
+ inline const std::string& get() const {
+ return _value;
+ }
+ inline void parse(const std::string& str_rep) {
+ set(str_rep);
+ }
+ inline virtual std::string to_string() const {
+ return key() + "=" + get();
+ }
+ inline bool operator==(const std::string& rhs) const {
+ return get() == boost::algorithm::to_lower_copy(rhs);
+ }
+ private:
+ std::string _value;
+ };
+ typedef str_arg<false> str_ci_arg;
+ typedef str_arg<true> str_cs_arg;
+
+ /*!
+ * Numeric argument type. The template type data_t allows the
+ * client to constrain the type of the number.
+ */
+ template<typename data_t>
+ class num_arg : public generic_arg {
+ public:
+ num_arg(const std::string& name, const data_t default_value) :
+ generic_arg(name) { set(default_value); }
+
+ inline void set(const data_t value) {
+ _value = value;
+ }
+ inline const data_t get() const {
+ return _value;
+ }
+ inline void parse(const std::string& str_rep) {
+ try {
+ _value = boost::lexical_cast<data_t>(str_rep);
+ } catch (std::exception& ex) {
+ throw uhd::value_error(str(boost::format(
+ "Error parsing numeric parameter %s: %s.") %
+ key() % ex.what()
+ ));
+ }
+ }
+ inline virtual std::string to_string() const {
+ return key() + "=" + boost::lexical_cast<std::string>(get());
+ }
+ private:
+ data_t _value;
+ };
+
+ /*!
+ * Enumeration argument type. The template type enum_t allows the
+ * client to use their own enum and specify a string mapping for
+ * the values of the enum
+ *
+ * NOTE: The constraint on enum_t is that the values must start with
+ * 0 and be sequential
+ */
+ template<typename enum_t>
+ class enum_arg : public generic_arg {
+ public:
+ enum_arg(
+ const std::string& name,
+ const enum_t default_value,
+ const std::vector<std::string>& values) :
+ generic_arg(name), _str_values(values)
+ { set(default_value); }
+
+ inline void set(const enum_t value) {
+ _value = value;
+ }
+ inline const enum_t get() const {
+ return _value;
+ }
+ inline void parse(const std::string& str_rep, bool assert_invalid = true) {
+ std::string valid_values_str;
+ for (size_t i = 0; i < _str_values.size(); i++) {
+ if (boost::algorithm::to_lower_copy(str_rep) ==
+ boost::algorithm::to_lower_copy(_str_values[i]))
+ {
+ valid_values_str += ((i==0)?"":", ") + _str_values[i];
+ set(static_cast<enum_t>(static_cast<int>(i)));
+ return;
+ }
+ }
+ //If we reach here then, the string enum value was invalid
+ if (assert_invalid) {
+ throw uhd::value_error(str(boost::format(
+ "Invalid device arg value: %s=%s (Valid: {%s})") %
+ key() % str_rep % valid_values_str
+ ));
+ }
+ }
+ inline virtual std::string to_string() const {
+ size_t index = static_cast<size_t>(static_cast<int>(_value));
+ UHD_ASSERT_THROW(index < _str_values.size());
+ return key() + "=" + _str_values[index];
+ }
+
+ private:
+ enum_t _value;
+ std::vector<std::string> _str_values;
+ };
+
+ /*!
+ * Boolean argument type.
+ */
+ class bool_arg : public generic_arg {
+ public:
+ bool_arg(const std::string& name, const bool default_value) :
+ generic_arg(name) { set(default_value); }
+
+ inline void set(const bool value) {
+ _value = value;
+ }
+ inline bool get() const {
+ return _value;
+ }
+ inline void parse(const std::string& str_rep) {
+ try {
+ _value = (boost::lexical_cast<int>(str_rep) != 0);
+ } catch (std::exception& ex) {
+ if (str_rep.empty()) {
+ //If str_rep is empty then the device_addr was set
+ //without a value which means that the user "set" the flag
+ _value = true;
+ } else if (boost::algorithm::to_lower_copy(str_rep) == "true" ||
+ boost::algorithm::to_lower_copy(str_rep) == "yes" ||
+ boost::algorithm::to_lower_copy(str_rep) == "y") {
+ _value = true;
+ } else if (boost::algorithm::to_lower_copy(str_rep) == "false" ||
+ boost::algorithm::to_lower_copy(str_rep) == "no" ||
+ boost::algorithm::to_lower_copy(str_rep) == "n") {
+ _value = false;
+ } else {
+ throw uhd::value_error(str(boost::format(
+ "Error parsing boolean parameter %s: %s.") %
+ key() % ex.what()
+ ));
+ }
+ }
+ }
+ inline virtual std::string to_string() const {
+ return key() + "=" + (get() ? "true" : "false");
+ }
+ private:
+ bool _value;
+ };
+
+ public: //Methods
+ constrained_device_args_t() {}
+ virtual ~constrained_device_args_t() {}
+
+ void parse(const std::string& str_args) {
+ device_addr_t dev_args(str_args);
+ _parse(dev_args);
+ }
+
+ void parse(const device_addr_t& dev_args) {
+ _parse(dev_args);
+ }
+
+ inline virtual std::string to_string() const = 0;
+
+ protected: //Methods
+ //Override _parse to provide an implementation to parse all
+ //client specific device args
+ virtual void _parse(const device_addr_t& dev_args) = 0;
+
+ /*!
+ * Utility: Ensure that the value of the device arg is between min and max
+ */
+ template<typename num_data_t>
+ static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) {
+ if (arg.get() > max || arg.get() < min) {
+ throw uhd::value_error(str(boost::format(
+ "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") %
+ arg.to_string() %
+ boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max)));
+ }
+ }
+
+ /*!
+ * Utility: Ensure that the value of the device arg is is contained in valid_values
+ */
+ template<typename arg_t, typename data_t>
+ static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) {
+ bool match = false;
+ BOOST_FOREACH(const data_t& val, valid_values) {
+ if (val == arg.get()) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ std::string valid_values_str;
+ for (size_t i = 0; i < valid_values.size(); i++) {
+ valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]);
+ throw uhd::value_error(str(boost::format(
+ "Invalid device arg value: %s (Valid: {%s})") %
+ arg.to_string() % valid_values_str
+ ));
+ }
+ }
+ }
+ };
+}} //namespaces
+
+#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */
diff --git a/host/lib/usrp/common/fw_comm_protocol.h b/host/lib/usrp/common/fw_comm_protocol.h
new file mode 100644
index 000000000..14adb33a9
--- /dev/null
+++ b/host/lib/usrp/common/fw_comm_protocol.h
@@ -0,0 +1,102 @@
+//
+// 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_FW_COMM_PROTOCOL
+#define INCLUDED_FW_COMM_PROTOCOL
+
+#include <stdint.h>
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+/*!
+ * Structs and constants for communication between firmware and host.
+ * This header is shared by the firmware and host code.
+ * Therefore, this header may only contain valid C code.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define FW_COMM_PROTOCOL_SIGNATURE 0xACE3
+#define FW_COMM_PROTOCOL_VERSION 0
+#define FW_COMM_MAX_DATA_WORDS 16
+#define FW_COMM_PROTOCOL_MTU 256
+
+#define FW_COMM_FLAGS_ACK 0x00000001
+#define FW_COMM_FLAGS_CMD_MASK 0x00000FF0
+#define FW_COMM_FLAGS_ERROR_MASK 0xFF000000
+
+#define FW_COMM_CMD_ECHO 0x00000000
+#define FW_COMM_CMD_POKE32 0x00000010
+#define FW_COMM_CMD_PEEK32 0x00000020
+#define FW_COMM_CMD_BLOCK_POKE32 0x00000030
+#define FW_COMM_CMD_BLOCK_PEEK32 0x00000040
+
+#define FW_COMM_ERR_PKT_ERROR 0x80000000
+#define FW_COMM_ERR_CMD_ERROR 0x40000000
+#define FW_COMM_ERR_SIZE_ERROR 0x20000000
+
+#define FW_COMM_GENERATE_ID(prod) ((((uint32_t) FW_COMM_PROTOCOL_SIGNATURE) << 0) | \
+ (((uint32_t) prod) << 16) | \
+ (((uint32_t) FW_COMM_PROTOCOL_VERSION) << 24))
+
+#define FW_COMM_GET_PROTOCOL_SIG(id) ((uint16_t)(id & 0xFFFF))
+#define FW_COMM_GET_PRODUCT_ID(id) ((uint8_t)(id >> 16))
+#define FW_COMM_GET_PROTOCOL_VER(id) ((uint8_t)(id >> 24))
+
+typedef struct
+{
+ uint32_t id; //Protocol and device identifier
+ uint32_t flags; //Holds commands and ack messages
+ uint32_t sequence; //Sequence number (specific to FW communication transactions)
+ uint32_t data_words; //Number of data words in payload
+ uint32_t addr; //Address field for the command in flags
+ uint32_t data[FW_COMM_MAX_DATA_WORDS]; //Data field for the command in flags
+} fw_comm_pkt_t;
+
+#ifdef __cplusplus
+} //extern "C"
+#endif
+
+// The following definitions are only useful in firmware. Exclude in host code.
+#ifndef __cplusplus
+
+typedef void (*poke32_func)(const uint32_t addr, const uint32_t data);
+typedef uint32_t (*peek32_func)(const uint32_t addr);
+
+/*!
+ * Process a firmware communication packet and compute a response.
+ * Args:
+ * - (in) request: Pointer to the request struct
+ * - (out) response: Pointer to the response struct
+ * - (in) product_id: The 8-bit usrp3 specific product ID (for request filtering)
+ * - (func) poke_callback, peek_callback: Callback functions for a single peek/poke
+ * - return value: Send a response packet
+ */
+bool process_fw_comm_protocol_pkt(
+ const fw_comm_pkt_t* request,
+ fw_comm_pkt_t* response,
+ uint8_t product_id,
+ uint32_t iface_id,
+ poke32_func poke_callback,
+ peek32_func peek_callback
+);
+
+#endif //ifdef __cplusplus
+
+#endif /* INCLUDED_FW_COMM_PROTOCOL */
diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp
new file mode 100644
index 000000000..ef541e37f
--- /dev/null
+++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp
@@ -0,0 +1,246 @@
+//
+// Copyright 2013 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "usrp3_fw_ctrl_iface.hpp"
+
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/exception.hpp>
+#include <boost/format.hpp>
+#include <boost/asio.hpp> //used for htonl and ntohl
+#include <boost/foreach.hpp>
+#include "fw_comm_protocol.h"
+
+namespace uhd { namespace usrp { namespace usrp3 {
+
+//----------------------------------------------------------
+// Factory method
+//----------------------------------------------------------
+uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make(
+ uhd::transport::udp_simple::sptr udp_xport,
+ const boost::uint16_t product_id,
+ const bool verbose)
+{
+ return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id, verbose));
+}
+
+//----------------------------------------------------------
+// udp_fw_ctrl_iface
+//----------------------------------------------------------
+
+usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface(
+ uhd::transport::udp_simple::sptr udp_xport,
+ const boost::uint16_t product_id,
+ const bool verbose) :
+ _product_id(product_id), _verbose(verbose), _udp_xport(udp_xport),
+ _seq_num(0)
+{
+ flush();
+ peek32(0);
+}
+
+usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface()
+{
+ flush();
+}
+
+void usrp3_fw_ctrl_iface::flush()
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ _flush();
+}
+
+void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+
+ for (size_t i = 1; i <= NUM_RETRIES; i++) {
+ try {
+ _poke32(addr, data);
+ return;
+ } catch(const std::exception &ex) {
+ const std::string error_msg = str(boost::format(
+ "udp fw poke32 failure #%u\n%s") % i % ex.what());
+ if (_verbose) UHD_MSG(warning) << error_msg << std::endl;
+ if (i == NUM_RETRIES) throw uhd::io_error(error_msg);
+ }
+ }
+}
+
+boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+
+ for (size_t i = 1; i <= NUM_RETRIES; i++) {
+ try {
+ return _peek32(addr);
+ } catch(const std::exception &ex) {
+ const std::string error_msg = str(boost::format(
+ "udp fw peek32 failure #%u\n%s") % i % ex.what());
+ if (_verbose) UHD_MSG(warning) << error_msg << std::endl;
+ if (i == NUM_RETRIES) throw uhd::io_error(error_msg);
+ }
+ }
+ return 0;
+}
+
+void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data)
+{
+ //Load request struct
+ fw_comm_pkt_t request;
+ request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id));
+ request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32);
+ request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++);
+ request.addr = uhd::htonx(addr);
+ request.data_words = 1;
+ request.data[0] = uhd::htonx(data);
+
+ //Send request
+ _flush();
+ _udp_xport->send(boost::asio::buffer(&request, sizeof(request)));
+
+ //Recv reply
+ fw_comm_pkt_t reply;
+ const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0);
+ if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out");
+
+ //Sanity checks
+ const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags);
+ UHD_ASSERT_THROW(nbytes == sizeof(reply));
+ UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK));
+ UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32);
+ UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK);
+ UHD_ASSERT_THROW(reply.sequence == request.sequence);
+ UHD_ASSERT_THROW(reply.addr == request.addr);
+ UHD_ASSERT_THROW(reply.data[0] == request.data[0]);
+}
+
+boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr)
+{
+ //Load request struct
+ fw_comm_pkt_t request;
+ request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id));
+ request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32);
+ request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++);
+ request.addr = uhd::htonx(addr);
+ request.data_words = 1;
+ request.data[0] = 0;
+
+ //Send request
+ _flush();
+ _udp_xport->send(boost::asio::buffer(&request, sizeof(request)));
+
+ //Recv reply
+ fw_comm_pkt_t reply;
+ const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0);
+ if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out");
+
+ //Sanity checks
+ const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags);
+ UHD_ASSERT_THROW(nbytes == sizeof(reply));
+ UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK));
+ UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32);
+ UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK);
+ UHD_ASSERT_THROW(reply.sequence == request.sequence);
+ UHD_ASSERT_THROW(reply.addr == request.addr);
+
+ //return result!
+ return uhd::ntohx<boost::uint32_t>(reply.data[0]);
+}
+
+void usrp3_fw_ctrl_iface::_flush(void)
+{
+ char buff[FW_COMM_PROTOCOL_MTU] = {};
+ while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) {
+ /*NOP*/
+ }
+}
+
+std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices(
+ const std::string& addr_hint, const std::string& port,
+ boost::uint16_t product_id)
+{
+ std::vector<std::string> addrs;
+
+ //Create a UDP transport to communicate:
+ //Some devices will cause a throw when opened for a broadcast address.
+ //We print and recover so the caller can loop through all bcast addrs.
+ uhd::transport::udp_simple::sptr udp_bcast_xport;
+ try {
+ udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port);
+ } catch(const std::exception &e) {
+ UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s")
+ % addr_hint % e.what() << std::endl;
+ return addrs;
+ }
+
+ //Send dummy request
+ fw_comm_pkt_t request;
+ request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id));
+ request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO);
+ request.sequence = uhd::htonx<boost::uint32_t>(std::rand());
+ udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request)));
+
+ //loop for replies until timeout
+ while (true) {
+ char buff[FW_COMM_PROTOCOL_MTU] = {};
+ const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050);
+ if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid
+
+ const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff;
+ if (request.id == reply->id &&
+ request.flags == reply->flags &&
+ request.sequence == reply->sequence)
+ {
+ addrs.push_back(udp_bcast_xport->get_recv_addr());
+ }
+ }
+
+ return addrs;
+}
+
+boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id(
+ const std::string& addr, const std::string& port,
+ boost::uint16_t product_id)
+{
+ uhd::transport::udp_simple::sptr udp_xport =
+ uhd::transport::udp_simple::make_connected(addr, port);
+
+ //Send dummy request
+ fw_comm_pkt_t request;
+ request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id));
+ request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO);
+ request.sequence = uhd::htonx<boost::uint32_t>(std::rand());
+ udp_xport->send(boost::asio::buffer(&request, sizeof(request)));
+
+ //loop for replies until timeout
+ char buff[FW_COMM_PROTOCOL_MTU] = {};
+ const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0);
+
+ const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff;
+ if (nbytes > 0 &&
+ request.id == reply->id &&
+ request.flags == reply->flags &&
+ request.sequence == reply->sequence)
+ {
+ return uhd::ntohx<boost::uint32_t>(reply->data[0]);
+ } else {
+ throw uhd::io_error("udp get_iface_id - bad response");
+ }
+}
+
+}}} //namespace
diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp
new file mode 100644
index 000000000..33286861b
--- /dev/null
+++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp
@@ -0,0 +1,72 @@
+//
+// 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_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP
+#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP
+
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/transport/udp_simple.hpp>
+#include <boost/thread/mutex.hpp>
+#include <vector>
+
+namespace uhd { namespace usrp { namespace usrp3 {
+
+class usrp3_fw_ctrl_iface : public uhd::wb_iface
+{
+public:
+ usrp3_fw_ctrl_iface(
+ uhd::transport::udp_simple::sptr udp_xport,
+ const boost::uint16_t product_id,
+ const bool verbose);
+ virtual ~usrp3_fw_ctrl_iface();
+
+ // -- uhd::wb_iface --
+ void poke32(const wb_addr_type addr, const boost::uint32_t data);
+ boost::uint32_t peek32(const wb_addr_type addr);
+ void flush();
+
+ static uhd::wb_iface::sptr make(
+ uhd::transport::udp_simple::sptr udp_xport,
+ const boost::uint16_t product_id,
+ const bool verbose = true);
+ // -- uhd::wb_iface --
+
+ static std::vector<std::string> discover_devices(
+ const std::string& addr_hint, const std::string& port,
+ boost::uint16_t product_id);
+
+ static boost::uint32_t get_iface_id(
+ const std::string& addr, const std::string& port,
+ boost::uint16_t product_id);
+
+private:
+ void _poke32(const wb_addr_type addr, const boost::uint32_t data);
+ boost::uint32_t _peek32(const wb_addr_type addr);
+ void _flush(void);
+
+ const boost::uint16_t _product_id;
+ const bool _verbose;
+ uhd::transport::udp_simple::sptr _udp_xport;
+ boost::uint32_t _seq_num;
+ boost::mutex _mutex;
+
+ static const size_t NUM_RETRIES = 3;
+};
+
+}}} //namespace
+
+#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP
diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt
index f28ae040f..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
@@ -37,7 +38,11 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/time_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spi_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100_wb32.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/dsp_core_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_3000.cpp
${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_core_3000.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/gpio_atr_3000.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_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
new file mode 100644
index 000000000..5df28f7c2
--- /dev/null
+++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp
@@ -0,0 +1,401 @@
+//
+// 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 "dma_fifo_core_3000.hpp"
+#include <uhd/exception.hpp>
+#include <boost/thread/thread.hpp> //sleep
+#include <uhd/utils/soft_register.hpp>
+#include <uhd/utils/msg.hpp>
+
+using namespace uhd;
+
+#define SR_DRAM_BIST_BASE 16
+
+dma_fifo_core_3000::~dma_fifo_core_3000(void) {
+ /* NOP */
+}
+
+class dma_fifo_core_3000_impl : public dma_fifo_core_3000
+{
+protected:
+ class rb_addr_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ADDR, /*width*/ 3, /*shift*/ 0); //[2:0]
+
+ static const boost::uint32_t RB_FIFO_STATUS = 0;
+ static const boost::uint32_t RB_BIST_STATUS = 1;
+ static const boost::uint32_t RB_BIST_XFER_CNT = 2;
+ static const boost::uint32_t RB_BIST_CYC_CNT = 3;
+
+ rb_addr_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 0)
+ {
+ //Initial values
+ set(ADDR, RB_FIFO_STATUS);
+ }
+ };
+
+ class fifo_ctrl_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(CLEAR_FIFO, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_EN, /*width*/ 1, /*shift*/ 1); //[1]
+ UHD_DEFINE_SOFT_REG_FIELD(BURST_TIMEOUT, /*width*/ 12, /*shift*/ 4); //[15:4]
+ UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_THRESH, /*width*/ 16, /*shift*/ 16); //[31:16]
+
+ fifo_ctrl_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 4)
+ {
+ //Initial values
+ set(CLEAR_FIFO, 1);
+ set(RD_SUPPRESS_EN, 0);
+ set(BURST_TIMEOUT, 256);
+ set(RD_SUPPRESS_THRESH, 0);
+ }
+ };
+
+ class base_addr_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(BASE_ADDR, /*width*/ 30, /*shift*/ 0); //[29:0]
+
+ base_addr_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 8)
+ {
+ //Initial values
+ set(BASE_ADDR, 0x00000000);
+ }
+ };
+
+ class addr_mask_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ADDR_MASK, /*width*/ 30, /*shift*/ 0); //[29:0]
+
+ addr_mask_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 12)
+ {
+ //Initial values
+ set(ADDR_MASK, 0xFF000000);
+ }
+ };
+
+ class bist_ctrl_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(GO, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(CONTINUOUS_MODE, /*width*/ 1, /*shift*/ 1); //[1]
+ UHD_DEFINE_SOFT_REG_FIELD(TEST_PATT, /*width*/ 2, /*shift*/ 4); //[5:4]
+
+ static const boost::uint32_t TEST_PATT_ZERO_ONE = 0;
+ static const boost::uint32_t TEST_PATT_CHECKERBOARD = 1;
+ static const boost::uint32_t TEST_PATT_COUNT = 2;
+ static const boost::uint32_t TEST_PATT_COUNT_INV = 3;
+
+ bist_ctrl_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 16)
+ {
+ //Initial values
+ set(GO, 0);
+ set(CONTINUOUS_MODE, 0);
+ set(TEST_PATT, TEST_PATT_ZERO_ONE);
+ }
+ };
+
+ class bist_cfg_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(MAX_PKTS, /*width*/ 18, /*shift*/ 0); //[17:0]
+ UHD_DEFINE_SOFT_REG_FIELD(MAX_PKT_SIZE, /*width*/ 13, /*shift*/ 18); //[30:18]
+ UHD_DEFINE_SOFT_REG_FIELD(PKT_SIZE_RAMP, /*width*/ 1, /*shift*/ 31); //[31]
+
+ bist_cfg_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 20)
+ {
+ //Initial values
+ set(MAX_PKTS, 0);
+ set(MAX_PKT_SIZE, 0);
+ set(PKT_SIZE_RAMP, 0);
+ }
+ };
+
+ class bist_delay_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(TX_PKT_DELAY, /*width*/ 16, /*shift*/ 0); //[15:0]
+ UHD_DEFINE_SOFT_REG_FIELD(RX_SAMP_DELAY, /*width*/ 8, /*shift*/ 16); //[23:16]
+
+ bist_delay_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 24)
+ {
+ //Initial values
+ set(TX_PKT_DELAY, 0);
+ set(RX_SAMP_DELAY, 0);
+ }
+ };
+
+ class bist_sid_reg_t : public soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SID, /*width*/ 32, /*shift*/ 0); //[31:0]
+
+ bist_sid_reg_t(boost::uint32_t base):
+ soft_reg32_wo_t(base + 28)
+ {
+ //Initial values
+ set(SID, 0);
+ }
+ };
+
+public:
+ class fifo_readback {
+ public:
+ fifo_readback(wb_iface::sptr iface, const size_t base, const size_t rb_addr) :
+ _iface(iface), _addr_reg(base), _rb_addr(rb_addr)
+ {
+ _addr_reg.initialize(*iface, true);
+ }
+
+ bool is_fifo_instantiated() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS);
+ return _iface->peek32(_rb_addr) & 0x80000000;
+ }
+
+ boost::uint32_t get_occupied_cnt() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS);
+ return _iface->peek32(_rb_addr) & 0x7FFFFFF;
+ }
+
+ boost::uint32_t is_fifo_busy() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS);
+ return _iface->peek32(_rb_addr) & 0x40000000;
+ }
+
+ struct bist_status_t {
+ bool running;
+ bool finished;
+ boost::uint8_t error;
+ };
+
+ bist_status_t get_bist_status() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS);
+ boost::uint32_t st32 = _iface->peek32(_rb_addr) & 0xF;
+ bist_status_t status;
+ status.running = st32 & 0x1;
+ status.finished = st32 & 0x2;
+ status.error = static_cast<boost::uint8_t>((st32>>2) & 0x3);
+ return status;
+ }
+
+ bool is_ext_bist_supported() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS);
+ return _iface->peek32(_rb_addr) & 0x80000000;
+ }
+
+ double get_xfer_ratio() {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ boost::uint32_t xfer_cnt = 0, cyc_cnt = 0;
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_XFER_CNT);
+ xfer_cnt = _iface->peek32(_rb_addr);
+ _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_CYC_CNT);
+ cyc_cnt = _iface->peek32(_rb_addr);
+ return (static_cast<double>(xfer_cnt)/cyc_cnt);
+ }
+
+ private:
+ wb_iface::sptr _iface;
+ rb_addr_reg_t _addr_reg;
+ const size_t _rb_addr;
+ boost::mutex _mutex;
+ };
+
+public:
+ dma_fifo_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback):
+ _iface(iface), _base(base), _fifo_readback(iface, base, readback),
+ _fifo_ctrl_reg(base), _base_addr_reg(base), _addr_mask_reg(base),
+ _bist_ctrl_reg(base), _bist_cfg_reg(base), _bist_delay_reg(base), _bist_sid_reg(base)
+ {
+ _fifo_ctrl_reg.initialize(*iface, true);
+ _base_addr_reg.initialize(*iface, true);
+ _addr_mask_reg.initialize(*iface, true);
+ _bist_ctrl_reg.initialize(*iface, true);
+ _bist_cfg_reg.initialize(*iface, true);
+ _has_ext_bist = _fifo_readback.is_ext_bist_supported();
+ if (_has_ext_bist) {
+ _bist_delay_reg.initialize(*iface, true);
+ _bist_sid_reg.initialize(*iface, true);
+ }
+ 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);
+ //Re-arm the FIFO
+ _wait_for_fifo_empty();
+ _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 0);
+ }
+
+ virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) {
+ //Validate parameters
+ if (size < 8192) throw uhd::runtime_error("DMA FIFO must be larger than 8KiB");
+ boost::uint32_t size_mask = size - 1;
+ if (size & size_mask) throw uhd::runtime_error("DMA FIFO size must be a power of 2");
+
+ //Clear the FIFO and hold it in that state
+ _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1);
+ //Write base address and mask
+ _base_addr_reg.write(base_addr_reg_t::BASE_ADDR, base_addr);
+ _addr_mask_reg.write(addr_mask_reg_t::ADDR_MASK, ~size_mask);
+
+ //Re-arm the FIFO
+ flush();
+ }
+
+ virtual boost::uint32_t get_bytes_occupied() {
+ return _fifo_readback.get_occupied_cnt() * 8;
+ }
+
+ virtual bool ext_bist_supported() {
+ return _fifo_readback.is_ext_bist_supported();
+ }
+
+ virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) {
+ return run_ext_bist(finite, 0, 0, 0, timeout_ms);
+ }
+
+ virtual boost::uint8_t run_ext_bist(
+ bool finite,
+ boost::uint32_t rx_samp_delay,
+ boost::uint32_t tx_pkt_delay,
+ boost::uint32_t sid,
+ boost::uint32_t timeout_ms = 500
+ ) {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ _wait_for_bist_done(timeout_ms, true); //Stop previous BIST and wait (if running)
+ _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); //Reset
+
+ _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKTS, (2^18)-1);
+ _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKT_SIZE, 8000);
+ _bist_cfg_reg.set(bist_cfg_reg_t::PKT_SIZE_RAMP, 0);
+ _bist_cfg_reg.flush();
+
+ if (_has_ext_bist) {
+ _bist_delay_reg.set(bist_delay_reg_t::RX_SAMP_DELAY, rx_samp_delay);
+ _bist_delay_reg.set(bist_delay_reg_t::TX_PKT_DELAY, tx_pkt_delay);
+ _bist_delay_reg.flush();
+
+ _bist_sid_reg.write(bist_sid_reg_t::SID, sid);
+ } else {
+ if (rx_samp_delay != 0 || tx_pkt_delay != 0 || sid != 0) {
+ throw uhd::not_implemented_error(
+ "dma_fifo_core_3000: Runtime delay and SID support only available on FPGA images with extended BIST enabled");
+ }
+ }
+
+ _bist_ctrl_reg.set(bist_ctrl_reg_t::TEST_PATT, bist_ctrl_reg_t::TEST_PATT_COUNT);
+ _bist_ctrl_reg.set(bist_ctrl_reg_t::CONTINUOUS_MODE, finite ? 0 : 1);
+ _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 1);
+
+ if (!finite) {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(timeout_ms));
+ }
+
+ _wait_for_bist_done(timeout_ms, !finite);
+ if (!_fifo_readback.get_bist_status().finished) {
+ throw uhd::runtime_error("dma_fifo_core_3000: DRAM BIST state machine is in a bad state.");
+ }
+
+ return _fifo_readback.get_bist_status().error;
+ }
+
+ virtual double get_bist_throughput(double fifo_clock_rate) {
+ if (_has_ext_bist) {
+ _wait_for_bist_done(1000);
+ static const double BYTES_PER_CYC = 8;
+ return _fifo_readback.get_xfer_ratio() * fifo_clock_rate * BYTES_PER_CYC;
+ } else {
+ throw uhd::not_implemented_error(
+ "dma_fifo_core_3000: Throughput counter only available on FPGA images with extended BIST enabled");
+ }
+ }
+
+private:
+ void _wait_for_fifo_empty()
+ {
+ boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time();
+ boost::posix_time::time_duration elapsed;
+
+ while (_fifo_readback.is_fifo_busy()) {
+ boost::this_thread::sleep(boost::posix_time::microsec(1000));
+ elapsed = boost::posix_time::microsec_clock::local_time() - start_time;
+ if (elapsed.total_milliseconds() > 100) break;
+ }
+ }
+
+ void _wait_for_bist_done(boost::uint32_t timeout_ms, bool force_stop = false)
+ {
+ boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time();
+ boost::posix_time::time_duration elapsed;
+
+ while (_fifo_readback.get_bist_status().running) {
+ if (force_stop) {
+ _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0);
+ force_stop = false;
+ }
+ boost::this_thread::sleep(boost::posix_time::microsec(1000));
+ elapsed = boost::posix_time::microsec_clock::local_time() - start_time;
+ if (elapsed.total_milliseconds() > timeout_ms) break;
+ }
+ }
+
+private:
+ wb_iface::sptr _iface;
+ const size_t _base;
+ boost::mutex _mutex;
+ bool _has_ext_bist;
+
+ fifo_readback _fifo_readback;
+ fifo_ctrl_reg_t _fifo_ctrl_reg;
+ base_addr_reg_t _base_addr_reg;
+ addr_mask_reg_t _addr_mask_reg;
+ bist_ctrl_reg_t _bist_ctrl_reg;
+ bist_cfg_reg_t _bist_cfg_reg;
+ bist_delay_reg_t _bist_delay_reg;
+ bist_sid_reg_t _bist_sid_reg;
+};
+
+//
+// Static make function
+//
+dma_fifo_core_3000::sptr dma_fifo_core_3000::make(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr)
+{
+ if (check(iface, set_base, rb_addr)) {
+ return sptr(new dma_fifo_core_3000_impl(iface, set_base, rb_addr));
+ } else {
+ throw uhd::runtime_error("");
+ }
+}
+
+bool dma_fifo_core_3000::check(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr)
+{
+ dma_fifo_core_3000_impl::fifo_readback fifo_rb(iface, set_base, rb_addr);
+ return fifo_rb.is_fifo_instantiated();
+}
diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.hpp b/host/lib/usrp/cores/dma_fifo_core_3000.hpp
new file mode 100644
index 000000000..41430e5c3
--- /dev/null
+++ b/host/lib/usrp/cores/dma_fifo_core_3000.hpp
@@ -0,0 +1,86 @@
+//
+// 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_USRP_DMA_FIFO_CORE_3000_HPP
+#define INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP
+
+#include <uhd/config.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+#include <uhd/types/wb_iface.hpp>
+
+
+class dma_fifo_core_3000 : boost::noncopyable
+{
+public:
+ typedef boost::shared_ptr<dma_fifo_core_3000> sptr;
+ virtual ~dma_fifo_core_3000(void) = 0;
+
+ /*!
+ * Create a DMA FIFO controller using the given bus, settings and readback base
+ * Throws uhd::runtime_error if a DMA FIFO is not instantiated in the FPGA
+ */
+ static sptr make(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr);
+
+ /*!
+ * Check if a DMA FIFO is instantiated in the FPGA
+ */
+ static bool check(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr);
+
+ /*!
+ * Flush the DMA FIFO. Will clear all contents.
+ */
+ virtual void flush() = 0;
+
+ /*!
+ * Resize and rebase the DMA FIFO. Will clear all contents.
+ */
+ virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) = 0;
+
+ /*!
+ * Get the (approx) number of bytes currently in the DMA FIFO
+ */
+ virtual boost::uint32_t get_bytes_occupied() = 0;
+
+ /*!
+ * Run the built-in-self-test routine for the DMA FIFO
+ */
+ virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) = 0;
+
+ /*!
+ * Is extended BIST supported
+ */
+ virtual bool ext_bist_supported() = 0;
+
+ /*!
+ * Run the built-in-self-test routine for the DMA FIFO (extended BIST only)
+ */
+ virtual boost::uint8_t run_ext_bist(
+ bool finite,
+ boost::uint32_t rx_samp_delay,
+ boost::uint32_t tx_pkt_delay,
+ boost::uint32_t sid,
+ boost::uint32_t timeout_ms = 500) = 0;
+
+ /*!
+ * Get the throughput measured from the last invocation of the BIST (extended BIST only)
+ */
+ virtual double get_bist_throughput(double fifo_clock_rate) = 0;
+
+};
+
+#endif /* INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP */
diff --git a/host/lib/usrp/cores/dsp_core_utils.cpp b/host/lib/usrp/cores/dsp_core_utils.cpp
new file mode 100644
index 000000000..aea809ae8
--- /dev/null
+++ b/host/lib/usrp/cores/dsp_core_utils.cpp
@@ -0,0 +1,66 @@
+//
+// 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 "dsp_core_utils.hpp"
+#include <uhd/utils/math.hpp>
+#include <uhd/exception.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <boost/math/special_functions/sign.hpp>
+
+static const int32_t MAX_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::highest();
+static const int32_t MIN_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::lowest();
+
+void get_freq_and_freq_word(
+ const double requested_freq,
+ const double tick_rate,
+ double &actual_freq,
+ int32_t &freq_word
+) {
+ //correct for outside of rate (wrap around)
+ double freq = std::fmod(requested_freq, tick_rate);
+ if (std::abs(freq) > tick_rate/2.0)
+ freq -= boost::math::sign(freq) * tick_rate;
+
+ //confirm that the target frequency is within range of the CORDIC
+ UHD_ASSERT_THROW(std::abs(freq) <= tick_rate/2.0);
+
+ /* Now calculate the frequency word. It is possible for this calculation
+ * to cause an overflow. As the requested DSP frequency approaches the
+ * master clock rate, that ratio multiplied by the scaling factor (2^32)
+ * will generally overflow within the last few kHz of tunable range.
+ * Thus, we check to see if the operation will overflow before doing it,
+ * and if it will, we set it to the integer min or max of this system.
+ */
+ freq_word = 0;
+
+ static const double scale_factor = std::pow(2.0, 32);
+ if ((freq / tick_rate) >= (MAX_FREQ_WORD / scale_factor)) {
+ /* Operation would have caused a positive overflow of int32. */
+ freq_word = MAX_FREQ_WORD;
+
+ } else if ((freq / tick_rate) <= (MIN_FREQ_WORD / scale_factor)) {
+ /* Operation would have caused a negative overflow of int32. */
+ freq_word = MIN_FREQ_WORD;
+
+ } else {
+ /* The operation is safe. Perform normally. */
+ freq_word = int32_t(boost::math::round((freq / tick_rate) * scale_factor));
+ }
+
+ actual_freq = (double(freq_word) / scale_factor) * tick_rate;
+}
+
diff --git a/host/lib/usrp/cores/dsp_core_utils.hpp b/host/lib/usrp/cores/dsp_core_utils.hpp
new file mode 100644
index 000000000..d5d43f236
--- /dev/null
+++ b/host/lib/usrp/cores/dsp_core_utils.hpp
@@ -0,0 +1,33 @@
+//
+// 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_DSP_CORE_UTILS_HPP
+#define INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP
+
+#include <stdint.h>
+
+/*! For a requested frequency and sampling rate, return the
+ * correct frequency word (to set the CORDIC) and the actual frequency.
+ */
+void get_freq_and_freq_word(
+ const double requested_freq,
+ const double tick_rate,
+ double &actual_freq,
+ int32_t &freq_word
+);
+
+#endif /* INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP */
diff --git a/host/lib/usrp/cores/gpio_atr_3000.cpp b/host/lib/usrp/cores/gpio_atr_3000.cpp
new file mode 100644
index 000000000..5844af601
--- /dev/null
+++ b/host/lib/usrp/cores/gpio_atr_3000.cpp
@@ -0,0 +1,341 @@
+//
+// Copyright 2011,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 "gpio_atr_3000.hpp"
+#include <uhd/types/dict.hpp>
+#include <uhd/utils/soft_register.hpp>
+
+using namespace uhd;
+using namespace usrp;
+
+//-------------------------------------------------------------
+// gpio_atr_3000
+//-------------------------------------------------------------
+
+#define REG_ATR_IDLE_OFFSET (base + 0)
+#define REG_ATR_RX_OFFSET (base + 4)
+#define REG_ATR_TX_OFFSET (base + 8)
+#define REG_ATR_FDX_OFFSET (base + 12)
+#define REG_DDR_OFFSET (base + 16)
+#define REG_ATR_DISABLE_OFFSET (base + 20)
+
+namespace uhd { namespace usrp { namespace gpio_atr {
+
+class gpio_atr_3000_impl : public gpio_atr_3000{
+public:
+ gpio_atr_3000_impl(
+ wb_iface::sptr iface,
+ const wb_iface::wb_addr_type base,
+ const wb_iface::wb_addr_type rb_addr = READBACK_DISABLED
+ ):
+ _iface(iface), _rb_addr(rb_addr),
+ _atr_idle_reg(REG_ATR_IDLE_OFFSET, _atr_disable_reg),
+ _atr_rx_reg(REG_ATR_RX_OFFSET),
+ _atr_tx_reg(REG_ATR_TX_OFFSET),
+ _atr_fdx_reg(REG_ATR_FDX_OFFSET),
+ _ddr_reg(REG_DDR_OFFSET),
+ _atr_disable_reg(REG_ATR_DISABLE_OFFSET)
+ {
+ _atr_idle_reg.initialize(*_iface, true);
+ _atr_rx_reg.initialize(*_iface, true);
+ _atr_tx_reg.initialize(*_iface, true);
+ _atr_fdx_reg.initialize(*_iface, true);
+ _ddr_reg.initialize(*_iface, true);
+ _atr_disable_reg.initialize(*_iface, true);
+ }
+
+ virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask)
+ {
+ //Each bit in the "ATR Disable" register determines whether the respective bit in the GPIO
+ //output bus is driven by the ATR engine or a static register.
+ //For each bit position, a 1 means that the bit is static and 0 means that the bit
+ //is driven by the ATR state machine.
+ //This setting will only get applied to all bits in the "mask" that are 1. All other
+ //bits will retain their old value.
+ _atr_disable_reg.set_with_mask((mode==MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL, mask);
+ _atr_disable_reg.flush();
+ }
+
+ virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask)
+ {
+ //Each bit in the "DDR" register determines whether the respective bit in the GPIO
+ //bus is an input or an output.
+ //For each bit position, a 1 means that the bit is an output and 0 means that the bit
+ //is an input.
+ //This setting will only get applied to all bits in the "mask" that are 1. All other
+ //bits will retain their old value.
+ _ddr_reg.set_with_mask((dir==DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL, mask);
+ _ddr_reg.flush();
+ }
+
+ virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL)
+ {
+ //Set the value of the specified ATR register. For bits with ATR Disable set to 1,
+ //the IDLE register will hold the output state
+ //This setting will only get applied to all bits in the "mask" that are 1. All other
+ //bits will retain their old value.
+ masked_reg_t* reg = NULL;
+ switch (atr) {
+ case ATR_REG_IDLE: reg = &_atr_idle_reg; break;
+ case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break;
+ case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break;
+ case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break;
+ default: reg = &_atr_idle_reg; break;
+ }
+ //For protection we only write to bits that have the mode ATR by masking the user
+ //specified "mask" with ~atr_disable.
+ reg->set_with_mask(value, mask);
+ reg->flush();
+ }
+
+ virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) {
+ //Set the value of the specified GPIO output register.
+ //This setting will only get applied to all bits in the "mask" that are 1. All other
+ //bits will retain their old value.
+
+ //For protection we only write to bits that have the mode GPIO by masking the user
+ //specified "mask" with atr_disable.
+ _atr_idle_reg.set_gpio_out_with_mask(value, mask);
+ _atr_idle_reg.flush();
+ }
+
+ virtual boost::uint32_t read_gpio()
+ {
+ //Read the state of the GPIO pins
+ //If a pin is configured as an input, reads the actual value of the pin
+ //If a pin is configured as an output, reads the last value written to the pin
+ if (_rb_addr != READBACK_DISABLED) {
+ return _iface->peek32(_rb_addr);
+ } else {
+ throw uhd::runtime_error("read_gpio not supported for write-only interface.");
+ }
+ }
+
+ inline virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value)
+ {
+ //An attribute based API to configure all settings for the GPIO bus in one function
+ //call. This API does not have a mask so it configures all bits at the same time.
+ switch (attr)
+ {
+ case GPIO_CTRL:
+ set_atr_mode(MODE_ATR, value); //Configure mode=ATR for all bits that are set
+ set_atr_mode(MODE_GPIO, ~value); //Configure mode=GPIO for all bits that are unset
+ break;
+ case GPIO_DDR:
+ set_gpio_ddr(DDR_OUTPUT, value); //Configure as output for all bits that are set
+ set_gpio_ddr(DDR_INPUT, ~value); //Configure as input for all bits that are unset
+ break;
+ case GPIO_OUT:
+ //Only set bits that are driven statically
+ set_gpio_out(value);
+ break;
+ case GPIO_ATR_0X:
+ //Only set bits that are driven by the ATR engine
+ set_atr_reg(ATR_REG_IDLE, value);
+ break;
+ case GPIO_ATR_RX:
+ //Only set bits that are driven by the ATR engine
+ set_atr_reg(ATR_REG_RX_ONLY, value);
+ break;
+ case GPIO_ATR_TX:
+ //Only set bits that are driven by the ATR engine
+ set_atr_reg(ATR_REG_TX_ONLY, value);
+ break;
+ case GPIO_ATR_XX:
+ //Only set bits that are driven by the ATR engine
+ set_atr_reg(ATR_REG_FULL_DUPLEX, value);
+ break;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ }
+
+protected:
+ //Special RB addr value to indicate no readback
+ //This value is invalid as a real address because it is not a multiple of 4
+ static const wb_iface::wb_addr_type READBACK_DISABLED = 0xFFFFFFFF;
+
+ class masked_reg_t : public uhd::soft_reg32_wo_t {
+ public:
+ masked_reg_t(const wb_iface::wb_addr_type offset): uhd::soft_reg32_wo_t(offset) {
+ uhd::soft_reg32_wo_t::set(REGISTER, 0);
+ }
+
+ virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) {
+ uhd::soft_reg32_wo_t::set(REGISTER,
+ (value&mask)|(uhd::soft_reg32_wo_t::get(REGISTER)&(~mask)));
+ }
+
+ virtual boost::uint32_t get() {
+ return uhd::soft_reg32_wo_t::get(uhd::soft_reg32_wo_t::REGISTER);
+ }
+
+ virtual void flush() {
+ uhd::soft_reg32_wo_t::flush();
+ }
+ };
+
+ class atr_idle_reg_t : public masked_reg_t {
+ public:
+ atr_idle_reg_t(const wb_iface::wb_addr_type offset, masked_reg_t& atr_disable_reg):
+ masked_reg_t(offset),
+ _atr_idle_cache(0), _gpio_out_cache(0),
+ _atr_disable_reg(atr_disable_reg)
+ { }
+
+ virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) {
+ _atr_idle_cache = (value&mask)|(_atr_idle_cache&(~mask));
+ }
+
+ virtual boost::uint32_t get() {
+ return _atr_idle_cache;
+ }
+
+ void set_gpio_out_with_mask(const boost::uint32_t value, const boost::uint32_t mask) {
+ _gpio_out_cache = (value&mask)|(_gpio_out_cache&(~mask));
+ }
+
+ virtual boost::uint32_t get_gpio_out() {
+ return _gpio_out_cache;
+ }
+
+ virtual void flush() {
+ set(REGISTER,
+ (_atr_idle_cache & (~_atr_disable_reg.get())) |
+ (_gpio_out_cache & _atr_disable_reg.get())
+ );
+ masked_reg_t::flush();
+ }
+
+ private:
+ boost::uint32_t _atr_idle_cache;
+ boost::uint32_t _gpio_out_cache;
+ masked_reg_t& _atr_disable_reg;
+ };
+
+ wb_iface::sptr _iface;
+ wb_iface::wb_addr_type _rb_addr;
+ atr_idle_reg_t _atr_idle_reg;
+ masked_reg_t _atr_rx_reg;
+ masked_reg_t _atr_tx_reg;
+ masked_reg_t _atr_fdx_reg;
+ masked_reg_t _ddr_reg;
+ masked_reg_t _atr_disable_reg;
+};
+
+gpio_atr_3000::sptr gpio_atr_3000::make(
+ wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr
+) {
+ return sptr(new gpio_atr_3000_impl(iface, base, rb_addr));
+}
+
+gpio_atr_3000::sptr gpio_atr_3000::make_write_only(
+ wb_iface::sptr iface, const wb_iface::wb_addr_type base
+) {
+ gpio_atr_3000::sptr gpio_iface(new gpio_atr_3000_impl(iface, base));
+ gpio_iface->set_gpio_ddr(DDR_OUTPUT, MASK_SET_ALL);
+ return gpio_iface;
+}
+
+//-------------------------------------------------------------
+// db_gpio_atr_3000
+//-------------------------------------------------------------
+
+class db_gpio_atr_3000_impl : public gpio_atr_3000_impl, public db_gpio_atr_3000 {
+public:
+ db_gpio_atr_3000_impl(wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr):
+ gpio_atr_3000_impl(iface, base, rb_addr) { /* NOP */ }
+
+ inline void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask)
+ {
+ gpio_atr_3000_impl::set_atr_mode(MODE_ATR, compute_mask(unit, value&mask));
+ gpio_atr_3000_impl::set_atr_mode(MODE_GPIO, compute_mask(unit, (~value)&mask));
+ }
+
+ inline boost::uint32_t get_pin_ctrl(const db_unit_t unit)
+ {
+ return (~_atr_disable_reg.get()) >> compute_shift(unit);
+ }
+
+ inline void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask)
+ {
+ gpio_atr_3000_impl::set_gpio_ddr(DDR_OUTPUT, compute_mask(unit, value&mask));
+ gpio_atr_3000_impl::set_gpio_ddr(DDR_INPUT, compute_mask(unit, (~value)&mask));
+ }
+
+ inline boost::uint32_t get_gpio_ddr(const db_unit_t unit)
+ {
+ return _ddr_reg.get() >> compute_shift(unit);
+ }
+
+ inline void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask)
+ {
+ gpio_atr_3000_impl::set_atr_reg(atr, value << compute_shift(unit), compute_mask(unit, mask));
+ }
+
+ inline boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr)
+ {
+ masked_reg_t* reg = NULL;
+ switch (atr) {
+ case ATR_REG_IDLE: reg = &_atr_idle_reg; break;
+ case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break;
+ case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break;
+ case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break;
+ default: reg = &_atr_idle_reg; break;
+ }
+ return (reg->get() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit);
+ }
+
+ inline void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask)
+ {
+ gpio_atr_3000_impl::set_gpio_out(
+ static_cast<boost::uint32_t>(value) << compute_shift(unit),
+ compute_mask(unit, mask));
+ }
+
+ inline boost::uint32_t get_gpio_out(const db_unit_t unit)
+ {
+ return (_atr_idle_reg.get_gpio_out() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit);
+ }
+
+ inline boost::uint32_t read_gpio(const db_unit_t unit)
+ {
+ return (gpio_atr_3000_impl::read_gpio() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit);
+ }
+
+private:
+ inline boost::uint32_t compute_shift(const db_unit_t unit) {
+ switch (unit) {
+ case dboard_iface::UNIT_RX: return 0;
+ case dboard_iface::UNIT_TX: return 16;
+ default: return 0;
+ }
+ }
+
+ inline boost::uint32_t compute_mask(const db_unit_t unit, const boost::uint32_t mask) {
+ boost::uint32_t tmp_mask = (unit == dboard_iface::UNIT_BOTH) ? mask : (mask & 0xFFFF);
+ return tmp_mask << (compute_shift(unit));
+ }
+};
+
+db_gpio_atr_3000::sptr db_gpio_atr_3000::make(
+ wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr
+) {
+ return sptr(new db_gpio_atr_3000_impl(iface, base, rb_addr));
+}
+
+}}}
diff --git a/host/lib/usrp/cores/gpio_atr_3000.hpp b/host/lib/usrp/cores/gpio_atr_3000.hpp
new file mode 100644
index 000000000..7b90429fe
--- /dev/null
+++ b/host/lib/usrp/cores/gpio_atr_3000.hpp
@@ -0,0 +1,183 @@
+//
+// Copyright 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
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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_GPIO_CORE_3000_HPP
+#define INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP
+
+#include <uhd/config.hpp>
+#include <uhd/usrp/dboard_iface.hpp>
+#include <uhd/usrp/gpio_defs.hpp>
+#include <boost/shared_ptr.hpp>
+#include <uhd/types/wb_iface.hpp>
+
+namespace uhd { namespace usrp { namespace gpio_atr {
+
+class gpio_atr_3000 : boost::noncopyable {
+public:
+ typedef boost::shared_ptr<gpio_atr_3000> sptr;
+
+ static const boost::uint32_t MASK_SET_ALL = 0xFFFFFFFF;
+
+ virtual ~gpio_atr_3000(void) {};
+
+ /*!
+ * Create a read-write GPIO ATR interface object
+ *
+ * \param iface register iface to GPIO ATR registers
+ * \param base base settings offset for GPIO ATR registers
+ * \param base readback offset for GPIO ATR registers
+ */
+ static sptr make(
+ uhd::wb_iface::sptr iface,
+ const uhd::wb_iface::wb_addr_type base,
+ const uhd::wb_iface::wb_addr_type rb_addr);
+
+ /*!
+ * Create a write-only GPIO ATR interface object
+ *
+ * \param iface register iface to GPIO ATR registers
+ * \param base base settings offset for GPIO ATR registers
+ */
+ static sptr make_write_only(
+ uhd::wb_iface::sptr iface, const uhd::wb_iface::wb_addr_type base);
+
+ /*!
+ * Select the ATR mode for all bits in the mask
+ *
+ * \param mode the mode to apply {ATR = outputs driven by ATR state machine, GPIO = outputs static}
+ * \param mask apply the mode to all non-zero bits in the mask
+ */
+ virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) = 0;
+
+ /*!
+ * Select the data direction for all bits in the mask
+ *
+ * \param dir the direction {OUTPUT, INPUT}
+ * \param mask apply the mode to all non-zero bits in the mask
+ */
+ virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) = 0;
+
+ /*!
+ * Write the specified (masked) value to the ATR register
+ *
+ * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX}
+ * \param value the value to write
+ * \param mask only writes to the bits where mask is non-zero
+ */
+ virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0;
+
+ /*!
+ * Write to a static GPIO output
+ *
+ * \param value the value to write
+ * \param mask only writes to the bits where mask is non-zero
+ */
+ virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0;
+
+ /*!
+ * Read the state of the GPIO pins
+ * If a pin is configured as an input, reads the actual value of the pin
+ * If a pin is configured as an output, reads the last value written to the pin
+ *
+ * \return the value read back
+ */
+ virtual boost::uint32_t read_gpio() = 0;
+
+ /*!
+ * Set a GPIO attribute
+ *
+ * \param attr the attribute to set
+ * \param value the value to write to the attribute
+ */
+ virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) = 0;
+};
+
+class db_gpio_atr_3000 {
+public:
+ typedef boost::shared_ptr<db_gpio_atr_3000> sptr;
+
+ typedef uhd::usrp::dboard_iface::unit_t db_unit_t;
+
+ virtual ~db_gpio_atr_3000(void) {};
+
+ /*!
+ * Create a read-write GPIO ATR interface object for a daughterboard connector
+ *
+ * \param iface register iface to GPIO ATR registers
+ * \param base base settings offset for GPIO ATR registers
+ * \param base readback offset for GPIO ATR registers
+ */
+ static sptr make(
+ uhd::wb_iface::sptr iface,
+ const uhd::wb_iface::wb_addr_type base,
+ const uhd::wb_iface::wb_addr_type rb_addr);
+
+ /*!
+ * Configure the GPIO mode for all pins in the daughterboard connector
+ *
+ * \param unit the side of the daughterboard interface to configure (TX or RX)
+ * \param value if value[i] is 1, the i'th bit is in ATR mode otherwise it is in GPIO mode
+ */
+ virtual void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0;
+
+ virtual boost::uint32_t get_pin_ctrl(const db_unit_t unit) = 0;
+
+ /*!
+ * Configure the direction for all pins in the daughterboard connector
+ *
+ * \param unit the side of the daughterboard interface to configure (TX or RX)
+ * \param value if value[i] is 1, the i'th bit is an output otherwise it is an input
+ */
+ virtual void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0;
+
+ virtual boost::uint32_t get_gpio_ddr(const db_unit_t unit) = 0;
+
+ /*!
+ * Write the specified value to the ATR register (all bits)
+ *
+ * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX}
+ * \param unit the side of the daughterboard interface to configure (TX or RX)
+ * \param value the value to write
+ */
+ virtual void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) = 0;
+
+ virtual boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) = 0;
+
+ /*!
+ * Write the specified value to the GPIO register (all bits)
+ *
+ * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX}
+ * \param value the value to write
+ */
+ virtual void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0;
+
+ virtual boost::uint32_t get_gpio_out(const db_unit_t unit) = 0;
+
+ /*!
+ * Read the state of the GPIO pins
+ * If a pin is configured as an input, reads the actual value of the pin
+ * If a pin is configured as an output, reads the last value written to the pin
+ *
+ * \param unit the side of the daughterboard interface to configure (TX or RX)
+ * \return the value read back
+ */
+ virtual boost::uint32_t read_gpio(const db_unit_t unit) = 0;
+};
+
+}}} //namespaces
+
+#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP */
diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp
index 704a71d5f..8ada95b1f 100644
--- a/host/lib/usrp/cores/gpio_core_200.cpp
+++ b/host/lib/usrp/cores/gpio_core_200.cpp
@@ -27,6 +27,11 @@
using namespace uhd;
using namespace usrp;
+template <typename T>
+static void shadow_it(T &shadow, const T &value, const T &mask){
+ shadow = (shadow & ~mask) | (value & mask);
+}
+
gpio_core_200::~gpio_core_200(void){
/* NOP */
}
@@ -36,13 +41,20 @@ public:
gpio_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t rb_addr):
_iface(iface), _base(base), _rb_addr(rb_addr), _first_atr(true) { /* NOP */ }
- void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){
- _pin_ctrl[unit] = value; //shadow
+ void set_pin_ctrl(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ shadow_it(_pin_ctrl[unit], value, mask);
update(); //full update
}
- void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){
- _atr_regs[unit][atr] = value; //shadow
+ boost::uint16_t get_pin_ctrl(unit_t unit){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ return _pin_ctrl[unit];
+ }
+
+ void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ shadow_it(_atr_regs[unit][atr], value, mask);
if (_first_atr)
{
// To preserve legacy behavior, update all registers the first time
@@ -53,20 +65,38 @@ public:
update(atr);
}
- void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){
- _gpio_ddr[unit] = value; //shadow
+ boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ return _atr_regs[unit][reg];
+ }
+
+ void set_gpio_ddr(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ shadow_it(_gpio_ddr[unit], value, mask);
_iface->poke32(REG_GPIO_DDR, //update the 32 bit register
(boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) |
(boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX))
);
}
- void set_gpio_out(const unit_t unit, const boost::uint16_t value){
- _gpio_out[unit] = value; //shadow
+ boost::uint16_t get_gpio_ddr(unit_t unit){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ return _gpio_ddr[unit];
+ }
+
+ void set_gpio_out(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ shadow_it(_gpio_out[unit], value, mask);
this->update(); //full update
}
+ boost::uint16_t get_gpio_out(unit_t unit){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
+ return _gpio_out[unit];
+ }
+
boost::uint16_t read_gpio(const unit_t unit){
+ if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200");
return boost::uint16_t(_iface->peek32(_rb_addr) >> shift_by_unit(unit));
}
@@ -85,26 +115,26 @@ private:
}
void update(void){
- update(dboard_iface::ATR_REG_IDLE);
- update(dboard_iface::ATR_REG_TX_ONLY);
- update(dboard_iface::ATR_REG_RX_ONLY);
- update(dboard_iface::ATR_REG_FULL_DUPLEX);
+ update(gpio_atr::ATR_REG_IDLE);
+ update(gpio_atr::ATR_REG_TX_ONLY);
+ update(gpio_atr::ATR_REG_RX_ONLY);
+ update(gpio_atr::ATR_REG_FULL_DUPLEX);
}
void update(const atr_reg_t atr){
size_t addr;
switch (atr)
{
- case dboard_iface::ATR_REG_IDLE:
+ case gpio_atr::ATR_REG_IDLE:
addr = REG_GPIO_IDLE;
break;
- case dboard_iface::ATR_REG_TX_ONLY:
+ case gpio_atr::ATR_REG_TX_ONLY:
addr = REG_GPIO_TX_ONLY;
break;
- case dboard_iface::ATR_REG_RX_ONLY:
+ case gpio_atr::ATR_REG_RX_ONLY:
addr = REG_GPIO_RX_ONLY;
break;
- case dboard_iface::ATR_REG_FULL_DUPLEX:
+ case gpio_atr::ATR_REG_FULL_DUPLEX:
addr = REG_GPIO_BOTH;
break;
default:
@@ -144,27 +174,32 @@ public:
gpio_core_200_32wo_impl(wb_iface::sptr iface, const size_t base):
_iface(iface), _base(base)
{
+ set_ddr_reg();
+ }
+
+ void set_ddr_reg(){
_iface->poke32(REG_GPIO_DDR, 0xffffffff);
}
+
void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value){
- if (atr == dboard_iface::ATR_REG_IDLE)
+ if (atr == gpio_atr::ATR_REG_IDLE)
_iface->poke32(REG_GPIO_IDLE, value);
- else if (atr == dboard_iface::ATR_REG_TX_ONLY)
+ else if (atr == gpio_atr::ATR_REG_TX_ONLY)
_iface->poke32(REG_GPIO_TX_ONLY, value);
- else if (atr == dboard_iface::ATR_REG_RX_ONLY)
+ else if (atr == gpio_atr::ATR_REG_RX_ONLY)
_iface->poke32(REG_GPIO_RX_ONLY, value);
- else if (atr == dboard_iface::ATR_REG_FULL_DUPLEX)
+ else if (atr == gpio_atr::ATR_REG_FULL_DUPLEX)
_iface->poke32(REG_GPIO_BOTH, value);
else
UHD_THROW_INVALID_CODE_PATH();
}
void set_all_regs(const boost::uint32_t value){
- set_atr_reg(dboard_iface::ATR_REG_IDLE, value);
- set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, value);
- set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, value);
- set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, value);
+ set_atr_reg(gpio_atr::ATR_REG_IDLE, value);
+ set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, value);
+ set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, value);
+ set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, value);
}
private:
diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp
index e22834fd9..c697f0e77 100644
--- a/host/lib/usrp/cores/gpio_core_200.hpp
+++ b/host/lib/usrp/cores/gpio_core_200.hpp
@@ -20,6 +20,7 @@
#include <uhd/config.hpp>
#include <uhd/usrp/dboard_iface.hpp>
+#include <uhd/usrp/gpio_defs.hpp>
#include <boost/assign.hpp>
#include <boost/cstdint.hpp>
#include <boost/utility.hpp>
@@ -27,28 +28,6 @@
#include <uhd/types/wb_iface.hpp>
#include <map>
-typedef enum {
- GPIO_CTRL,
- GPIO_DDR,
- GPIO_OUT,
- GPIO_ATR_0X,
- GPIO_ATR_RX,
- GPIO_ATR_TX,
- GPIO_ATR_XX
-} gpio_attr_t;
-
-typedef std::map<gpio_attr_t,std::string> gpio_attr_map_t;
-static const gpio_attr_map_t gpio_attr_map =
- boost::assign::map_list_of
- (GPIO_CTRL, "CTRL")
- (GPIO_DDR, "DDR")
- (GPIO_OUT, "OUT")
- (GPIO_ATR_0X, "ATR_0X")
- (GPIO_ATR_RX, "ATR_RX")
- (GPIO_ATR_TX, "ATR_TX")
- (GPIO_ATR_XX, "ATR_XX")
-;
-
class gpio_core_200 : boost::noncopyable{
public:
typedef boost::shared_ptr<gpio_core_200> sptr;
@@ -59,20 +38,32 @@ public:
virtual ~gpio_core_200(void) = 0;
//! makes a new GPIO core from iface and slave base
- static sptr make(uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr);
+ static sptr make(
+ uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr);
//! 1 = ATR
- virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0;
+ virtual void set_pin_ctrl(
+ const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0;
+
+ virtual boost::uint16_t get_pin_ctrl(unit_t unit) = 0;
- virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0;
+ virtual void set_atr_reg(
+ const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask) = 0;
+
+ virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0;
//! 1 = OUTPUT
- virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0;
+ virtual void set_gpio_ddr(
+ const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0;
- virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0;
+ virtual boost::uint16_t get_gpio_ddr(unit_t unit) = 0;
- virtual boost::uint16_t read_gpio(const unit_t unit) = 0;
+ virtual void set_gpio_out(
+ const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0;
+ virtual boost::uint16_t get_gpio_out(unit_t unit) = 0;
+
+ virtual boost::uint16_t read_gpio(const unit_t unit) = 0;
};
//! Simple wrapper for 32 bit write only
@@ -86,6 +77,8 @@ public:
static sptr make(uhd::wb_iface::sptr iface, const size_t);
+ virtual void set_ddr_reg() = 0;
+
virtual void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value) = 0;
virtual void set_all_regs(const boost::uint32_t value) = 0;
diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp
index b899085c0..e51862d3b 100644
--- a/host/lib/usrp/cores/rx_dsp_core_200.cpp
+++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp
@@ -16,6 +16,7 @@
//
#include "rx_dsp_core_200.hpp"
+#include "dsp_core_utils.hpp"
#include <uhd/types/dict.hpp>
#include <uhd/exception.hpp>
#include <uhd/utils/math.hpp>
@@ -24,7 +25,6 @@
#include <boost/assign/list_of.hpp>
#include <boost/thread/thread.hpp> //thread sleep
#include <boost/math/special_functions/round.hpp>
-#include <boost/math/special_functions/sign.hpp>
#include <boost/numeric/conversion/bounds.hpp>
#include <algorithm>
#include <cmath>
@@ -223,42 +223,11 @@ public:
return _fxpt_scalar_correction*_host_extra_scaling/32767.;
}
- double set_freq(const double freq_){
- //correct for outside of rate (wrap around)
- double freq = std::fmod(freq_, _tick_rate);
- if (std::abs(freq) > _tick_rate/2.0)
- freq -= boost::math::sign(freq)*_tick_rate;
-
- //confirm that the target frequency is within range of the CORDIC
- UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0);
-
- /* Now calculate the frequency word. It is possible for this calculation
- * to cause an overflow. As the requested DSP frequency approaches the
- * master clock rate, that ratio multiplied by the scaling factor (2^32)
- * will generally overflow within the last few kHz of tunable range.
- * Thus, we check to see if the operation will overflow before doing it,
- * and if it will, we set it to the integer min or max of this system.
- */
- boost::int32_t freq_word = 0;
-
- static const double scale_factor = std::pow(2.0, 32);
- if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) {
- /* Operation would have caused a positive overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MAX;
-
- } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) {
- /* Operation would have caused a negative overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MIN;
-
- } else {
- /* The operation is safe. Perform normally. */
- freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor));
- }
-
- //program the frequency word into the device DSP
- const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate;
+ double set_freq(const double requested_freq){
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word);
_iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word));
-
return actual_freq;
}
diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.cpp b/host/lib/usrp/cores/rx_dsp_core_3000.cpp
index 035bc6a3f..eedbbef95 100644
--- a/host/lib/usrp/cores/rx_dsp_core_3000.cpp
+++ b/host/lib/usrp/cores/rx_dsp_core_3000.cpp
@@ -16,6 +16,7 @@
//
#include "rx_dsp_core_3000.hpp"
+#include "dsp_core_utils.hpp"
#include <uhd/types/dict.hpp>
#include <uhd/exception.hpp>
#include <uhd/utils/math.hpp>
@@ -24,7 +25,6 @@
#include <boost/assign/list_of.hpp>
#include <boost/thread/thread.hpp> //thread sleep
#include <boost/math/special_functions/round.hpp>
-#include <boost/math/special_functions/sign.hpp>
#include <algorithm>
#include <cmath>
@@ -69,6 +69,7 @@ public:
_scaling_adjustment = 1.0;
_dsp_extra_scaling = 1.0;
_tick_rate = 1.0;
+ _dsp_freq_offset = 0.0;
}
~rx_dsp_core_3000_impl(void)
@@ -79,17 +80,41 @@ public:
)
}
- void set_mux(const std::string &mode, const bool fe_swapped, const bool invert_i, const bool invert_q){
- static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of
- ("IQ", 0)
- ("QI", FLAG_DSP_RX_MUX_SWAP_IQ)
- ("I", FLAG_DSP_RX_MUX_REAL_MODE)
- ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE)
- ;
- _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode]
- | (fe_swapped ? FLAG_DSP_RX_MUX_SWAP_IQ : 0)
- | (invert_i ? FLAG_DSP_RX_MUX_INVERT_I : 0)
- | (invert_q ? FLAG_DSP_RX_MUX_INVERT_Q : 0));
+ void set_mux(const uhd::usrp::fe_connection_t& fe_conn){
+ boost::uint32_t reg_val = 0;
+ switch (fe_conn.get_sampling_mode()) {
+ case uhd::usrp::fe_connection_t::REAL:
+ case uhd::usrp::fe_connection_t::HETERODYNE:
+ reg_val = FLAG_DSP_RX_MUX_REAL_MODE;
+ break;
+ default:
+ reg_val = 0;
+ break;
+ }
+
+ if (fe_conn.is_iq_swapped()) reg_val |= FLAG_DSP_RX_MUX_SWAP_IQ;
+ if (fe_conn.is_i_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_I;
+ if (fe_conn.is_q_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_Q;
+
+ _iface->poke32(REG_DSP_RX_MUX, reg_val);
+
+ if (fe_conn.get_sampling_mode() == uhd::usrp::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, _tick_rate)
+ double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _tick_rate));
+ //3. Map IF frequency to the range [-_tick_rate/2, _tick_rate/2)
+ // This is the aliased frequency
+ if (if_freq > (_tick_rate / 2.0)) {
+ if_freq -= _tick_rate;
+ }
+ //4. Set DSP offset to spin the signal in the opposite
+ // direction as the aliased frequency
+ _dsp_freq_offset = if_freq * (-if_freq_sign);
+ } else {
+ _dsp_freq_offset = 0.0;
+ }
}
void set_tick_rate(const double rate){
@@ -209,47 +234,18 @@ public:
return _fxpt_scalar_correction*_host_extra_scaling/32767.;
}
- double set_freq(const double freq_){
- //correct for outside of rate (wrap around)
- double freq = std::fmod(freq_, _tick_rate);
- if (std::abs(freq) > _tick_rate/2.0)
- freq -= boost::math::sign(freq)*_tick_rate;
-
- //confirm that the target frequency is within range of the CORDIC
- UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0);
-
- /* Now calculate the frequency word. It is possible for this calculation
- * to cause an overflow. As the requested DSP frequency approaches the
- * master clock rate, that ratio multiplied by the scaling factor (2^32)
- * will generally overflow within the last few kHz of tunable range.
- * Thus, we check to see if the operation will overflow before doing it,
- * and if it will, we set it to the integer min or max of this system.
- */
- boost::int32_t freq_word = 0;
-
- static const double scale_factor = std::pow(2.0, 32);
- if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) {
- /* Operation would have caused a positive overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MAX;
-
- } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) {
- /* Operation would have caused a negative overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MIN;
-
- } else {
- /* The operation is safe. Perform normally. */
- freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor));
- }
-
- //program the frequency word into the device DSP
- const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate;
+ double set_freq(const double requested_freq){
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq + _dsp_freq_offset, _tick_rate, actual_freq, freq_word);
_iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word));
-
return actual_freq;
}
uhd::meta_range_t get_freq_range(void){
- return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32));
+ //Too keep the DSP range symmetric about 0, we use abs(_dsp_freq_offset)
+ const double offset = std::abs<double>(_dsp_freq_offset);
+ return uhd::meta_range_t(-(_tick_rate-offset)/2, +(_tick_rate-offset)/2, _tick_rate/std::pow(2.0, 32));
}
void setup(const uhd::stream_args_t &stream_args){
@@ -284,18 +280,18 @@ public:
void populate_subtree(property_tree::sptr subtree)
{
subtree->create<meta_range_t>("rate/range")
- .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, this))
+ .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, this))
;
subtree->create<double>("rate/value")
.set(DEFAULT_RATE)
- .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1))
+ .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1))
;
subtree->create<double>("freq/value")
.set(DEFAULT_CORDIC_FREQ)
- .coerce(boost::bind(&rx_dsp_core_3000::set_freq, this, _1))
+ .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, this, _1))
;
subtree->create<meta_range_t>("freq/range")
- .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, this))
+ .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, this))
;
}
@@ -305,6 +301,7 @@ private:
const bool _is_b200; //TODO: Obsolete this when we switch to the new DDC on the B200
double _tick_rate, _link_rate;
double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction;
+ double _dsp_freq_offset;
};
rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */)
diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.hpp b/host/lib/usrp/cores/rx_dsp_core_3000.hpp
index 65801de1d..41b328357 100644
--- a/host/lib/usrp/cores/rx_dsp_core_3000.hpp
+++ b/host/lib/usrp/cores/rx_dsp_core_3000.hpp
@@ -24,6 +24,7 @@
#include <uhd/types/stream_cmd.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 <string>
@@ -43,7 +44,7 @@ public:
const bool is_b200 = false //TODO: Obsolete this when we switch to the new DDC on the B200
);
- virtual void set_mux(const std::string &mode, const bool fe_swapped = false, const bool invert_i = false, const bool invert_q = false) = 0;
+ virtual void set_mux(const uhd::usrp::fe_connection_t& fe_conn) = 0;
virtual void set_tick_rate(const double rate) = 0;
diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp
index 7ac920553..0a60bf87c 100644
--- a/host/lib/usrp/cores/rx_frontend_core_200.cpp
+++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp
@@ -83,15 +83,15 @@ public:
{
subtree->create<std::complex<double> >("dc_offset/value")
.set(DEFAULT_DC_OFFSET_VALUE)
- .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1))
+ .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1))
;
subtree->create<bool>("dc_offset/enable")
.set(DEFAULT_DC_OFFSET_ENABLE)
- .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1))
;
subtree->create<std::complex<double> >("iq_balance/value")
.set(DEFAULT_IQ_BALANCE_VALUE)
- .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _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 f61da7cc3..54c57c2d5 100644
--- a/host/lib/usrp/cores/rx_vita_core_3000.cpp
+++ b/host/lib/usrp/cores/rx_vita_core_3000.cpp
@@ -20,6 +20,8 @@
#include <uhd/utils/safe_call.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/tuple/tuple.hpp>
+#include <boost/date_time.hpp>
+#include <boost/thread.hpp>
#define REG_FRAMER_MAXLEN _base + 4*4 + 0
#define REG_FRAMER_SID _base + 4*4 + 4
@@ -63,13 +65,26 @@ struct rx_vita_core_3000_impl : rx_vita_core_3000
void configure_flow_control(const size_t window_size)
{
+ // The window needs to be disabled in the case where this object is
+ // uncleanly destroyed and the FC window is left enabled
+ _iface->poke32(REG_FC_ENABLE, 0);
+
+ // Sleep for a large amount of time to allow the source flow control
+ // module in the FPGA to flush all the packets buffered upstream.
+ // At 1 ms * 200 MHz = 200k cycles, 8 bytes * 200k cycles = 1.6 MB
+ // of flushed data, when the typical amount of data buffered
+ // is on the order of kilobytes
+ boost::this_thread::sleep(boost::posix_time::milliseconds(1.0));
+
_iface->poke32(REG_FC_WINDOW, window_size-1);
_iface->poke32(REG_FC_ENABLE, window_size?1:0);
}
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/cores/spi_core_3000.cpp b/host/lib/usrp/cores/spi_core_3000.cpp
index 0656d910a..01df71cec 100644
--- a/host/lib/usrp/cores/spi_core_3000.cpp
+++ b/host/lib/usrp/cores/spi_core_3000.cpp
@@ -20,9 +20,10 @@
#include <uhd/utils/msg.hpp>
#include <boost/thread/thread.hpp> //sleep
-#define SPI_DIV _base + 0
-#define SPI_CTRL _base + 4
-#define SPI_DATA _base + 8
+#define SPI_DIV _base + 0
+#define SPI_CTRL _base + 4
+#define SPI_DATA _base + 8
+#define SPI_SHUTDOWN _base + 12
using namespace uhd;
@@ -34,7 +35,7 @@ class spi_core_3000_impl : public spi_core_3000
{
public:
spi_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback):
- _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0)
+ _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0), _divider_cache(0)
{
this->set_divider(30);
}
@@ -46,7 +47,21 @@ public:
size_t num_bits,
bool readback
){
- boost::mutex::scoped_lock lock(_mutex);
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ //load SPI divider
+ size_t spi_divider = _div;
+ if (config.use_custom_divider) {
+ //The resulting SPI frequency will be f_system/(2*(divider+1))
+ //This math ensures the frequency will be equal to or less than the target
+ spi_divider = (config.divider-1)/2;
+ }
+
+ //conditionally send SPI divider
+ if (spi_divider != _divider_cache) {
+ _iface->poke32(SPI_DIV, spi_divider);
+ _divider_cache = spi_divider;
+ }
//load control word
boost::uint32_t ctrl_word = 0;
@@ -55,17 +70,16 @@ public:
if (config.mosi_edge == spi_config_t::EDGE_FALL) ctrl_word |= (1 << 31);
if (config.miso_edge == spi_config_t::EDGE_RISE) ctrl_word |= (1 << 30);
- //load data word (must be in upper bits)
- const boost::uint32_t data_out = data << (32 - num_bits);
-
//conditionally send control word
if (_ctrl_word_cache != ctrl_word)
{
- _iface->poke32(SPI_DIV, _div);
_iface->poke32(SPI_CTRL, ctrl_word);
_ctrl_word_cache = ctrl_word;
}
+ //load data word (must be in upper bits)
+ const boost::uint32_t data_out = data << (32 - num_bits);
+
//send data word
_iface->poke32(SPI_DATA, data_out);
@@ -78,6 +92,17 @@ public:
return 0;
}
+ void set_shutdown(const bool shutdown)
+ {
+ _shutdown_cache = shutdown;
+ _iface->poke32(SPI_SHUTDOWN, _shutdown_cache);
+ }
+
+ bool get_shutdown()
+ {
+ return(_shutdown_cache);
+ }
+
void set_divider(const double div)
{
_div = size_t((div/2) - 0.5);
@@ -89,8 +114,10 @@ private:
const size_t _base;
const size_t _readback;
boost::uint32_t _ctrl_word_cache;
+ bool _shutdown_cache;
boost::mutex _mutex;
size_t _div;
+ size_t _divider_cache;
};
spi_core_3000::sptr spi_core_3000::make(wb_iface::sptr iface, const size_t base, const size_t readback)
diff --git a/host/lib/usrp/cores/spi_core_3000.hpp b/host/lib/usrp/cores/spi_core_3000.hpp
index 6a439772e..8d5177196 100644
--- a/host/lib/usrp/cores/spi_core_3000.hpp
+++ b/host/lib/usrp/cores/spi_core_3000.hpp
@@ -36,6 +36,13 @@ public:
//! Set the spi clock divider to something usable
virtual void set_divider(const double div) = 0;
+
+ //! Place SPI core in shutdown mode. All attempted SPI transactions are dropped by
+ // the core.
+ virtual void set_shutdown(const bool shutdown) = 0;
+
+ //! Get state of shutdown register
+ virtual bool get_shutdown() = 0;
};
#endif /* INCLUDED_LIBUHD_USRP_SPI_CORE_3000_HPP */
diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp
index 2ef9f4406..4c456a10d 100644
--- a/host/lib/usrp/cores/tx_dsp_core_200.cpp
+++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp
@@ -16,13 +16,13 @@
//
#include "tx_dsp_core_200.hpp"
+#include "dsp_core_utils.hpp"
#include <uhd/types/dict.hpp>
#include <uhd/exception.hpp>
#include <uhd/utils/math.hpp>
#include <uhd/utils/msg.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/math/special_functions/round.hpp>
-#include <boost/math/special_functions/sign.hpp>
#include <boost/thread/thread.hpp> //sleep
#include <algorithm>
#include <cmath>
@@ -163,42 +163,11 @@ public:
return _fxpt_scalar_correction*_host_extra_scaling*32767.;
}
- double set_freq(const double freq_){
- //correct for outside of rate (wrap around)
- double freq = std::fmod(freq_, _tick_rate);
- if (std::abs(freq) > _tick_rate/2.0)
- freq -= boost::math::sign(freq)*_tick_rate;
-
- //confirm that the target frequency is within range of the CORDIC
- UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0);
-
- /* Now calculate the frequency word. It is possible for this calculation
- * to cause an overflow. As the requested DSP frequency approaches the
- * master clock rate, that ratio multiplied by the scaling factor (2^32)
- * will generally overflow within the last few kHz of tunable range.
- * Thus, we check to see if the operation will overflow before doing it,
- * and if it will, we set it to the integer min or max of this system.
- */
- boost::int32_t freq_word = 0;
-
- static const double scale_factor = std::pow(2.0, 32);
- if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) {
- /* Operation would have caused a positive overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MAX;
-
- } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) {
- /* Operation would have caused a negative overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MIN;
-
- } else {
- /* The operation is safe. Perform normally. */
- freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor));
- }
-
- //program the frequency word into the device DSP
- const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate;
+ double set_freq(const double requested_freq){
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word);
_iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word));
-
return actual_freq;
}
diff --git a/host/lib/usrp/cores/tx_dsp_core_3000.cpp b/host/lib/usrp/cores/tx_dsp_core_3000.cpp
index 7e447ae7d..3889bbdc4 100644
--- a/host/lib/usrp/cores/tx_dsp_core_3000.cpp
+++ b/host/lib/usrp/cores/tx_dsp_core_3000.cpp
@@ -16,13 +16,13 @@
//
#include "tx_dsp_core_3000.hpp"
+#include "dsp_core_utils.hpp"
#include <uhd/types/dict.hpp>
#include <uhd/exception.hpp>
#include <uhd/utils/math.hpp>
#include <uhd/utils/msg.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/math/special_functions/round.hpp>
-#include <boost/math/special_functions/sign.hpp>
#include <boost/thread/thread.hpp> //sleep
#include <algorithm>
#include <cmath>
@@ -136,42 +136,11 @@ public:
return _fxpt_scalar_correction*_host_extra_scaling*32767.;
}
- double set_freq(const double freq_){
- //correct for outside of rate (wrap around)
- double freq = std::fmod(freq_, _tick_rate);
- if (std::abs(freq) > _tick_rate/2.0)
- freq -= boost::math::sign(freq)*_tick_rate;
-
- //confirm that the target frequency is within range of the CORDIC
- UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0);
-
- /* Now calculate the frequency word. It is possible for this calculation
- * to cause an overflow. As the requested DSP frequency approaches the
- * master clock rate, that ratio multiplied by the scaling factor (2^32)
- * will generally overflow within the last few kHz of tunable range.
- * Thus, we check to see if the operation will overflow before doing it,
- * and if it will, we set it to the integer min or max of this system.
- */
- boost::int32_t freq_word = 0;
-
- static const double scale_factor = std::pow(2.0, 32);
- if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) {
- /* Operation would have caused a positive overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MAX;
-
- } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) {
- /* Operation would have caused a negative overflow of int32. */
- freq_word = uhd::math::BOOST_INT32_MIN;
-
- } else {
- /* The operation is safe. Perform normally. */
- freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor));
- }
-
- //program the frequency word into the device DSP
- const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate;
+ double set_freq(const double requested_freq) {
+ double actual_freq;
+ int32_t freq_word;
+ get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word);
_iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word));
-
return actual_freq;
}
@@ -211,18 +180,18 @@ public:
void populate_subtree(property_tree::sptr subtree)
{
subtree->create<meta_range_t>("rate/range")
- .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, this))
+ .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, this))
;
subtree->create<double>("rate/value")
.set(DEFAULT_RATE)
- .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1))
+ .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1))
;
subtree->create<double>("freq/value")
.set(DEFAULT_CORDIC_FREQ)
- .coerce(boost::bind(&tx_dsp_core_3000::set_freq, this, _1))
+ .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, this, _1))
;
subtree->create<meta_range_t>("freq/range")
- .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, this))
+ .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, this))
;
}
diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp
index 0fa028571..be4f77f39 100644
--- a/host/lib/usrp/cores/tx_frontend_core_200.cpp
+++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp
@@ -79,11 +79,11 @@ public:
{
subtree->create< std::complex<double> >("dc_offset/value")
.set(DEFAULT_DC_OFFSET_VALUE)
- .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1))
+ .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1))
;
subtree->create< std::complex<double> >("iq_balance/value")
.set(DEFAULT_IQ_BALANCE_VALUE)
- .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1))
+ .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1))
;
}
diff --git a/host/lib/usrp/cores/tx_vita_core_3000.cpp b/host/lib/usrp/cores/tx_vita_core_3000.cpp
index 71a2b7e21..c76b384d9 100644
--- a/host/lib/usrp/cores/tx_vita_core_3000.cpp
+++ b/host/lib/usrp/cores/tx_vita_core_3000.cpp
@@ -18,9 +18,11 @@
#include "tx_vita_core_3000.hpp"
#include <uhd/utils/safe_call.hpp>
-#define REG_CTRL_ERROR_POLICY _base + 0
-#define REG_DEFRAMER_CYCLE_FC_UPS _base + 2*4 + 0
-#define REG_DEFRAMER_PACKET_FC_UPS _base + 2*4 + 4
+#define REG_CTRL_ERROR_POLICY (_base + 0)
+#define REG_FC_PRE_RADIO_RESP_BASE (_base + 2*4)
+#define REG_FC_PRE_FIFO_RESP_BASE (_base + 4*4)
+#define REG_CTRL_FC_CYCLE_OFFSET (0*4)
+#define REG_CTRL_FC_PACKET_OFFSET (1*4)
using namespace uhd;
@@ -32,12 +34,22 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000
{
tx_vita_core_3000_impl(
wb_iface::sptr iface,
- const size_t base
+ const size_t base,
+ fc_monitor_loc fc_location
):
_iface(iface),
- _base(base)
+ _base(base),
+ _fc_base((fc_location==FC_PRE_RADIO or fc_location==FC_DEFAULT) ?
+ REG_FC_PRE_RADIO_RESP_BASE : REG_FC_PRE_FIFO_RESP_BASE),
+ _fc_location(fc_location)
{
- this->set_tick_rate(1); //init to non zero
+ if (fc_location != FC_DEFAULT) {
+ //Turn off the other FC monitoring module
+ const size_t other_fc_base = (fc_location==FC_PRE_RADIO) ?
+ REG_FC_PRE_FIFO_RESP_BASE : REG_FC_PRE_RADIO_RESP_BASE;
+ _iface->poke32(other_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0);
+ _iface->poke32(other_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0);
+ }
this->set_underflow_policy("next_packet");
this->clear();
}
@@ -56,11 +68,6 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000
this->set_underflow_policy(_policy); //clears the seq
}
- void set_tick_rate(const double rate)
- {
- _tick_rate = rate;
- }
-
void set_underflow_policy(const std::string &policy)
{
if (policy == "next_packet")
@@ -89,23 +96,35 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000
void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up)
{
- if (cycs_per_up == 0) _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, 0);
- else _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, (1 << 31) | ((cycs_per_up) & 0xffffff));
+ if (cycs_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0);
+ else _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, (1 << 31) | ((cycs_per_up) & 0xffffff));
- if (pkts_per_up == 0) _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, 0);
- else _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, (1 << 31) | ((pkts_per_up) & 0xffff));
+ if (pkts_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0);
+ else _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, (1 << 31) | ((pkts_per_up) & 0xffff));
}
- wb_iface::sptr _iface;
- const size_t _base;
- double _tick_rate;
- std::string _policy;
+ wb_iface::sptr _iface;
+ const size_t _base;
+ const size_t _fc_base;
+ std::string _policy;
+ fc_monitor_loc _fc_location;
+
};
tx_vita_core_3000::sptr tx_vita_core_3000::make(
wb_iface::sptr iface,
+ const size_t base,
+ fc_monitor_loc fc_location
+)
+{
+ return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, fc_location));
+}
+
+tx_vita_core_3000::sptr tx_vita_core_3000::make_no_radio_buff(
+ wb_iface::sptr iface,
const size_t base
)
{
- return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base));
+ //No internal radio buffer so only pre-radio monitoring is supported.
+ return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, FC_DEFAULT));
}
diff --git a/host/lib/usrp/cores/tx_vita_core_3000.hpp b/host/lib/usrp/cores/tx_vita_core_3000.hpp
index 4c0052d4f..bd0f20ba4 100644
--- a/host/lib/usrp/cores/tx_vita_core_3000.hpp
+++ b/host/lib/usrp/cores/tx_vita_core_3000.hpp
@@ -32,17 +32,27 @@ class tx_vita_core_3000 : boost::noncopyable
public:
typedef boost::shared_ptr<tx_vita_core_3000> sptr;
+ enum fc_monitor_loc {
+ FC_DEFAULT,
+ FC_PRE_RADIO,
+ FC_PRE_FIFO
+ };
+
virtual ~tx_vita_core_3000(void) = 0;
static sptr make(
uhd::wb_iface::sptr iface,
+ const size_t base,
+ fc_monitor_loc fc_location = FC_PRE_RADIO
+ );
+
+ static sptr make_no_radio_buff(
+ uhd::wb_iface::sptr iface,
const size_t base
);
virtual void clear(void) = 0;
- virtual void set_tick_rate(const double rate) = 0;
-
virtual void setup(const uhd::stream_args_t &stream_args) = 0;
virtual void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) = 0;
diff --git a/host/lib/usrp/cores/user_settings_core_3000.cpp b/host/lib/usrp/cores/user_settings_core_3000.cpp
new file mode 100644
index 000000000..549264f57
--- /dev/null
+++ b/host/lib/usrp/cores/user_settings_core_3000.cpp
@@ -0,0 +1,85 @@
+//
+// Copyright 2012 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "user_settings_core_3000.hpp"
+#include <uhd/exception.hpp>
+#include <boost/thread/thread.hpp>
+
+using namespace uhd;
+
+#define REG_USER_SR_ADDR _sr_base_addr + 0
+#define REG_USER_SR_DATA _sr_base_addr + 4
+#define REG_USER_RB_ADDR _sr_base_addr + 8
+
+class user_settings_core_3000_impl : public user_settings_core_3000 {
+public:
+ user_settings_core_3000_impl(
+ wb_iface::sptr iface,
+ const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr):
+ _iface(iface), _sr_base_addr(sr_base_addr), _rb_reg_addr(rb_reg_addr)
+ {
+ }
+
+ void poke64(const wb_addr_type offset, const boost::uint64_t value)
+ {
+ if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("poke64: Incorrect address alignment");
+ poke32(offset, static_cast<boost::uint32_t>(value));
+ poke32(offset + 4, static_cast<boost::uint32_t>(value >> 32));
+ }
+
+ boost::uint64_t peek64(const wb_addr_type offset)
+ {
+ if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("peek64: Incorrect address alignment");
+
+ boost::unique_lock<boost::mutex> lock(_mutex);
+ _iface->poke32(REG_USER_RB_ADDR, offset >> 3); //Translate byte offset to 64-bit offset
+ return _iface->peek64(_rb_reg_addr);
+ }
+
+ void poke32(const wb_addr_type offset, const boost::uint32_t value)
+ {
+ if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("poke32: Incorrect address alignment");
+
+ boost::unique_lock<boost::mutex> lock(_mutex);
+ _iface->poke32(REG_USER_SR_ADDR, offset >> 2); //Translate byte offset to 64-bit offset
+ _iface->poke32(REG_USER_SR_DATA, value);
+ }
+
+ boost::uint32_t peek32(const wb_addr_type offset)
+ {
+ if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("peek32: Incorrect address alignment");
+
+ boost::uint64_t value = peek64((offset >> 3) << 3);
+ if ((offset & 0x7) == 0) {
+ return static_cast<boost::uint32_t>(value);
+ } else {
+ return static_cast<boost::uint32_t>(value >> 32);
+ }
+ }
+
+private:
+ wb_iface::sptr _iface;
+ const wb_addr_type _sr_base_addr;
+ const wb_addr_type _rb_reg_addr;
+ boost::mutex _mutex;
+};
+
+wb_iface::sptr user_settings_core_3000::make(wb_iface::sptr iface,
+ const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr)
+{
+ return sptr(new user_settings_core_3000_impl(iface, sr_base_addr, rb_reg_addr));
+}
diff --git a/host/lib/usrp/cores/user_settings_core_3000.hpp b/host/lib/usrp/cores/user_settings_core_3000.hpp
new file mode 100644
index 000000000..6891b9e81
--- /dev/null
+++ b/host/lib/usrp/cores/user_settings_core_3000.hpp
@@ -0,0 +1,35 @@
+//
+// Copyright 2012 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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_USER_SETTINGS_CORE_3000_HPP
+#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP
+
+#include <uhd/config.hpp>
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <uhd/types/wb_iface.hpp>
+
+class user_settings_core_3000 : public uhd::wb_iface {
+public:
+ virtual ~user_settings_core_3000() {}
+
+ static sptr make(
+ wb_iface::sptr iface,
+ const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr);
+};
+
+#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP */
diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt
index 6cebecdbf..183496734 100644
--- a/host/lib/usrp/dboard/CMakeLists.txt
+++ b/host/lib/usrp/dboard/CMakeLists.txt
@@ -39,5 +39,9 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp
${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp
${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_ctrl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_experts.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_gain_tables.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/db_twinrx.cpp
)
diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp
index 2b30dab52..941a80ea4 100644
--- a/host/lib/usrp/dboard/db_basic_and_lf.cpp
+++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp
@@ -50,7 +50,7 @@ static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_o
class basic_rx : public rx_dboard_base{
public:
basic_rx(ctor_args_t args, double max_freq);
- ~basic_rx(void);
+ virtual ~basic_rx(void);
private:
double _max_freq;
@@ -59,7 +59,7 @@ private:
class basic_tx : public tx_dboard_base{
public:
basic_tx(ctor_args_t args, double max_freq);
- ~basic_tx(void);
+ virtual ~basic_tx(void);
private:
double _max_freq;
@@ -121,7 +121,7 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){
this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists
this->get_rx_subtree()->create<double>("freq/value")
- .publish(&always_zero_freq);
+ .set_publisher(&always_zero_freq);
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(freq_range_t(-_max_freq, +_max_freq));
this->get_rx_subtree()->create<std::string>("antenna/value")
@@ -176,7 +176,7 @@ basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){
this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists
this->get_tx_subtree()->create<double>("freq/value")
- .publish(&always_zero_freq);
+ .set_publisher(&always_zero_freq);
this->get_tx_subtree()->create<meta_range_t>("freq/range")
.set(freq_range_t(-_max_freq, +_max_freq));
this->get_tx_subtree()->create<std::string>("antenna/value")
diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp
index 9d04d8e16..6e1846fb8 100644
--- a/host/lib/usrp/dboard/db_dbsrx.cpp
+++ b/host/lib/usrp/dboard/db_dbsrx.cpp
@@ -66,7 +66,7 @@ static const double usrp1_gpio_clock_rate_limit = 4e6;
class dbsrx : public rx_dboard_base{
public:
dbsrx(ctor_args_t args);
- ~dbsrx(void);
+ virtual ~dbsrx(void);
private:
double _lo_freq;
@@ -204,16 +204,16 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<std::string>("name")
.set("DBSRX");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&dbsrx::get_locked, this));
+ .set_publisher(boost::bind(&dbsrx::get_locked, this));
BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&dbsrx::set_gain, this, _1, name))
+ .set_coercer(boost::bind(&dbsrx::set_gain, this, _1, name))
.set(dbsrx_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(dbsrx_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1));
+ .set_coercer(boost::bind(&dbsrx::set_lo_freq, this, _1));
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(dbsrx_freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
@@ -227,7 +227,7 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<bool>("use_lo_offset")
.set(false);
this->get_rx_subtree()->create<double>("bandwidth/value")
- .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1));
+ .set_coercer(boost::bind(&dbsrx::set_bandwidth, this, _1));
this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
.set(dbsrx_bandwidth_range);
diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp
index 1debe3c8f..11d706ed6 100644
--- a/host/lib/usrp/dboard/db_dbsrx2.cpp
+++ b/host/lib/usrp/dboard/db_dbsrx2.cpp
@@ -60,7 +60,7 @@ static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_
class dbsrx2 : public rx_dboard_base{
public:
dbsrx2(ctor_args_t args);
- ~dbsrx2(void);
+ virtual ~dbsrx2(void);
private:
double _lo_freq;
@@ -191,16 +191,16 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<std::string>("name")
.set("DBSRX2");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&dbsrx2::get_locked, this));
+ .set_publisher(boost::bind(&dbsrx2::get_locked, this));
BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name))
+ .set_coercer(boost::bind(&dbsrx2::set_gain, this, _1, name))
.set(dbsrx2_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(dbsrx2_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1))
+ .set_coercer(boost::bind(&dbsrx2::set_lo_freq, this, _1))
.set(dbsrx2_freq_range.start());
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(dbsrx2_freq_range);
@@ -218,7 +218,7 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){
double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX);
this->get_rx_subtree()->create<double>("bandwidth/value")
- .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1))
+ .set_coercer(boost::bind(&dbsrx2::set_bandwidth, this, _1))
.set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass
//default to anti-alias at different codec_rate
this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp
index 523927d49..c7cc52d73 100644
--- a/host/lib/usrp/dboard/db_e3x0.cpp
+++ b/host/lib/usrp/dboard/db_e3x0.cpp
@@ -29,7 +29,7 @@ class e310_dboard : public xcvr_dboard_base{
public:
e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {}
- ~e310_dboard(void) {}
+ virtual ~e310_dboard(void) {}
};
/***********************************************************************
@@ -40,7 +40,7 @@ class e300_dboard : public xcvr_dboard_base{
public:
e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {}
- ~e300_dboard(void) {}
+ virtual ~e300_dboard(void) {}
};
/***********************************************************************
diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp
index 1342c913d..dbb1600ec 100644
--- a/host/lib/usrp/dboard/db_rfx.cpp
+++ b/host/lib/usrp/dboard/db_rfx.cpp
@@ -78,7 +78,7 @@ public:
const freq_range_t &freq_range,
bool rx_div2, bool tx_div2
);
- ~rfx_xcvr(void);
+ virtual ~rfx_xcvr(void);
private:
const freq_range_t _freq_range;
@@ -183,20 +183,20 @@ rfx_xcvr::rfx_xcvr(
else this->get_rx_subtree()->create<std::string>("name").set("RFX RX");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
+ .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name))
+ .set_coercer(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name))
.set(_rx_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(_rx_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
.set((_freq_range.start() + _freq_range.stop())/2.0);
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_rx_ant, this, _1))
.set("RX2");
this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(rfx_rx_antennas);
@@ -219,14 +219,14 @@ rfx_xcvr::rfx_xcvr(
else this->get_tx_subtree()->create<std::string>("name").set("RFX TX");
this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
+ .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
.set((_freq_range.start() + _freq_range.stop())/2.0);
this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range);
this->get_tx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0));
+ .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0));
this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(rfx_tx_antennas);
this->get_tx_subtree()->create<std::string>("connection").set("IQ");
@@ -248,15 +248,15 @@ rfx_xcvr::rfx_xcvr(
this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables);
//setup the tx atr (this does not change with antenna)
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB);
//setup the rx atr (this does not change with antenna)
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB);
}
rfx_xcvr::~rfx_xcvr(void){
@@ -272,14 +272,14 @@ void rfx_xcvr::set_rx_ant(const std::string &ant){
//set the rx atr regs that change with antenna setting
if (ant == "CAL") {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX );
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX );
}
else {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB |
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB |
((ant == "TX/RX")? ANT_TXRX : ANT_RX2));
}
@@ -292,12 +292,12 @@ void rfx_xcvr::set_tx_ant(const std::string &ant){
//set the tx atr regs that change with antenna setting
if (ant == "CAL") {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB);
}
else {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB);
}
}
diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp
index ce5166c4c..be02cf77a 100644
--- a/host/lib/usrp/dboard/db_sbx_common.cpp
+++ b/host/lib/usrp/dboard/db_sbx_common.cpp
@@ -159,20 +159,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){
else this->get_rx_subtree()->create<std::string>("name").set("SBX/CBX RX");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
+ .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX));
BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name))
+ .set_coercer(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name))
.set(sbx_rx_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(sbx_rx_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
.set((freq_range.start() + freq_range.stop())/2.0);
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_rx_ant, this, _1))
.set("RX2");
this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(sbx_rx_antennas);
@@ -200,20 +200,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){
else this->get_tx_subtree()->create<std::string>("name").set("SBX/CBX TX");
this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
+ .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX));
BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){
this->get_tx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name))
+ .set_coercer(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name))
.set(sbx_tx_gain_ranges[name].start());
this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(sbx_tx_gain_ranges[name]);
}
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
.set((freq_range.start() + freq_range.stop())/2.0);
this->get_tx_subtree()->create<meta_range_t>("freq/range").set(freq_range);
this->get_tx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_tx_ant, this, _1))
.set(sbx_tx_antennas.at(0));
this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(sbx_tx_antennas);
@@ -237,8 +237,8 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){
this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
- //flash LEDs
- flash_leds();
+ //Initialize ATR registers after direction and pin ctrl configuration
+ update_atr();
UHD_LOGV(often) << boost::format(
"SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x"
@@ -265,39 +265,39 @@ void sbx_xcvr::update_atr(void){
//setup the tx atr (this does not change with antenna)
this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_IDLE, 0 | tx_lo_lpf_en \
+ gpio_atr::ATR_REG_IDLE, 0 | tx_lo_lpf_en \
| tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS);
//setup the rx atr (this does not change with antenna)
this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \
+ gpio_atr::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \
| rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS);
//set the RX atr regs that change with antenna setting
this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \
+ gpio_atr::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \
| rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \
| ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2));
this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \
+ gpio_atr::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \
| rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \
| ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2));
this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \
+ gpio_atr::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \
| rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \
| ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2));
//set the TX atr regs that change with antenna setting
this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \
+ gpio_atr::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \
| tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \
| ((_rx_ant != "RX2")? ANT_RX : ANT_TX));
this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \
+ gpio_atr::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \
| tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \
| ((_tx_ant == "CAL")? ANT_RX : ANT_TX));
this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \
+ gpio_atr::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \
| tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \
| ((_tx_ant == "CAL")? ANT_RX : ANT_TX));
}
@@ -352,45 +352,3 @@ sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) {
return sensor_value_t("LO", locked, "locked", "unlocked");
}
-
-
-void sbx_xcvr::flash_leds(void) {
- //Remove LED gpios from ATR control temporarily and set to outputs
- this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK);
- this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK);
- this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO));
- this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \
- TX_LED_TXRX|TX_LED_LD, TX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \
- RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO);
- boost::this_thread::sleep(boost::posix_time::milliseconds(100));
-
- //Put LED gpios back in ATR control and update atr
- this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
- this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
- this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO));
- this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO));
-}
-
diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp
index 4800bbd83..c0e29f263 100644
--- a/host/lib/usrp/dboard/db_sbx_common.hpp
+++ b/host/lib/usrp/dboard/db_sbx_common.hpp
@@ -16,10 +16,15 @@
//
#include <uhd/types/device_addr.hpp>
-
-#include "adf435x_common.hpp"
+#include "adf435x.hpp"
#include "max287x.hpp"
+// LO Related
+#define ADF435X_CE (1 << 3)
+#define ADF435X_PDBRF (1 << 2)
+#define ADF435X_MUXOUT (1 << 1) // INPUT!!!
+#define LOCKDET_MASK (1 << 0) // INPUT!!!
+
// Common IO Pins
#define LO_LPF_EN (1 << 15)
@@ -36,6 +41,8 @@
#define RX_LED_LD (1 << 6) // LED for RX Lock Detect
#define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX
#define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and Baseband
+#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control
+#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control
// TX Attenuator Pins
#define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control
@@ -184,12 +191,16 @@ protected:
class sbx_version3 : public sbx_versionx {
public:
sbx_version3(sbx_xcvr *_self_sbx_xcvr);
- ~sbx_version3(void);
+ virtual ~sbx_version3(void);
double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
/*! This is the registered instance of the wrapper class, sbx_base. */
sbx_xcvr *self_base;
+ private:
+ adf435x_iface::sptr _txlo;
+ adf435x_iface::sptr _rxlo;
+ void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs);
};
/*!
@@ -200,12 +211,16 @@ protected:
class sbx_version4 : public sbx_versionx {
public:
sbx_version4(sbx_xcvr *_self_sbx_xcvr);
- ~sbx_version4(void);
+ virtual ~sbx_version4(void);
double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
/*! This is the registered instance of the wrapper class, sbx_base. */
sbx_xcvr *self_base;
+ private:
+ adf435x_iface::sptr _txlo;
+ adf435x_iface::sptr _rxlo;
+ void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs);
};
/*!
@@ -218,7 +233,7 @@ protected:
class cbx : public sbx_versionx {
public:
cbx(sbx_xcvr *_self_sbx_xcvr);
- ~cbx(void);
+ virtual ~cbx(void);
double set_lo_freq(dboard_iface::unit_t unit, double target_freq);
diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp
index b848097d1..76ad7b04f 100644
--- a/host/lib/usrp/dboard/db_sbx_version3.cpp
+++ b/host/lib/usrp/dboard/db_sbx_version3.cpp
@@ -16,9 +16,7 @@
//
-#include "adf4350_regs.hpp"
#include "db_sbx_common.hpp"
-#include "../common/adf435x_common.hpp"
#include <uhd/types/tune_request.hpp>
#include <boost/algorithm/string.hpp>
@@ -32,12 +30,21 @@ using namespace boost::assign;
sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) {
//register the handle to our base SBX class
self_base = _self_sbx_xcvr;
+ _txlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_TX, _1));
+ _rxlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_RX, _1));
}
sbx_xcvr::sbx_version3::~sbx_version3(void){
/* NOP */
}
+void sbx_xcvr::sbx_version3::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs)
+{
+ BOOST_FOREACH(boost::uint32_t reg, regs)
+ {
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32);
+ }
+}
/***********************************************************************
* Tuning
@@ -57,95 +64,27 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
- //clip the input
- target_freq = sbx_freq_range.clip(target_freq);
-
- //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
- static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
- (0,23) //adf4350_regs_t::PRESCALER_4_5
- (1,75) //adf4350_regs_t::PRESCALER_8_9
- ;
-
- //map rf divider select output dividers to enums
- static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
- (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1)
- (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2)
- (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4)
- (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8)
- (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16)
- ;
-
- //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
- adf4350_regs_t::prescaler_t prescaler = target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5;
-
- adf435x_tuning_constraints tuning_constraints;
- tuning_constraints.force_frac0 = is_int_n;
- tuning_constraints.band_sel_freq_max = 100e3;
- tuning_constraints.ref_doubler_threshold = 12.5e6;
- tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field
- tuning_constraints.pfd_freq_max = 25e6;
- tuning_constraints.rf_divider_range = uhd::range_t(1, 16);
- tuning_constraints.feedback_after_divider = true;
+ //Select the LO
+ adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo;
+ lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED);
+ lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit));
- double actual_freq;
- adf435x_tuning_settings tuning_settings = tune_adf435x_synth(
- target_freq, self_base->get_iface()->get_clock_rate(unit),
- tuning_constraints, actual_freq);
+ //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ lo_iface->set_prescaler(target_freq > 3e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
- //load the register values
- adf4350_regs_t regs;
+ //Configure the LO
+ double actual_freq = 0.0;
+ actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n);
- if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))
- regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM;
- else
- regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM;
-
- regs.frac_12_bit = tuning_settings.frac_12_bit;
- regs.int_16_bit = tuning_settings.int_16_bit;
- regs.mod_12_bit = tuning_settings.mod_12_bit;
- regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit;
- regs.feedback_select = tuning_constraints.feedback_after_divider ?
- adf4350_regs_t::FEEDBACK_SELECT_DIVIDED :
- adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
- regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
- regs.prescaler = prescaler;
- regs.r_counter_10_bit = tuning_settings.r_counter_10_bit;
- regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ?
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
- regs.reference_doubler = tuning_settings.r_doubler_en ?
- adf4350_regs_t::REFERENCE_DOUBLER_ENABLED :
- adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;
- regs.band_select_clock_div = tuning_settings.band_select_clock_div;
- UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider));
- regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider];
- regs.ldf = is_int_n ?
- adf4350_regs_t::LDF_INT_N :
- adf4350_regs_t::LDF_FRAC_N;
-
- //reset the N and R counter
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
- self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
-
- //write the registers
- //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
- int addr;
-
- for(addr=5; addr>=0; addr--){
- UHD_LOGV(often) << boost::format(
- "SBX SPI Reg (0x%02x): 0x%08x"
- ) % addr % regs.get_reg(addr) << std::endl;
- self_base->get_iface()->write_spi(
- unit, spi_config_t::EDGE_RISE,
- regs.get_reg(addr), 32
- );
+ if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) {
+ lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM);
+ } else {
+ lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM);
}
- //return the actual frequency
- UHD_LOGV(often) << boost::format(
- "SBX tune: actual frequency %f MHz"
- ) % (actual_freq/1e6) << std::endl;
+ //Write to hardware
+ lo_iface->commit();
+
return actual_freq;
}
diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp
index 8f7e747bc..639bce250 100644
--- a/host/lib/usrp/dboard/db_sbx_version4.cpp
+++ b/host/lib/usrp/dboard/db_sbx_version4.cpp
@@ -16,9 +16,7 @@
//
-#include "adf4351_regs.hpp"
#include "db_sbx_common.hpp"
-#include "../common/adf435x_common.hpp"
#include <uhd/types/tune_request.hpp>
#include <boost/algorithm/string.hpp>
@@ -32,6 +30,8 @@ using namespace boost::assign;
sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) {
//register the handle to our base SBX class
self_base = _self_sbx_xcvr;
+ _txlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_TX, _1));
+ _rxlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_RX, _1));
}
@@ -39,6 +39,14 @@ sbx_xcvr::sbx_version4::~sbx_version4(void){
/* NOP */
}
+void sbx_xcvr::sbx_version4::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs)
+{
+ BOOST_FOREACH(boost::uint32_t reg, regs)
+ {
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32);
+ }
+}
+
/***********************************************************************
* Tuning
@@ -58,99 +66,27 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
- //clip the input
- target_freq = sbx_freq_range.clip(target_freq);
-
- //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
- static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
- (0,23) //adf4351_regs_t::PRESCALER_4_5
- (1,75) //adf4351_regs_t::PRESCALER_8_9
- ;
-
- //map rf divider select output dividers to enums
- static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
- (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1)
- (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2)
- (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4)
- (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8)
- (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16)
- (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32)
- (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64)
- ;
-
- //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
- adf4351_regs_t::prescaler_t prescaler = target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5;
-
- adf435x_tuning_constraints tuning_constraints;
- tuning_constraints.force_frac0 = is_int_n;
- tuning_constraints.band_sel_freq_max = 100e3;
- tuning_constraints.ref_doubler_threshold = 12.5e6;
- tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field
- tuning_constraints.pfd_freq_max = 25e6;
- tuning_constraints.rf_divider_range = uhd::range_t(1, 64);
- tuning_constraints.feedback_after_divider = true;
-
- double actual_freq;
- adf435x_tuning_settings tuning_settings = tune_adf435x_synth(
- target_freq, self_base->get_iface()->get_clock_rate(unit),
- tuning_constraints, actual_freq);
-
- //load the register values
- adf4351_regs_t regs;
-
- if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)))
- regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM;
- else
- regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM;
-
- regs.frac_12_bit = tuning_settings.frac_12_bit;
- regs.int_16_bit = tuning_settings.int_16_bit;
- regs.mod_12_bit = tuning_settings.mod_12_bit;
- regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit;
- regs.feedback_select = tuning_constraints.feedback_after_divider ?
- adf4351_regs_t::FEEDBACK_SELECT_DIVIDED :
- adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
- regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE;
- regs.prescaler = prescaler;
- regs.r_counter_10_bit = tuning_settings.r_counter_10_bit;
- regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ?
- adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
- adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
- regs.reference_doubler = tuning_settings.r_doubler_en ?
- adf4351_regs_t::REFERENCE_DOUBLER_ENABLED :
- adf4351_regs_t::REFERENCE_DOUBLER_DISABLED;
- regs.band_select_clock_div = tuning_settings.band_select_clock_div;
- UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider));
- regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider];
- regs.ldf = is_int_n ?
- adf4351_regs_t::LDF_INT_N :
- adf4351_regs_t::LDF_FRAC_N;
-
- //reset the N and R counter
- regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED;
- self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
- regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED;
-
- //write the registers
- //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
- int addr;
-
- boost::uint16_t rx_id = self_base->get_rx_id().to_uint16();
- std::string board_name = (rx_id == 0x0083) ? "SBX-120" : "SBX";
- for(addr=5; addr>=0; addr--){
- UHD_LOGV(often) << boost::format(
- "%s SPI Reg (0x%02x): 0x%08x"
- ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl;
- self_base->get_iface()->write_spi(
- unit, spi_config_t::EDGE_RISE,
- regs.get_reg(addr), 32
- );
+ //Select the LO
+ adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo;
+ lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED);
+ lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit));
+
+ //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ lo_iface->set_prescaler(target_freq > 3.6e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
+
+ //Configure the LO
+ double actual_freq = 0.0;
+ actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n);
+
+ if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) {
+ lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM);
+ } else {
+ lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM);
}
- //return the actual frequency
- UHD_LOGV(often) << boost::format(
- "%s tune: actual frequency %f MHz"
- ) % board_name.c_str() % (actual_freq/1e6) << std::endl;
+ //Write to hardware
+ lo_iface->commit();
+
return actual_freq;
}
diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp
index e9f60f765..0f84cd68a 100644
--- a/host/lib/usrp/dboard/db_tvrx.cpp
+++ b/host/lib/usrp/dboard/db_tvrx.cpp
@@ -134,7 +134,7 @@ static const double reference_freq = 4.0e6;
class tvrx : public rx_dboard_base{
public:
tvrx(ctor_args_t args);
- ~tvrx(void);
+ virtual ~tvrx(void);
private:
uhd::dict<std::string, double> _gains;
@@ -190,12 +190,12 @@ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists
BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&tvrx::set_gain, this, _1, name));
+ .set_coercer(boost::bind(&tvrx::set_gain, this, _1, name));
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(get_tvrx_gain_ranges()[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&tvrx::set_freq, this, _1));
+ .set_coercer(boost::bind(&tvrx::set_freq, this, _1));
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(tvrx_freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp
index ccbac0b5d..6f0604f72 100644
--- a/host/lib/usrp/dboard/db_tvrx2.cpp
+++ b/host/lib/usrp/dboard/db_tvrx2.cpp
@@ -751,7 +751,7 @@ static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_o
class tvrx2 : public rx_dboard_base{
public:
tvrx2(ctor_args_t args);
- ~tvrx2(void);
+ virtual ~tvrx2(void);
private:
double _freq_scalar;
@@ -957,19 +957,19 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<std::string>("name")
.set("TVRX2");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&tvrx2::get_locked, this));
+ .set_publisher(boost::bind(&tvrx2::get_locked, this));
this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi")
- .publish(boost::bind(&tvrx2::get_rssi, this));
+ .set_publisher(boost::bind(&tvrx2::get_rssi, this));
this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature")
- .publish(boost::bind(&tvrx2::get_temp, this));
+ .set_publisher(boost::bind(&tvrx2::get_temp, this));
BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&tvrx2::set_gain, this, _1, name));
+ .set_coercer(boost::bind(&tvrx2::set_gain, this, _1, name));
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(tvrx2_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1));
+ .set_coercer(boost::bind(&tvrx2::set_lo_freq, this, _1));
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(tvrx2_freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
@@ -979,12 +979,12 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){
this->get_rx_subtree()->create<std::string>("connection")
.set(tvrx2_sd_name_to_conn[get_subdev_name()]);
this->get_rx_subtree()->create<bool>("enabled")
- .coerce(boost::bind(&tvrx2::set_enabled, this, _1))
+ .set_coercer(boost::bind(&tvrx2::set_enabled, this, _1))
.set(_enabled);
this->get_rx_subtree()->create<bool>("use_lo_offset")
.set(false);
this->get_rx_subtree()->create<double>("bandwidth/value")
- .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1))
+ .set_coercer(boost::bind(&tvrx2::set_bandwidth, this, _1))
.set(_bandwidth);
this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
.set(tvrx2_bandwidth_range);
diff --git a/host/lib/usrp/dboard/db_twinrx.cpp b/host/lib/usrp/dboard/db_twinrx.cpp
new file mode 100644
index 000000000..e960f134f
--- /dev/null
+++ b/host/lib/usrp/dboard/db_twinrx.cpp
@@ -0,0 +1,337 @@
+//
+// Copyright 2014-15 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "twinrx/twinrx_experts.hpp"
+#include "twinrx/twinrx_ctrl.hpp"
+#include "twinrx/twinrx_io.hpp"
+#include <expert_factory.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/usrp/dboard_manager.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/static.hpp>
+#include "dboard_ctor_args.hpp"
+#include <boost/assign/list_of.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/mutex.hpp>
+//#include <fstream> //Needed for _expert->to_dot() below
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::usrp::dboard::twinrx;
+using namespace uhd::experts;
+
+static const dboard_id_t TWINRX_V100_000_ID(0x91);
+
+/*!
+ * twinrx_rcvr_fe is the dbaord class (dboard_base) that
+ * represents each front-end of a TwinRX board. UHD will
+ * create and hold two instances of this class per TwinRX
+ * dboard.
+ *
+ */
+class twinrx_rcvr_fe : public rx_dboard_base
+{
+public:
+ twinrx_rcvr_fe(
+ ctor_args_t args,
+ expert_container::sptr expert,
+ twinrx_ctrl::sptr ctrl
+ ) :
+ rx_dboard_base(args), _expert(expert), _ctrl(ctrl),
+ _ch_name(dboard_ctor_args_t::cast(args).sd_name)
+ {
+ //---------------------------------------------------------
+ // Add user-visible, channel specific properties to front-end tree
+ //---------------------------------------------------------
+
+ //Generic
+ get_rx_subtree()->create<std::string>("name")
+ .set("TwinRX RX" + _ch_name);
+ get_rx_subtree()->create<bool>("use_lo_offset")
+ .set(false);
+ get_rx_subtree()->create<std::string>("connection")
+ .set(_ch_name == "0" ? "II" : "QQ"); //Ch->ADC port mapping
+ static const double BW = 80e6;
+ get_rx_subtree()->create<double>("bandwidth/value")
+ .set(BW);
+ get_rx_subtree()->create<meta_range_t>("bandwidth/range")
+ .set(freq_range_t(BW, BW));
+
+ //Frequency Specific
+ get_rx_subtree()->create<meta_range_t>("freq/range")
+ .set(freq_range_t(10e6, 6.0e9));
+ expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+ "freq/value", prepend_ch("freq/desired", _ch_name), prepend_ch("freq/coerced", _ch_name),
+ 1.0e9, AUTO_RESOLVE_ON_READ_WRITE);
+ get_rx_subtree()->create<device_addr_t>("tune_args")
+ .set(device_addr_t());
+
+ static const double DEFAULT_IF_FREQ = 150e6;
+ meta_range_t if_freq_range;
+ if_freq_range.push_back(range_t(-DEFAULT_IF_FREQ-(BW/2), -DEFAULT_IF_FREQ+(BW/2)));
+ if_freq_range.push_back(range_t( DEFAULT_IF_FREQ-(BW/2), DEFAULT_IF_FREQ+(BW/2)));
+ get_rx_subtree()->create<meta_range_t>("if_freq/range")
+ .set(if_freq_range);
+ expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+ "if_freq/value", prepend_ch("if_freq/desired", _ch_name), prepend_ch("if_freq/coerced", _ch_name),
+ DEFAULT_IF_FREQ, AUTO_RESOLVE_ON_WRITE);
+
+ //LO Specific
+ get_rx_subtree()->create<meta_range_t>("los/LO1/freq/range")
+ .set(freq_range_t(2.0e9, 6.8e9));
+ expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+ "los/LO1/freq/value", prepend_ch("los/LO1/freq/desired", _ch_name), prepend_ch("los/LO1/freq/coerced", _ch_name),
+ 0.0, AUTO_RESOLVE_ON_READ_WRITE);
+ get_rx_subtree()->create<meta_range_t>("los/LO2/freq/range")
+ .set(freq_range_t(1.0e9, 3.0e9));
+ expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(),
+ "los/LO2/freq/value", prepend_ch("los/LO2/freq/desired", _ch_name), prepend_ch("los/LO2/freq/coerced", _ch_name),
+ 0.0, AUTO_RESOLVE_ON_READ_WRITE);
+ get_rx_subtree()->create<std::vector<std::string> >("los/all/source/options")
+ .set(boost::assign::list_of("internal")("external")("companion")("disabled"));
+ expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+ "los/all/source/value", prepend_ch("los/all/source", _ch_name),
+ "internal", AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(),
+ "los/all/export", prepend_ch("los/all/export", _ch_name),
+ false, AUTO_RESOLVE_ON_WRITE);
+
+ //Gain Specific
+ get_rx_subtree()->create<meta_range_t>("gains/all/range")
+ .set(gain_range_t(0, 95, double(1.0)));
+ expert_factory::add_prop_node<double>(_expert, get_rx_subtree(),
+ "gains/all/value", prepend_ch("gain", _ch_name),
+ 0.0, AUTO_RESOLVE_ON_WRITE);
+ get_rx_subtree()->create<std::vector<std::string> >("gains/all/profile/options")
+ .set(boost::assign::list_of("low-noise")("low-distortion")("default"));
+ expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+ "gains/all/profile/value", prepend_ch("gain_profile", _ch_name),
+ "default", AUTO_RESOLVE_ON_WRITE);
+
+ //Antenna Specific
+ get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
+ .set(boost::assign::list_of("RX1")("RX2"));
+ expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(),
+ "antenna/value", prepend_ch("antenna", _ch_name),
+ (_ch_name == "0" ? "RX1" : "RX2"), AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(),
+ "enabled", prepend_ch("enabled", _ch_name),
+ false, AUTO_RESOLVE_ON_WRITE);
+
+ //Readback
+ get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
+ .set_publisher(boost::bind(&twinrx_rcvr_fe::get_lo_locked, this));
+
+ //---------------------------------------------------------
+ // Add internal channel-specific data nodes to expert
+ //---------------------------------------------------------
+ expert_factory::add_data_node<lo_inj_side_t>(_expert,
+ prepend_ch("ch/LO1/inj_side", _ch_name), INJ_LOW_SIDE);
+ expert_factory::add_data_node<lo_inj_side_t>(_expert,
+ prepend_ch("ch/LO2/inj_side", _ch_name), INJ_LOW_SIDE);
+ expert_factory::add_data_node<twinrx_ctrl::signal_path_t>(_expert,
+ prepend_ch("ch/signal_path", _ch_name), twinrx_ctrl::PATH_LOWBAND);
+ expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert,
+ prepend_ch("ch/lb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
+ expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert,
+ prepend_ch("ch/hb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
+ expert_factory::add_data_node<bool>(_expert,
+ prepend_ch("ch/lb_preamp_presel", _ch_name), false);
+ expert_factory::add_data_node<bool>(_expert,
+ prepend_ch("ant/lb_preamp_presel", _ch_name), false);
+ expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert,
+ prepend_ch("ch/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
+ expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert,
+ prepend_ch("ant/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
+ expert_factory::add_data_node<bool>(_expert,
+ prepend_ch("ch/preamp2", _ch_name), false);
+ expert_factory::add_data_node<bool>(_expert,
+ prepend_ch("ant/preamp2", _ch_name), false);
+ expert_factory::add_data_node<boost::uint8_t>(_expert,
+ prepend_ch("ch/input_atten", _ch_name), 0);
+ expert_factory::add_data_node<boost::uint8_t>(_expert,
+ prepend_ch("ant/input_atten", _ch_name), 0);
+ expert_factory::add_data_node<boost::uint8_t>(_expert,
+ prepend_ch("ch/lb_atten", _ch_name), 0);
+ expert_factory::add_data_node<boost::uint8_t>(_expert,
+ prepend_ch("ch/hb_atten", _ch_name), 0);
+ expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert,
+ prepend_ch("ch/LO1/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
+ expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert,
+ prepend_ch("ch/LO2/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
+ expert_factory::add_data_node<lo_synth_mapping_t>(_expert,
+ prepend_ch("synth/LO1/mapping", _ch_name), MAPPING_NONE);
+ expert_factory::add_data_node<lo_synth_mapping_t>(_expert,
+ prepend_ch("synth/LO2/mapping", _ch_name), MAPPING_NONE);
+
+ }
+
+ virtual ~twinrx_rcvr_fe(void)
+ {
+ }
+
+ sensor_value_t get_lo_locked()
+ {
+ bool locked = true;
+ twinrx_ctrl::channel_t ch = (_ch_name == "0") ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+ locked &= _ctrl->read_lo1_locked(ch);
+ locked &= _ctrl->read_lo2_locked(ch);
+ return sensor_value_t("LO", locked, "locked", "unlocked");
+ }
+
+private:
+ expert_container::sptr _expert;
+ twinrx_ctrl::sptr _ctrl;
+ const std::string _ch_name;
+};
+
+/*!
+ * twinrx_rcvr is the top-level container for each
+ * TwinRX board. UHD will hold one instance of this
+ * class per TwinRX dboard. This class is responsible
+ * for owning all the control classes for the board.
+ *
+ */
+class twinrx_rcvr : public rx_dboard_base
+{
+public:
+ typedef boost::shared_ptr<twinrx_rcvr> sptr;
+
+ twinrx_rcvr(ctor_args_t args) : rx_dboard_base(args)
+ {
+ _db_iface = get_iface();
+ twinrx_gpio::sptr gpio_iface = boost::make_shared<twinrx_gpio>(_db_iface);
+ twinrx_cpld_regmap::sptr cpld_regs = boost::make_shared<twinrx_cpld_regmap>();
+ cpld_regs->initialize(*gpio_iface, false);
+ _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs);
+ _expert = expert_factory::create_container("twinrx_expert");
+ }
+
+ virtual ~twinrx_rcvr(void)
+ {
+ }
+
+ inline expert_container::sptr get_expert() {
+ return _expert;
+ }
+
+ inline twinrx_ctrl::sptr get_ctrl() {
+ return _ctrl;
+ }
+
+ virtual void initialize()
+ {
+ //---------------------------------------------------------
+ // Add internal channel-agnostic data nodes to expert
+ //---------------------------------------------------------
+ expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert,
+ "com/LO1/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
+ expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert,
+ "com/LO2/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
+ expert_factory::add_data_node<twinrx_ctrl::antenna_mapping_t>(_expert,
+ "com/ant_mapping", twinrx_ctrl::ANTX_NATIVE);
+ expert_factory::add_data_node<twinrx_ctrl::cal_mode_t>(_expert,
+ "com/cal_mode", twinrx_ctrl::CAL_DISABLED);
+ expert_factory::add_data_node<bool>(_expert,
+ "com/synth/LO1/hopping_enabled", false);
+ expert_factory::add_data_node<bool>(_expert,
+ "com/synth/LO2/hopping_enabled", false);
+
+ //---------------------------------------------------------
+ // Add workers to expert
+ //---------------------------------------------------------
+ //Channel (front-end) specific
+ BOOST_FOREACH(const std::string& fe, _fe_names) {
+ expert_factory::add_worker_node<twinrx_freq_path_expert>(_expert, _expert->node_retriever(), fe);
+ expert_factory::add_worker_node<twinrx_freq_coercion_expert>(_expert, _expert->node_retriever(), fe);
+ expert_factory::add_worker_node<twinrx_chan_gain_expert>(_expert, _expert->node_retriever(), fe);
+ expert_factory::add_worker_node<twinrx_nyquist_expert>(_expert, _expert->node_retriever(), fe, _db_iface);
+ }
+
+ //Channel (front-end) agnostic
+ expert_factory::add_worker_node<twinrx_lo_config_expert>(_expert, _expert->node_retriever());
+ expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO1);
+ expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO2);
+ expert_factory::add_worker_node<twinrx_antenna_expert>(_expert, _expert->node_retriever());
+ expert_factory::add_worker_node<twinrx_ant_gain_expert>(_expert, _expert->node_retriever());
+ expert_factory::add_worker_node<twinrx_settings_expert>(_expert, _expert->node_retriever(), _ctrl);
+
+ /*//Expert debug code
+ std::ofstream dot_file("/tmp/twinrx.dot", std::ios::out);
+ dot_file << _expert->to_dot();
+ dot_file.close();
+ */
+
+ _expert->debug_audit();
+ _expert->resolve_all(true);
+ }
+
+ static dboard_base::sptr make_twinrx_fe(dboard_base::ctor_args_t args)
+ {
+ const dboard_ctor_args_t& db_args = dboard_ctor_args_t::cast(args);
+ sptr container = boost::dynamic_pointer_cast<twinrx_rcvr>(db_args.rx_container);
+ if (container) {
+ dboard_base::sptr fe = dboard_base::sptr(
+ new twinrx_rcvr_fe(args, container->get_expert(), container->get_ctrl()));
+ container->add_twinrx_fe(db_args.sd_name);
+ return fe;
+ } else {
+ throw uhd::assertion_error("error creating twinrx frontend");
+ }
+ }
+
+protected:
+ inline void add_twinrx_fe(const std::string& name) {
+ _fe_names.push_back(name);
+ }
+
+private:
+ typedef std::map<std::string, dboard_base::sptr> twinrx_fe_map_t;
+
+ dboard_iface::sptr _db_iface;
+ twinrx_ctrl::sptr _ctrl;
+ std::vector<std::string> _fe_names;
+ expert_container::sptr _expert;
+};
+
+/*!
+ * Initialization Sequence for each TwinRX board:
+ * - make_twinrx_container is called which creates an instance of twinrx_rcvr
+ * - twinrx_rcvr::make_twinrx_fe is called with channel "0" which creates an instance of twinrx_rcvr_fe
+ * - twinrx_rcvr::make_twinrx_fe is called with channel "1" which creates an instance of twinrx_rcvr_fe
+ * - twinrx_rcvr::initialize is called with finishes the init sequence
+ *
+ */
+static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args)
+{
+ return dboard_base::sptr(new twinrx_rcvr(args));
+}
+
+UHD_STATIC_BLOCK(reg_twinrx_dboards)
+{
+ dboard_manager::register_dboard_restricted(
+ TWINRX_V100_000_ID,
+ &twinrx_rcvr::make_twinrx_fe,
+ "TwinRX v1.0",
+ boost::assign::list_of("0")("1"),
+ &make_twinrx_container
+ );
+}
diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp
index 15a4d17a9..be3bdd17d 100644
--- a/host/lib/usrp/dboard/db_ubx.cpp
+++ b/host/lib/usrp/dboard/db_ubx.cpp
@@ -27,7 +27,9 @@
#include <uhd/usrp/dboard_manager.hpp>
#include <uhd/utils/assert_has.hpp>
#include <uhd/utils/log.hpp>
+#include <uhd/utils/msg.hpp>
#include <uhd/utils/static.hpp>
+#include <uhd/utils/safe_call.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/math/special_functions/round.hpp>
@@ -319,14 +321,14 @@ public:
write_gpio();
// Configure ATR
- _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _tx_gpio_reg.atr_idle);
- _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx);
- _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx);
- _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex);
- _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _rx_gpio_reg.atr_idle);
- _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx);
- _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx);
- _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex);
+ _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle);
+ _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx);
+ _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx);
+ _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex);
+ _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle);
+ _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx);
+ _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx);
+ _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex);
// Engage ATR control (1 is ATR control, 0 is manual control)
_iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask);
@@ -385,23 +387,23 @@ public:
get_rx_subtree()->create<std::vector<std::string> >("power_mode/options")
.set(ubx_power_modes);
get_rx_subtree()->create<std::string>("power_mode/value")
- .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1))
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_power_mode, this, _1))
.set("performance");
get_rx_subtree()->create<std::vector<std::string> >("xcvr_mode/options")
.set(ubx_xcvr_modes);
get_rx_subtree()->create<std::string>("xcvr_mode/value")
- .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1))
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1))
.set("FDX");
get_tx_subtree()->create<std::vector<std::string> >("power_mode/options")
.set(ubx_power_modes);
get_tx_subtree()->create<std::string>("power_mode/value")
- .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1))
- .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value")));
+ .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1))
+ .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value")));
get_tx_subtree()->create<std::vector<std::string> >("xcvr_mode/options")
.set(ubx_xcvr_modes);
get_tx_subtree()->create<std::string>("xcvr_mode/value")
- .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1))
- .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value")));
+ .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1))
+ .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value")));
////////////////////////////////////////////////////////////////////
// Register TX properties
@@ -410,20 +412,20 @@ public:
get_tx_subtree()->create<device_addr_t>("tune_args")
.set(device_addr_t());
get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO"));
+ .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "TXLO"));
get_tx_subtree()->create<double>("gains/PGA0/value")
- .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0);
+ .set_coercer(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0);
get_tx_subtree()->create<meta_range_t>("gains/PGA0/range")
.set(ubx_tx_gain_range);
get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1))
+ .set_coercer(boost::bind(&ubx_xcvr::set_tx_freq, this, _1))
.set(ubx_freq_range.start());
get_tx_subtree()->create<meta_range_t>("freq/range")
.set(ubx_freq_range);
get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(ubx_tx_antennas);
get_tx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_tx_ant, this, _1))
.set(ubx_tx_antennas.at(0));
get_tx_subtree()->create<std::string>("connection")
.set("QI");
@@ -436,7 +438,7 @@ public:
get_tx_subtree()->create<meta_range_t>("bandwidth/range")
.set(freq_range_t(bw, bw));
get_tx_subtree()->create<int64_t>("sync_delay")
- .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1))
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1))
.set(-8);
////////////////////////////////////////////////////////////////////
@@ -446,21 +448,21 @@ public:
get_rx_subtree()->create<device_addr_t>("tune_args")
.set(device_addr_t());
get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO"));
+ .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "RXLO"));
get_rx_subtree()->create<double>("gains/PGA0/value")
- .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1))
+ .set_coercer(boost::bind(&ubx_xcvr::set_rx_gain, this, _1))
.set(0);
get_rx_subtree()->create<meta_range_t>("gains/PGA0/range")
.set(ubx_rx_gain_range);
get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1))
+ .set_coercer(boost::bind(&ubx_xcvr::set_rx_freq, this, _1))
.set(ubx_freq_range.start());
get_rx_subtree()->create<meta_range_t>("freq/range")
.set(ubx_freq_range);
get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(ubx_rx_antennas);
get_rx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2");
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2");
get_rx_subtree()->create<std::string>("connection")
.set("IQ");
get_rx_subtree()->create<bool>("enabled")
@@ -472,35 +474,38 @@ public:
get_rx_subtree()->create<meta_range_t>("bandwidth/range")
.set(freq_range_t(bw, bw));
get_rx_subtree()->create<int64_t>("sync_delay")
- .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1))
+ .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1))
.set(-8);
}
- ~ubx_xcvr(void)
+ virtual ~ubx_xcvr(void)
{
- // Shutdown synthesizers
- _txlo1->shutdown();
- _txlo2->shutdown();
- _rxlo1->shutdown();
- _rxlo2->shutdown();
+ UHD_SAFE_CALL
+ (
+ // Shutdown synthesizers
+ _txlo1->shutdown();
+ _txlo2->shutdown();
+ _rxlo1->shutdown();
+ _rxlo2->shutdown();
- // Reset CPLD values
- _cpld_reg.value = 0;
- write_cpld_reg();
+ // Reset CPLD values
+ _cpld_reg.value = 0;
+ write_cpld_reg();
- // Reset GPIO values
- set_gpio_field(TX_GAIN, 0);
- set_gpio_field(CPLD_RST_N, 0);
- set_gpio_field(RX_ANT, 1);
- set_gpio_field(TX_EN_N, 1);
- set_gpio_field(RX_EN_N, 1);
- set_gpio_field(SPI_ADDR, 0x7);
- set_gpio_field(RX_GAIN, 0);
- set_gpio_field(TXLO1_SYNC, 0);
- set_gpio_field(TXLO2_SYNC, 0);
- set_gpio_field(RXLO1_SYNC, 0);
- set_gpio_field(RXLO1_SYNC, 0);
- write_gpio();
+ // Reset GPIO values
+ set_gpio_field(TX_GAIN, 0);
+ set_gpio_field(CPLD_RST_N, 0);
+ set_gpio_field(RX_ANT, 1);
+ set_gpio_field(TX_EN_N, 1);
+ set_gpio_field(RX_EN_N, 1);
+ set_gpio_field(SPI_ADDR, 0x7);
+ set_gpio_field(RX_GAIN, 0);
+ set_gpio_field(TXLO1_SYNC, 0);
+ set_gpio_field(TXLO2_SYNC, 0);
+ set_gpio_field(RXLO1_SYNC, 0);
+ set_gpio_field(RXLO1_SYNC, 0);
+ write_gpio();
+ )
}
private:
@@ -638,23 +643,23 @@ private:
uint16_t mask = lo1_field_info.mask | lo2_field_info.mask;
dboard_iface::unit_t unit = lo1_field_info.unit;
UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, value, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, value, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, value, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask);
cmd_time += uhd::time_spec_t(1/pfd_freq);
_iface->set_command_time(cmd_time);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, value, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask);
// De-assert SYNC
// Head of line blocking means the command time does not need to be set.
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, 0, mask);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, 0, mask);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, 0, mask);
- _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, 0, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask);
+ _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask);
}
}
@@ -760,6 +765,20 @@ private:
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
UHD_LOGV(rarely) << boost::format("UBX TX: the requested frequency is %f MHz") % (freq/1e6) << std::endl;
+ double target_pfd_freq = _tx_target_pfd_freq;
+ if (is_int_n and tune_args.has_key("int_n_step"))
+ {
+ target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq);
+ if (target_pfd_freq > _tx_target_pfd_freq)
+ {
+ UHD_MSG(warning)
+ << boost::format("Requested int_n_step of %f MHz too large, clipping to %f MHz")
+ % (target_pfd_freq/1e6)
+ % (_tx_target_pfd_freq/1e6)
+ << std::endl;
+ target_pfd_freq = _tx_target_pfd_freq;
+ }
+ }
// Clip the frequency to the valid range
freq = ubx_freq_range.clip(freq);
@@ -796,10 +815,10 @@ private:
set_cpld_field(TXLB_SEL, 1);
set_cpld_field(TXHB_SEL, 0);
// Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage)
- freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
- freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz)))
@@ -809,7 +828,7 @@ private:
set_cpld_field(TXLO1_FSEL1, 1);
set_cpld_field(TXLB_SEL, 0);
set_cpld_field(TXHB_SEL, 1);
- freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz)))
@@ -819,7 +838,7 @@ private:
set_cpld_field(TXLO1_FSEL1, 1);
set_cpld_field(TXLB_SEL, 0);
set_cpld_field(TXHB_SEL, 1);
- freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz)))
@@ -829,7 +848,7 @@ private:
set_cpld_field(TXLO1_FSEL1, 0);
set_cpld_field(TXLB_SEL, 0);
set_cpld_field(TXHB_SEL, 1);
- freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz)))
@@ -839,7 +858,7 @@ private:
set_cpld_field(TXLO1_FSEL1, 0);
set_cpld_field(TXLB_SEL, 0);
set_cpld_field(TXHB_SEL, 1);
- freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz)))
@@ -849,7 +868,7 @@ private:
set_cpld_field(TXLO1_FSEL1, 0);
set_cpld_field(TXLB_SEL, 0);
set_cpld_field(TXHB_SEL, 1);
- freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n);
+ freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
@@ -902,6 +921,20 @@ private:
property_tree::sptr subtree = this->get_rx_subtree();
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+ double target_pfd_freq = _rx_target_pfd_freq;
+ if (is_int_n and tune_args.has_key("int_n_step"))
+ {
+ target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq);
+ if (target_pfd_freq > _rx_target_pfd_freq)
+ {
+ UHD_MSG(warning)
+ << boost::format("Requested int_n_step of %f Mhz too large, clipping to %f MHz")
+ % (target_pfd_freq/1e6)
+ % (_rx_target_pfd_freq/1e6)
+ << std::endl;
+ target_pfd_freq = _rx_target_pfd_freq;
+ }
+ }
// Clip the frequency to the valid range
freq = ubx_freq_range.clip(freq);
@@ -940,10 +973,10 @@ private:
set_cpld_field(RXLB_SEL, 1);
set_cpld_field(RXHB_SEL, 0);
// Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage)
- freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
- freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 100*fMHz) && (freq < 500*fMHz))
@@ -956,10 +989,10 @@ private:
set_cpld_field(RXLB_SEL, 1);
set_cpld_field(RXHB_SEL, 0);
// Set LO1 to IF of 2440 (center of filter)
- freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
// Set LO2 to IF minus desired frequency
- freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 500*fMHz) && (freq < 800*fMHz))
@@ -971,7 +1004,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 1);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 800*fMHz) && (freq < 1000*fMHz))
@@ -983,7 +1016,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 1);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz))
@@ -995,7 +1028,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 0);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz))
@@ -1007,7 +1040,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 0);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz))
@@ -1019,7 +1052,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 0);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
}
else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz))
@@ -1031,7 +1064,7 @@ private:
set_cpld_field(RXLO1_FSEL1, 0);
set_cpld_field(RXLB_SEL, 0);
set_cpld_field(RXHB_SEL, 1);
- freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n);
+ freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
_rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
}
diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp
index 97357bc90..6539e798a 100644
--- a/host/lib/usrp/dboard/db_wbx_common.cpp
+++ b/host/lib/usrp/dboard/db_wbx_common.cpp
@@ -69,17 +69,17 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){
this->get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t());
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX));
+ .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX));
BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name))
+ .set_coercer(boost::bind(&wbx_base::set_rx_gain, this, _1, name))
.set(wbx_rx_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(wbx_rx_gain_ranges[name]);
}
this->get_rx_subtree()->create<std::string>("connection").set("IQ");
this->get_rx_subtree()->create<bool>("enabled")
- .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_base::set_rx_enabled, this, _1))
.set(true); //start enabled
this->get_rx_subtree()->create<bool>("use_lo_offset").set(false);
@@ -94,7 +94,7 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){
this->get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t());
this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX));
+ .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX));
this->get_tx_subtree()->create<std::string>("connection").set("IQ");
this->get_tx_subtree()->create<bool>("use_lo_offset").set(false);
@@ -156,3 +156,9 @@ sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){
const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;
return sensor_value_t("LO", locked, "locked", "unlocked");
}
+
+void wbx_base::wbx_versionx::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs) {
+ BOOST_FOREACH(boost::uint32_t reg, regs) {
+ self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32);
+ }
+}
diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp
index 6a4224048..0e339e4a3 100644
--- a/host/lib/usrp/dboard/db_wbx_common.hpp
+++ b/host/lib/usrp/dboard/db_wbx_common.hpp
@@ -19,8 +19,13 @@
#define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP
#include <uhd/types/device_addr.hpp>
+#include "adf435x.hpp"
-#include "../common/adf435x_common.hpp"
+// LO Related
+#define ADF435X_CE (1 << 3)
+#define ADF435X_PDBRF (1 << 2)
+#define ADF435X_MUXOUT (1 << 1) // INPUT!!!
+#define LOCKDET_MASK (1 << 0) // INPUT!!!
// TX IO Pins
#define TX_PUP_5V (1 << 7) // enables 5.0V power supply
@@ -40,6 +45,9 @@
#define TX_ATTN_1 (1 << 1)
#define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control
+#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control
+#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control
+
// Mixer functions
#define TX_MIXER_ENB (TXMOD_EN|ADF435X_PDBRF) // for v3, TXMOD_EN tied to ADF435X_PDBRF rather than separate
#define TX_MIXER_DIS 0
@@ -142,6 +150,10 @@ protected:
property_tree::sptr get_tx_subtree(void){
return self_base->get_tx_subtree();
}
+
+ adf435x_iface::sptr _txlo;
+ adf435x_iface::sptr _rxlo;
+ void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs);
};
@@ -153,7 +165,7 @@ protected:
class wbx_version2 : public wbx_versionx {
public:
wbx_version2(wbx_base *_self_wbx_base);
- ~wbx_version2(void);
+ virtual ~wbx_version2(void);
double set_tx_gain(double gain, const std::string &name);
void set_tx_enabled(bool enb);
@@ -168,7 +180,7 @@ protected:
class wbx_version3 : public wbx_versionx {
public:
wbx_version3(wbx_base *_self_wbx_base);
- ~wbx_version3(void);
+ virtual ~wbx_version3(void);
double set_tx_gain(double gain, const std::string &name);
void set_tx_enabled(bool enb);
@@ -183,7 +195,7 @@ protected:
class wbx_version4 : public wbx_versionx {
public:
wbx_version4(wbx_base *_self_wbx_base);
- ~wbx_version4(void);
+ virtual ~wbx_version4(void);
double set_tx_gain(double gain, const std::string &name);
void set_tx_enabled(bool enb);
diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp
index c8f2be155..062e1294b 100644
--- a/host/lib/usrp/dboard/db_wbx_simple.cpp
+++ b/host/lib/usrp/dboard/db_wbx_simple.cpp
@@ -46,7 +46,7 @@ static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")(
class wbx_simple : public wbx_base{
public:
wbx_simple(ctor_args_t args);
- ~wbx_simple(void);
+ virtual ~wbx_simple(void);
private:
void set_rx_ant(const std::string &ant);
@@ -88,7 +88,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){
std::string(str(boost::format("%s+GDB") % this->get_rx_subtree()->access<std::string>("name").get()
)));
this->get_rx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_simple::set_rx_ant, this, _1))
.set("RX2");
this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(wbx_rx_antennas);
@@ -100,7 +100,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){
std::string(str(boost::format("%s+GDB") % this->get_tx_subtree()->access<std::string>("name").get()
)));
this->get_tx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_simple::set_tx_ant, this, _1))
.set(wbx_tx_antennas.at(0));
this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(wbx_tx_antennas);
@@ -112,14 +112,14 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){
this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO);
//setup ATR for the antenna switches (constant)
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
-
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
+
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
}
wbx_simple::~wbx_simple(void){
@@ -138,14 +138,14 @@ void wbx_simple::set_rx_ant(const std::string &ant){
//write the new antenna setting to atr regs
if (_rx_ant == "CAL") {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO);
}
else {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO);
}
}
@@ -154,11 +154,11 @@ void wbx_simple::set_tx_ant(const std::string &ant){
//write the new antenna setting to atr regs
if (ant == "CAL") {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO);
}
else {
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO);
}
}
diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp
index 03c8f37e9..489291881 100644
--- a/host/lib/usrp/dboard/db_wbx_version2.cpp
+++ b/host/lib/usrp/dboard/db_wbx_version2.cpp
@@ -16,8 +16,6 @@
//
#include "db_wbx_common.hpp"
-#include "adf4350_regs.hpp"
-#include "../common/adf435x_common.hpp"
#include <uhd/types/tune_request.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/types/dict.hpp>
@@ -77,13 +75,15 @@ static double tx_pga0_gain_to_dac_volts(double &gain){
wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) {
//register our handle on the primary wbx_base instance
self_base = _self_wbx_base;
+ _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1));
+ _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1));
////////////////////////////////////////////////////////////////////
// Register RX properties
////////////////////////////////////////////////////////////////////
this->get_rx_subtree()->create<std::string>("name").set("WBXv2 RX");
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
.set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0);
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range);
@@ -93,17 +93,17 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) {
this->get_tx_subtree()->create<std::string>("name").set("WBXv2 TX");
BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){
self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name))
+ .set_coercer(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name))
.set(wbx_v2_tx_gain_ranges[name].start());
self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(wbx_v2_tx_gain_ranges[name]);
}
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
.set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0);
this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range);
this->get_tx_subtree()->create<bool>("enabled")
- .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1))
.set(true); //start enabled
//set attenuator control bits
@@ -117,15 +117,15 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) {
self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF435X_CE|RXBB_PDB|ADF435X_PDBRF|RX_ATTN_MASK);
//setup ATR for the mixer enables (always enabled to prevent phase slip between bursts)
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
-
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod);
+
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
}
wbx_base::wbx_version2::~wbx_version2(void){
@@ -178,111 +178,45 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar
: self_base->get_tx_subtree();
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+ double reference_freq = self_base->get_iface()->get_clock_rate(unit);
- //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
- static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
- (0,23) //adf4350_regs_t::PRESCALER_4_5
- (1,75) //adf4350_regs_t::PRESCALER_8_9
- ;
-
- //map rf divider select output dividers to enums
- static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
- (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1)
- (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2)
- (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4)
- (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8)
- (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16)
- ;
+ //Select the LO
+ adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo;
+ lo_iface->set_reference_freq(reference_freq);
- double reference_freq = self_base->get_iface()->get_clock_rate(unit);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
- //frequency must 2x the target frequency. This introduces a 180 degree
- //phase ambiguity
+ //frequency must 2x the target frequency
double synth_target_freq = target_freq * 2;
- adf4350_regs_t::prescaler_t prescaler =
- synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5;
+ //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ lo_iface->set_prescaler(synth_target_freq > 3e9 ?
+ adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
- adf435x_tuning_constraints tuning_constraints;
- tuning_constraints.force_frac0 = is_int_n;
- tuning_constraints.band_sel_freq_max = 100e3;
- tuning_constraints.ref_doubler_threshold = 12.5e6;
- tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095);
- tuning_constraints.pfd_freq_max = 25e6;
- tuning_constraints.rf_divider_range = uhd::range_t(1, 16);
//The feedback of the divided frequency must be disabled whenever the target frequency
//divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value.
//If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD
//frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz)
//will have too much ambiguity to synchronize.
- tuning_constraints.feedback_after_divider =
- (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]);
+ lo_iface->set_feedback_select(
+ (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ?
+ adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL));
- double synth_actual_freq = 0;
- adf435x_tuning_settings tuning_settings = tune_adf435x_synth(
- synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq);
+ double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
//actual_freq must /2 the synth_actual_freq
double actual_freq = synth_actual_freq / 2;
- //load the register values
- adf4350_regs_t regs;
-
- if (unit == dboard_iface::UNIT_RX)
- regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM
- : adf4350_regs_t::OUTPUT_POWER_2DBM;
- else
- regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM
- : adf4350_regs_t::OUTPUT_POWER_M1DBM;
-
- regs.frac_12_bit = tuning_settings.frac_12_bit;
- regs.int_16_bit = tuning_settings.int_16_bit;
- regs.mod_12_bit = tuning_settings.mod_12_bit;
- regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit;
- regs.feedback_select = tuning_constraints.feedback_after_divider ?
- adf4350_regs_t::FEEDBACK_SELECT_DIVIDED :
- adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
- regs.clock_div_mode = tuning_constraints.feedback_after_divider ?
- adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE :
- adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK;
- regs.prescaler = prescaler;
- regs.r_counter_10_bit = tuning_settings.r_counter_10_bit;
- regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ?
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
- regs.reference_doubler = tuning_settings.r_doubler_en ?
- adf4350_regs_t::REFERENCE_DOUBLER_ENABLED :
- adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;
- regs.band_select_clock_div = tuning_settings.band_select_clock_div;
- UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider));
- regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider];
- regs.ldf = is_int_n ?
- adf4350_regs_t::LDF_INT_N :
- adf4350_regs_t::LDF_FRAC_N;
-
- //reset the N and R counter
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
- self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
-
- //write the registers
- //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
- int addr;
-
- for(addr=5; addr>=0; addr--){
- UHD_LOGV(often) << boost::format(
- "WBX SPI Reg (0x%02x): 0x%08x"
- ) % addr % regs.get_reg(addr) << std::endl;
- self_base->get_iface()->write_spi(
- unit, spi_config_t::EDGE_RISE,
- regs.get_reg(addr), 32
- );
+ if (unit == dboard_iface::UNIT_RX) {
+ lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM);
+ } else {
+ lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM);
}
- //return the actual frequency
- UHD_LOGV(often) << boost::format(
- "WBX tune: actual frequency %f MHz"
- ) % (actual_freq/1e6) << std::endl;
+ //Write to hardware
+ lo_iface->commit();
+
return actual_freq;
}
diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp
index a559a7b14..4dcf3bb71 100644
--- a/host/lib/usrp/dboard/db_wbx_version3.cpp
+++ b/host/lib/usrp/dboard/db_wbx_version3.cpp
@@ -16,8 +16,6 @@
//
#include "db_wbx_common.hpp"
-#include "adf4350_regs.hpp"
-#include "../common/adf435x_common.hpp"
#include <uhd/utils/log.hpp>
#include <uhd/types/dict.hpp>
#include <uhd/types/ranges.hpp>
@@ -82,13 +80,15 @@ static int tx_pga0_gain_to_iobits(double &gain){
wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) {
//register our handle on the primary wbx_base instance
self_base = _self_wbx_base;
+ _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1));
+ _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1));
////////////////////////////////////////////////////////////////////
// Register RX properties
////////////////////////////////////////////////////////////////////
this->get_rx_subtree()->create<std::string>("name").set("WBXv3 RX");
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
.set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0);
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range);
@@ -98,17 +98,17 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) {
this->get_tx_subtree()->create<std::string>("name").set("WBXv3 TX");
BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){
self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name))
+ .set_coercer(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name))
.set(wbx_v3_tx_gain_ranges[name].start());
self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(wbx_v3_tx_gain_ranges[name]);
}
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
.set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0);
this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range);
this->get_tx_subtree()->create<bool>("enabled")
- .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1))
.set(true); //start enabled
//set attenuator control bits
@@ -129,29 +129,29 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) {
//slip between bursts). set TX gain iobits to min gain (max attenuation)
//when RX_ONLY or IDLE to suppress LO leakage
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_IDLE, v3_tx_mod, \
+ gpio_atr::ATR_REG_IDLE, v3_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \
+ gpio_atr::ATR_REG_RX_ONLY, v3_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \
+ gpio_atr::ATR_REG_TX_ONLY, v3_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \
+ gpio_atr::ATR_REG_FULL_DUPLEX, v3_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_IDLE, \
+ gpio_atr::ATR_REG_IDLE, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_TX_ONLY, \
+ gpio_atr::ATR_REG_TX_ONLY, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_RX_ONLY, \
+ gpio_atr::ATR_REG_RX_ONLY, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, \
+ gpio_atr::ATR_REG_FULL_DUPLEX, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
}
@@ -181,8 +181,8 @@ double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name)
//write the new gain to tx gpio outputs
//Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
}
else UHD_THROW_INVALID_CODE_PATH();
return self_base->_tx_gains[name]; //shadow
@@ -209,111 +209,45 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar
: self_base->get_tx_subtree();
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+ double reference_freq = self_base->get_iface()->get_clock_rate(unit);
- //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
- static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
- (0,23) //adf4350_regs_t::PRESCALER_4_5
- (1,75) //adf4350_regs_t::PRESCALER_8_9
- ;
-
- //map rf divider select output dividers to enums
- static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
- (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1)
- (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2)
- (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4)
- (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8)
- (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16)
- ;
+ //Select the LO
+ adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo;
+ lo_iface->set_reference_freq(reference_freq);
- double reference_freq = self_base->get_iface()->get_clock_rate(unit);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
- //frequency must 2x the target frequency. This introduces a 180 degree
- //phase ambiguity
+ //frequency must 2x the target frequency
double synth_target_freq = target_freq * 2;
- adf4350_regs_t::prescaler_t prescaler =
- synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5;
+ //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ lo_iface->set_prescaler(synth_target_freq > 3e9 ?
+ adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
- adf435x_tuning_constraints tuning_constraints;
- tuning_constraints.force_frac0 = is_int_n;
- tuning_constraints.band_sel_freq_max = 100e3;
- tuning_constraints.ref_doubler_threshold = 12.5e6;
- tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095);
- tuning_constraints.pfd_freq_max = 25e6;
- tuning_constraints.rf_divider_range = uhd::range_t(1, 16);
//The feedback of the divided frequency must be disabled whenever the target frequency
//divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value.
//If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD
//frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz)
//will have too much ambiguity to synchronize.
- tuning_constraints.feedback_after_divider =
- (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]);
+ lo_iface->set_feedback_select(
+ (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ?
+ adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL));
- double synth_actual_freq = 0;
- adf435x_tuning_settings tuning_settings = tune_adf435x_synth(
- synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq);
+ double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
//actual_freq must /2 the synth_actual_freq
double actual_freq = synth_actual_freq / 2;
- //load the register values
- adf4350_regs_t regs;
-
- if (unit == dboard_iface::UNIT_RX)
- regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM
- : adf4350_regs_t::OUTPUT_POWER_2DBM;
- else
- regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM
- : adf4350_regs_t::OUTPUT_POWER_M1DBM;
-
- regs.frac_12_bit = tuning_settings.frac_12_bit;
- regs.int_16_bit = tuning_settings.int_16_bit;
- regs.mod_12_bit = tuning_settings.mod_12_bit;
- regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit;
- regs.feedback_select = tuning_constraints.feedback_after_divider ?
- adf4350_regs_t::FEEDBACK_SELECT_DIVIDED :
- adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
- regs.clock_div_mode = tuning_constraints.feedback_after_divider ?
- adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE :
- adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK;
- regs.prescaler = prescaler;
- regs.r_counter_10_bit = tuning_settings.r_counter_10_bit;
- regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ?
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
- adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
- regs.reference_doubler = tuning_settings.r_doubler_en ?
- adf4350_regs_t::REFERENCE_DOUBLER_ENABLED :
- adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;
- regs.band_select_clock_div = tuning_settings.band_select_clock_div;
- UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider));
- regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider];
- regs.ldf = is_int_n ?
- adf4350_regs_t::LDF_INT_N :
- adf4350_regs_t::LDF_FRAC_N;
-
- //reset the N and R counter
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED;
- self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
- regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED;
-
- //write the registers
- //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
- int addr;
-
- for(addr=5; addr>=0; addr--){
- UHD_LOGV(often) << boost::format(
- "WBX SPI Reg (0x%02x): 0x%08x"
- ) % addr % regs.get_reg(addr) << std::endl;
- self_base->get_iface()->write_spi(
- unit, spi_config_t::EDGE_RISE,
- regs.get_reg(addr), 32
- );
+ if (unit == dboard_iface::UNIT_RX) {
+ lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM);
+ } else {
+ lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM);
}
- //return the actual frequency
- UHD_LOGV(often) << boost::format(
- "WBX tune: actual frequency %f MHz"
- ) % (actual_freq/1e6) << std::endl;
+ //Write to hardware
+ lo_iface->commit();
+
return actual_freq;
}
diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp
index 81cdaefac..dc351af1d 100644
--- a/host/lib/usrp/dboard/db_wbx_version4.cpp
+++ b/host/lib/usrp/dboard/db_wbx_version4.cpp
@@ -16,8 +16,6 @@
//
#include "db_wbx_common.hpp"
-#include "adf4351_regs.hpp"
-#include "../common/adf435x_common.hpp"
#include <uhd/utils/log.hpp>
#include <uhd/types/dict.hpp>
#include <uhd/types/ranges.hpp>
@@ -83,6 +81,8 @@ static int tx_pga0_gain_to_iobits(double &gain){
wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) {
//register our handle on the primary wbx_base instance
self_base = _self_wbx_base;
+ _txlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1));
+ _rxlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1));
////////////////////////////////////////////////////////////////////
// Register RX properties
@@ -92,7 +92,7 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) {
if(rx_id == 0x0063) this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX");
else if(rx_id == 0x0081) this->get_rx_subtree()->create<std::string>("name").set("WBX-120 RX");
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1))
.set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0);
this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range);
@@ -105,17 +105,17 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) {
else if(rx_id == 0x0081) this->get_tx_subtree()->create<std::string>("name").set("WBX-120 TX");
BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){
self_base->get_tx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name))
+ .set_coercer(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name))
.set(wbx_v4_tx_gain_ranges[name].start());
self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(wbx_v4_tx_gain_ranges[name]);
}
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
+ .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1))
.set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0);
this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range);
this->get_tx_subtree()->create<bool>("enabled")
- .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1))
+ .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1))
.set(true); //start enabled
//set attenuator control bits
@@ -136,29 +136,29 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) {
//between bursts) set TX gain iobits to min gain (max attenuation) when
//RX_ONLY or IDLE to suppress LO leakage
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_IDLE, v4_tx_mod, \
+ gpio_atr::ATR_REG_IDLE, v4_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, \
+ gpio_atr::ATR_REG_RX_ONLY, v4_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, \
+ gpio_atr::ATR_REG_TX_ONLY, v4_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, \
+ gpio_atr::ATR_REG_FULL_DUPLEX, v4_tx_mod, \
TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_IDLE, \
+ gpio_atr::ATR_REG_IDLE, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_TX_ONLY, \
+ gpio_atr::ATR_REG_TX_ONLY, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_RX_ONLY, \
+ gpio_atr::ATR_REG_RX_ONLY, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \
- dboard_iface::ATR_REG_FULL_DUPLEX, \
+ gpio_atr::ATR_REG_FULL_DUPLEX, \
RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB);
}
@@ -188,8 +188,8 @@ double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name)
//write the new gain to tx gpio outputs
//Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
- self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK);
+ self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK);
}
else UHD_THROW_INVALID_CODE_PATH();
@@ -217,116 +217,46 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar
: self_base->get_tx_subtree();
device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer");
+ double reference_freq = self_base->get_iface()->get_clock_rate(unit);
- //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler)
- static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of
- (adf4351_regs_t::PRESCALER_4_5, 23)
- (adf4351_regs_t::PRESCALER_8_9, 75)
- ;
-
- //map rf divider select output dividers to enums
- static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of
- (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1)
- (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2)
- (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4)
- (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8)
- (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16)
- (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32)
- (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64)
- ;
+ //Select the LO
+ adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo;
+ lo_iface->set_reference_freq(reference_freq);
- double reference_freq = self_base->get_iface()->get_clock_rate(unit);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
//frequency must 2x the target frequency. This introduces a 180 degree phase
//ambiguity when trying to synchronize the phase of multiple boards.
double synth_target_freq = target_freq * 2;
- adf4351_regs_t::prescaler_t prescaler =
- synth_target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5;
+ //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler)
+ lo_iface->set_prescaler(synth_target_freq > 3.6e9 ?
+ adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
- adf435x_tuning_constraints tuning_constraints;
- tuning_constraints.force_frac0 = is_int_n;
- tuning_constraints.band_sel_freq_max = 100e3;
- tuning_constraints.ref_doubler_threshold = 12.5e6;
- tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095);
- tuning_constraints.pfd_freq_max = 25e6;
- tuning_constraints.rf_divider_range = uhd::range_t(1, 64);
//The feedback of the divided frequency must be disabled whenever the target frequency
//divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value.
//If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD
//frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz)
//will have too much ambiguity to synchronize.
- tuning_constraints.feedback_after_divider =
- (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]);
+ lo_iface->set_feedback_select(
+ (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ?
+ adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL));
- double synth_actual_freq = 0;
- adf435x_tuning_settings tuning_settings = tune_adf435x_synth(
- synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq);
+ double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n);
//The mixer has a divide-by-2 stage on the LO port so the synthesizer
//actual_freq must /2 the synth_actual_freq
double actual_freq = synth_actual_freq / 2;
- //load the register values
- adf4351_regs_t regs;
-
- if (unit == dboard_iface::UNIT_RX)
- regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM
- : adf4351_regs_t::OUTPUT_POWER_2DBM;
- else
- regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM
- : adf4351_regs_t::OUTPUT_POWER_M1DBM;
-
- regs.frac_12_bit = tuning_settings.frac_12_bit;
- regs.int_16_bit = tuning_settings.int_16_bit;
- regs.mod_12_bit = tuning_settings.mod_12_bit;
- regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit;
- regs.feedback_select = tuning_constraints.feedback_after_divider ?
- adf4351_regs_t::FEEDBACK_SELECT_DIVIDED :
- adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL;
- regs.clock_div_mode = tuning_constraints.feedback_after_divider ?
- adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE :
- adf4351_regs_t::CLOCK_DIV_MODE_FAST_LOCK;
- regs.prescaler = prescaler;
- regs.r_counter_10_bit = tuning_settings.r_counter_10_bit;
- regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ?
- adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED :
- adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED;
- regs.reference_doubler = tuning_settings.r_doubler_en ?
- adf4351_regs_t::REFERENCE_DOUBLER_ENABLED :
- adf4351_regs_t::REFERENCE_DOUBLER_DISABLED;
- regs.band_select_clock_div = tuning_settings.band_select_clock_div;
- UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider));
- regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider];
- regs.ldf = is_int_n ?
- adf4351_regs_t::LDF_INT_N :
- adf4351_regs_t::LDF_FRAC_N;
-
- //reset the N and R counter
- regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED;
- self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32);
- regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED;
-
- //write the registers
- //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0)
- int addr;
-
- boost::uint16_t rx_id = self_base->get_rx_id().to_uint16();
- std::string board_name = (rx_id == 0x0081) ? "WBX-120" : "WBX";
- for(addr=5; addr>=0; addr--){
- UHD_LOGV(often) << boost::format(
- "%s SPI Reg (0x%02x): 0x%08x"
- ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl;
- self_base->get_iface()->write_spi(
- unit, spi_config_t::EDGE_RISE,
- regs.get_reg(addr), 32
- );
+ if (unit == dboard_iface::UNIT_RX) {
+ lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM);
+ } else {
+ lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ?
+ adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM);
}
- //return the actual frequency
- UHD_LOGV(often) << boost::format(
- "%s tune: actual frequency %f MHz"
- ) % board_name.c_str() % (actual_freq/1e6) << std::endl;
+ //Write to hardware
+ lo_iface->commit();
return actual_freq;
}
diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp
index 50c67991a..4a3f69f69 100644
--- a/host/lib/usrp/dboard/db_xcvr2450.cpp
+++ b/host/lib/usrp/dboard/db_xcvr2450.cpp
@@ -112,7 +112,7 @@ static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list
class xcvr2450 : public xcvr_dboard_base{
public:
xcvr2450(ctor_args_t args);
- ~xcvr2450(void);
+ virtual ~xcvr2450(void);
private:
double _lo_freq;
@@ -231,23 +231,23 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){
this->get_rx_subtree()->create<std::string>("name")
.set("XCVR2450 RX");
this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&xcvr2450::get_locked, this));
+ .set_publisher(boost::bind(&xcvr2450::get_locked, this));
this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi")
- .publish(boost::bind(&xcvr2450::get_rssi, this));
+ .set_publisher(boost::bind(&xcvr2450::get_rssi, this));
BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){
this->get_rx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name))
+ .set_coercer(boost::bind(&xcvr2450::set_rx_gain, this, _1, name))
.set(xcvr_rx_gain_ranges[name].start());
this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(xcvr_rx_gain_ranges[name]);
}
this->get_rx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1))
+ .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1))
.set(double(2.45e9));
this->get_rx_subtree()->create<meta_range_t>("freq/range")
.set(xcvr_freq_range);
this->get_rx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&xcvr2450::set_rx_ant, this, _1))
.set(xcvr_antennas.at(0));
this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(xcvr_antennas);
@@ -258,7 +258,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){
this->get_rx_subtree()->create<bool>("use_lo_offset")
.set(false);
this->get_rx_subtree()->create<double>("bandwidth/value")
- .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth
+ .set_coercer(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth
.set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass
this->get_rx_subtree()->create<meta_range_t>("bandwidth/range")
.set(xcvr_rx_bandwidth_range);
@@ -269,21 +269,21 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){
this->get_tx_subtree()->create<std::string>("name")
.set("XCVR2450 TX");
this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked")
- .publish(boost::bind(&xcvr2450::get_locked, this));
+ .set_publisher(boost::bind(&xcvr2450::get_locked, this));
BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){
this->get_tx_subtree()->create<double>("gains/"+name+"/value")
- .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name))
+ .set_coercer(boost::bind(&xcvr2450::set_tx_gain, this, _1, name))
.set(xcvr_tx_gain_ranges[name].start());
this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range")
.set(xcvr_tx_gain_ranges[name]);
}
this->get_tx_subtree()->create<double>("freq/value")
- .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1))
+ .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1))
.set(double(2.45e9));
this->get_tx_subtree()->create<meta_range_t>("freq/range")
.set(xcvr_freq_range);
this->get_tx_subtree()->create<std::string>("antenna/value")
- .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1))
+ .add_coerced_subscriber(boost::bind(&xcvr2450::set_tx_ant, this, _1))
.set(xcvr_antennas.at(1));
this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options")
.set(xcvr_antennas);
@@ -294,7 +294,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){
this->get_tx_subtree()->create<bool>("use_lo_offset")
.set(false);
this->get_tx_subtree()->create<double>("bandwidth/value")
- .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth
+ .set_coercer(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth
.set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass
this->get_tx_subtree()->create<meta_range_t>("bandwidth/range")
.set(xcvr_tx_bandwidth_range);
@@ -315,12 +315,12 @@ xcvr2450::~xcvr2450(void){
void xcvr2450::spi_reset(void){
//spi reset mode: global enable = off, tx and rx enable = on
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, TX_ENB_TXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO);
boost::this_thread::sleep(boost::posix_time::milliseconds(10));
//take it back out of spi reset mode and wait a bit
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO);
boost::this_thread::sleep(boost::posix_time::milliseconds(10));
}
@@ -337,16 +337,16 @@ void xcvr2450::update_atr(void){
int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO;
//set the tx registers
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel);
//set the rx registers
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO);
- this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO);
+ this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO);
}
/***********************************************************************
diff --git a/host/lib/usrp/dboard/twinrx/table_to_cpp.py b/host/lib/usrp/dboard/twinrx/table_to_cpp.py
new file mode 100644
index 000000000..ea0e3bf66
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/table_to_cpp.py
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+import sys
+
+if len(sys.argv) != 3:
+ print 'Usage: ' + sys.argv[0] + ' <table filename> <table name>'
+ sys.exit(1)
+
+with open(sys.argv[1], 'rb') as f:
+ print ('static const std::vector<twinrx_gain_config_t> ' +
+ sys.argv[2] + ' = boost::assign::list_of')
+ i = -1
+ for line in f.readlines():
+ if (i != -1):
+ row = line.strip().split(',')
+ print (' ( twinrx_gain_config_t( %s, %6.1f, %s, %s, %s, %s ) )' % (
+ str(i).rjust(5),
+ float(row[0].rjust(6)),
+ row[1].rjust(6), row[2].rjust(6),
+ ('true' if int(row[3]) == 1 else 'false').rjust(5),
+ ('true' if int(row[4]) == 1 else 'false').rjust(5)))
+ else:
+ print (' // %s, %s, %s, %s, %s, %s' % (
+ 'Index', ' Gain', 'Atten1', 'Atten2', ' Amp1', ' Amp2'))
+ i += 1
+ print ';'
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp
new file mode 100644
index 000000000..172992f0a
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp
@@ -0,0 +1,643 @@
+//
+// 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 <boost/thread/thread.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include "twinrx_ctrl.hpp"
+#include "adf435x.hpp"
+#include "adf5355.hpp"
+
+using namespace uhd;
+using namespace usrp;
+using namespace dboard::twinrx;
+typedef twinrx_cpld_regmap rm;
+
+static boost::uint32_t bool2bin(bool x) { return x ? 1 : 0; }
+
+static const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6;
+
+class twinrx_ctrl_impl : public twinrx_ctrl {
+public:
+ twinrx_ctrl_impl(
+ dboard_iface::sptr db_iface,
+ twinrx_gpio::sptr gpio_iface,
+ twinrx_cpld_regmap::sptr cpld_regmap
+ ) : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap)
+ {
+
+ //Turn on switcher and wait for power good
+ _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 1);
+ size_t timeout_ms = 100;
+ while (_gpio_iface->get_field(twinrx_gpio::FIELD_SWPS_PWR_GOOD) == 0) {
+ boost::this_thread::sleep(boost::posix_time::microsec(1000));
+ if (--timeout_ms == 0) {
+ throw uhd::runtime_error("power supply failure");
+ }
+ }
+ //Initialize synthesizer objects
+ _lo1_iface[size_t(CH1)] = adf5355_iface::make(
+ boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1));
+ _lo1_iface[size_t(CH2)] = adf5355_iface::make(
+ boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1));
+
+ _lo2_iface[size_t(CH1)] = adf435x_iface::make_adf4351(
+ boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1));
+ _lo2_iface[size_t(CH2)] = adf435x_iface::make_adf4351(
+ boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1));
+
+ // Assert synthesizer chip enables
+ _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH1, 1);
+ _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH2, 1);
+ _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH1, 1);
+ _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH2, 1);
+
+ //Initialize default state
+ set_chan_enabled(BOTH, false, false);
+ set_preamp1(BOTH, PREAMP_BYPASS, false);
+ set_preamp2(BOTH, false, false);
+ set_lb_preamp_preselector(BOTH, false, false);
+ set_signal_path(BOTH, PATH_LOWBAND, false);
+ set_lb_preselector(BOTH, PRESEL_PATH3, false);
+ set_hb_preselector(BOTH, PRESEL_PATH1, false);
+ set_input_atten(BOTH, 31, false);
+ set_lb_atten(BOTH, 31, false);
+ set_hb_atten(BOTH, 31, false);
+ set_lo1_source(BOTH, LO_INTERNAL, false);
+ set_lo2_source(BOTH, LO_INTERNAL, false);
+ set_lo1_export_source(LO_EXPORT_DISABLED, false);
+ set_lo2_export_source(LO_EXPORT_DISABLED, false);
+ set_antenna_mapping(ANTX_NATIVE, false);
+ set_crossover_cal_mode(CAL_DISABLED, false);
+ commit();
+
+ //Initialize clocks and LO
+ bool found_rate = false;
+ BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_TX)) {
+ found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ);
+ }
+ BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_RX)) {
+ found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ);
+ }
+ if (not found_rate) {
+ throw uhd::runtime_error("TwinRX not supported on this motherboard");
+ }
+ _db_iface->set_clock_rate(dboard_iface::UNIT_TX, TWINRX_DESIRED_REFERENCE_FREQ);
+ _db_iface->set_clock_rate(dboard_iface::UNIT_RX, TWINRX_DESIRED_REFERENCE_FREQ);
+
+ _db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
+ _db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true);
+ for (size_t i = 0; i < NUM_CHANS; i++) {
+ _config_lo1_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2);
+ _config_lo2_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2);
+ _lo1_iface[i]->set_output_power(adf5355_iface::OUTPUT_POWER_5DBM);
+ _lo1_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ);
+ _lo1_iface[i]->set_muxout_mode(adf5355_iface::MUXOUT_DLD);
+ _lo1_iface[i]->set_frequency(3e9, 1.0e3);
+ _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED);
+ _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM);
+ _lo2_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ);
+ _lo2_iface[i]->set_muxout_mode(adf435x_iface::MUXOUT_DLD);
+ _lo1_iface[i]->commit();
+ _lo2_iface[i]->commit();
+ }
+ _config_lo1_route(LO_CONFIG_NONE);
+ _config_lo2_route(LO_CONFIG_NONE);
+ }
+
+ ~twinrx_ctrl_impl()
+ {
+ UHD_SAFE_CALL(
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 0);
+ )
+ }
+
+ void commit()
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _commit();
+ }
+
+ void set_chan_enabled(channel_t ch, bool enabled, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::AMP_LO1_EN_CH1, bool2bin(enabled));
+ _cpld_regs->if0_reg3.set(rm::if0_reg3_t::IF1_IF2_EN_CH1, bool2bin(enabled));
+ _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH1, bool2bin(enabled));
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::AMP_LO1_EN_CH2, bool2bin(enabled));
+ _cpld_regs->if0_reg4.set(rm::if0_reg4_t::IF1_IF2_EN_CH2, bool2bin(enabled));
+ _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH2, bool2bin(enabled));
+ }
+ if (commit) _commit();
+ }
+
+ void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA1_CTL_CH1, bool2bin(value==PREAMP_HIGHBAND));
+ _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SWPA2_CTRL_CH1, bool2bin(value==PREAMP_BYPASS));
+ _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::HB_PREAMP_EN_CH1, bool2bin(value==PREAMP_HIGHBAND));
+ _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::LB_PREAMP_EN_CH1, bool2bin(value==PREAMP_LOWBAND));
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA1_CTRL_CH2, bool2bin(value==PREAMP_HIGHBAND));
+ _cpld_regs->rf2_reg5.set(rm::rf2_reg5_t::SWPA2_CTRL_CH2, bool2bin(value==PREAMP_BYPASS));
+ _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::HB_PREAMP_EN_CH2, bool2bin(value==PREAMP_HIGHBAND));
+ _cpld_regs->rf2_reg6.set(rm::rf2_reg6_t::LB_PREAMP_EN_CH2, bool2bin(value==PREAMP_LOWBAND));
+ }
+ if (commit) _commit();
+ }
+
+ void set_preamp2(channel_t ch, bool enabled, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SWPA4_CTRL_CH1, bool2bin(not enabled));
+ _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::PREAMP2_EN_CH1, bool2bin(enabled));
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SWPA4_CTRL_CH2, bool2bin(not enabled));
+ _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::PREAMP2_EN_CH2, bool2bin(enabled));
+ }
+ if (commit) _commit();
+ }
+
+ void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA3_CTRL_CH1, bool2bin(not enabled));
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA3_CTRL_CH2, bool2bin(not enabled));
+ }
+ if (commit) _commit();
+ }
+
+ void set_signal_path(channel_t ch, signal_path_t path, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SW11_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::SW12_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::HB_PRESEL_PGA_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW6_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW13_CTRL_CH1, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH1, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_HB_IF1_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::AMP_HB_EN_CH1, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::AMP_LB_EN_CH1, bool2bin(path==PATH_LOWBAND));
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW11_CTRL_CH2, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW12_CTRL_CH2, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::HB_PRESEL_PGA_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW6_CTRL_CH2, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->if0_reg6.set(rm::if0_reg6_t::SW13_CTRL_CH2, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH2, bool2bin(path==PATH_LOWBAND));
+ _cpld_regs->if0_reg6.set(rm::if0_reg6_t::AMP_HB_IF1_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::AMP_HB_EN_CH2, bool2bin(path==PATH_HIGHBAND));
+ _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::AMP_LB_EN_CH2, bool2bin(path==PATH_LOWBAND));
+ }
+ if (commit) _commit();
+ }
+
+ void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ boost::uint32_t sw7val = 0, sw8val = 0;
+ switch (path) {
+ case PRESEL_PATH1: sw7val = 3; sw8val = 1; break;
+ case PRESEL_PATH2: sw7val = 2; sw8val = 0; break;
+ case PRESEL_PATH3: sw7val = 0; sw8val = 2; break;
+ case PRESEL_PATH4: sw7val = 1; sw8val = 3; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW7_CTRL_CH1, sw7val);
+ _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW8_CTRL_CH1, sw8val);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW7_CTRL_CH2, sw7val);
+ _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW8_CTRL_CH2, sw8val);
+ }
+ if (commit) _commit();
+ }
+
+ void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ boost::uint32_t sw9ch1val = 0, sw10ch1val = 0, sw9ch2val = 0, sw10ch2val = 0;
+ switch (path) {
+ case PRESEL_PATH1: sw9ch1val = 3; sw10ch1val = 0; sw9ch2val = 0; sw10ch2val = 3; break;
+ case PRESEL_PATH2: sw9ch1val = 1; sw10ch1val = 2; sw9ch2val = 1; sw10ch2val = 1; break;
+ case PRESEL_PATH3: sw9ch1val = 2; sw10ch1val = 1; sw9ch2val = 2; sw10ch2val = 2; break;
+ case PRESEL_PATH4: sw9ch1val = 0; sw10ch1val = 3; sw9ch2val = 3; sw10ch2val = 0; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW9_CTRL_CH1, sw9ch1val);
+ _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW10_CTRL_CH1, sw10ch1val);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW9_CTRL_CH2, sw9ch2val);
+ _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW10_CTRL_CH2, sw10ch2val);
+ }
+ if (commit) _commit();
+
+ }
+
+ void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf0_reg0.set(rm::rf0_reg0_t::ATTEN_IN_CH1, atten&0x1F);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf0_reg4.set(rm::rf0_reg4_t::ATTEN_IN_CH2, atten&0x1F);
+ }
+ if (commit) _commit();
+ }
+
+ void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf2_reg0.set(rm::rf2_reg0_t::ATTEN_LB_CH1, atten&0x1F);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf2_reg4.set(rm::rf2_reg4_t::ATTEN_LB_CH2, atten&0x1F);
+ }
+ if (commit) _commit();
+ }
+
+ void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf1_reg0.set(rm::rf1_reg0_t::ATTEN_HB_CH1, atten&0x1F);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf1_reg4.set(rm::rf1_reg4_t::ATTEN_HB_CH2, atten&0x1F);
+ }
+ if (commit) _commit();
+ }
+
+ void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW14_CTRL_CH2, bool2bin(source!=LO_COMPANION));
+ _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW15_CTRL_CH1, bool2bin(source==LO_EXTERNAL));
+ _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW16_CTRL_CH1, bool2bin(source!=LO_INTERNAL));
+ _lo1_src[size_t(CH1)] = source;
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW14_CTRL_CH1, bool2bin(source==LO_COMPANION));
+ _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW15_CTRL_CH2, bool2bin(source!=LO_INTERNAL));
+ _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW16_CTRL_CH2, bool2bin(source==LO_INTERNAL));
+ _lo1_src[size_t(CH2)] = source;
+ }
+ if (commit) _commit();
+ }
+
+ void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (ch == CH1 or ch == BOTH) {
+ _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW19_CTRL_CH2, bool2bin(source==LO_COMPANION));
+ _cpld_regs->if0_reg1.set(rm::if0_reg1_t::SW20_CTRL_CH1, bool2bin(source==LO_COMPANION));
+ _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH1, bool2bin(source==LO_INTERNAL));
+ _lo2_src[size_t(CH1)] = source;
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW19_CTRL_CH1, bool2bin(source==LO_EXTERNAL));
+ _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW20_CTRL_CH2, bool2bin(source==LO_INTERNAL||source==LO_DISABLED));
+ _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH2, bool2bin(source==LO_INTERNAL));
+ _lo2_src[size_t(CH2)] = source;
+ }
+ if (commit) _commit();
+ }
+
+ void set_lo1_export_source(lo_export_source_t source, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ //SW22 may conflict with the cal switch but this attr takes priority and we assume
+ //that the cal switch is disabled (by disabling it!)
+ _set_cal_mode(CAL_DISABLED, source);
+ _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW23_CTRL, bool2bin(source!=LO_CH1_SYNTH));
+ _lo1_export = source;
+
+ if (commit) _commit();
+ }
+
+ void set_lo2_export_source(lo_export_source_t source, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _cpld_regs->if0_reg7.set(rm::if0_reg7_t::SW24_CTRL_CH2, bool2bin(source==LO_CH2_SYNTH));
+ _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW25_CTRL, bool2bin(source!=LO_CH1_SYNTH));
+ _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW24_CTRL_CH1, bool2bin(source!=LO_CH1_SYNTH));
+ _lo2_export = source;
+
+ if (commit) _commit();
+ }
+
+ void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ enum switch_path_t { CONNECT, TERM, EXPORT, IMPORT, SWAP };
+ switch_path_t path1, path2;
+
+ switch (mapping) {
+ case ANTX_NATIVE:
+ path1 = CONNECT; path2 = CONNECT; break;
+ case ANT1_SHARED:
+ path1 = EXPORT; path2 = IMPORT; break;
+ case ANT2_SHARED:
+ path1 = IMPORT; path2 = EXPORT; break;
+ case ANTX_SWAPPED:
+ path1 = SWAP; path2 = SWAP; break;
+ default:
+ path1 = TERM; path2 = TERM; break;
+ }
+
+ _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW3_CTRL_CH1, bool2bin(path1==EXPORT||path1==SWAP));
+ _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW4_CTRL_CH1, bool2bin(!(path1==IMPORT||path1==SWAP)));
+ _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW5_CTRL_CH1, bool2bin(path1==CONNECT));
+ _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW3_CTRL_CH2, bool2bin(path2==EXPORT||path2==SWAP));
+ _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW4_CTRL_CH2, bool2bin(path2==IMPORT||path2==SWAP));
+ _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW5_CTRL_CH2, bool2bin(path2==CONNECT));
+
+ if (commit) _commit();
+ }
+
+ void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ if (_lo1_export == LO_CH1_SYNTH && cal_mode == CAL_CH2) {
+ throw uhd::runtime_error("cannot enable cal crossover on CH2 when LO1 in CH1 is exported");
+ }
+ if (_lo1_export == LO_CH2_SYNTH && cal_mode == CAL_CH1) {
+ throw uhd::runtime_error("cannot enable cal crossover on CH1 when LO1 in CH2 is exported");
+ }
+ _set_cal_mode(cal_mode, _lo1_export);
+
+ if (commit) _commit();
+ }
+
+ double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ static const double RESOLUTION = 1e3;
+
+ double coerced_freq = 0.0;
+ if (ch == CH1 or ch == BOTH) {
+ coerced_freq = _lo1_iface[size_t(CH1)]->set_frequency(freq, RESOLUTION, false);
+ _lo1_freq[size_t(CH1)] = tune_freq_t(freq);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ coerced_freq = _lo1_iface[size_t(CH2)]->set_frequency(freq, RESOLUTION, false);
+ _lo1_freq[size_t(CH2)] = tune_freq_t(freq);
+ }
+
+ if (commit) _commit();
+ return coerced_freq;
+ }
+
+ double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ static const double PRESCALER_THRESH = 3.6e9;
+
+ double coerced_freq = 0.0;
+ if (ch == CH1 or ch == BOTH) {
+ _lo2_iface[size_t(CH1)]->set_prescaler(freq > PRESCALER_THRESH ?
+ adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
+ coerced_freq = _lo2_iface[size_t(CH1)]->set_frequency(freq, false, false);
+ _lo2_freq[size_t(CH1)] = tune_freq_t(freq);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ _lo2_iface[size_t(CH2)]->set_prescaler(freq > PRESCALER_THRESH ?
+ adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5);
+ coerced_freq = _lo2_iface[size_t(CH2)]->set_frequency(freq, false, false);
+ _lo2_freq[size_t(CH2)] = tune_freq_t(freq);
+ }
+
+ if (commit) _commit();
+ return coerced_freq;
+ }
+
+ bool read_lo1_locked(channel_t ch)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ bool locked = true;
+ if (ch == CH1 or ch == BOTH) {
+ locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH1) == 1);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH2) == 1);
+ }
+ return locked;
+ }
+
+ bool read_lo2_locked(channel_t ch)
+ {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+
+ bool locked = true;
+ if (ch == CH1 or ch == BOTH) {
+ locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH1) == 1);
+ }
+ if (ch == CH2 or ch == BOTH) {
+ locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH2) == 1);
+ }
+ return locked;
+ }
+
+private: //Functions
+ void _set_cal_mode(cal_mode_t cal_mode, lo_export_source_t lo1_export_src)
+ {
+ _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW17_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1));
+ _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW17_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2));
+ _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW18_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1));
+ _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW18_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2));
+ _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW22_CTRL_CH1, bool2bin((lo1_export_src!=LO_CH1_SYNTH)||(cal_mode==CAL_CH1)));
+ _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW22_CTRL_CH2, bool2bin((lo1_export_src!=LO_CH2_SYNTH)||(cal_mode==CAL_CH2)));
+ }
+
+ void _config_lo1_route(lo_config_route_t source)
+ {
+ //Route SPI LEs through CPLD (will not assert them)
+ _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH));
+ _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH));
+ _cpld_regs->rf0_reg2.flush();
+ }
+
+ void _config_lo2_route(lo_config_route_t source)
+ {
+ //Route SPI LEs through CPLD (will not assert them)
+ _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH));
+ _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH));
+ _cpld_regs->if0_reg2.flush();
+ }
+
+ void _write_lo_spi(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> &regs)
+ {
+ BOOST_FOREACH(boost::uint32_t reg, regs) {
+ spi_config_t spi_config = spi_config_t(spi_config_t::EDGE_RISE);
+ spi_config.use_custom_divider = true;
+ spi_config.divider = 67;
+ _db_iface->write_spi(unit, spi_config, reg, 32);
+ }
+ }
+
+ void _commit()
+ {
+ //Commit everything except the LO synthesizers
+ _cpld_regs->flush();
+
+ // Disable unused LO synthesizers
+ _lo1_enable[size_t(CH1)] = _lo1_src[size_t(CH1)] == LO_INTERNAL ||
+ _lo1_src[size_t(CH2)] == LO_COMPANION ||
+ _lo1_export == LO_CH1_SYNTH;
+
+ _lo1_enable[size_t(CH2)] = _lo1_src[size_t(CH2)] == LO_INTERNAL ||
+ _lo1_src[size_t(CH1)] == LO_COMPANION ||
+ _lo1_export == LO_CH2_SYNTH;
+ _lo2_enable[size_t(CH1)] = _lo2_src[size_t(CH1)] == LO_INTERNAL ||
+ _lo2_src[size_t(CH2)] == LO_COMPANION ||
+ _lo2_export == LO_CH1_SYNTH;
+
+ _lo2_enable[size_t(CH2)] = _lo2_src[size_t(CH2)] == LO_INTERNAL ||
+ _lo2_src[size_t(CH1)] == LO_COMPANION ||
+ _lo2_export == LO_CH2_SYNTH;
+
+ _lo1_iface[size_t(CH1)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get());
+ _lo1_iface[size_t(CH2)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get());
+
+ _lo2_iface[size_t(CH1)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get());
+ _lo2_iface[size_t(CH2)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get());
+
+ //Commit LO1 frequency
+ // Commit Channel 1's settings to both channels simultaneously if the frequency is the same.
+ bool simultaneous_commit_lo1 = _lo1_freq[size_t(CH1)].is_dirty() and
+ _lo1_freq[size_t(CH2)].is_dirty() and
+ _lo1_freq[size_t(CH1)].get() == _lo1_freq[size_t(CH2)].get() and
+ _lo1_enable[size_t(CH1)].get() == _lo1_enable[size_t(CH2)].get();
+
+ if (simultaneous_commit_lo1) {
+ _config_lo1_route(LO_CONFIG_BOTH);
+ //Only commit one of the channels. The route LO_CONFIG_BOTH
+ //will ensure that the LEs for both channels are enabled
+ _lo1_iface[size_t(CH1)]->commit();
+ _lo1_freq[size_t(CH1)].mark_clean();
+ _lo1_freq[size_t(CH2)].mark_clean();
+ _lo1_enable[size_t(CH1)].mark_clean();
+ _lo1_enable[size_t(CH2)].mark_clean();
+ _config_lo1_route(LO_CONFIG_NONE);
+ } else {
+ if (_lo1_freq[size_t(CH1)].is_dirty() || _lo1_enable[size_t(CH1)].is_dirty()) {
+ _config_lo1_route(LO_CONFIG_CH1);
+ _lo1_iface[size_t(CH1)]->commit();
+ _lo1_freq[size_t(CH1)].mark_clean();
+ _lo1_enable[size_t(CH1)].mark_clean();
+ _config_lo1_route(LO_CONFIG_NONE);
+ }
+ if (_lo1_freq[size_t(CH2)].is_dirty() || _lo1_enable[size_t(CH2)].is_dirty()) {
+ _config_lo1_route(LO_CONFIG_CH2);
+ _lo1_iface[size_t(CH2)]->commit();
+ _lo1_freq[size_t(CH2)].mark_clean();
+ _lo1_enable[size_t(CH2)].mark_clean();
+ _config_lo1_route(LO_CONFIG_NONE);
+ }
+ }
+
+ //Commit LO2 frequency
+ bool simultaneous_commit_lo2 = _lo2_freq[size_t(CH1)].is_dirty() and
+ _lo2_freq[size_t(CH2)].is_dirty() and
+ _lo2_freq[size_t(CH1)].get() == _lo2_freq[size_t(CH2)].get() and
+ _lo2_enable[size_t(CH1)].get() == _lo2_enable[size_t(CH2)].get();
+
+ if (simultaneous_commit_lo2) {
+ _config_lo2_route(LO_CONFIG_BOTH);
+ //Only commit one of the channels. The route LO_CONFIG_BOTH
+ //will ensure that the LEs for both channels are enabled
+ _lo2_iface[size_t(CH1)]->commit();
+ _lo2_freq[size_t(CH1)].mark_clean();
+ _lo2_freq[size_t(CH2)].mark_clean();
+ _lo2_enable[size_t(CH1)].mark_clean();
+ _lo2_enable[size_t(CH2)].mark_clean();
+ _config_lo2_route(LO_CONFIG_NONE);
+ } else {
+ if (_lo2_freq[size_t(CH1)].is_dirty() || _lo2_enable[size_t(CH1)].is_dirty()) {
+ _config_lo2_route(LO_CONFIG_CH1);
+ _lo2_iface[size_t(CH1)]->commit();
+ _lo2_freq[size_t(CH1)].mark_clean();
+ _lo2_enable[size_t(CH1)].mark_clean();
+ _config_lo2_route(LO_CONFIG_NONE);
+ }
+ if (_lo2_freq[size_t(CH2)].is_dirty() || _lo2_enable[size_t(CH2)].is_dirty()) {
+ _config_lo2_route(LO_CONFIG_CH2);
+ _lo2_iface[size_t(CH2)]->commit();
+ _lo2_freq[size_t(CH2)].mark_clean();
+ _lo2_enable[size_t(CH2)].mark_clean();
+ _config_lo2_route(LO_CONFIG_NONE);
+ }
+ }
+ }
+
+private: //Members
+ static const size_t NUM_CHANS = 2;
+
+ struct tune_freq_t : public uhd::math::fp_compare::fp_compare_delta<double> {
+ tune_freq_t() : uhd::math::fp_compare::fp_compare_delta<double>(
+ 0.0, uhd::math::FREQ_COMPARISON_DELTA_HZ) {}
+
+ tune_freq_t(double freq) : uhd::math::fp_compare::fp_compare_delta<double>(
+ freq, uhd::math::FREQ_COMPARISON_DELTA_HZ) {}
+ };
+
+ boost::mutex _mutex;
+ dboard_iface::sptr _db_iface;
+ twinrx_gpio::sptr _gpio_iface;
+ twinrx_cpld_regmap::sptr _cpld_regs;
+ adf5355_iface::sptr _lo1_iface[NUM_CHANS];
+ adf435x_iface::sptr _lo2_iface[NUM_CHANS];
+ lo_source_t _lo1_src[NUM_CHANS];
+ lo_source_t _lo2_src[NUM_CHANS];
+ dirty_tracked<tune_freq_t> _lo1_freq[NUM_CHANS];
+ dirty_tracked<tune_freq_t> _lo2_freq[NUM_CHANS];
+ dirty_tracked<bool> _lo1_enable[NUM_CHANS];
+ dirty_tracked<bool> _lo2_enable[NUM_CHANS];
+ lo_export_source_t _lo1_export;
+ lo_export_source_t _lo2_export;
+};
+
+twinrx_ctrl::sptr twinrx_ctrl::make(
+ dboard_iface::sptr db_iface,
+ twinrx_gpio::sptr gpio_iface,
+ twinrx_cpld_regmap::sptr cpld_regmap
+) {
+ return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap));
+}
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp
new file mode 100644
index 000000000..521e27ae9
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp
@@ -0,0 +1,101 @@
+//
+// 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_DBOARD_TWINRX_CTRL_HPP
+#define INCLUDED_DBOARD_TWINRX_CTRL_HPP
+
+#include <boost/noncopyable.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include "twinrx_io.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+class twinrx_ctrl : public boost::noncopyable {
+public:
+ typedef boost::shared_ptr<twinrx_ctrl> sptr;
+
+ static sptr make(
+ dboard_iface::sptr db_iface,
+ twinrx_gpio::sptr gpio_iface,
+ twinrx_cpld_regmap::sptr cpld_regmap);
+
+ virtual ~twinrx_ctrl() {}
+
+ enum channel_t { CH1 = 0, CH2 = 1, BOTH = 2};
+
+ enum preamp_state_t { PREAMP_LOWBAND, PREAMP_HIGHBAND, PREAMP_BYPASS };
+
+ enum signal_path_t { PATH_LOWBAND, PATH_HIGHBAND };
+
+ enum preselector_path_t { PRESEL_PATH1, PRESEL_PATH2, PRESEL_PATH3, PRESEL_PATH4 };
+
+ enum lo_source_t { LO_INTERNAL, LO_EXTERNAL, LO_COMPANION, LO_DISABLED };
+
+ enum lo_export_source_t { LO_CH1_SYNTH, LO_CH2_SYNTH, LO_EXPORT_DISABLED };
+
+ enum antenna_mapping_t { ANTX_NATIVE, ANT1_SHARED, ANT2_SHARED, ANTX_SWAPPED, ANTX_DISABLED };
+
+ enum lo_config_route_t { LO_CONFIG_CH1, LO_CONFIG_CH2, LO_CONFIG_BOTH, LO_CONFIG_NONE };
+
+ enum cal_mode_t { CAL_DISABLED, CAL_CH1, CAL_CH2 };
+
+ virtual void commit() = 0;
+
+ virtual void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) = 0;
+
+ virtual void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) = 0;
+
+ virtual void set_preamp2(channel_t ch, bool enabled, bool commit = true) = 0;
+
+ virtual void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) = 0;
+
+ virtual void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) = 0;
+
+ virtual void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0;
+
+ virtual void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0;
+
+ virtual void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+ virtual void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+ virtual void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0;
+
+ virtual void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) = 0;
+
+ virtual void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) = 0;
+
+ virtual void set_lo1_export_source(lo_export_source_t source, bool commit = true) = 0;
+
+ virtual void set_lo2_export_source(lo_export_source_t source, bool commit = true) = 0;
+
+ virtual void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) = 0;
+
+ virtual void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) = 0;
+
+ virtual double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) = 0;
+
+ virtual double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) = 0;
+
+ virtual bool read_lo1_locked(channel_t ch) = 0;
+
+ virtual bool read_lo2_locked(channel_t ch) = 0;
+};
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_CTRL_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp
new file mode 100644
index 000000000..ddaa4211e
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp
@@ -0,0 +1,637 @@
+//
+// 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 "twinrx_experts.hpp"
+#include "twinrx_gain_tables.hpp"
+#include <uhd/utils/math.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/dict.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/math/special_functions/round.hpp>
+
+using namespace uhd::experts;
+using namespace uhd::math;
+using namespace uhd::usrp::dboard::twinrx;
+
+/*!---------------------------------------------------------
+ * twinrx_freq_path_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_freq_path_expert::resolve()
+{
+ //Lowband/highband switch point
+ static const double LB_HB_THRESHOLD_FREQ = 1.8e9;
+ static const double LB_TARGET_IF1_FREQ = 2.345e9;
+ static const double HB_TARGET_IF1_FREQ = 1.25e9;
+ static const double INJ_SIDE_THRESHOLD_FREQ = 5.1e9;
+
+ static const double FIXED_LO1_THRESHOLD_FREQ= 50e6;
+
+ //Preselector filter switch point
+ static const double LB_FILT1_THRESHOLD_FREQ = 0.5e9;
+ static const double LB_FILT2_THRESHOLD_FREQ = 0.8e9;
+ static const double LB_FILT3_THRESHOLD_FREQ = 1.2e9;
+ static const double LB_FILT4_THRESHOLD_FREQ = 1.8e9;
+ static const double HB_FILT1_THRESHOLD_FREQ = 3.0e9;
+ static const double HB_FILT2_THRESHOLD_FREQ = 4.1e9;
+ static const double HB_FILT3_THRESHOLD_FREQ = 5.1e9;
+ static const double HB_FILT4_THRESHOLD_FREQ = 6.0e9;
+
+ static const double LB_PREAMP_PRESEL_THRESHOLD_FREQ = 0.8e9;
+
+ //Misc
+ static const double INST_BANDWIDTH = 80e6;
+ static const double MANUAL_LO_HYSTERESIS_PPM = 1.0;
+
+ static const freq_range_t FREQ_RANGE(10e6, 6e9);
+ rf_freq_abs_t rf_freq(FREQ_RANGE.clip(_rf_freq_d));
+
+ // Choose low-band vs high-band depending on frequency
+ _signal_path = (rf_freq > LB_HB_THRESHOLD_FREQ) ?
+ twinrx_ctrl::PATH_HIGHBAND : twinrx_ctrl::PATH_LOWBAND;
+ if (_signal_path == twinrx_ctrl::PATH_LOWBAND) {
+ // Choose low-band preselector filter
+ if (rf_freq < LB_FILT1_THRESHOLD_FREQ) {
+ _lb_presel = twinrx_ctrl::PRESEL_PATH1;
+ } else if (rf_freq < LB_FILT2_THRESHOLD_FREQ) {
+ _lb_presel = twinrx_ctrl::PRESEL_PATH2;
+ } else if (rf_freq < LB_FILT3_THRESHOLD_FREQ) {
+ _lb_presel = twinrx_ctrl::PRESEL_PATH3;
+ } else if (rf_freq < LB_FILT4_THRESHOLD_FREQ) {
+ _lb_presel = twinrx_ctrl::PRESEL_PATH4;
+ } else {
+ _lb_presel = twinrx_ctrl::PRESEL_PATH4;
+ }
+ } else if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+ // Choose high-band preselector filter
+ if (rf_freq < HB_FILT1_THRESHOLD_FREQ) {
+ _hb_presel = twinrx_ctrl::PRESEL_PATH1;
+ } else if (rf_freq < HB_FILT2_THRESHOLD_FREQ) {
+ _hb_presel = twinrx_ctrl::PRESEL_PATH2;
+ } else if (rf_freq < HB_FILT3_THRESHOLD_FREQ) {
+ _hb_presel = twinrx_ctrl::PRESEL_PATH3;
+ } else if (rf_freq < HB_FILT4_THRESHOLD_FREQ) {
+ _hb_presel = twinrx_ctrl::PRESEL_PATH4;
+ } else {
+ _hb_presel = twinrx_ctrl::PRESEL_PATH4;
+ }
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ //Choose low-band preamp preselector
+ _lb_preamp_presel = (rf_freq > LB_PREAMP_PRESEL_THRESHOLD_FREQ);
+
+ //Choose LO frequencies
+ const double target_if1_freq = (_signal_path == twinrx_ctrl::PATH_HIGHBAND) ?
+ HB_TARGET_IF1_FREQ : LB_TARGET_IF1_FREQ;
+ const double target_if2_freq = _if_freq_d;
+
+ // LO1
+ double lo1_freq_ideal = 0.0, lo2_freq_ideal = 0.0;
+ if (rf_freq <= FIXED_LO1_THRESHOLD_FREQ) {
+ //LO1 Freq static
+ lo1_freq_ideal = target_if1_freq + FIXED_LO1_THRESHOLD_FREQ;
+ } else if (rf_freq <= INJ_SIDE_THRESHOLD_FREQ) {
+ //High-side LO1 Injection
+ lo1_freq_ideal = rf_freq.get() + target_if1_freq;
+ } else {
+ //Low-side LO1 Injection
+ lo1_freq_ideal = rf_freq.get() - target_if1_freq;
+ }
+
+ if (_lo1_freq_d.get_author() == experts::AUTHOR_USER) {
+ if (_lo1_freq_d.is_dirty()) { //Are we here because the LO frequency was set?
+ // The user explicitly requested to set the LO freq so don't touch it!
+ } else {
+ // Something else changed which may cause the LO frequency to update.
+ // Only commit if the frequency is stale. If the user's value is stale
+ // reset the author to expert.
+ if (rf_freq_ppm_t(lo1_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo1_freq_d.get()) {
+ _lo1_freq_d = lo1_freq_ideal; //Reset author
+ }
+ }
+ } else {
+ // The LO frequency was never set by the user. Let the expert take care of it
+ _lo1_freq_d = lo1_freq_ideal; //Reset author
+ }
+
+ // LO2
+ lo_inj_side_t lo2_inj_side_ideal = _compute_lo2_inj_side(
+ lo1_freq_ideal, target_if1_freq, target_if2_freq, INST_BANDWIDTH);
+ if (lo2_inj_side_ideal == INJ_HIGH_SIDE) {
+ lo2_freq_ideal = target_if1_freq + target_if2_freq;
+ } else {
+ lo2_freq_ideal = target_if1_freq - target_if2_freq;
+ }
+
+ if (_lo2_freq_d.get_author() == experts::AUTHOR_USER) {
+ if (_lo2_freq_d.is_dirty()) { //Are we here because the LO frequency was set?
+ // The user explicitly requested to set the LO freq so don't touch it!
+ } else {
+ // Something else changed which may cause the LO frequency to update.
+ // Only commit if the frequency is stale. If the user's value is stale
+ // reset the author to expert.
+ if (rf_freq_ppm_t(lo2_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo2_freq_d.get()) {
+ _lo2_freq_d = lo2_freq_ideal; //Reset author
+ }
+ }
+ } else {
+ // The LO frequency was never set by the user. Let the expert take care of it
+ _lo2_freq_d = lo2_freq_ideal; //Reset author
+ }
+
+ // Determine injection side using the final LO frequency
+ _lo1_inj_side = (_lo1_freq_d > rf_freq.get()) ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
+ _lo2_inj_side = (_lo2_freq_d > target_if1_freq) ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
+}
+
+lo_inj_side_t twinrx_freq_path_expert::_compute_lo2_inj_side(
+ double lo1_freq, double if1_freq, double if2_freq, double bandwidth
+) {
+ static const int MAX_SPUR_ORDER = 5;
+ for (int ord = MAX_SPUR_ORDER; ord >= 1; ord--) {
+ // Check high-side injection first
+ if (not _has_mixer_spurs(lo1_freq, if1_freq + if2_freq, if2_freq, bandwidth, ord)) {
+ return INJ_HIGH_SIDE;
+ }
+ // Check low-side injection second
+ if (not _has_mixer_spurs(lo1_freq, if1_freq - if2_freq, if2_freq, bandwidth, ord)) {
+ return INJ_LOW_SIDE;
+ }
+ }
+ // If we reached here, then there are spurs everywhere. Pick high-side as the default
+ return INJ_HIGH_SIDE;
+}
+
+bool twinrx_freq_path_expert::_has_mixer_spurs(
+ double lo1_freq, double lo2_freq, double if2_freq,
+ double bandwidth, int spur_order
+) {
+ // Iterate through all N-th order harmomic combinations
+ // of LOs...
+ for (int lo1h_i = 1; lo1h_i <= spur_order; lo1h_i++) {
+ double lo1harm_freq = lo1_freq * lo1h_i;
+ for (int lo2h_i = 1; lo2h_i <= spur_order; lo2h_i++) {
+ double lo2harm_freq = lo2_freq * lo2h_i;
+ double hdelta = lo1harm_freq - lo2harm_freq;
+ // .. and check if there is a mixer spur in the IF band
+ if (std::abs(hdelta + if2_freq) < bandwidth/2 or
+ std::abs(hdelta - if2_freq) < bandwidth/2) {
+ return true;
+ }
+ }
+ }
+ // No spurs were found after NxN search
+ return false;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_freq_coercion_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_freq_coercion_expert::resolve()
+{
+ const double actual_if2_freq = _if_freq_d;
+ const double actual_if1_freq = (_lo2_inj_side == INJ_LOW_SIDE) ?
+ (_lo2_freq_c + actual_if2_freq) : (_lo2_freq_c - actual_if2_freq);
+
+ _rf_freq_c = (_lo1_inj_side == INJ_LOW_SIDE) ?
+ (_lo1_freq_c + actual_if1_freq) : (_lo1_freq_c - actual_if1_freq);
+}
+
+/*!---------------------------------------------------------
+ * twinrx_nyquist_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_nyquist_expert::resolve()
+{
+ double if_freq_sign = 1.0;
+ if (_lo1_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0;
+ if (_lo2_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0;
+ _if_freq_c = _if_freq_d * if_freq_sign;
+
+ _db_iface->set_fe_connection(dboard_iface::UNIT_RX, _channel,
+ usrp::fe_connection_t(_codec_conn, _if_freq_c));
+}
+
+/*!---------------------------------------------------------
+ * twinrx_chan_gain_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_chan_gain_expert::resolve()
+{
+ if (_gain_profile != "default") {
+ //TODO: Implement me!
+ throw uhd::not_implemented_error("custom gain strategies not implemeted yet");
+ }
+
+ //Lookup table using settings
+ const twinrx_gain_table table = twinrx_gain_table::lookup_table(
+ _signal_path,
+ (_signal_path==twinrx_ctrl::PATH_HIGHBAND) ? _hb_presel : _lb_presel,
+ _gain_profile);
+
+ //Compute minimum gain. The user-specified gain value will be interpreted as
+ //the gain applied on top of the minimum gain state.
+ //If antennas are shared or swapped, the switch has 6dB of loss
+ size_t gain_index = std::min(static_cast<size_t>(boost::math::round(_gain.get())), table.get_num_entries()-1);
+
+ //Translate gain to an index in the gain table
+ const twinrx_gain_config_t& config = table.find_by_index(gain_index);
+
+ _input_atten = config.atten1;
+ if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+ _hb_atten = config.atten2;
+ } else {
+ _lb_atten = config.atten2;
+ }
+
+ // Preamp 1 should use the Highband amp for frequencies above 3 GHz
+ if (_signal_path == twinrx_ctrl::PATH_HIGHBAND && _hb_presel != twinrx_ctrl::PRESEL_PATH1) {
+ _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_HIGHBAND : twinrx_ctrl::PREAMP_BYPASS;
+ } else {
+ _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_LOWBAND : twinrx_ctrl::PREAMP_BYPASS;
+ }
+
+ _preamp2 = config.amp2;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_lo_config_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_lo_config_expert::resolve()
+{
+ static const uhd::dict<std::string, twinrx_ctrl::lo_source_t> src_lookup =
+ boost::assign::map_list_of
+ ("internal", twinrx_ctrl::LO_INTERNAL)
+ ("external", twinrx_ctrl::LO_EXTERNAL)
+ ("companion", twinrx_ctrl::LO_COMPANION)
+ ("disabled", twinrx_ctrl::LO_DISABLED);
+
+ if (src_lookup.has_key(_lo_source_ch0)) {
+ _lo1_src_ch0 = _lo2_src_ch0 = src_lookup[_lo_source_ch0];
+ } else {
+ throw uhd::value_error("Invalid LO source for channel 0.Choose from {internal, external, companion}");
+ }
+ if (src_lookup.has_key(_lo_source_ch1)) {
+ _lo1_src_ch1 = _lo2_src_ch1 = src_lookup[_lo_source_ch1];
+ } else {
+ throw uhd::value_error("Invalid LO source for channel 1.Choose from {internal, external, companion}");
+ }
+
+ twinrx_ctrl::lo_export_source_t export_src = twinrx_ctrl::LO_EXPORT_DISABLED;
+ if (_lo_export_ch0 and (_lo_source_ch0 == "external")) {
+ throw uhd::value_error("Cannot export an external LO for channel 0");
+ }
+ if (_lo_export_ch1 and (_lo_source_ch1 == "external")) {
+ throw uhd::value_error("Cannot export an external LO for channel 1");
+ }
+ if (_lo_export_ch0 and _lo_export_ch1) {
+ throw uhd::value_error("Cannot export LOs for both channels");
+ } else if (_lo_export_ch0) {
+ export_src = (_lo1_src_ch0 == twinrx_ctrl::LO_INTERNAL) ?
+ twinrx_ctrl::LO_CH1_SYNTH : twinrx_ctrl::LO_CH2_SYNTH;
+ } else if (_lo_export_ch1) {
+ export_src = (_lo1_src_ch1 == twinrx_ctrl::LO_INTERNAL) ?
+ twinrx_ctrl::LO_CH2_SYNTH : twinrx_ctrl::LO_CH1_SYNTH;
+ }
+ _lo1_export_src = _lo2_export_src = export_src;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_lo_freq_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_lo_mapping_expert::resolve()
+{
+ static const size_t CH0_MSK = 0x1;
+ static const size_t CH1_MSK = 0x2;
+
+ // Determine which channels are "driving" each synthesizer
+ // First check for explicit requests i.e. lo_source "internal" or "companion"
+ size_t synth_map[] = {0, 0};
+ if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) {
+ synth_map[0] = synth_map[0] | CH0_MSK;
+ } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
+ synth_map[1] = synth_map[1] | CH0_MSK;
+ }
+ if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) {
+ synth_map[1] = synth_map[1] | CH1_MSK;
+ } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
+ synth_map[0] = synth_map[0] | CH1_MSK;
+ }
+
+ // If a particular channel has its LO source disabled then the other
+ // channel is automatically put in hop mode i.e. the synthesizer that
+ // belongs to the disabled channel can be re-purposed as a redundant LO
+ // to overlap tuning with signal dwell time.
+ bool hopping_enabled = false;
+ if (_lox_src_ch0 == twinrx_ctrl::LO_DISABLED) {
+ if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) {
+ synth_map[0] = synth_map[0] | CH0_MSK;
+ hopping_enabled = true;
+ } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
+ synth_map[1] = synth_map[1] | CH0_MSK;
+ hopping_enabled = true;
+ }
+ }
+ if (_lox_src_ch1 == twinrx_ctrl::LO_DISABLED) {
+ if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) {
+ synth_map[1] = synth_map[1] | CH1_MSK;
+ hopping_enabled = true;
+ } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
+ synth_map[0] = synth_map[0] | CH1_MSK;
+ hopping_enabled = true;
+ }
+ }
+
+ // For each synthesizer come up with the final mapping
+ for (size_t synth = 0; synth < 2; synth++) {
+ experts::data_writer_t<lo_synth_mapping_t>& lox_mapping =
+ (synth == 0) ? _lox_mapping_synth0 : _lox_mapping_synth1;
+ if (synth_map[synth] == (CH0_MSK|CH1_MSK)) {
+ lox_mapping = MAPPING_SHARED;
+ } else if (synth_map[synth] == CH0_MSK) {
+ lox_mapping = MAPPING_CH0;
+ } else if (synth_map[synth] == CH1_MSK) {
+ lox_mapping = MAPPING_CH1;
+ } else {
+ lox_mapping = MAPPING_NONE;
+ }
+ }
+ _lox_hopping_enabled = hopping_enabled;
+}
+
+/*!---------------------------------------------------------
+ * twinrx_antenna_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_antenna_expert::resolve()
+{
+ static const std::string ANT0 = "RX1", ANT1 = "RX2";
+
+ if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT1) {
+ _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+ } else if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT0) {
+ if (_enabled_ch0 and _enabled_ch1) {
+ _ant_mapping = twinrx_ctrl::ANT1_SHARED;
+ } else if (_enabled_ch0) {
+ _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+ } else if (_enabled_ch1) {
+ _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+ }
+ } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT1) {
+ if (_enabled_ch0 and _enabled_ch1) {
+ _ant_mapping = twinrx_ctrl::ANT2_SHARED;
+ } else if (_enabled_ch0) {
+ _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+ } else if (_enabled_ch1) {
+ _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
+ }
+ } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT0) {
+ _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
+ } else if (_antenna_ch0 != ANT0 and _antenna_ch0 != ANT1) {
+ throw uhd::value_error("Invalid antenna selection " + _antenna_ch0.get() + " for channel 0. Must be " + ANT0 + " or " + ANT1);
+ } else if (_antenna_ch1 != ANT0 and _antenna_ch1 != ANT1) {
+ throw uhd::value_error("Invalid antenna selection " + _antenna_ch1.get() + " for channel 1. Must be " + ANT0 + " or " + ANT1);
+ }
+
+ //TODO: Implement hooks for the calibration switch
+ _cal_mode = twinrx_ctrl::CAL_DISABLED;
+
+ if (_cal_mode == twinrx_ctrl::CAL_CH1 and _lo_export_ch1) {
+ throw uhd::value_error("Cannot calibrate channel 0 and export the LO for channel 1.");
+ } else if (_cal_mode == twinrx_ctrl::CAL_CH2 and _lo_export_ch0) {
+ throw uhd::value_error("Cannot calibrate channel 1 and export the LO for channel 0.");
+ }
+}
+
+/*!---------------------------------------------------------
+ * twinrx_ant_gain_expert::resolve
+ * ---------------------------------------------------------
+ */
+void twinrx_ant_gain_expert::resolve()
+{
+ switch (_ant_mapping) {
+ case twinrx_ctrl::ANTX_NATIVE:
+ _ant0_input_atten = _ch0_input_atten;
+ _ant0_preamp1 = _ch0_preamp1;
+ _ant0_preamp2 = _ch0_preamp2;
+ _ant0_lb_preamp_presel = _ch0_lb_preamp_presel;
+ _ant1_input_atten = _ch1_input_atten;
+ _ant1_preamp1 = _ch1_preamp1;
+ _ant1_preamp2 = _ch1_preamp2;
+ _ant1_lb_preamp_presel = _ch1_lb_preamp_presel;
+ break;
+ case twinrx_ctrl::ANTX_SWAPPED:
+ _ant0_input_atten = _ch1_input_atten;
+ _ant0_preamp1 = _ch1_preamp1;
+ _ant0_preamp2 = _ch1_preamp2;
+ _ant0_lb_preamp_presel = _ch1_lb_preamp_presel;
+ _ant1_input_atten = _ch0_input_atten;
+ _ant1_preamp1 = _ch0_preamp1;
+ _ant1_preamp2 = _ch0_preamp2;
+ _ant1_lb_preamp_presel = _ch0_lb_preamp_presel;
+ break;
+ case twinrx_ctrl::ANT1_SHARED:
+ if ((_ch0_input_atten != _ch1_input_atten) or
+ (_ch0_preamp1 != _ch1_preamp1) or
+ (_ch0_preamp2 != _ch1_preamp2) or
+ (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel))
+ {
+ UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1.";
+ }
+ _ant0_input_atten = _ch0_input_atten;
+ _ant0_preamp1 = _ch0_preamp1;
+ _ant0_preamp2 = _ch0_preamp2;
+ _ant0_lb_preamp_presel = _ch0_lb_preamp_presel;
+
+ _ant1_input_atten = 0;
+ _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS;
+ _ant1_preamp2 = false;
+ _ant1_lb_preamp_presel = false;
+ break;
+ case twinrx_ctrl::ANT2_SHARED:
+ if ((_ch0_input_atten != _ch1_input_atten) or
+ (_ch0_preamp1 != _ch1_preamp1) or
+ (_ch0_preamp2 != _ch1_preamp2) or
+ (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel))
+ {
+ UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1.";
+ }
+ _ant1_input_atten = _ch0_input_atten;
+ _ant1_preamp1 = _ch0_preamp1;
+ _ant1_preamp2 = _ch0_preamp2;
+ _ant1_lb_preamp_presel = _ch0_lb_preamp_presel;
+
+ _ant0_input_atten = 0;
+ _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS;
+ _ant0_preamp2 = false;
+ _ant0_lb_preamp_presel = false;
+ break;
+ default:
+ _ant0_input_atten = 0;
+ _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS;
+ _ant0_preamp2 = false;
+ _ant0_lb_preamp_presel = false;
+ _ant1_input_atten = 0;
+ _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS;
+ _ant1_preamp2 = false;
+ _ant1_lb_preamp_presel = false;
+ break;
+ }
+}
+
+/*!---------------------------------------------------------
+ * twinrx_settings_expert::resolve
+ * ---------------------------------------------------------
+ */
+const bool twinrx_settings_expert::FORCE_COMMIT = false;
+
+void twinrx_settings_expert::resolve()
+{
+ for (size_t i = 0; i < 2; i++) {
+ ch_settings& ch_set = (i == 1) ? _ch1 : _ch0;
+ twinrx_ctrl::channel_t ch = (i == 1) ? twinrx_ctrl::CH2 : twinrx_ctrl::CH1;
+ _ctrl->set_chan_enabled(ch, ch_set.chan_enabled, FORCE_COMMIT);
+ _ctrl->set_preamp1(ch, ch_set.preamp1, FORCE_COMMIT);
+ _ctrl->set_preamp2(ch, ch_set.preamp2, FORCE_COMMIT);
+ _ctrl->set_lb_preamp_preselector(ch, ch_set.lb_preamp_presel, FORCE_COMMIT);
+ _ctrl->set_signal_path(ch, ch_set.signal_path, FORCE_COMMIT);
+ _ctrl->set_lb_preselector(ch, ch_set.lb_presel, FORCE_COMMIT);
+ _ctrl->set_hb_preselector(ch, ch_set.hb_presel, FORCE_COMMIT);
+ _ctrl->set_input_atten(ch, ch_set.input_atten, FORCE_COMMIT);
+ _ctrl->set_lb_atten(ch, ch_set.lb_atten, FORCE_COMMIT);
+ _ctrl->set_hb_atten(ch, ch_set.hb_atten, FORCE_COMMIT);
+ _ctrl->set_lo1_source(ch, ch_set.lo1_source, FORCE_COMMIT);
+ _ctrl->set_lo2_source(ch, ch_set.lo2_source, FORCE_COMMIT);
+ }
+
+ _resolve_lox_freq(STAGE_LO1,
+ _ch0.lo1_freq_d, _ch1.lo1_freq_d, _ch0.lo1_freq_c, _ch1.lo1_freq_c,
+ _ch0.lo1_source, _ch1.lo1_source, _lo1_synth0_mapping, _lo1_synth1_mapping,
+ _lo1_hopping_enabled);
+ _resolve_lox_freq(STAGE_LO2,
+ _ch0.lo2_freq_d, _ch1.lo2_freq_d, _ch0.lo2_freq_c, _ch1.lo2_freq_c,
+ _ch0.lo2_source, _ch1.lo2_source, _lo2_synth0_mapping, _lo2_synth1_mapping,
+ _lo2_hopping_enabled);
+
+ _ctrl->set_lo1_export_source(_lo1_export_src, FORCE_COMMIT);
+ _ctrl->set_lo2_export_source(_lo2_export_src, FORCE_COMMIT);
+ _ctrl->set_antenna_mapping(_ant_mapping, FORCE_COMMIT);
+ //TODO: Re-enable this when we support this mode
+ //_ctrl->set_crossover_cal_mode(_cal_mode, FORCE_COMMIT);
+
+ _ctrl->commit();
+}
+
+void twinrx_settings_expert::_resolve_lox_freq(
+ lo_stage_t lo_stage,
+ uhd::experts::data_reader_t<double>& ch0_freq_d,
+ uhd::experts::data_reader_t<double>& ch1_freq_d,
+ uhd::experts::data_writer_t<double>& ch0_freq_c,
+ uhd::experts::data_writer_t<double>& ch1_freq_c,
+ twinrx_ctrl::lo_source_t ch0_lo_source,
+ twinrx_ctrl::lo_source_t ch1_lo_source,
+ lo_synth_mapping_t synth0_mapping,
+ lo_synth_mapping_t synth1_mapping,
+ bool hopping_enabled)
+{
+ if (ch0_lo_source == twinrx_ctrl::LO_EXTERNAL) {
+ // If the LO is external then we don't need to program any synthesizers
+ ch0_freq_c = ch0_freq_d;
+ } else {
+ // When in hopping mode, only attempt to write the LO frequency if it is actually
+ // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
+ // hopping, then always write the frequency because other inputs might require
+ // an LO re-commit
+ const bool freq_update_request = (not hopping_enabled) or ch0_freq_d.is_dirty();
+ if (synth0_mapping == MAPPING_CH0 and freq_update_request) {
+ ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch0_freq_d);
+ } else if (synth1_mapping == MAPPING_CH0 and freq_update_request) {
+ ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch0_freq_d);
+ } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
+ // If any synthesizer is being shared then we are not in hopping mode
+ if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) {
+ UHD_MSG(warning) <<
+ "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels.";
+ }
+ twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+ ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
+ ch1_freq_c = ch0_freq_c;
+ }
+ }
+
+ if (ch1_lo_source == twinrx_ctrl::LO_EXTERNAL) {
+ // If the LO is external then we don't need to program any synthesizers
+ ch1_freq_c = ch1_freq_d;
+ } else {
+ // When in hopping mode, only attempt to write the LO frequency if it is actually
+ // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
+ // hopping, then always write the frequency because other inputs might require
+ // an LO re-commit
+ const bool freq_update_request = (not hopping_enabled) or ch1_freq_d.is_dirty();
+ // As an additional layer of protection from unnecessarily committing the LO, check
+ // if the frequency has actually changed.
+ if (synth0_mapping == MAPPING_CH1 and freq_update_request) {
+ ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch1_freq_d);
+ } else if (synth1_mapping == MAPPING_CH1 and freq_update_request) {
+ ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch1_freq_d);
+ } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
+ // If any synthesizer is being shared then we are not in hopping mode
+ if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) {
+ UHD_MSG(warning) <<
+ "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels.";
+ }
+ twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
+ ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
+ ch1_freq_c = ch0_freq_c;
+ }
+ }
+}
+
+double twinrx_settings_expert::_set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq)
+{
+ lo_freq_cache_t* freq_cache = NULL;
+ if (stage == STAGE_LO1) {
+ freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo1_synth0_freq : &_cached_lo1_synth1_freq;
+ } else if (stage == STAGE_LO2) {
+ freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo2_synth0_freq : &_cached_lo2_synth1_freq;
+ } else {
+ throw uhd::assertion_error("Invalid LO stage");
+ }
+
+ // Check if the frequency has actually changed before configuring synthesizers
+ double coerced_freq = 0.0;
+ if (freq_cache->desired != freq) {
+ if (stage == STAGE_LO1) {
+ coerced_freq = _ctrl->set_lo1_synth_freq(ch, freq, FORCE_COMMIT);
+ } else {
+ coerced_freq = _ctrl->set_lo2_synth_freq(ch, freq, FORCE_COMMIT);
+ }
+ freq_cache->desired = rf_freq_ppm_t(freq);
+ freq_cache->coerced = coerced_freq;
+ } else {
+ coerced_freq = freq_cache->coerced;
+ }
+ return coerced_freq;
+}
+
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp
new file mode 100644
index 000000000..f2601a09b
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp
@@ -0,0 +1,630 @@
+//
+// 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_DBOARD_TWINRX_EXPERTS_HPP
+#define INCLUDED_DBOARD_TWINRX_EXPERTS_HPP
+
+#include "twinrx_ctrl.hpp"
+#include "expert_nodes.hpp"
+#include <uhd/utils/math.hpp>
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+//---------------------------------------------------------
+// Misc types and definitions
+//---------------------------------------------------------
+
+struct rf_freq_abs_t : public uhd::math::fp_compare::fp_compare_delta<double> {
+ rf_freq_abs_t(double freq = 0.0, double epsilon = 1.0 /* 1Hz epsilon */) :
+ uhd::math::fp_compare::fp_compare_delta<double>(freq, epsilon) {}
+ inline double get() const { return _value; }
+};
+
+struct rf_freq_ppm_t : public rf_freq_abs_t {
+ rf_freq_ppm_t(double freq = 0.0, double epsilon_ppm = 0.1 /* 1PPM epsilon */) :
+ rf_freq_abs_t(freq, 1e-6 * freq * epsilon_ppm) {}
+};
+
+enum lo_stage_t { STAGE_LO1, STAGE_LO2 };
+enum lo_inj_side_t { INJ_LOW_SIDE, INJ_HIGH_SIDE };
+enum lo_synth_mapping_t { MAPPING_NONE, MAPPING_CH0, MAPPING_CH1, MAPPING_SHARED };
+
+static const std::string prepend_ch(std::string name, const std::string& ch) {
+ return ch + "/" + name;
+}
+
+static const std::string lo_stage_str(lo_stage_t stage, bool lower = false) {
+ std::string prefix = lower ? "lo" : "LO";
+ return prefix + ((stage == STAGE_LO1) ? "1" : "2");
+}
+
+/*!---------------------------------------------------------
+ * twinrx_freq_path_expert
+ *
+ * This expert is responsble for translating a user-specified
+ * RF and IF center frequency into TwinRX specific settings
+ * like band, preselector path, LO frequency and injection
+ * sides for both the LO stages.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_freq_path_expert : public experts::worker_node_t {
+public:
+ twinrx_freq_path_expert(const experts::node_retriever_t& db, std::string ch)
+ : experts::worker_node_t(prepend_ch("twinrx_freq_path_expert", ch)),
+ _rf_freq_d (db, prepend_ch("freq/desired", ch)),
+ _if_freq_d (db, prepend_ch("if_freq/desired", ch)),
+ _signal_path (db, prepend_ch("ch/signal_path", ch)),
+ _lb_presel (db, prepend_ch("ch/lb_presel", ch)),
+ _hb_presel (db, prepend_ch("ch/hb_presel", ch)),
+ _lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", ch)),
+ _lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)),
+ _lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)),
+ _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)),
+ _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch))
+ {
+ bind_accessor(_rf_freq_d);
+ bind_accessor(_if_freq_d);
+ bind_accessor(_signal_path);
+ bind_accessor(_lb_presel);
+ bind_accessor(_hb_presel);
+ bind_accessor(_lb_preamp_presel);
+ bind_accessor(_lo1_freq_d);
+ bind_accessor(_lo2_freq_d);
+ bind_accessor(_lo1_inj_side);
+ bind_accessor(_lo2_inj_side);
+ }
+
+private:
+ virtual void resolve();
+ static lo_inj_side_t _compute_lo2_inj_side(
+ double lo1_freq, double if1_freq, double if2_freq, double bandwidth);
+ static bool _has_mixer_spurs(
+ double lo1_freq, double lo2_freq, double if2_freq,
+ double bandwidth, int spur_order);
+
+ //Inputs
+ experts::data_reader_t<double> _rf_freq_d;
+ experts::data_reader_t<double> _if_freq_d;
+ //Outputs
+ experts::data_writer_t<twinrx_ctrl::signal_path_t> _signal_path;
+ experts::data_writer_t<twinrx_ctrl::preselector_path_t> _lb_presel;
+ experts::data_writer_t<twinrx_ctrl::preselector_path_t> _hb_presel;
+ experts::data_writer_t<bool> _lb_preamp_presel;
+ experts::data_writer_t<double> _lo1_freq_d;
+ experts::data_writer_t<double> _lo2_freq_d;
+ experts::data_writer_t<lo_inj_side_t> _lo1_inj_side;
+ experts::data_writer_t<lo_inj_side_t> _lo2_inj_side;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_lo_config_expert
+ *
+ * This expert is responsible for translating high level
+ * channel-scoped LO source and export settings to low-level
+ * channel-scoped settings. The expert only deals with
+ * the source and export attributes, not frequency.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_lo_config_expert : public experts::worker_node_t {
+public:
+ twinrx_lo_config_expert(const experts::node_retriever_t& db)
+ : experts::worker_node_t("twinrx_lo_config_expert"),
+ _lo_source_ch0 (db, prepend_ch("los/all/source", "0")),
+ _lo_source_ch1 (db, prepend_ch("los/all/source", "1")),
+ _lo_export_ch0 (db, prepend_ch("los/all/export", "0")),
+ _lo_export_ch1 (db, prepend_ch("los/all/export", "1")),
+ _lo1_src_ch0 (db, prepend_ch("ch/LO1/source", "0")),
+ _lo1_src_ch1 (db, prepend_ch("ch/LO1/source", "1")),
+ _lo2_src_ch0 (db, prepend_ch("ch/LO2/source", "0")),
+ _lo2_src_ch1 (db, prepend_ch("ch/LO2/source", "1")),
+ _lo1_export_src (db, "com/LO1/export_source"),
+ _lo2_export_src (db, "com/LO2/export_source")
+ {
+ bind_accessor(_lo_source_ch0);
+ bind_accessor(_lo_source_ch1);
+ bind_accessor(_lo_export_ch0);
+ bind_accessor(_lo_export_ch1);
+ bind_accessor(_lo1_src_ch0);
+ bind_accessor(_lo1_src_ch1);
+ bind_accessor(_lo2_src_ch0);
+ bind_accessor(_lo2_src_ch1);
+ bind_accessor(_lo1_export_src);
+ bind_accessor(_lo2_export_src);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<std::string> _lo_source_ch0;
+ experts::data_reader_t<std::string> _lo_source_ch1;
+ experts::data_reader_t<bool> _lo_export_ch0;
+ experts::data_reader_t<bool> _lo_export_ch1;
+ //Outputs
+ experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch0;
+ experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch1;
+ experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch0;
+ experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch1;
+ experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
+ experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_lo_mapping_expert
+ *
+ * This expert is responsible for translating low-level
+ * channel-scoped LO source and export settings to low-level
+ * synthesizer-scoped settings. The expert deals with the
+ * extremely flexible channel->synthesizer mapping and handles
+ * frequency hopping modes.
+ *
+ * One instance of this expert is required for each LO stage
+ * ---------------------------------------------------------
+ */
+class twinrx_lo_mapping_expert : public experts::worker_node_t {
+public:
+ twinrx_lo_mapping_expert(const experts::node_retriever_t& db, lo_stage_t stage)
+ : experts::worker_node_t("twinrx_" + lo_stage_str(stage, true) + "_mapping_expert"),
+ _lox_src_ch0 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "0")),
+ _lox_src_ch1 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "1")),
+ _lox_mapping_synth0 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "0")),
+ _lox_mapping_synth1 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "1")),
+ _lox_hopping_enabled (db, "com/synth/" + lo_stage_str(stage) + "/hopping_enabled")
+ {
+ bind_accessor(_lox_src_ch0);
+ bind_accessor(_lox_src_ch1);
+ bind_accessor(_lox_mapping_synth0);
+ bind_accessor(_lox_mapping_synth1);
+ bind_accessor(_lox_hopping_enabled);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch0;
+ experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch1;
+ //Outputs
+ experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth0;
+ experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth1;
+ experts::data_writer_t<bool> _lox_hopping_enabled;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_freq_coercion_expert
+ *
+ * This expert is responsible for calculating the coerced
+ * RF frequency after most settings and modes have been
+ * resolved.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_freq_coercion_expert : public experts::worker_node_t {
+public:
+ twinrx_freq_coercion_expert(const experts::node_retriever_t& db, std::string ch)
+ : experts::worker_node_t(prepend_ch("twinrx_freq_coercion_expert", ch)),
+ _lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)),
+ _lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch)),
+ _if_freq_d (db, prepend_ch("if_freq/desired", ch)),
+ _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)),
+ _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)),
+ _rf_freq_c (db, prepend_ch("freq/coerced", ch))
+ {
+ bind_accessor(_lo1_freq_c);
+ bind_accessor(_lo2_freq_c);
+ bind_accessor(_if_freq_d);
+ bind_accessor(_lo1_inj_side);
+ bind_accessor(_lo2_inj_side);
+ bind_accessor(_rf_freq_c);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<double> _lo1_freq_c;
+ experts::data_reader_t<double> _lo2_freq_c;
+ experts::data_reader_t<double> _if_freq_d;
+ experts::data_reader_t<lo_inj_side_t> _lo1_inj_side;
+ experts::data_reader_t<lo_inj_side_t> _lo2_inj_side;
+ //Outputs
+ experts::data_writer_t<double> _rf_freq_c;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_nyquist_expert
+ *
+ * This expert is responsible for figuring out the DSP
+ * front-end settings required for each channel
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_nyquist_expert : public experts::worker_node_t {
+public:
+ twinrx_nyquist_expert(const experts::node_retriever_t& db, std::string ch,
+ dboard_iface::sptr db_iface)
+ : experts::worker_node_t(prepend_ch("twinrx_nyquist_expert", ch)),
+ _channel (ch),
+ _codec_conn (ch=="0"?"II":"QQ"), //Ch->ADC Port mapping
+ _if_freq_d (db, prepend_ch("if_freq/desired", ch)),
+ _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)),
+ _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)),
+ _if_freq_c (db, prepend_ch("if_freq/coerced", ch)),
+ _db_iface (db_iface)
+ {
+ bind_accessor(_if_freq_d);
+ bind_accessor(_lo1_inj_side);
+ bind_accessor(_lo2_inj_side);
+ bind_accessor(_if_freq_c);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ const std::string _channel;
+ const std::string _codec_conn;
+ experts::data_reader_t<double> _if_freq_d;
+ experts::data_reader_t<lo_inj_side_t> _lo1_inj_side;
+ experts::data_reader_t<lo_inj_side_t> _lo2_inj_side;
+ //Outputs
+ experts::data_writer_t<double> _if_freq_c;
+ dboard_iface::sptr _db_iface;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_antenna_expert
+ *
+ * This expert is responsible for translating high-level
+ * antenna selection settings and channel enables to low-level
+ * switch configurations.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_antenna_expert : public experts::worker_node_t {
+public:
+ twinrx_antenna_expert(const experts::node_retriever_t& db)
+ : experts::worker_node_t("twinrx_antenna_expert"),
+ _antenna_ch0 (db, prepend_ch("antenna", "0")),
+ _antenna_ch1 (db, prepend_ch("antenna", "1")),
+ _enabled_ch0 (db, prepend_ch("enabled", "0")),
+ _enabled_ch1 (db, prepend_ch("enabled", "1")),
+ _lo_export_ch0 (db, prepend_ch("los/all/export", "0")),
+ _lo_export_ch1 (db, prepend_ch("los/all/export", "1")),
+ _ant_mapping (db, "com/ant_mapping"),
+ _cal_mode (db, "com/cal_mode")
+ {
+ bind_accessor(_antenna_ch0);
+ bind_accessor(_antenna_ch1);
+ bind_accessor(_enabled_ch0);
+ bind_accessor(_enabled_ch1);
+ bind_accessor(_lo_export_ch0);
+ bind_accessor(_lo_export_ch1);
+ bind_accessor(_ant_mapping);
+ bind_accessor(_cal_mode);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<std::string> _antenna_ch0;
+ experts::data_reader_t<std::string> _antenna_ch1;
+ experts::data_reader_t<bool> _enabled_ch0;
+ experts::data_reader_t<bool> _enabled_ch1;
+ experts::data_reader_t<bool> _lo_export_ch0;
+ experts::data_reader_t<bool> _lo_export_ch1;
+ //Outputs
+ experts::data_writer_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
+ experts::data_writer_t<twinrx_ctrl::cal_mode_t> _cal_mode;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_chan_gain_expert
+ *
+ * This expert is responsible for mapping high-level channel
+ * gain settings to individual attenuator and amp configurations
+ * that are also channel-scoped. This expert will implement
+ * the gain distribution strategy.
+ *
+ * One instance of this expert is required for each channel
+ * ---------------------------------------------------------
+ */
+class twinrx_chan_gain_expert : public experts::worker_node_t {
+public:
+ twinrx_chan_gain_expert(const experts::node_retriever_t& db, std::string ch)
+ : experts::worker_node_t(prepend_ch("twinrx_chan_gain_expert", ch)),
+ _gain (db, prepend_ch("gain", ch)),
+ _gain_profile (db, prepend_ch("gain_profile", ch)),
+ _signal_path (db, prepend_ch("ch/signal_path", ch)),
+ _lb_presel (db, prepend_ch("ch/lb_presel", ch)),
+ _hb_presel (db, prepend_ch("ch/hb_presel", ch)),
+ _ant_mapping (db, "com/ant_mapping"),
+ _input_atten (db, prepend_ch("ch/input_atten", ch)),
+ _lb_atten (db, prepend_ch("ch/lb_atten", ch)),
+ _hb_atten (db, prepend_ch("ch/hb_atten", ch)),
+ _preamp1 (db, prepend_ch("ch/preamp1", ch)),
+ _preamp2 (db, prepend_ch("ch/preamp2", ch))
+ {
+ bind_accessor(_gain);
+ bind_accessor(_gain_profile);
+ bind_accessor(_signal_path);
+ bind_accessor(_lb_presel);
+ bind_accessor(_hb_presel);
+ bind_accessor(_ant_mapping);
+ bind_accessor(_input_atten);
+ bind_accessor(_lb_atten);
+ bind_accessor(_hb_atten);
+ bind_accessor(_preamp1);
+ bind_accessor(_preamp2);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<double> _gain;
+ experts::data_reader_t<std::string> _gain_profile;
+ experts::data_reader_t<twinrx_ctrl::signal_path_t> _signal_path;
+ experts::data_reader_t<twinrx_ctrl::preselector_path_t> _lb_presel;
+ experts::data_reader_t<twinrx_ctrl::preselector_path_t> _hb_presel;
+ experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
+ //Outputs
+ experts::data_writer_t<boost::uint8_t> _input_atten;
+ experts::data_writer_t<boost::uint8_t> _lb_atten;
+ experts::data_writer_t<boost::uint8_t> _hb_atten;
+ experts::data_writer_t<twinrx_ctrl::preamp_state_t> _preamp1;
+ experts::data_writer_t<bool> _preamp2;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_ant_gain_expert
+ *
+ * This expert is responsible for translating between the
+ * channel-scoped low-level gain settings to antenna-scoped
+ * gain settings.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_ant_gain_expert : public experts::worker_node_t {
+public:
+ twinrx_ant_gain_expert(const experts::node_retriever_t& db)
+ : experts::worker_node_t("twinrx_ant_gain_expert"),
+ _ant_mapping (db, "com/ant_mapping"),
+ _ch0_input_atten (db, prepend_ch("ch/input_atten", "0")),
+ _ch0_preamp1 (db, prepend_ch("ch/preamp1", "0")),
+ _ch0_preamp2 (db, prepend_ch("ch/preamp2", "0")),
+ _ch0_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "0")),
+ _ch1_input_atten (db, prepend_ch("ch/input_atten", "1")),
+ _ch1_preamp1 (db, prepend_ch("ch/preamp1", "1")),
+ _ch1_preamp2 (db, prepend_ch("ch/preamp2", "1")),
+ _ch1_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "1")),
+ _ant0_input_atten (db, prepend_ch("ant/input_atten", "0")),
+ _ant0_preamp1 (db, prepend_ch("ant/preamp1", "0")),
+ _ant0_preamp2 (db, prepend_ch("ant/preamp2", "0")),
+ _ant0_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "0")),
+ _ant1_input_atten (db, prepend_ch("ant/input_atten", "1")),
+ _ant1_preamp1 (db, prepend_ch("ant/preamp1", "1")),
+ _ant1_preamp2 (db, prepend_ch("ant/preamp2", "1")),
+ _ant1_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "1"))
+ {
+ bind_accessor(_ant_mapping);
+ bind_accessor(_ch0_input_atten);
+ bind_accessor(_ch0_preamp1);
+ bind_accessor(_ch0_preamp2);
+ bind_accessor(_ch0_lb_preamp_presel);
+ bind_accessor(_ch1_input_atten);
+ bind_accessor(_ch1_preamp1);
+ bind_accessor(_ch1_preamp2);
+ bind_accessor(_ch1_lb_preamp_presel);
+ bind_accessor(_ant0_input_atten);
+ bind_accessor(_ant0_preamp1);
+ bind_accessor(_ant0_preamp2);
+ bind_accessor(_ant0_lb_preamp_presel);
+ bind_accessor(_ant1_input_atten);
+ bind_accessor(_ant1_preamp1);
+ bind_accessor(_ant1_preamp2);
+ bind_accessor(_ant1_lb_preamp_presel);
+ }
+
+private:
+ virtual void resolve();
+
+ //Inputs
+ experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
+ experts::data_reader_t<boost::uint8_t> _ch0_input_atten;
+ experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch0_preamp1;
+ experts::data_reader_t<bool> _ch0_preamp2;
+ experts::data_reader_t<bool> _ch0_lb_preamp_presel;
+ experts::data_reader_t<boost::uint8_t> _ch1_input_atten;
+ experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch1_preamp1;
+ experts::data_reader_t<bool> _ch1_preamp2;
+ experts::data_reader_t<bool> _ch1_lb_preamp_presel;
+
+ //Outputs
+ experts::data_writer_t<boost::uint8_t> _ant0_input_atten;
+ experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant0_preamp1;
+ experts::data_writer_t<bool> _ant0_preamp2;
+ experts::data_writer_t<bool> _ant0_lb_preamp_presel;
+ experts::data_writer_t<boost::uint8_t> _ant1_input_atten;
+ experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant1_preamp1;
+ experts::data_writer_t<bool> _ant1_preamp2;
+ experts::data_writer_t<bool> _ant1_lb_preamp_presel;
+};
+
+/*!---------------------------------------------------------
+ * twinrx_settings_expert
+ *
+ * This expert is responsible for gathering all low-level
+ * settings and writing them to hardware. All LO frequency
+ * settings are cached with a hysteresis. All other settings
+ * are always written to twinrx_ctrl and rely on register
+ * level caching.
+ *
+ * One instance of this expert is required for all channels
+ * ---------------------------------------------------------
+ */
+class twinrx_settings_expert : public experts::worker_node_t {
+public:
+ twinrx_settings_expert(const experts::node_retriever_t& db, twinrx_ctrl::sptr ctrl)
+ : experts::worker_node_t("twinrx_settings_expert"), _ctrl(ctrl),
+ _ch0 (db, "0"),
+ _ch1 (db, "1"),
+ _lo1_synth0_mapping(db, "0/synth/LO1/mapping"),
+ _lo1_synth1_mapping(db, "1/synth/LO1/mapping"),
+ _lo2_synth0_mapping(db, "0/synth/LO2/mapping"),
+ _lo2_synth1_mapping(db, "1/synth/LO2/mapping"),
+ _lo1_hopping_enabled(db, "com/synth/LO1/hopping_enabled"),
+ _lo2_hopping_enabled(db, "com/synth/LO2/hopping_enabled"),
+ _lo1_export_src (db, "com/LO1/export_source"),
+ _lo2_export_src (db, "com/LO2/export_source"),
+ _ant_mapping (db, "com/ant_mapping"),
+ _cal_mode (db, "com/cal_mode")
+ {
+ for (size_t i = 0; i < 2; i++) {
+ ch_settings& ch = (i==1) ? _ch1 : _ch0;
+ bind_accessor(ch.chan_enabled);
+ bind_accessor(ch.preamp1);
+ bind_accessor(ch.preamp2);
+ bind_accessor(ch.lb_preamp_presel);
+ bind_accessor(ch.signal_path);
+ bind_accessor(ch.lb_presel);
+ bind_accessor(ch.hb_presel);
+ bind_accessor(ch.input_atten);
+ bind_accessor(ch.lb_atten);
+ bind_accessor(ch.hb_atten);
+ bind_accessor(ch.lo1_source);
+ bind_accessor(ch.lo2_source);
+ bind_accessor(ch.lo1_freq_d);
+ bind_accessor(ch.lo2_freq_d);
+ bind_accessor(ch.lo1_freq_c);
+ bind_accessor(ch.lo2_freq_c);
+ }
+ bind_accessor(_lo1_synth0_mapping);
+ bind_accessor(_lo1_synth1_mapping);
+ bind_accessor(_lo2_synth0_mapping);
+ bind_accessor(_lo2_synth1_mapping);
+ bind_accessor(_lo1_hopping_enabled);
+ bind_accessor(_lo2_hopping_enabled);
+ bind_accessor(_lo1_export_src);
+ bind_accessor(_lo2_export_src);
+ bind_accessor(_ant_mapping);
+ bind_accessor(_cal_mode);
+ }
+
+private:
+ virtual void resolve();
+ void _resolve_lox_freq(
+ lo_stage_t lo_stage,
+ experts::data_reader_t<double>& ch0_freq_d,
+ experts::data_reader_t<double>& ch1_freq_d,
+ experts::data_writer_t<double>& ch0_freq_c,
+ experts::data_writer_t<double>& ch1_freq_c,
+ twinrx_ctrl::lo_source_t ch0_lo_source,
+ twinrx_ctrl::lo_source_t ch1_lo_source,
+ lo_synth_mapping_t synth0_mapping,
+ lo_synth_mapping_t synth1_mapping,
+ bool hopping_enabled);
+ double _set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq);
+
+ class ch_settings {
+ public:
+ ch_settings(const experts::node_retriever_t& db, const std::string& ch) :
+ chan_enabled (db, prepend_ch("enabled", ch)),
+ preamp1 (db, prepend_ch("ant/preamp1", ch)),
+ preamp2 (db, prepend_ch("ant/preamp2", ch)),
+ lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", ch)),
+ signal_path (db, prepend_ch("ch/signal_path", ch)),
+ lb_presel (db, prepend_ch("ch/lb_presel", ch)),
+ hb_presel (db, prepend_ch("ch/hb_presel", ch)),
+ input_atten (db, prepend_ch("ant/input_atten", ch)),
+ lb_atten (db, prepend_ch("ch/lb_atten", ch)),
+ hb_atten (db, prepend_ch("ch/hb_atten", ch)),
+ lo1_source (db, prepend_ch("ch/LO1/source", ch)),
+ lo2_source (db, prepend_ch("ch/LO2/source", ch)),
+ lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)),
+ lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)),
+ lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)),
+ lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch))
+ {}
+
+ //Inputs (channel specific)
+ experts::data_reader_t<bool> chan_enabled;
+ experts::data_reader_t<twinrx_ctrl::preamp_state_t> preamp1;
+ experts::data_reader_t<bool> preamp2;
+ experts::data_reader_t<bool> lb_preamp_presel;
+ experts::data_reader_t<twinrx_ctrl::signal_path_t> signal_path;
+ experts::data_reader_t<twinrx_ctrl::preselector_path_t> lb_presel;
+ experts::data_reader_t<twinrx_ctrl::preselector_path_t> hb_presel;
+ experts::data_reader_t<boost::uint8_t> input_atten;
+ experts::data_reader_t<boost::uint8_t> lb_atten;
+ experts::data_reader_t<boost::uint8_t> hb_atten;
+ experts::data_reader_t<twinrx_ctrl::lo_source_t> lo1_source;
+ experts::data_reader_t<twinrx_ctrl::lo_source_t> lo2_source;
+ experts::data_reader_t<double> lo1_freq_d;
+ experts::data_reader_t<double> lo2_freq_d;
+
+ //Output (channel specific)
+ experts::data_writer_t<double> lo1_freq_c;
+ experts::data_writer_t<double> lo2_freq_c;
+ };
+
+ //External interface
+ twinrx_ctrl::sptr _ctrl;
+
+ //Inputs (channel agnostic)
+ ch_settings _ch0;
+ ch_settings _ch1;
+ experts::data_reader_t<lo_synth_mapping_t> _lo1_synth0_mapping;
+ experts::data_reader_t<lo_synth_mapping_t> _lo1_synth1_mapping;
+ experts::data_reader_t<lo_synth_mapping_t> _lo2_synth0_mapping;
+ experts::data_reader_t<lo_synth_mapping_t> _lo2_synth1_mapping;
+ experts::data_reader_t<bool> _lo1_hopping_enabled;
+ experts::data_reader_t<bool> _lo2_hopping_enabled;
+ experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
+ experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
+ experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
+ experts::data_reader_t<twinrx_ctrl::cal_mode_t> _cal_mode;
+
+ //Outputs (channel agnostic)
+ //None
+
+ //Misc
+ struct lo_freq_cache_t {
+ rf_freq_ppm_t desired;
+ double coerced;
+ };
+ lo_freq_cache_t _cached_lo1_synth0_freq;
+ lo_freq_cache_t _cached_lo2_synth0_freq;
+ lo_freq_cache_t _cached_lo1_synth1_freq;
+ lo_freq_cache_t _cached_lo2_synth1_freq;
+
+ static const bool FORCE_COMMIT;
+};
+
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_EXPERTS_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp
new file mode 100644
index 000000000..5cc8b49f3
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp
@@ -0,0 +1,860 @@
+//
+// 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 "twinrx_gain_tables.hpp"
+#include <uhd/exception.hpp>
+#include <boost/assign/list_of.hpp>
+
+using namespace uhd::usrp::dboard::twinrx;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND1_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 4, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 5, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 6, -28.3, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 7, -27.3, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 8, -26.3, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 9, -25.3, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 10, -24.3, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 11, -23.3, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 12, -22.3, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 13, -21.3, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 14, -20.3, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 15, -19.3, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 16, -18.3, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 17, -17.3, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 18, -16.3, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 19, -15.3, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 20, -14.3, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 21, -13.3, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 22, -12.3, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 23, -11.3, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 24, -10.3, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 25, -9.3, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 26, -8.3, 30, 12, false, false ) )
+ ( twinrx_gain_config_t( 27, -7.3, 30, 11, false, false ) )
+ ( twinrx_gain_config_t( 28, -6.3, 29, 11, false, false ) )
+ ( twinrx_gain_config_t( 29, -5.3, 28, 11, false, false ) )
+ ( twinrx_gain_config_t( 30, -4.3, 27, 11, false, false ) )
+ ( twinrx_gain_config_t( 31, -3.3, 27, 10, false, false ) )
+ ( twinrx_gain_config_t( 32, -2.3, 26, 10, false, false ) )
+ ( twinrx_gain_config_t( 33, -1.3, 25, 10, false, false ) )
+ ( twinrx_gain_config_t( 34, -0.3, 24, 10, false, false ) )
+ ( twinrx_gain_config_t( 35, 0.7, 23, 10, false, false ) )
+ ( twinrx_gain_config_t( 36, 1.7, 22, 10, false, false ) )
+ ( twinrx_gain_config_t( 37, 2.7, 21, 10, false, false ) )
+ ( twinrx_gain_config_t( 38, 3.7, 21, 9, false, false ) )
+ ( twinrx_gain_config_t( 39, 4.7, 20, 9, false, false ) )
+ ( twinrx_gain_config_t( 40, 5.7, 19, 9, false, false ) )
+ ( twinrx_gain_config_t( 41, 6.7, 18, 9, false, false ) )
+ ( twinrx_gain_config_t( 42, 7.7, 17, 9, false, false ) )
+ ( twinrx_gain_config_t( 43, 8.7, 16, 9, false, false ) )
+ ( twinrx_gain_config_t( 44, 9.7, 15, 9, false, false ) )
+ ( twinrx_gain_config_t( 45, 10.7, 14, 9, false, false ) )
+ ( twinrx_gain_config_t( 46, 11.7, 13, 9, false, false ) )
+ ( twinrx_gain_config_t( 47, 12.7, 12, 9, false, false ) )
+ ( twinrx_gain_config_t( 48, 13.7, 11, 9, false, false ) )
+ ( twinrx_gain_config_t( 49, 14.7, 10, 9, false, false ) )
+ ( twinrx_gain_config_t( 50, 15.7, 9, 9, false, false ) )
+ ( twinrx_gain_config_t( 51, 16.7, 8, 9, false, false ) )
+ ( twinrx_gain_config_t( 52, 17.7, 7, 9, false, false ) )
+ ( twinrx_gain_config_t( 53, 18.7, 6, 9, false, false ) )
+ ( twinrx_gain_config_t( 54, 19.7, 5, 9, false, false ) )
+ ( twinrx_gain_config_t( 55, 20.7, 4, 9, false, false ) )
+ ( twinrx_gain_config_t( 56, 21.7, 3, 9, false, false ) )
+ ( twinrx_gain_config_t( 57, 22.7, 2, 9, false, false ) )
+ ( twinrx_gain_config_t( 58, 23.7, 1, 9, false, false ) )
+ ( twinrx_gain_config_t( 59, 24.7, 0, 9, false, false ) )
+ ( twinrx_gain_config_t( 60, 25.7, 0, 8, false, false ) )
+ ( twinrx_gain_config_t( 61, 26.7, 0, 7, false, false ) )
+ ( twinrx_gain_config_t( 62, 27.7, 0, 6, false, false ) )
+ ( twinrx_gain_config_t( 63, 28.7, 0, 5, false, false ) )
+ ( twinrx_gain_config_t( 64, 29.7, 0, 4, false, false ) )
+ ( twinrx_gain_config_t( 65, 30.7, 0, 3, false, false ) )
+ ( twinrx_gain_config_t( 66, 31.7, 0, 2, false, false ) )
+ ( twinrx_gain_config_t( 67, 32.7, 0, 1, false, false ) )
+ ( twinrx_gain_config_t( 68, 33.7, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 69, 33.9, 3, 9, true, false ) )
+ ( twinrx_gain_config_t( 70, 34.9, 2, 9, true, false ) )
+ ( twinrx_gain_config_t( 71, 35.9, 1, 9, true, false ) )
+ ( twinrx_gain_config_t( 72, 36.9, 0, 9, true, false ) )
+ ( twinrx_gain_config_t( 73, 37.9, 0, 8, true, false ) )
+ ( twinrx_gain_config_t( 74, 38.9, 0, 7, true, false ) )
+ ( twinrx_gain_config_t( 75, 39.9, 0, 6, true, false ) )
+ ( twinrx_gain_config_t( 76, 40.9, 0, 5, true, false ) )
+ ( twinrx_gain_config_t( 77, 41.9, 0, 4, true, false ) )
+ ( twinrx_gain_config_t( 78, 42.9, 0, 3, true, false ) )
+ ( twinrx_gain_config_t( 79, 43.9, 0, 2, true, false ) )
+ ( twinrx_gain_config_t( 80, 44.9, 0, 1, true, false ) )
+ ( twinrx_gain_config_t( 81, 45.9, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 82, 47.3, 1, 10, true, true ) )
+ ( twinrx_gain_config_t( 83, 48.3, 0, 10, true, true ) )
+ ( twinrx_gain_config_t( 84, 49.3, 0, 9, true, true ) )
+ ( twinrx_gain_config_t( 85, 50.3, 0, 8, true, true ) )
+ ( twinrx_gain_config_t( 86, 51.3, 0, 7, true, true ) )
+ ( twinrx_gain_config_t( 87, 52.3, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 53.3, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 54.3, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 55.3, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 56.3, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 57.3, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 58.3, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND2_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 4, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 5, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 6, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 7, -30.9, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 8, -29.9, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 9, -28.9, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 10, -27.9, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 11, -26.9, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 12, -25.9, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 13, -24.9, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 14, -23.9, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 15, -22.9, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 16, -21.9, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 17, -20.9, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 18, -19.9, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 19, -18.9, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 20, -17.9, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 21, -16.9, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 22, -15.9, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 23, -14.9, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 24, -13.9, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 25, -12.9, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 26, -11.9, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 27, -10.9, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 28, -9.9, 30, 11, false, false ) )
+ ( twinrx_gain_config_t( 29, -8.9, 29, 11, false, false ) )
+ ( twinrx_gain_config_t( 30, -7.9, 29, 10, false, false ) )
+ ( twinrx_gain_config_t( 31, -6.9, 28, 10, false, false ) )
+ ( twinrx_gain_config_t( 32, -5.9, 27, 10, false, false ) )
+ ( twinrx_gain_config_t( 33, -4.9, 27, 9, false, false ) )
+ ( twinrx_gain_config_t( 34, -3.9, 26, 9, false, false ) )
+ ( twinrx_gain_config_t( 35, -2.9, 25, 9, false, false ) )
+ ( twinrx_gain_config_t( 36, -1.9, 24, 9, false, false ) )
+ ( twinrx_gain_config_t( 37, -0.9, 23, 9, false, false ) )
+ ( twinrx_gain_config_t( 38, 0.1, 23, 8, false, false ) )
+ ( twinrx_gain_config_t( 39, 1.1, 22, 8, false, false ) )
+ ( twinrx_gain_config_t( 40, 2.1, 21, 8, false, false ) )
+ ( twinrx_gain_config_t( 41, 3.1, 20, 8, false, false ) )
+ ( twinrx_gain_config_t( 42, 4.1, 19, 8, false, false ) )
+ ( twinrx_gain_config_t( 43, 5.1, 18, 8, false, false ) )
+ ( twinrx_gain_config_t( 44, 6.1, 17, 8, false, false ) )
+ ( twinrx_gain_config_t( 45, 7.1, 16, 8, false, false ) )
+ ( twinrx_gain_config_t( 46, 8.1, 15, 8, false, false ) )
+ ( twinrx_gain_config_t( 47, 9.1, 14, 8, false, false ) )
+ ( twinrx_gain_config_t( 48, 10.1, 13, 8, false, false ) )
+ ( twinrx_gain_config_t( 49, 11.1, 12, 8, false, false ) )
+ ( twinrx_gain_config_t( 50, 12.1, 11, 8, false, false ) )
+ ( twinrx_gain_config_t( 51, 13.1, 10, 8, false, false ) )
+ ( twinrx_gain_config_t( 52, 14.1, 9, 8, false, false ) )
+ ( twinrx_gain_config_t( 53, 15.1, 8, 8, false, false ) )
+ ( twinrx_gain_config_t( 54, 16.1, 7, 8, false, false ) )
+ ( twinrx_gain_config_t( 55, 17.1, 6, 8, false, false ) )
+ ( twinrx_gain_config_t( 56, 18.1, 5, 8, false, false ) )
+ ( twinrx_gain_config_t( 57, 19.1, 4, 8, false, false ) )
+ ( twinrx_gain_config_t( 58, 20.1, 3, 8, false, false ) )
+ ( twinrx_gain_config_t( 59, 21.1, 2, 8, false, false ) )
+ ( twinrx_gain_config_t( 60, 22.1, 1, 8, false, false ) )
+ ( twinrx_gain_config_t( 61, 23.1, 0, 8, false, false ) )
+ ( twinrx_gain_config_t( 62, 24.1, 0, 7, false, false ) )
+ ( twinrx_gain_config_t( 63, 25.1, 0, 6, false, false ) )
+ ( twinrx_gain_config_t( 64, 26.1, 0, 5, false, false ) )
+ ( twinrx_gain_config_t( 65, 27.1, 0, 4, false, false ) )
+ ( twinrx_gain_config_t( 66, 28.1, 0, 3, false, false ) )
+ ( twinrx_gain_config_t( 67, 29.1, 0, 2, false, false ) )
+ ( twinrx_gain_config_t( 68, 30.1, 0, 1, false, false ) )
+ ( twinrx_gain_config_t( 69, 31.9, 0, 10, false, true ) )
+ ( twinrx_gain_config_t( 70, 31.9, 0, 10, false, true ) )
+ ( twinrx_gain_config_t( 71, 32.9, 0, 9, false, true ) )
+ ( twinrx_gain_config_t( 72, 33.9, 0, 8, false, true ) )
+ ( twinrx_gain_config_t( 73, 34.9, 0, 7, false, true ) )
+ ( twinrx_gain_config_t( 74, 35.9, 0, 6, false, true ) )
+ ( twinrx_gain_config_t( 75, 36.9, 0, 5, false, true ) )
+ ( twinrx_gain_config_t( 76, 38.6, 0, 6, true, false ) )
+ ( twinrx_gain_config_t( 77, 39.6, 0, 5, true, false ) )
+ ( twinrx_gain_config_t( 78, 40.6, 0, 4, true, false ) )
+ ( twinrx_gain_config_t( 79, 41.6, 0, 3, true, false ) )
+ ( twinrx_gain_config_t( 80, 42.6, 0, 2, true, false ) )
+ ( twinrx_gain_config_t( 81, 43.6, 0, 1, true, false ) )
+ ( twinrx_gain_config_t( 82, 44.4, 2, 9, true, true ) )
+ ( twinrx_gain_config_t( 83, 45.4, 1, 9, true, true ) )
+ ( twinrx_gain_config_t( 84, 46.4, 0, 9, true, true ) )
+ ( twinrx_gain_config_t( 85, 47.4, 0, 8, true, true ) )
+ ( twinrx_gain_config_t( 86, 48.4, 0, 7, true, true ) )
+ ( twinrx_gain_config_t( 87, 49.4, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 50.4, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 51.4, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 52.4, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 53.4, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 54.4, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 55.4, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND3_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 4, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 5, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 6, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 7, -30.1, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 8, -29.1, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 9, -28.1, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 10, -27.1, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 11, -26.1, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 12, -25.1, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 13, -24.1, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 14, -23.1, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 15, -22.1, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 16, -21.1, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 17, -20.1, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 18, -19.1, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 19, -18.1, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 20, -17.1, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 21, -16.1, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 22, -15.1, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 23, -14.1, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 24, -13.1, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 25, -12.1, 30, 13, false, false ) )
+ ( twinrx_gain_config_t( 26, -11.1, 30, 12, false, false ) )
+ ( twinrx_gain_config_t( 27, -10.1, 29, 12, false, false ) )
+ ( twinrx_gain_config_t( 28, -9.1, 28, 12, false, false ) )
+ ( twinrx_gain_config_t( 29, -8.1, 28, 11, false, false ) )
+ ( twinrx_gain_config_t( 30, -7.1, 27, 11, false, false ) )
+ ( twinrx_gain_config_t( 31, -6.1, 26, 11, false, false ) )
+ ( twinrx_gain_config_t( 32, -5.1, 26, 10, false, false ) )
+ ( twinrx_gain_config_t( 33, -4.1, 25, 10, false, false ) )
+ ( twinrx_gain_config_t( 34, -3.1, 24, 10, false, false ) )
+ ( twinrx_gain_config_t( 35, -2.1, 23, 10, false, false ) )
+ ( twinrx_gain_config_t( 36, -1.1, 22, 10, false, false ) )
+ ( twinrx_gain_config_t( 37, -0.1, 21, 10, false, false ) )
+ ( twinrx_gain_config_t( 38, 0.9, 21, 9, false, false ) )
+ ( twinrx_gain_config_t( 39, 1.9, 20, 9, false, false ) )
+ ( twinrx_gain_config_t( 40, 2.9, 19, 9, false, false ) )
+ ( twinrx_gain_config_t( 41, 3.9, 18, 9, false, false ) )
+ ( twinrx_gain_config_t( 42, 4.9, 17, 9, false, false ) )
+ ( twinrx_gain_config_t( 43, 5.9, 16, 9, false, false ) )
+ ( twinrx_gain_config_t( 44, 6.9, 15, 9, false, false ) )
+ ( twinrx_gain_config_t( 45, 7.9, 14, 9, false, false ) )
+ ( twinrx_gain_config_t( 46, 8.9, 13, 9, false, false ) )
+ ( twinrx_gain_config_t( 47, 9.9, 12, 9, false, false ) )
+ ( twinrx_gain_config_t( 48, 10.9, 11, 9, false, false ) )
+ ( twinrx_gain_config_t( 49, 11.9, 10, 9, false, false ) )
+ ( twinrx_gain_config_t( 50, 12.9, 9, 9, false, false ) )
+ ( twinrx_gain_config_t( 51, 13.9, 8, 9, false, false ) )
+ ( twinrx_gain_config_t( 52, 14.9, 7, 9, false, false ) )
+ ( twinrx_gain_config_t( 53, 15.9, 6, 9, false, false ) )
+ ( twinrx_gain_config_t( 54, 16.9, 5, 9, false, false ) )
+ ( twinrx_gain_config_t( 55, 17.9, 4, 9, false, false ) )
+ ( twinrx_gain_config_t( 56, 18.9, 3, 9, false, false ) )
+ ( twinrx_gain_config_t( 57, 19.9, 2, 9, false, false ) )
+ ( twinrx_gain_config_t( 58, 20.9, 1, 9, false, false ) )
+ ( twinrx_gain_config_t( 59, 21.9, 0, 9, false, false ) )
+ ( twinrx_gain_config_t( 60, 22.9, 0, 8, false, false ) )
+ ( twinrx_gain_config_t( 61, 23.9, 0, 7, false, false ) )
+ ( twinrx_gain_config_t( 62, 24.9, 0, 6, false, false ) )
+ ( twinrx_gain_config_t( 63, 25.9, 0, 5, false, false ) )
+ ( twinrx_gain_config_t( 64, 26.9, 0, 4, false, false ) )
+ ( twinrx_gain_config_t( 65, 27.9, 0, 3, false, false ) )
+ ( twinrx_gain_config_t( 66, 28.9, 0, 2, false, false ) )
+ ( twinrx_gain_config_t( 67, 29.9, 0, 1, false, false ) )
+ ( twinrx_gain_config_t( 68, 31.3, 0, 9, false, true ) )
+ ( twinrx_gain_config_t( 69, 32.3, 0, 8, false, true ) )
+ ( twinrx_gain_config_t( 70, 33.3, 0, 7, false, true ) )
+ ( twinrx_gain_config_t( 71, 34.3, 0, 6, false, true ) )
+ ( twinrx_gain_config_t( 72, 35.3, 0, 5, false, true ) )
+ ( twinrx_gain_config_t( 73, 36.3, 0, 4, false, true ) )
+ ( twinrx_gain_config_t( 74, 37.3, 0, 3, false, true ) )
+ ( twinrx_gain_config_t( 75, 37.6, 0, 9, true, false ) )
+ ( twinrx_gain_config_t( 76, 38.6, 0, 8, true, false ) )
+ ( twinrx_gain_config_t( 77, 39.6, 0, 7, true, false ) )
+ ( twinrx_gain_config_t( 78, 40.6, 0, 6, true, false ) )
+ ( twinrx_gain_config_t( 79, 41.6, 0, 5, true, false ) )
+ ( twinrx_gain_config_t( 80, 42.6, 0, 4, true, false ) )
+ ( twinrx_gain_config_t( 81, 43.6, 0, 3, true, false ) )
+ ( twinrx_gain_config_t( 82, 44.6, 0, 2, true, false ) )
+ ( twinrx_gain_config_t( 83, 45.6, 0, 1, true, false ) )
+ ( twinrx_gain_config_t( 84, 47.0, 0, 9, true, true ) )
+ ( twinrx_gain_config_t( 85, 48.0, 0, 8, true, true ) )
+ ( twinrx_gain_config_t( 86, 49.0, 0, 7, true, true ) )
+ ( twinrx_gain_config_t( 87, 50.0, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 51.0, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 52.0, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 53.0, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 54.0, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 55.0, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 56.0, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> HIGHBAND4_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 4, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 5, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 6, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 7, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 8, -37.2, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 9, -36.2, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 10, -35.2, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 11, -34.2, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 12, -33.2, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 13, -32.2, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 14, -31.2, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 15, -30.2, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 16, -29.2, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 17, -28.2, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 18, -27.2, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 19, -26.2, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 20, -25.2, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 21, -24.2, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 22, -23.2, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 23, -22.2, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 24, -21.2, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 25, -20.2, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 26, -19.2, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 27, -18.2, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 28, -17.2, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 29, -16.2, 31, 10, false, false ) )
+ ( twinrx_gain_config_t( 30, -15.2, 30, 10, false, false ) )
+ ( twinrx_gain_config_t( 31, -14.2, 30, 9, false, false ) )
+ ( twinrx_gain_config_t( 32, -13.2, 29, 9, false, false ) )
+ ( twinrx_gain_config_t( 33, -12.2, 28, 9, false, false ) )
+ ( twinrx_gain_config_t( 34, -11.2, 28, 8, false, false ) )
+ ( twinrx_gain_config_t( 35, -10.2, 27, 8, false, false ) )
+ ( twinrx_gain_config_t( 36, -9.2, 27, 7, false, false ) )
+ ( twinrx_gain_config_t( 37, -8.2, 26, 7, false, false ) )
+ ( twinrx_gain_config_t( 38, -7.2, 25, 7, false, false ) )
+ ( twinrx_gain_config_t( 39, -6.2, 24, 7, false, false ) )
+ ( twinrx_gain_config_t( 40, -5.2, 24, 6, false, false ) )
+ ( twinrx_gain_config_t( 41, -4.2, 23, 6, false, false ) )
+ ( twinrx_gain_config_t( 42, -3.2, 22, 6, false, false ) )
+ ( twinrx_gain_config_t( 43, -2.2, 21, 6, false, false ) )
+ ( twinrx_gain_config_t( 44, -1.2, 20, 6, false, false ) )
+ ( twinrx_gain_config_t( 45, -0.2, 19, 6, false, false ) )
+ ( twinrx_gain_config_t( 46, 0.8, 18, 6, false, false ) )
+ ( twinrx_gain_config_t( 47, 1.8, 17, 6, false, false ) )
+ ( twinrx_gain_config_t( 48, 2.8, 16, 6, false, false ) )
+ ( twinrx_gain_config_t( 49, 3.8, 16, 5, false, false ) )
+ ( twinrx_gain_config_t( 50, 4.8, 15, 5, false, false ) )
+ ( twinrx_gain_config_t( 51, 5.8, 14, 5, false, false ) )
+ ( twinrx_gain_config_t( 52, 6.8, 13, 5, false, false ) )
+ ( twinrx_gain_config_t( 53, 7.8, 12, 5, false, false ) )
+ ( twinrx_gain_config_t( 54, 8.8, 11, 5, false, false ) )
+ ( twinrx_gain_config_t( 55, 9.8, 10, 5, false, false ) )
+ ( twinrx_gain_config_t( 56, 10.8, 9, 5, false, false ) )
+ ( twinrx_gain_config_t( 57, 11.8, 8, 5, false, false ) )
+ ( twinrx_gain_config_t( 58, 12.8, 7, 5, false, false ) )
+ ( twinrx_gain_config_t( 59, 13.8, 6, 5, false, false ) )
+ ( twinrx_gain_config_t( 60, 14.8, 5, 5, false, false ) )
+ ( twinrx_gain_config_t( 61, 15.8, 4, 5, false, false ) )
+ ( twinrx_gain_config_t( 62, 16.8, 3, 5, false, false ) )
+ ( twinrx_gain_config_t( 63, 17.8, 2, 5, false, false ) )
+ ( twinrx_gain_config_t( 64, 18.8, 1, 5, false, false ) )
+ ( twinrx_gain_config_t( 65, 19.8, 0, 5, false, false ) )
+ ( twinrx_gain_config_t( 66, 20.8, 0, 4, false, false ) )
+ ( twinrx_gain_config_t( 67, 21.8, 0, 3, false, false ) )
+ ( twinrx_gain_config_t( 68, 22.8, 0, 2, false, false ) )
+ ( twinrx_gain_config_t( 69, 23.8, 0, 1, false, false ) )
+ ( twinrx_gain_config_t( 70, 24.8, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 71, 26.1, 0, 6, false, true ) )
+ ( twinrx_gain_config_t( 72, 26.1, 0, 6, false, true ) )
+ ( twinrx_gain_config_t( 73, 27.1, 0, 5, false, true ) )
+ ( twinrx_gain_config_t( 74, 28.1, 0, 4, false, true ) )
+ ( twinrx_gain_config_t( 75, 29.1, 0, 3, false, true ) )
+ ( twinrx_gain_config_t( 76, 30.1, 0, 2, false, true ) )
+ ( twinrx_gain_config_t( 77, 31.1, 0, 1, false, true ) )
+ ( twinrx_gain_config_t( 78, 32.1, 0, 0, false, true ) )
+ ( twinrx_gain_config_t( 79, 33.3, 0, 7, true, false ) )
+ ( twinrx_gain_config_t( 80, 34.3, 0, 6, true, false ) )
+ ( twinrx_gain_config_t( 81, 35.3, 0, 5, true, false ) )
+ ( twinrx_gain_config_t( 82, 36.3, 0, 4, true, false ) )
+ ( twinrx_gain_config_t( 83, 37.3, 0, 3, true, false ) )
+ ( twinrx_gain_config_t( 84, 38.3, 0, 2, true, false ) )
+ ( twinrx_gain_config_t( 85, 39.3, 0, 1, true, false ) )
+ ( twinrx_gain_config_t( 86, 40.3, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 87, 41.6, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 42.6, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 43.6, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 44.6, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 45.6, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 46.6, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 47.6, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND1_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -30.1, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 2, -29.1, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 3, -28.1, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 4, -27.1, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 5, -26.1, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 6, -25.1, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 7, -24.1, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 8, -23.1, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 9, -22.1, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 10, -21.1, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 11, -20.1, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 12, -19.1, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 13, -18.1, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 14, -17.1, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 15, -16.1, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 16, -15.1, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 17, -14.1, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 18, -13.1, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 19, -12.1, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 20, -11.1, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 21, -10.1, 31, 10, false, false ) )
+ ( twinrx_gain_config_t( 22, -9.1, 31, 9, false, false ) )
+ ( twinrx_gain_config_t( 23, -8.1, 31, 8, false, false ) )
+ ( twinrx_gain_config_t( 24, -7.1, 31, 7, false, false ) )
+ ( twinrx_gain_config_t( 25, -6.1, 31, 6, false, false ) )
+ ( twinrx_gain_config_t( 26, -5.1, 31, 5, false, false ) )
+ ( twinrx_gain_config_t( 27, -4.1, 31, 4, false, false ) )
+ ( twinrx_gain_config_t( 28, -3.1, 31, 3, false, false ) )
+ ( twinrx_gain_config_t( 29, -2.1, 31, 2, false, false ) )
+ ( twinrx_gain_config_t( 30, -1.1, 31, 1, false, false ) )
+ ( twinrx_gain_config_t( 31, -0.1, 31, 0, false, false ) )
+ ( twinrx_gain_config_t( 32, 0.9, 30, 0, false, false ) )
+ ( twinrx_gain_config_t( 33, 1.9, 29, 0, false, false ) )
+ ( twinrx_gain_config_t( 34, 2.9, 28, 0, false, false ) )
+ ( twinrx_gain_config_t( 35, 3.9, 27, 0, false, false ) )
+ ( twinrx_gain_config_t( 36, 4.9, 26, 0, false, false ) )
+ ( twinrx_gain_config_t( 37, 5.9, 25, 0, false, false ) )
+ ( twinrx_gain_config_t( 38, 6.9, 24, 0, false, false ) )
+ ( twinrx_gain_config_t( 39, 7.9, 23, 0, false, false ) )
+ ( twinrx_gain_config_t( 40, 8.9, 22, 0, false, false ) )
+ ( twinrx_gain_config_t( 41, 9.9, 21, 0, false, false ) )
+ ( twinrx_gain_config_t( 42, 10.9, 20, 0, false, false ) )
+ ( twinrx_gain_config_t( 43, 11.9, 19, 0, false, false ) )
+ ( twinrx_gain_config_t( 44, 12.9, 18, 0, false, false ) )
+ ( twinrx_gain_config_t( 45, 13.9, 17, 0, false, false ) )
+ ( twinrx_gain_config_t( 46, 14.9, 16, 0, false, false ) )
+ ( twinrx_gain_config_t( 47, 15.9, 15, 0, false, false ) )
+ ( twinrx_gain_config_t( 48, 16.9, 14, 0, false, false ) )
+ ( twinrx_gain_config_t( 49, 17.9, 13, 0, false, false ) )
+ ( twinrx_gain_config_t( 50, 18.9, 12, 0, false, false ) )
+ ( twinrx_gain_config_t( 51, 19.9, 11, 0, false, false ) )
+ ( twinrx_gain_config_t( 52, 20.9, 10, 0, false, false ) )
+ ( twinrx_gain_config_t( 53, 21.9, 9, 0, false, false ) )
+ ( twinrx_gain_config_t( 54, 22.9, 8, 0, false, false ) )
+ ( twinrx_gain_config_t( 55, 23.9, 7, 0, false, false ) )
+ ( twinrx_gain_config_t( 56, 24.9, 6, 0, false, false ) )
+ ( twinrx_gain_config_t( 57, 25.9, 5, 0, false, false ) )
+ ( twinrx_gain_config_t( 58, 26.9, 4, 0, false, false ) )
+ ( twinrx_gain_config_t( 59, 27.9, 3, 0, false, false ) )
+ ( twinrx_gain_config_t( 60, 28.9, 2, 0, false, false ) )
+ ( twinrx_gain_config_t( 61, 29.9, 1, 0, false, false ) )
+ ( twinrx_gain_config_t( 62, 30.9, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 63, 31.2, 4, 11, false, true ) )
+ ( twinrx_gain_config_t( 64, 32.2, 3, 11, false, true ) )
+ ( twinrx_gain_config_t( 65, 33.2, 2, 11, false, true ) )
+ ( twinrx_gain_config_t( 66, 34.2, 1, 11, false, true ) )
+ ( twinrx_gain_config_t( 67, 35.2, 0, 11, false, true ) )
+ ( twinrx_gain_config_t( 68, 36.2, 10, 0, true, false ) )
+ ( twinrx_gain_config_t( 69, 37.2, 9, 0, true, false ) )
+ ( twinrx_gain_config_t( 70, 38.2, 8, 0, true, false ) )
+ ( twinrx_gain_config_t( 71, 39.2, 7, 0, true, false ) )
+ ( twinrx_gain_config_t( 72, 40.2, 6, 0, true, false ) )
+ ( twinrx_gain_config_t( 73, 41.2, 5, 0, true, false ) )
+ ( twinrx_gain_config_t( 74, 42.2, 4, 0, true, false ) )
+ ( twinrx_gain_config_t( 75, 43.2, 3, 0, true, false ) )
+ ( twinrx_gain_config_t( 76, 44.2, 2, 0, true, false ) )
+ ( twinrx_gain_config_t( 77, 45.2, 1, 0, true, false ) )
+ ( twinrx_gain_config_t( 78, 46.2, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 79, 47.5, 4, 10, true, true ) )
+ ( twinrx_gain_config_t( 80, 48.5, 3, 10, true, true ) )
+ ( twinrx_gain_config_t( 81, 49.5, 3, 9, true, true ) )
+ ( twinrx_gain_config_t( 82, 50.5, 2, 9, true, true ) )
+ ( twinrx_gain_config_t( 83, 51.5, 1, 9, true, true ) )
+ ( twinrx_gain_config_t( 84, 52.5, 1, 8, true, true ) )
+ ( twinrx_gain_config_t( 85, 53.5, 0, 8, true, true ) )
+ ( twinrx_gain_config_t( 86, 54.5, 0, 7, true, true ) )
+ ( twinrx_gain_config_t( 87, 55.5, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 56.5, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 57.5, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 58.5, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 59.5, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 60.5, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 61.5, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND2_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -33.4, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -33.4, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -32.4, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 3, -31.4, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 4, -30.4, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 5, -29.4, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 6, -28.4, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 7, -27.4, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 8, -26.4, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 9, -25.4, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 10, -24.4, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 11, -23.4, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 12, -22.4, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 13, -21.4, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 14, -20.4, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 15, -19.4, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 16, -18.4, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 17, -17.4, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 18, -16.4, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 19, -15.4, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 20, -14.4, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 21, -13.4, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 22, -12.4, 31, 10, false, false ) )
+ ( twinrx_gain_config_t( 23, -11.4, 31, 9, false, false ) )
+ ( twinrx_gain_config_t( 24, -10.4, 31, 8, false, false ) )
+ ( twinrx_gain_config_t( 25, -9.4, 31, 7, false, false ) )
+ ( twinrx_gain_config_t( 26, -8.4, 31, 6, false, false ) )
+ ( twinrx_gain_config_t( 27, -7.4, 31, 5, false, false ) )
+ ( twinrx_gain_config_t( 28, -6.4, 31, 4, false, false ) )
+ ( twinrx_gain_config_t( 29, -5.4, 31, 3, false, false ) )
+ ( twinrx_gain_config_t( 30, -4.4, 31, 2, false, false ) )
+ ( twinrx_gain_config_t( 31, -3.4, 31, 1, false, false ) )
+ ( twinrx_gain_config_t( 32, -2.4, 31, 0, false, false ) )
+ ( twinrx_gain_config_t( 33, -1.4, 30, 0, false, false ) )
+ ( twinrx_gain_config_t( 34, -0.4, 29, 0, false, false ) )
+ ( twinrx_gain_config_t( 35, 0.6, 28, 0, false, false ) )
+ ( twinrx_gain_config_t( 36, 1.6, 27, 0, false, false ) )
+ ( twinrx_gain_config_t( 37, 2.6, 26, 0, false, false ) )
+ ( twinrx_gain_config_t( 38, 3.6, 25, 0, false, false ) )
+ ( twinrx_gain_config_t( 39, 4.6, 24, 0, false, false ) )
+ ( twinrx_gain_config_t( 40, 5.6, 23, 0, false, false ) )
+ ( twinrx_gain_config_t( 41, 6.6, 22, 0, false, false ) )
+ ( twinrx_gain_config_t( 42, 7.6, 21, 0, false, false ) )
+ ( twinrx_gain_config_t( 43, 8.6, 20, 0, false, false ) )
+ ( twinrx_gain_config_t( 44, 9.6, 19, 0, false, false ) )
+ ( twinrx_gain_config_t( 45, 10.6, 18, 0, false, false ) )
+ ( twinrx_gain_config_t( 46, 11.6, 17, 0, false, false ) )
+ ( twinrx_gain_config_t( 47, 12.6, 16, 0, false, false ) )
+ ( twinrx_gain_config_t( 48, 13.6, 15, 0, false, false ) )
+ ( twinrx_gain_config_t( 49, 14.6, 14, 0, false, false ) )
+ ( twinrx_gain_config_t( 50, 15.6, 13, 0, false, false ) )
+ ( twinrx_gain_config_t( 51, 16.6, 12, 0, false, false ) )
+ ( twinrx_gain_config_t( 52, 17.6, 11, 0, false, false ) )
+ ( twinrx_gain_config_t( 53, 18.6, 10, 0, false, false ) )
+ ( twinrx_gain_config_t( 54, 19.6, 9, 0, false, false ) )
+ ( twinrx_gain_config_t( 55, 20.6, 8, 0, false, false ) )
+ ( twinrx_gain_config_t( 56, 21.6, 7, 0, false, false ) )
+ ( twinrx_gain_config_t( 57, 22.6, 6, 0, false, false ) )
+ ( twinrx_gain_config_t( 58, 23.6, 5, 0, false, false ) )
+ ( twinrx_gain_config_t( 59, 24.6, 4, 0, false, false ) )
+ ( twinrx_gain_config_t( 60, 25.6, 3, 0, false, false ) )
+ ( twinrx_gain_config_t( 61, 26.6, 2, 0, false, false ) )
+ ( twinrx_gain_config_t( 62, 27.6, 1, 0, false, false ) )
+ ( twinrx_gain_config_t( 63, 28.6, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 64, 29.7, 5, 9, false, true ) )
+ ( twinrx_gain_config_t( 65, 30.7, 4, 9, false, true ) )
+ ( twinrx_gain_config_t( 66, 31.7, 3, 9, false, true ) )
+ ( twinrx_gain_config_t( 67, 32.7, 2, 9, false, true ) )
+ ( twinrx_gain_config_t( 68, 33.7, 1, 9, false, true ) )
+ ( twinrx_gain_config_t( 69, 34.7, 0, 9, false, true ) )
+ ( twinrx_gain_config_t( 70, 35.7, 0, 8, false, true ) )
+ ( twinrx_gain_config_t( 71, 36.7, 7, 0, true, false ) )
+ ( twinrx_gain_config_t( 72, 37.7, 6, 0, true, false ) )
+ ( twinrx_gain_config_t( 73, 38.7, 5, 0, true, false ) )
+ ( twinrx_gain_config_t( 74, 39.7, 4, 0, true, false ) )
+ ( twinrx_gain_config_t( 75, 40.7, 3, 0, true, false ) )
+ ( twinrx_gain_config_t( 76, 41.7, 2, 0, true, false ) )
+ ( twinrx_gain_config_t( 77, 42.7, 1, 0, true, false ) )
+ ( twinrx_gain_config_t( 78, 43.7, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 79, 44.8, 6, 8, true, true ) )
+ ( twinrx_gain_config_t( 80, 45.8, 5, 8, true, true ) )
+ ( twinrx_gain_config_t( 81, 46.8, 4, 8, true, true ) )
+ ( twinrx_gain_config_t( 82, 47.8, 4, 7, true, true ) )
+ ( twinrx_gain_config_t( 83, 48.8, 3, 7, true, true ) )
+ ( twinrx_gain_config_t( 84, 49.8, 2, 7, true, true ) )
+ ( twinrx_gain_config_t( 85, 50.8, 1, 7, true, true ) )
+ ( twinrx_gain_config_t( 86, 51.8, 1, 6, true, true ) )
+ ( twinrx_gain_config_t( 87, 52.8, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 53.8, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 54.8, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 55.8, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 56.8, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 57.8, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 58.8, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND3_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -34.0, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -34.0, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -34.0, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -33.0, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 4, -32.0, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 5, -31.0, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 6, -30.0, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 7, -29.0, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 8, -28.0, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 9, -27.0, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 10, -26.0, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 11, -25.0, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 12, -24.0, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 13, -23.0, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 14, -22.0, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 15, -21.0, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 16, -20.0, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 17, -19.0, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 18, -18.0, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 19, -17.0, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 20, -16.0, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 21, -15.0, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 22, -14.0, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 23, -13.0, 31, 10, false, false ) )
+ ( twinrx_gain_config_t( 24, -12.0, 31, 9, false, false ) )
+ ( twinrx_gain_config_t( 25, -11.0, 31, 8, false, false ) )
+ ( twinrx_gain_config_t( 26, -10.0, 31, 7, false, false ) )
+ ( twinrx_gain_config_t( 27, -9.0, 31, 6, false, false ) )
+ ( twinrx_gain_config_t( 28, -8.0, 31, 5, false, false ) )
+ ( twinrx_gain_config_t( 29, -7.0, 31, 4, false, false ) )
+ ( twinrx_gain_config_t( 30, -6.0, 31, 3, false, false ) )
+ ( twinrx_gain_config_t( 31, -5.0, 31, 2, false, false ) )
+ ( twinrx_gain_config_t( 32, -4.0, 31, 1, false, false ) )
+ ( twinrx_gain_config_t( 33, -3.0, 31, 0, false, false ) )
+ ( twinrx_gain_config_t( 34, -2.0, 30, 0, false, false ) )
+ ( twinrx_gain_config_t( 35, -1.0, 29, 0, false, false ) )
+ ( twinrx_gain_config_t( 36, -0.0, 28, 0, false, false ) )
+ ( twinrx_gain_config_t( 37, 1.0, 27, 0, false, false ) )
+ ( twinrx_gain_config_t( 38, 2.0, 26, 0, false, false ) )
+ ( twinrx_gain_config_t( 39, 3.0, 25, 0, false, false ) )
+ ( twinrx_gain_config_t( 40, 4.0, 24, 0, false, false ) )
+ ( twinrx_gain_config_t( 41, 5.0, 23, 0, false, false ) )
+ ( twinrx_gain_config_t( 42, 6.0, 22, 0, false, false ) )
+ ( twinrx_gain_config_t( 43, 7.0, 21, 0, false, false ) )
+ ( twinrx_gain_config_t( 44, 8.0, 20, 0, false, false ) )
+ ( twinrx_gain_config_t( 45, 9.0, 19, 0, false, false ) )
+ ( twinrx_gain_config_t( 46, 10.0, 18, 0, false, false ) )
+ ( twinrx_gain_config_t( 47, 11.0, 17, 0, false, false ) )
+ ( twinrx_gain_config_t( 48, 12.0, 16, 0, false, false ) )
+ ( twinrx_gain_config_t( 49, 13.0, 15, 0, false, false ) )
+ ( twinrx_gain_config_t( 50, 14.0, 14, 0, false, false ) )
+ ( twinrx_gain_config_t( 51, 15.0, 13, 0, false, false ) )
+ ( twinrx_gain_config_t( 52, 16.0, 12, 0, false, false ) )
+ ( twinrx_gain_config_t( 53, 17.0, 11, 0, false, false ) )
+ ( twinrx_gain_config_t( 54, 18.0, 10, 0, false, false ) )
+ ( twinrx_gain_config_t( 55, 19.0, 9, 0, false, false ) )
+ ( twinrx_gain_config_t( 56, 20.0, 8, 0, false, false ) )
+ ( twinrx_gain_config_t( 57, 21.0, 7, 0, false, false ) )
+ ( twinrx_gain_config_t( 58, 22.0, 6, 0, false, false ) )
+ ( twinrx_gain_config_t( 59, 23.0, 5, 0, false, false ) )
+ ( twinrx_gain_config_t( 60, 24.0, 4, 0, false, false ) )
+ ( twinrx_gain_config_t( 61, 25.0, 3, 0, false, false ) )
+ ( twinrx_gain_config_t( 62, 26.0, 2, 0, false, false ) )
+ ( twinrx_gain_config_t( 63, 27.0, 1, 0, false, false ) )
+ ( twinrx_gain_config_t( 64, 28.0, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 65, 29.5, 5, 8, false, true ) )
+ ( twinrx_gain_config_t( 66, 30.5, 4, 8, false, true ) )
+ ( twinrx_gain_config_t( 67, 31.5, 3, 8, false, true ) )
+ ( twinrx_gain_config_t( 68, 32.5, 2, 8, false, true ) )
+ ( twinrx_gain_config_t( 69, 33.5, 1, 8, false, true ) )
+ ( twinrx_gain_config_t( 70, 34.5, 0, 8, false, true ) )
+ ( twinrx_gain_config_t( 71, 34.5, 0, 8, false, true ) )
+ ( twinrx_gain_config_t( 72, 36.5, 6, 0, true, false ) )
+ ( twinrx_gain_config_t( 73, 36.5, 6, 0, true, false ) )
+ ( twinrx_gain_config_t( 74, 37.5, 5, 0, true, false ) )
+ ( twinrx_gain_config_t( 75, 38.5, 4, 0, true, false ) )
+ ( twinrx_gain_config_t( 76, 39.5, 3, 0, true, false ) )
+ ( twinrx_gain_config_t( 77, 40.5, 2, 0, true, false ) )
+ ( twinrx_gain_config_t( 78, 41.5, 1, 0, true, false ) )
+ ( twinrx_gain_config_t( 79, 42.5, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 80, 44.0, 6, 7, true, true ) )
+ ( twinrx_gain_config_t( 81, 45.0, 5, 7, true, true ) )
+ ( twinrx_gain_config_t( 82, 46.0, 4, 7, true, true ) )
+ ( twinrx_gain_config_t( 83, 47.0, 3, 7, true, true ) )
+ ( twinrx_gain_config_t( 84, 48.0, 3, 6, true, true ) )
+ ( twinrx_gain_config_t( 85, 49.0, 2, 6, true, true ) )
+ ( twinrx_gain_config_t( 86, 50.0, 1, 6, true, true ) )
+ ( twinrx_gain_config_t( 87, 51.0, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 52.0, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 53.0, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 54.0, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 55.0, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 56.0, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 57.0, 0, 0, true, true ) )
+;
+
+static const std::vector<twinrx_gain_config_t> LOWBAND4_TABLE = boost::assign::list_of
+ // Index, Gain, Atten1, Atten2, Amp1, Amp2
+ ( twinrx_gain_config_t( 0, -32.8, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 1, -32.8, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 2, -32.8, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 3, -32.8, 31, 31, false, false ) )
+ ( twinrx_gain_config_t( 4, -31.8, 31, 30, false, false ) )
+ ( twinrx_gain_config_t( 5, -30.8, 31, 29, false, false ) )
+ ( twinrx_gain_config_t( 6, -29.8, 31, 28, false, false ) )
+ ( twinrx_gain_config_t( 7, -28.8, 31, 27, false, false ) )
+ ( twinrx_gain_config_t( 8, -27.8, 31, 26, false, false ) )
+ ( twinrx_gain_config_t( 9, -26.8, 31, 25, false, false ) )
+ ( twinrx_gain_config_t( 10, -25.8, 31, 24, false, false ) )
+ ( twinrx_gain_config_t( 11, -24.8, 31, 23, false, false ) )
+ ( twinrx_gain_config_t( 12, -23.8, 31, 22, false, false ) )
+ ( twinrx_gain_config_t( 13, -22.8, 31, 21, false, false ) )
+ ( twinrx_gain_config_t( 14, -21.8, 31, 20, false, false ) )
+ ( twinrx_gain_config_t( 15, -20.8, 31, 19, false, false ) )
+ ( twinrx_gain_config_t( 16, -19.8, 31, 18, false, false ) )
+ ( twinrx_gain_config_t( 17, -18.8, 31, 17, false, false ) )
+ ( twinrx_gain_config_t( 18, -17.8, 31, 16, false, false ) )
+ ( twinrx_gain_config_t( 19, -16.8, 31, 15, false, false ) )
+ ( twinrx_gain_config_t( 20, -15.8, 31, 14, false, false ) )
+ ( twinrx_gain_config_t( 21, -14.8, 31, 13, false, false ) )
+ ( twinrx_gain_config_t( 22, -13.8, 31, 12, false, false ) )
+ ( twinrx_gain_config_t( 23, -12.8, 31, 11, false, false ) )
+ ( twinrx_gain_config_t( 24, -11.8, 31, 10, false, false ) )
+ ( twinrx_gain_config_t( 25, -10.8, 31, 9, false, false ) )
+ ( twinrx_gain_config_t( 26, -9.8, 31, 8, false, false ) )
+ ( twinrx_gain_config_t( 27, -8.8, 31, 7, false, false ) )
+ ( twinrx_gain_config_t( 28, -7.8, 31, 6, false, false ) )
+ ( twinrx_gain_config_t( 29, -6.8, 31, 5, false, false ) )
+ ( twinrx_gain_config_t( 30, -5.8, 31, 4, false, false ) )
+ ( twinrx_gain_config_t( 31, -4.8, 31, 3, false, false ) )
+ ( twinrx_gain_config_t( 32, -3.8, 31, 2, false, false ) )
+ ( twinrx_gain_config_t( 33, -2.8, 31, 1, false, false ) )
+ ( twinrx_gain_config_t( 34, -1.8, 31, 0, false, false ) )
+ ( twinrx_gain_config_t( 35, -0.8, 30, 0, false, false ) )
+ ( twinrx_gain_config_t( 36, 0.2, 29, 0, false, false ) )
+ ( twinrx_gain_config_t( 37, 1.2, 28, 0, false, false ) )
+ ( twinrx_gain_config_t( 38, 2.2, 27, 0, false, false ) )
+ ( twinrx_gain_config_t( 39, 3.2, 26, 0, false, false ) )
+ ( twinrx_gain_config_t( 40, 4.2, 25, 0, false, false ) )
+ ( twinrx_gain_config_t( 41, 5.2, 24, 0, false, false ) )
+ ( twinrx_gain_config_t( 42, 6.2, 23, 0, false, false ) )
+ ( twinrx_gain_config_t( 43, 7.2, 22, 0, false, false ) )
+ ( twinrx_gain_config_t( 44, 8.2, 21, 0, false, false ) )
+ ( twinrx_gain_config_t( 45, 9.2, 20, 0, false, false ) )
+ ( twinrx_gain_config_t( 46, 10.2, 19, 0, false, false ) )
+ ( twinrx_gain_config_t( 47, 11.2, 18, 0, false, false ) )
+ ( twinrx_gain_config_t( 48, 12.2, 17, 0, false, false ) )
+ ( twinrx_gain_config_t( 49, 13.2, 16, 0, false, false ) )
+ ( twinrx_gain_config_t( 50, 14.2, 15, 0, false, false ) )
+ ( twinrx_gain_config_t( 51, 15.2, 14, 0, false, false ) )
+ ( twinrx_gain_config_t( 52, 16.2, 13, 0, false, false ) )
+ ( twinrx_gain_config_t( 53, 17.2, 12, 0, false, false ) )
+ ( twinrx_gain_config_t( 54, 18.2, 11, 0, false, false ) )
+ ( twinrx_gain_config_t( 55, 19.2, 10, 0, false, false ) )
+ ( twinrx_gain_config_t( 56, 20.2, 9, 0, false, false ) )
+ ( twinrx_gain_config_t( 57, 21.2, 8, 0, false, false ) )
+ ( twinrx_gain_config_t( 58, 22.2, 7, 0, false, false ) )
+ ( twinrx_gain_config_t( 59, 23.2, 6, 0, false, false ) )
+ ( twinrx_gain_config_t( 60, 24.2, 5, 0, false, false ) )
+ ( twinrx_gain_config_t( 61, 25.2, 4, 0, false, false ) )
+ ( twinrx_gain_config_t( 62, 26.2, 3, 0, false, false ) )
+ ( twinrx_gain_config_t( 63, 27.2, 2, 0, false, false ) )
+ ( twinrx_gain_config_t( 64, 28.2, 1, 0, false, false ) )
+ ( twinrx_gain_config_t( 65, 29.2, 0, 0, false, false ) )
+ ( twinrx_gain_config_t( 66, 30.4, 4, 9, false, true ) )
+ ( twinrx_gain_config_t( 67, 31.4, 3, 9, false, true ) )
+ ( twinrx_gain_config_t( 68, 32.4, 2, 9, false, true ) )
+ ( twinrx_gain_config_t( 69, 33.4, 1, 9, false, true ) )
+ ( twinrx_gain_config_t( 70, 34.4, 0, 9, false, true ) )
+ ( twinrx_gain_config_t( 71, 35.4, 8, 0, true, false ) )
+ ( twinrx_gain_config_t( 72, 36.4, 7, 0, true, false ) )
+ ( twinrx_gain_config_t( 73, 37.4, 6, 0, true, false ) )
+ ( twinrx_gain_config_t( 74, 38.4, 5, 0, true, false ) )
+ ( twinrx_gain_config_t( 75, 39.4, 4, 0, true, false ) )
+ ( twinrx_gain_config_t( 76, 40.4, 3, 0, true, false ) )
+ ( twinrx_gain_config_t( 77, 41.4, 2, 0, true, false ) )
+ ( twinrx_gain_config_t( 78, 42.4, 1, 0, true, false ) )
+ ( twinrx_gain_config_t( 79, 43.4, 0, 0, true, false ) )
+ ( twinrx_gain_config_t( 80, 44.6, 4, 9, true, true ) )
+ ( twinrx_gain_config_t( 81, 45.6, 4, 8, true, true ) )
+ ( twinrx_gain_config_t( 82, 46.6, 3, 8, true, true ) )
+ ( twinrx_gain_config_t( 83, 47.6, 2, 8, true, true ) )
+ ( twinrx_gain_config_t( 84, 48.6, 1, 8, true, true ) )
+ ( twinrx_gain_config_t( 85, 49.6, 1, 7, true, true ) )
+ ( twinrx_gain_config_t( 86, 50.6, 0, 7, true, true ) )
+ ( twinrx_gain_config_t( 87, 51.6, 0, 6, true, true ) )
+ ( twinrx_gain_config_t( 88, 52.6, 0, 5, true, true ) )
+ ( twinrx_gain_config_t( 89, 53.6, 0, 4, true, true ) )
+ ( twinrx_gain_config_t( 90, 54.6, 0, 3, true, true ) )
+ ( twinrx_gain_config_t( 91, 55.6, 0, 2, true, true ) )
+ ( twinrx_gain_config_t( 92, 56.6, 0, 1, true, true ) )
+ ( twinrx_gain_config_t( 93, 57.6, 0, 0, true, true ) )
+;
+
+const twinrx_gain_table twinrx_gain_table::lookup_table
+(
+ twinrx_ctrl::signal_path_t signal_path,
+ twinrx_ctrl::preselector_path_t preselector_path,
+ std::string
+) {
+
+ if (signal_path == twinrx_ctrl::PATH_HIGHBAND) {
+ switch (preselector_path) {
+ case twinrx_ctrl::PRESEL_PATH1:
+ return twinrx_gain_table(HIGHBAND1_TABLE);
+ case twinrx_ctrl::PRESEL_PATH2:
+ return twinrx_gain_table(HIGHBAND2_TABLE);
+ case twinrx_ctrl::PRESEL_PATH3:
+ return twinrx_gain_table(HIGHBAND3_TABLE);
+ case twinrx_ctrl::PRESEL_PATH4:
+ return twinrx_gain_table(HIGHBAND4_TABLE);
+ }
+ } else {
+ switch (preselector_path) {
+ case twinrx_ctrl::PRESEL_PATH1:
+ return twinrx_gain_table(LOWBAND1_TABLE);
+ case twinrx_ctrl::PRESEL_PATH2:
+ return twinrx_gain_table(LOWBAND2_TABLE);
+ case twinrx_ctrl::PRESEL_PATH3:
+ return twinrx_gain_table(LOWBAND3_TABLE);
+ case twinrx_ctrl::PRESEL_PATH4:
+ return twinrx_gain_table(LOWBAND4_TABLE);
+ }
+ }
+ throw runtime_error("NO GAIN TABLE SELECTED");
+ return twinrx_gain_table(HIGHBAND1_TABLE);
+}
+
+const twinrx_gain_config_t& twinrx_gain_table::find_by_index(size_t index) const {
+ if (index >= get_num_entries()) throw uhd::value_error("invalid gain table index");
+ return _tbl.at(index);
+}
+
+uhd::gain_range_t twinrx_gain_table::get_gain_range() const {
+ double max = std::numeric_limits<double>::min();
+ double min = std::numeric_limits<double>::max();
+ for (size_t i = 0; i < get_num_entries(); i++) {
+ const twinrx_gain_config_t& config = find_by_index(i);
+ if (config.sys_gain > max) {
+ max = config.sys_gain;
+ }
+ if (config.sys_gain < min) {
+ min = config.sys_gain;
+ }
+ }
+ return uhd::gain_range_t(min, max, 1.0);
+}
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp
new file mode 100644
index 000000000..0148965da
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp
@@ -0,0 +1,83 @@
+//
+// 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_DBOARD_TWINRX_GAIN_TABLES_HPP
+#define INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP
+
+#include <uhd/config.hpp>
+#include <boost/cstdint.hpp>
+#include <uhd/types/ranges.hpp>
+#include "twinrx_ctrl.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+class twinrx_gain_config_t {
+public:
+ twinrx_gain_config_t(
+ size_t index_, double sys_gain_,
+ boost::uint8_t atten1_, boost::uint8_t atten2_,
+ bool amp1_, bool amp2_
+ ): index(index_), sys_gain(sys_gain_), atten1(atten1_), atten2(atten2_),
+ amp1(amp1_), amp2(amp2_)
+ {}
+
+ twinrx_gain_config_t& operator=(const twinrx_gain_config_t& src) {
+ if (this != &src) {
+ this->index = src.index;
+ this->sys_gain = src.sys_gain;
+ this->atten1 = src.atten1;
+ this->atten2 = src.atten2;
+ this->amp1 = src.amp1;
+ this->amp2 = src.amp2;
+ }
+ return *this;
+ }
+
+ size_t index;
+ double sys_gain;
+ boost::uint8_t atten1;
+ boost::uint8_t atten2;
+ bool amp1;
+ bool amp2;
+};
+
+class twinrx_gain_table {
+public:
+ static const twinrx_gain_table lookup_table(
+ twinrx_ctrl::signal_path_t signal_path,
+ twinrx_ctrl::preselector_path_t presel_path,
+ std::string profile);
+
+ twinrx_gain_table(const std::vector<twinrx_gain_config_t>& tbl)
+ : _tbl(tbl) {}
+
+ const twinrx_gain_config_t& find_by_index(size_t index) const;
+
+ inline size_t get_num_entries() const {
+ return _tbl.size();
+ }
+
+ uhd::gain_range_t get_gain_range() const;
+
+private:
+ const std::vector<twinrx_gain_config_t>& _tbl;
+};
+
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP */
diff --git a/host/lib/usrp/dboard/twinrx/twinrx_io.hpp b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp
new file mode 100644
index 000000000..5d099e361
--- /dev/null
+++ b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp
@@ -0,0 +1,523 @@
+//
+// 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_DBOARD_TWINRX_IO_HPP
+#define INCLUDED_DBOARD_TWINRX_IO_HPP
+
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/usrp/dboard_base.hpp>
+#include <uhd/utils/soft_register.hpp>
+#include <boost/thread.hpp>
+#include "gpio_atr_3000.hpp"
+
+namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
+
+static const boost::uint32_t SET_ALL_BITS = 0xFFFFFFFF;
+
+namespace cpld {
+static wb_iface::wb_addr_type addr(boost::uint8_t cpld_num, boost::uint8_t cpld_addr) {
+ //Decode CPLD addressing for the following bitmap:
+ // {CPLD1_EN, CPLD2_EN, CPLD3_EN, CPLD4_EN, CPLD_ADDR[2:0]}
+ boost::uint8_t addr = 0;
+ switch (cpld_num) {
+ case 1: addr = 0x8 << 3; break;
+ case 2: addr = 0x4 << 3; break;
+ case 3: addr = 0x2 << 3; break;
+ case 4: addr = 0x1 << 3; break;
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ return static_cast<wb_iface::wb_addr_type>(addr | (cpld_addr & 0x7));
+}
+
+static boost::uint32_t get_reg(wb_iface::wb_addr_type addr) {
+ return static_cast<boost::uint32_t>(addr) & 0x7;
+}
+}
+
+class twinrx_gpio : public timed_wb_iface {
+public:
+ typedef boost::shared_ptr<twinrx_gpio> sptr;
+
+ //----------------------------------------------
+ //Public GPIO fields
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH1, /*width*/ 1, /*shift*/ 0); //GPIO[0] OUT
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH2, /*width*/ 1, /*shift*/ 1); //GPIO[1] OUT
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH1, /*width*/ 1, /*shift*/ 2); //GPIO[2] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH2, /*width*/ 1, /*shift*/ 3); //GPIO[3] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH1, /*width*/ 1, /*shift*/ 4); //GPIO[4] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH2, /*width*/ 1, /*shift*/ 5); //GPIO[5] IN
+ // NO CONNECT //GPIO[8:6]
+ // PRIVATE //GPIO[15:9]
+ // NO CONNECT //GPIO[16]
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH1, /*width*/ 1, /*shift*/ 17); //GPIO[17] OUT
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH2, /*width*/ 1, /*shift*/ 18); //GPIO[18] OUT
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH1, /*width*/ 1, /*shift*/ 19); //GPIO[19] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH2, /*width*/ 1, /*shift*/ 20); //GPIO[20] IN
+ // NO CONNECT //GPIO[21:23]
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_CLK, /*width*/ 1, /*shift*/ 24); //GPIO[24] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_PWR_GOOD, /*width*/ 1, /*shift*/ 25); //GPIO[25] IN
+ UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_EN, /*width*/ 1, /*shift*/ 26); //GPIO[26] OUT
+ // PRIVATE //GPIO[27:31]
+ //----------------------------------------------
+
+ twinrx_gpio(dboard_iface::sptr iface) : _db_iface(iface) {
+ _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, GPIO_OUTPUT_MASK, SET_ALL_BITS);
+ _db_iface->set_pin_ctrl(dboard_iface::UNIT_BOTH, GPIO_PINCTRL_MASK, SET_ALL_BITS);
+ _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, 0, ~GPIO_PINCTRL_MASK);
+ }
+
+ ~twinrx_gpio() {
+ _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, ~GPIO_OUTPUT_MASK, SET_ALL_BITS);
+ }
+
+ void set_field(const uhd::soft_reg_field_t field, const boost::uint32_t value) {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ using namespace soft_reg_field;
+
+ _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+ (value << shift(field)),
+ mask<boost::uint32_t>(field));
+ }
+
+ boost::uint32_t get_field(const uhd::soft_reg_field_t field) {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ using namespace soft_reg_field;
+ return (_db_iface->read_gpio(dboard_iface::UNIT_BOTH) & mask<boost::uint32_t>(field)) >> shift(field);
+ }
+
+ // CPLD register write-only interface
+ void poke32(const wb_addr_type addr, const boost::uint32_t data) {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ using namespace soft_reg_field;
+
+ //Step 1: Write the reg offset and data to the GPIO bus and de-assert all enables
+ _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+ (cpld::get_reg(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)),
+ mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA));
+ //Sleep for 166ns to ensure that we don't toggle the enables too quickly
+ //The underlying sleep function rounds to microsecond precision.
+ _db_iface->sleep(boost::chrono::nanoseconds(166));
+ //Step 2: Write the reg offset and data, and assert the necessary enable
+ _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH,
+ (static_cast<boost::uint32_t>(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)),
+ mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA));
+ }
+
+ // Timed command interface
+ inline time_spec_t get_time() {
+ return _db_iface->get_command_time();
+ }
+
+ void set_time(const time_spec_t& t) {
+ boost::lock_guard<boost::mutex> lock(_mutex);
+ _db_iface->set_command_time(t);
+ }
+
+private: //Members/definitions
+ static const boost::uint32_t GPIO_OUTPUT_MASK = 0xFC06FE03;
+ static const boost::uint32_t GPIO_PINCTRL_MASK = 0x00000000;
+
+ //Private GPIO fields
+ UHD_DEFINE_SOFT_REG_FIELD(CPLD_FULL_ADDR, /*width*/ 7, /*shift*/ 9); //GPIO[15:9]
+ UHD_DEFINE_SOFT_REG_FIELD(CPLD_DATA, /*width*/ 5, /*shift*/ 27); //GPIO[31:27]
+
+ //Members
+ dboard_iface::sptr _db_iface;
+ boost::mutex _mutex;
+};
+
+class twinrx_cpld_regmap : public uhd::soft_regmap_t {
+public:
+ typedef boost::shared_ptr<twinrx_cpld_regmap> sptr;
+
+ //----------------------------------------------
+ // IF CCA: CPLD 1
+ //----------------------------------------------
+ class if0_reg0_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH2, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH2, /*width*/ 1, /*shift*/ 4);
+
+ if0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 0), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg0;
+
+ class if0_reg1_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH1, /*width*/ 1, /*shift*/ 2);
+
+ if0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 1), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg1;
+
+ class if0_reg2_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH1, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH2, /*width*/ 1, /*shift*/ 4);
+
+ if0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 2), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg2;
+
+ class if0_reg3_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH1, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH1, /*width*/ 1, /*shift*/ 3);
+
+ if0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 3), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg3;
+
+ class if0_reg4_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW25_CTRL, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH1, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH1, /*width*/ 1, /*shift*/ 4);
+
+ if0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 4), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg4;
+
+ class if0_reg6_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH2, /*width*/ 1, /*shift*/ 2);
+
+ if0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 6), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg6;
+
+ class if0_reg7_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+
+ if0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 7), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } if0_reg7;
+
+ //----------------------------------------------
+ // RF CCA: CPLD 2
+ //----------------------------------------------
+ class rf0_reg0_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH1, /*width*/ 5, /*shift*/ 0);
+
+ rf0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 0), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg0;
+
+ class rf0_reg1_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTL_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 1), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg1;
+
+ class rf0_reg2_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH1, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH1, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 2), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg2;
+
+ class rf0_reg3_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH2, /*width*/ 2, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH1, /*width*/ 2, /*shift*/ 2);
+
+ rf0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 3), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg3;
+
+ class rf0_reg4_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH2, /*width*/ 5, /*shift*/ 0);
+
+ rf0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 4), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg4;
+
+ class rf0_reg5_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH1, /*width*/ 2, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH1, /*width*/ 1, /*shift*/ 4);
+
+ rf0_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 5), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg5;
+
+ class rf0_reg6_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH2, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 6), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg6;
+
+ class rf0_reg7_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH2, /*width*/ 2, /*shift*/ 3);
+
+ rf0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 7), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf0_reg7;
+
+ //----------------------------------------------
+ // RF CCA: CPLD 3
+ //----------------------------------------------
+ class rf1_reg0_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH1, /*width*/ 5, /*shift*/ 0);
+
+ rf1_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 0), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg0;
+
+ class rf1_reg1_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH1, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH1, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH1, /*width*/ 1, /*shift*/ 4);
+
+ rf1_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 1), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg1;
+
+ class rf1_reg2_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH2, /*width*/ 1, /*shift*/ 2);
+
+ rf1_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 2), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg2;
+
+ class rf1_reg3_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW23_CTRL, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH1, /*width*/ 2, /*shift*/ 2);
+
+ rf1_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 3), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg3;
+
+ class rf1_reg4_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH2, /*width*/ 5, /*shift*/ 0);
+
+ rf1_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 4), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg4;
+
+ class rf1_reg5_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH2, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH1, /*width*/ 1, /*shift*/ 4);
+
+ rf1_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 5), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg5;
+
+ class rf1_reg6_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH2, /*width*/ 1, /*shift*/ 2);
+ UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH2, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf1_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 6), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg6;
+
+ class rf1_reg7_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH2, /*width*/ 2, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH2, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf1_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 7), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf1_reg7;
+
+ //----------------------------------------------
+ // RF CCA: CPLD 4
+ //----------------------------------------------
+ class rf2_reg0_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH1, /*width*/ 5, /*shift*/ 0);
+
+ rf2_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 0), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg0;
+
+ class rf2_reg2_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH1, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH1, /*width*/ 1, /*shift*/ 2);
+
+ rf2_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 2), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg2;
+
+ class rf2_reg3_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH2, /*width*/ 1, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH1, /*width*/ 2, /*shift*/ 2);
+
+ rf2_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 3), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg3;
+
+ class rf2_reg4_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH2, /*width*/ 5, /*shift*/ 0);
+
+ rf2_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 4), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg4;
+
+ class rf2_reg5_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH2, /*width*/ 1, /*shift*/ 0);
+
+ rf2_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 5), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg5;
+
+ class rf2_reg6_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 0);
+
+ rf2_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 6), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg6;
+
+ class rf2_reg7_t : public uhd::soft_reg32_wo_t {
+ public:
+ UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH1, /*width*/ 1, /*shift*/ 0);
+ UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH2, /*width*/ 2, /*shift*/ 1);
+ UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH2, /*width*/ 1, /*shift*/ 3);
+ UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH2, /*width*/ 1, /*shift*/ 4);
+
+ rf2_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 7), OPTIMIZED_FLUSH) {
+ set(REGISTER, 0);
+ }
+ } rf2_reg7;
+
+ twinrx_cpld_regmap() : soft_regmap_t("twinrx_cpld") {
+ // IF CCA: CPLD 1
+ add_to_map(if0_reg0, "if0_reg0");
+ add_to_map(if0_reg1, "if0_reg1");
+ add_to_map(if0_reg2, "if0_reg2");
+ add_to_map(if0_reg3, "if0_reg3");
+ add_to_map(if0_reg4, "if0_reg4");
+ add_to_map(if0_reg6, "if0_reg6");
+ add_to_map(if0_reg7, "if0_reg7");
+ // RF CCA: CPLD 2
+ add_to_map(rf0_reg0, "rf0_reg0");
+ add_to_map(rf0_reg1, "rf0_reg1");
+ add_to_map(rf0_reg2, "rf0_reg2");
+ add_to_map(rf0_reg3, "rf0_reg3");
+ add_to_map(rf0_reg4, "rf0_reg4");
+ add_to_map(rf0_reg5, "rf0_reg5");
+ add_to_map(rf0_reg6, "rf0_reg6");
+ add_to_map(rf0_reg7, "rf0_reg7");
+ // RF CCA: CPLD 3
+ add_to_map(rf1_reg0, "rf1_reg0");
+ add_to_map(rf1_reg1, "rf1_reg1");
+ add_to_map(rf1_reg2, "rf1_reg2");
+ add_to_map(rf1_reg3, "rf1_reg3");
+ add_to_map(rf1_reg4, "rf1_reg4");
+ add_to_map(rf1_reg5, "rf1_reg5");
+ add_to_map(rf1_reg6, "rf1_reg6");
+ add_to_map(rf1_reg7, "rf1_reg7");
+ // RF CCA: CPLD 4
+ add_to_map(rf2_reg0, "rf2_reg0");
+ add_to_map(rf2_reg2, "rf2_reg2");
+ add_to_map(rf2_reg3, "rf2_reg3");
+ add_to_map(rf2_reg4, "rf2_reg4");
+ add_to_map(rf2_reg5, "rf2_reg5");
+ add_to_map(rf2_reg6, "rf2_reg6");
+ add_to_map(rf2_reg7, "rf2_reg7");
+ }
+};
+
+}}}} //namespaces
+
+#endif /* INCLUDED_DBOARD_TWINRX_IO_HPP */
diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp
index fe14c02b9..465b9e489 100644
--- a/host/lib/usrp/dboard_base.cpp
+++ b/host/lib/usrp/dboard_base.cpp
@@ -32,7 +32,7 @@ struct dboard_base::impl{
dboard_base::dboard_base(ctor_args_t args){
_impl = UHD_PIMPL_MAKE(impl, ());
- _impl->args = *static_cast<dboard_ctor_args_t *>(args);
+ _impl->args = dboard_ctor_args_t::cast(args);
}
std::string dboard_base::get_subdev_name(void){
diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp
index 99c071ff8..c8e4006d1 100644
--- a/host/lib/usrp/dboard_ctor_args.hpp
+++ b/host/lib/usrp/dboard_ctor_args.hpp
@@ -26,11 +26,17 @@
namespace uhd{ namespace usrp{
- struct dboard_ctor_args_t{
+ class dboard_ctor_args_t {
+ public:
std::string sd_name;
dboard_iface::sptr db_iface;
dboard_id_t rx_id, tx_id;
property_tree::sptr rx_subtree, tx_subtree;
+ dboard_base::sptr rx_container, tx_container;
+
+ static const dboard_ctor_args_t& cast(dboard_base::ctor_args_t args) {
+ return *static_cast<dboard_ctor_args_t*>(args);
+ }
};
}} //namespace
diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp
index 092e005f0..2a04095e8 100644
--- a/host/lib/usrp/dboard_iface.cpp
+++ b/host/lib/usrp/dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2010-2013,2015 Ettus Research LLC
+// 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
@@ -16,78 +16,16 @@
//
#include <uhd/usrp/dboard_iface.hpp>
-#include <uhd/types/dict.hpp>
using namespace uhd::usrp;
-struct dboard_iface::impl{
- uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow;
- uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow;
- uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow;
- uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow;
-};
-
-dboard_iface::dboard_iface(void){
- _impl = UHD_PIMPL_MAKE(impl, ());
-}
-
-dboard_iface::~dboard_iface(void)
-{
- //empty
-}
-
-template <typename T>
-static T shadow_it(T &shadow, const T &value, const T &mask){
- shadow = (shadow & ~mask) | (value & mask);
- return shadow;
-}
-
-void dboard_iface::set_pin_ctrl(
- unit_t unit, boost::uint16_t value, boost::uint16_t mask
-){
- _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask));
-}
-
-boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){
- return _impl->pin_ctrl_shadow[unit];
-}
-
-void dboard_iface::set_atr_reg(
- unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask
-){
- _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask));
-}
-
-boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){
- return _impl->atr_reg_shadow[unit][reg];
-}
-
-void dboard_iface::set_gpio_ddr(
- unit_t unit, boost::uint16_t value, boost::uint16_t mask
-){
- _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask));
-}
-
-boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){
- return _impl->gpio_ddr_shadow[unit];
-}
-
-void dboard_iface::set_gpio_out(
- unit_t unit, boost::uint16_t value, boost::uint16_t mask
-){
- _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask));
-}
-
-boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){
- return _impl->gpio_out_shadow[unit];
-}
-
-void dboard_iface::set_command_time(const uhd::time_spec_t&)
-{
- throw uhd::not_implemented_error("timed command feature not implemented on this hardware");
-}
-
-uhd::time_spec_t dboard_iface::get_command_time()
+void dboard_iface::sleep(const boost::chrono::nanoseconds& time)
{
- return uhd::time_spec_t(0.0);
+ //nanosleep is not really accurate in userland and it is also not very
+ //cross-platform. So just sleep for the minimum amount of time in us.
+ if (time < boost::chrono::microseconds(1)) {
+ boost::this_thread::sleep_for(boost::chrono::microseconds(1));
+ } else {
+ boost::this_thread::sleep_for(time);
+ }
}
diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp
index 340c1d3f9..56cd08fd7 100644
--- a/host/lib/usrp/dboard_manager.cpp
+++ b/host/lib/usrp/dboard_manager.cpp
@@ -37,11 +37,11 @@ using namespace uhd::usrp;
**********************************************************************/
class dboard_key_t{
public:
- dboard_key_t(const dboard_id_t &id = dboard_id_t::none()):
- _rx_id(id), _tx_id(id), _xcvr(false){}
+ dboard_key_t(const dboard_id_t &id = dboard_id_t::none(), bool restricted = false):
+ _rx_id(id), _tx_id(id), _xcvr(false), _restricted(restricted) {}
- dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id):
- _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){}
+ dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id, bool restricted = false):
+ _rx_id(rx_id), _tx_id(tx_id), _xcvr(true), _restricted(restricted) {}
dboard_id_t xx_id(void) const{
UHD_ASSERT_THROW(not this->is_xcvr());
@@ -62,9 +62,14 @@ public:
return this->_xcvr;
}
+ bool is_restricted(void) const{
+ return this->_restricted;
+ }
+
private:
dboard_id_t _rx_id, _tx_id;
bool _xcvr;
+ bool _restricted;
};
bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){
@@ -78,8 +83,8 @@ bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){
/***********************************************************************
* storage and registering for dboards
**********************************************************************/
-//dboard registry tuple: dboard constructor, canonical name, subdev names
-typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string> > args_t;
+//dboard registry tuple: dboard constructor, canonical name, subdev names, container constructor
+typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string>, dboard_manager::dboard_ctor_t> args_t;
//map a dboard id to a dboard constructor
typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t;
@@ -87,9 +92,10 @@ UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map)
static void register_dboard_key(
const dboard_key_t &dboard_key,
- dboard_manager::dboard_ctor_t dboard_ctor,
+ dboard_manager::dboard_ctor_t db_subdev_ctor,
const std::string &name,
- const std::vector<std::string> &subdev_names
+ const std::vector<std::string> &subdev_names,
+ dboard_manager::dboard_ctor_t db_container_ctor
){
UHD_LOGV(always) << "registering: " << name << std::endl;
if (get_id_to_args_map().has_key(dboard_key)){
@@ -103,26 +109,49 @@ static void register_dboard_key(
) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>()));
}
- get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names);
+ get_id_to_args_map()[dboard_key] = args_t(db_subdev_ctor, name, subdev_names, db_container_ctor);
}
void dboard_manager::register_dboard(
const dboard_id_t &dboard_id,
- dboard_ctor_t dboard_ctor,
+ dboard_ctor_t db_subdev_ctor,
const std::string &name,
- const std::vector<std::string> &subdev_names
+ const std::vector<std::string> &subdev_names,
+ dboard_ctor_t db_container_ctor
){
- register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names);
+ register_dboard_key(dboard_key_t(dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor);
}
void dboard_manager::register_dboard(
const dboard_id_t &rx_dboard_id,
const dboard_id_t &tx_dboard_id,
- dboard_ctor_t dboard_ctor,
+ dboard_ctor_t db_subdev_ctor,
+ const std::string &name,
+ const std::vector<std::string> &subdev_names,
+ dboard_ctor_t db_container_ctor
+){
+ register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor);
+}
+
+void dboard_manager::register_dboard_restricted(
+ const dboard_id_t &dboard_id,
+ dboard_ctor_t db_subdev_ctor,
const std::string &name,
- const std::vector<std::string> &subdev_names
+ const std::vector<std::string> &subdev_names,
+ dboard_ctor_t db_container_ctor
){
- register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names);
+ register_dboard_key(dboard_key_t(dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor);
+}
+
+void dboard_manager::register_dboard_restricted(
+ const dboard_id_t &rx_dboard_id,
+ const dboard_id_t &tx_dboard_id,
+ dboard_ctor_t db_subdev_ctor,
+ const std::string &name,
+ const std::vector<std::string> &subdev_names,
+ dboard_ctor_t db_container_ctor
+){
+ register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor);
}
std::string dboard_id_t::to_cname(void) const{
@@ -153,17 +182,32 @@ public:
dboard_id_t rx_dboard_id,
dboard_id_t tx_dboard_id,
dboard_iface::sptr iface,
- property_tree::sptr subtree
+ property_tree::sptr subtree,
+ bool defer_db_init
);
- ~dboard_manager_impl(void);
+ virtual ~dboard_manager_impl(void);
+
+ inline const std::vector<std::string>& get_rx_frontends() const {
+ return _rx_frontends;
+ }
+
+ inline const std::vector<std::string>& get_tx_frontends() const {
+ return _tx_frontends;
+ }
+
+ void initialize_dboards();
private:
- void init(dboard_id_t, dboard_id_t, property_tree::sptr);
+ void init(dboard_id_t, dboard_id_t, property_tree::sptr, bool);
//list of rx and tx dboards in this dboard_manager
//each dboard here is actually a subdevice proxy
//the subdevice proxy is internal to the cpp file
uhd::dict<std::string, dboard_base::sptr> _rx_dboards;
uhd::dict<std::string, dboard_base::sptr> _tx_dboards;
+ std::vector<dboard_base::sptr> _rx_containers;
+ std::vector<dboard_base::sptr> _tx_containers;
+ std::vector<std::string> _rx_frontends;
+ std::vector<std::string> _tx_frontends;
dboard_iface::sptr _iface;
void set_nice_dboard_if(void);
};
@@ -176,13 +220,14 @@ dboard_manager::sptr dboard_manager::make(
dboard_id_t tx_dboard_id,
dboard_id_t gdboard_id,
dboard_iface::sptr iface,
- property_tree::sptr subtree
+ property_tree::sptr subtree,
+ bool defer_db_init
){
return dboard_manager::sptr(
new dboard_manager_impl(
rx_dboard_id,
(gdboard_id == dboard_id_t::none())? tx_dboard_id : gdboard_id,
- iface, subtree
+ iface, subtree, defer_db_init
)
);
}
@@ -194,12 +239,13 @@ dboard_manager_impl::dboard_manager_impl(
dboard_id_t rx_dboard_id,
dboard_id_t tx_dboard_id,
dboard_iface::sptr iface,
- property_tree::sptr subtree
+ property_tree::sptr subtree,
+ bool defer_db_init
):
_iface(iface)
{
try{
- this->init(rx_dboard_id, tx_dboard_id, subtree);
+ this->init(rx_dboard_id, tx_dboard_id, subtree, defer_db_init);
}
catch(const std::exception &e){
UHD_MSG(error) << boost::format(
@@ -210,12 +256,13 @@ dboard_manager_impl::dboard_manager_impl(
//clean up the stuff added by the call above
if (subtree->exists("rx_frontends")) subtree->remove("rx_frontends");
if (subtree->exists("tx_frontends")) subtree->remove("tx_frontends");
- this->init(dboard_id_t::none(), dboard_id_t::none(), subtree);
+ if (subtree->exists("iface")) subtree->remove("iface");
+ this->init(dboard_id_t::none(), dboard_id_t::none(), subtree, false);
}
}
void dboard_manager_impl::init(
- dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree
+ dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree, bool defer_db_init
){
//find the dboard key matches for the dboard ids
dboard_key_t rx_dboard_key, tx_dboard_key, xcvr_dboard_key;
@@ -244,6 +291,11 @@ void dboard_manager_impl::init(
//initialize the gpio pins before creating subdevs
set_nice_dboard_if();
+ //conditionally register the dboard iface in the tree
+ if (not (rx_dboard_key.is_restricted() or tx_dboard_key.is_restricted() or xcvr_dboard_key.is_restricted())) {
+ subtree->create<dboard_iface::sptr>("iface").set(_iface);
+ }
+
//dboard constructor args
dboard_ctor_args_t db_ctor_args;
db_ctor_args.db_iface = _iface;
@@ -252,42 +304,89 @@ void dboard_manager_impl::init(
if (xcvr_dboard_key.is_xcvr()){
//extract data for the xcvr dboard key
- dboard_ctor_t dboard_ctor; std::string name; std::vector<std::string> subdevs;
- boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key];
+ dboard_ctor_t subdev_ctor; std::string name; std::vector<std::string> subdevs; dboard_ctor_t container_ctor;
+ boost::tie(subdev_ctor, name, subdevs, container_ctor) = get_id_to_args_map()[xcvr_dboard_key];
+
+ //create the container class.
+ //a container class exists per N subdevs registered in a register_dboard* call
+ db_ctor_args.sd_name = "common";
+ db_ctor_args.rx_id = rx_dboard_id;
+ db_ctor_args.tx_id = tx_dboard_id;
+ db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name);
+ db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name);
+ if (container_ctor) {
+ db_ctor_args.rx_container = container_ctor(&db_ctor_args);
+ } else {
+ db_ctor_args.rx_container = dboard_base::sptr();
+ }
+ db_ctor_args.tx_container = db_ctor_args.rx_container; //Same TX and RX container
//create the xcvr object for each subdevice
BOOST_FOREACH(const std::string &subdev, subdevs){
db_ctor_args.sd_name = subdev;
- db_ctor_args.rx_id = rx_dboard_id;
- db_ctor_args.tx_id = tx_dboard_id;
- db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev);
- db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev);
- dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args);
+ db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name);
+ db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name);
+ dboard_base::sptr xcvr_dboard = subdev_ctor(&db_ctor_args);
_rx_dboards[subdev] = xcvr_dboard;
_tx_dboards[subdev] = xcvr_dboard;
+ xcvr_dboard->initialize();
}
+
+ //initialize the container after all subdevs have been created
+ if (container_ctor) {
+ if (defer_db_init) {
+ _rx_containers.push_back(db_ctor_args.rx_container);
+ } else {
+ db_ctor_args.rx_container->initialize();
+ }
+ }
+
+ //Populate frontend names in-order.
+ //We cannot use _xx_dboards.keys() here because of the ordering requirement
+ _rx_frontends = subdevs;
+ _tx_frontends = subdevs;
}
//make tx and rx subdevs (separate subdevs for rx and tx dboards)
- else{
-
+ else
+ {
//force the rx key to the unknown board for bad combinations
if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){
rx_dboard_key = dboard_key_t(0xfff1);
}
//extract data for the rx dboard key
- dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs;
- boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key];
+ dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; dboard_ctor_t rx_cont_ctor;
+ boost::tie(rx_dboard_ctor, rx_name, rx_subdevs, rx_cont_ctor) = get_id_to_args_map()[rx_dboard_key];
+
+ //create the container class.
+ //a container class exists per N subdevs registered in a register_dboard* call
+ db_ctor_args.sd_name = "common";
+ db_ctor_args.rx_id = rx_dboard_id;
+ db_ctor_args.tx_id = dboard_id_t::none();
+ db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name);
+ db_ctor_args.tx_subtree = property_tree::sptr();
+ if (rx_cont_ctor) {
+ db_ctor_args.rx_container = rx_cont_ctor(&db_ctor_args);
+ } else {
+ db_ctor_args.rx_container = dboard_base::sptr();
+ }
//make the rx subdevs
BOOST_FOREACH(const std::string &subdev, rx_subdevs){
db_ctor_args.sd_name = subdev;
- db_ctor_args.rx_id = rx_dboard_id;
- db_ctor_args.tx_id = dboard_id_t::none();
- db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev);
- db_ctor_args.tx_subtree = property_tree::sptr(); //null
+ db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name);
_rx_dboards[subdev] = rx_dboard_ctor(&db_ctor_args);
+ _rx_dboards[subdev]->initialize();
+ }
+
+ //initialize the container after all subdevs have been created
+ if (rx_cont_ctor) {
+ if (defer_db_init) {
+ _rx_containers.push_back(db_ctor_args.rx_container);
+ } else {
+ db_ctor_args.rx_container->initialize();
+ }
}
//force the tx key to the unknown board for bad combinations
@@ -296,18 +395,53 @@ void dboard_manager_impl::init(
}
//extract data for the tx dboard key
- dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs;
- boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key];
+ dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; dboard_ctor_t tx_cont_ctor;
+ boost::tie(tx_dboard_ctor, tx_name, tx_subdevs, tx_cont_ctor) = get_id_to_args_map()[tx_dboard_key];
+
+ //create the container class.
+ //a container class exists per N subdevs registered in a register_dboard* call
+ db_ctor_args.sd_name = "common";
+ db_ctor_args.rx_id = dboard_id_t::none();
+ db_ctor_args.tx_id = tx_dboard_id;
+ db_ctor_args.rx_subtree = property_tree::sptr();
+ db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name);
+ if (tx_cont_ctor) {
+ db_ctor_args.tx_container = tx_cont_ctor(&db_ctor_args);
+ } else {
+ db_ctor_args.tx_container = dboard_base::sptr();
+ }
//make the tx subdevs
BOOST_FOREACH(const std::string &subdev, tx_subdevs){
db_ctor_args.sd_name = subdev;
- db_ctor_args.rx_id = dboard_id_t::none();
- db_ctor_args.tx_id = tx_dboard_id;
- db_ctor_args.rx_subtree = property_tree::sptr(); //null
- db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev);
+ db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name);
_tx_dboards[subdev] = tx_dboard_ctor(&db_ctor_args);
+ _tx_dboards[subdev]->initialize();
+ }
+
+ //initialize the container after all subdevs have been created
+ if (tx_cont_ctor) {
+ if (defer_db_init) {
+ _tx_containers.push_back(db_ctor_args.tx_container);
+ } else {
+ db_ctor_args.tx_container->initialize();
+ }
}
+
+ //Populate frontend names in-order.
+ //We cannot use _xx_dboards.keys() here because of the ordering requirement
+ _rx_frontends = rx_subdevs;
+ _tx_frontends = tx_subdevs;
+ }
+}
+
+void dboard_manager_impl::initialize_dboards(void) {
+ BOOST_FOREACH(dboard_base::sptr& _rx_container, _rx_containers) {
+ _rx_container->initialize();
+ }
+
+ BOOST_FOREACH(dboard_base::sptr& _tx_container, _tx_containers) {
+ _tx_container->initialize();
}
}
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/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt
index 2a1e14eab..da77b85dc 100644
--- a/host/lib/usrp/e100/CMakeLists.txt
+++ b/host/lib/usrp/e100/CMakeLists.txt
@@ -22,8 +22,6 @@
########################################################################
# Conditionally configure the USRP-E100 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF)
-
IF(ENABLE_E100)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp
index b5baf6c56..ce0ac026b 100644
--- a/host/lib/usrp/e100/dboard_iface.cpp
+++ b/host/lib/usrp/e100/dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2010-2011,2015 Ettus Research LLC
+// Copyright 2010-2011,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
@@ -66,12 +66,16 @@ public:
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, boost::uint16_t);
- void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t);
- void _set_gpio_ddr(unit_t, boost::uint16_t);
- void _set_gpio_out(unit_t, boost::uint16_t);
- void set_gpio_debug(unit_t, int);
- boost::uint16_t read_gpio(unit_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);
@@ -97,6 +101,7 @@ public:
double get_clock_rate(unit_t);
void set_clock_enabled(unit_t, bool);
double get_codec_rate(unit_t);
+ void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn);
private:
timed_wb_iface::sptr _wb_iface;
@@ -127,6 +132,7 @@ void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){
switch(unit){
case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate);
case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate);
+ case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return;
}
}
@@ -142,14 +148,15 @@ double e100_dboard_iface::get_clock_rate(unit_t unit){
switch(unit){
case UNIT_RX: return _clock->get_rx_clock_rate();
case UNIT_TX: return _clock->get_tx_clock_rate();
+ default: UHD_THROW_INVALID_CODE_PATH();
}
- UHD_THROW_INVALID_CODE_PATH();
}
void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){
switch(unit){
case UNIT_RX: return _clock->enable_rx_dboard_clock(enb);
case UNIT_TX: return _clock->enable_tx_dboard_clock(enb);
+ case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return;
}
}
@@ -160,28 +167,40 @@ double e100_dboard_iface::get_codec_rate(unit_t){
/***********************************************************************
* GPIO
**********************************************************************/
-void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){
- return _gpio->set_pin_ctrl(unit, value);
+void e100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_ddr(unit, value);
+boost::uint32_t e100_dboard_iface::get_pin_ctrl(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit));
}
-void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_out(unit, value);
+void e100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){
- return _gpio->read_gpio(unit);
+boost::uint32_t e100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){
+ return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg));
+}
+
+void e100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
+}
+
+boost::uint32_t e100_dboard_iface::get_gpio_ddr(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit));
}
-void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){
- return _gpio->set_atr_reg(unit, atr, value);
+void e100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void e100_dboard_iface::set_gpio_debug(unit_t, int){
- throw uhd::not_implemented_error("no set_gpio_debug implemented");
+boost::uint32_t e100_dboard_iface::get_gpio_out(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit));
+}
+
+boost::uint32_t e100_dboard_iface::read_gpio(unit_t unit){
+ return _gpio->read_gpio(unit);
}
/***********************************************************************
@@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){
switch(unit){
case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB;
case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
- UHD_THROW_INVALID_CODE_PATH();
}
void e100_dboard_iface::write_spi(
@@ -268,3 +287,8 @@ void e100_dboard_iface::set_command_time(const uhd::time_spec_t& t)
{
_wb_iface->set_time(t);
}
+
+void e100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&)
+{
+ throw uhd::not_implemented_error("fe connection configuration support not implemented");
+}
diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp
index 6d3c08534..1f8fe84cb 100644
--- a/host/lib/usrp/e100/e100_impl.cpp
+++ b/host/lib/usrp/e100/e100_impl.cpp
@@ -217,20 +217,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(mb_eeprom)
- .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_mb_eeprom, this, _1));
////////////////////////////////////////////////////////////////////
// create clock control objects
////////////////////////////////////////////////////////////////////
//^^^ clock created up top, just reg props here... ^^^
_tree->create<double>(mb_path / "tick_rate")
- .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl))
- .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1))
- .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1));
+ .set_publisher(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl))
+ .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1))
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_tick_rate, this, _1));
- //subscribe the command time while we are at it
+ //add_coerced_subscriber the command time while we are at it
_tree->create<time_spec_t>(mb_path / "time/cmd")
- .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1));
////////////////////////////////////////////////////////////////////
// create codec control objects
@@ -241,18 +241,18 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
_tree->create<std::string>(rx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range);
_tree->create<double>(rx_codec_path / "gains/pga/value")
- .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1));
+ .set_coercer(boost::bind(&e100_impl::update_rx_codec_gain, this, _1));
_tree->create<std::string>(tx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range);
_tree->create<double>(tx_codec_path / "gains/pga/value")
- .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1))
- .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl));
+ .add_coerced_subscriber(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1))
+ .set_publisher(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl));
////////////////////////////////////////////////////////////////////
// and do the misc mboard sensors
////////////////////////////////////////////////////////////////////
_tree->create<sensor_value_t>(mb_path / "sensors/ref_locked")
- .publish(boost::bind(&e100_impl::get_ref_locked, this));
+ .set_publisher(boost::bind(&e100_impl::get_ref_locked, this));
////////////////////////////////////////////////////////////////////
// Create the GPSDO control
@@ -272,7 +272,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
BOOST_FOREACH(const std::string &name, _gps->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name));
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name));
}
}
else
@@ -288,27 +288,27 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
_tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE));
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
- .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
- .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1));
const fs_path rx_fe_path = mb_path / "rx_frontends" / "A";
const fs_path tx_fe_path = mb_path / "tx_frontends" / "A";
_tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1))
+ .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
- .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1))
.set(true);
_tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1))
+ .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
////////////////////////////////////////////////////////////////////
@@ -327,20 +327,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
_rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1));
fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
_tree->create<meta_range_t>(rx_dsp_path / "rate/range")
- .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno]));
_tree->create<double>(rx_dsp_path / "rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1))
- .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1))
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1));
_tree->create<double>(rx_dsp_path / "freq/value")
- .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1));
_tree->create<meta_range_t>(rx_dsp_path / "freq/range")
- .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno]));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1));
}
////////////////////////////////////////////////////////////////////
@@ -351,17 +351,17 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
);
_tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1));
+ .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range")
- .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp));
_tree->create<double>(mb_path / "tx_dsps/0/rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1))
- .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1))
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1));
_tree->create<double>(mb_path / "tx_dsps/0/freq/value")
- .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range")
- .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp));
////////////////////////////////////////////////////////////////////
// create time control objects
@@ -375,21 +375,21 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
_fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases
);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1));
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time/now")
- .publish(boost::bind(&time64_core_200::get_time_now, _time64))
- .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1));
_tree->create<time_spec_t>(mb_path / "time/pps")
- .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64))
- .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1));
//setup time source props
_tree->create<std::string>(mb_path / "time_source/value")
- .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1));
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1));
_tree->create<std::vector<std::string> >(mb_path / "time_source/options")
- .publish(boost::bind(&time64_core_200::get_time_sources, _time64));
+ .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64));
//setup reference source props
_tree->create<std::string>(mb_path / "clock_source/value")
- .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::update_clock_source, this, _1));
std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto");
if (_gps and _gps->gps_detected()) clock_sources.push_back("gpsdo");
_tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources);
@@ -399,7 +399,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
_user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS));
_tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs")
- .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1));
+ .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1));
////////////////////////////////////////////////////////////////////
// create dboard control objects
@@ -417,32 +417,31 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
//create the properties and register subscribers
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")
.set(rx_db_eeprom)
- .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom")
.set(tx_db_eeprom)
- .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom")
.set(gdb_eeprom)
- .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1));
//create a new dboard interface and manager
- _dboard_iface = make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl);
- _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface);
_dboard_manager = dboard_manager::make(
rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id,
- _dboard_iface, _tree->subtree(mb_path / "dboards/A")
+ make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl),
+ _tree->subtree(mb_path / "dboards/A")
);
//bind frontend corrections to the dboard freq props
const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){
_tree->access<double>(db_tx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1));
}
const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){
_tree->access<double>(db_rx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1));
+ .add_coerced_subscriber(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1));
}
//initialize io handling
@@ -457,8 +456,8 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
this->update_rates();
- _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter
- .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1));
+ _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter
+ .add_coerced_subscriber(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1));
//reset cordic rates and their properties to zero
BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){
diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp
index d00668224..b05053f84 100644
--- a/host/lib/usrp/e100/e100_impl.hpp
+++ b/host/lib/usrp/e100/e100_impl.hpp
@@ -111,7 +111,6 @@ private:
//dboard stuff
uhd::usrp::dboard_manager::sptr _dboard_manager;
- uhd::usrp::dboard_iface::sptr _dboard_iface;
bool _ignore_cal_file;
std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers;
diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt
index 9c8aa29b9..68c3520e4 100644
--- a/host/lib/usrp/e300/CMakeLists.txt
+++ b/host/lib/usrp/e300/CMakeLists.txt
@@ -24,8 +24,6 @@
########################################################################
find_package(UDev)
-LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF)
-
IF(ENABLE_E300)
LIST(APPEND E300_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp
diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp
index 594461518..36dd47383 100644
--- a/host/lib/usrp/e300/e300_fpga_defs.hpp
+++ b/host/lib/usrp/e300/e300_fpga_defs.hpp
@@ -21,7 +21,7 @@ namespace uhd { namespace usrp { namespace e300 { namespace fpga {
static const size_t NUM_RADIOS = 2;
-static const boost::uint32_t COMPAT_MAJOR = 14;
+static const boost::uint32_t COMPAT_MAJOR = 16;
static const boost::uint32_t COMPAT_MINOR = 0;
}}}} // namespace
diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp
index a57c86c1d..114686b4f 100644
--- a/host/lib/usrp/e300/e300_impl.cpp
+++ b/host/lib/usrp/e300/e300_impl.cpp
@@ -48,6 +48,7 @@
using namespace uhd;
using namespace uhd::usrp;
+using namespace uhd::usrp::gpio_atr;
using namespace uhd::transport;
namespace fs = boost::filesystem;
namespace asio = boost::asio;
@@ -470,14 +471,14 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name));
+ .set_publisher(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name));
}
#ifdef E300_GPSD
if (_gps) {
BOOST_FOREACH(const std::string &name, _gps->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gpsd_iface::get_sensor, _gps, name));
+ .set_publisher(boost::bind(&gpsd_iface::get_sensor, _gps, name));
}
}
#endif
@@ -487,7 +488,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
////////////////////////////////////////////////////////////////////
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(_eeprom_manager->get_mb_eeprom()) // set first...
- .subscribe(boost::bind(
+ .add_coerced_subscriber(boost::bind(
&e300_eeprom_manager::write_mb_eeprom,
_eeprom_manager, _1));
@@ -495,9 +496,9 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
// clocking
////////////////////////////////////////////////////////////////////
_tree->create<double>(mb_path / "tick_rate")
- .coerce(boost::bind(&e300_impl::_set_tick_rate, this, _1))
- .publish(boost::bind(&e300_impl::_get_tick_rate, this))
- .subscribe(boost::bind(&e300_impl::_update_tick_rate, this, _1));
+ .set_coercer(boost::bind(&e300_impl::_set_tick_rate, this, _1))
+ .set_publisher(boost::bind(&e300_impl::_get_tick_rate, this))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_tick_rate, this, _1));
//default some chains on -- needed for setup purposes
_codec_ctrl->set_active_chains(true, false, true, false);
@@ -509,42 +510,47 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
for(size_t instance = 0; instance < fpga::NUM_RADIOS; instance++)
this->_setup_radio(instance);
- // Radio 0 loopback through AD9361
- _codec_mgr->loopback_self_test(_radio_perifs[0].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK);
- // Radio 1 loopback through AD9361
- _codec_mgr->loopback_self_test(_radio_perifs[1].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK);
-
+ //now test each radio module's connection to the codec interface
+ BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs)
+ {
+ _codec_mgr->loopback_self_test(
+ boost::bind(
+ &radio_ctrl_core_3000::poke32, perif.ctrl, radio::sr_addr(radio::CODEC_IDLE), _1
+ ),
+ boost::bind(&radio_ctrl_core_3000::peek64, perif.ctrl, radio::RB64_CODEC_READBACK)
+ );
+ }
////////////////////////////////////////////////////////////////////
// internal gpios
////////////////////////////////////////////////////////////////////
- gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO);
+ gpio_atr_3000::sptr fp_gpio = gpio_atr_3000::make(_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" / "INT0" / attr.second)
- .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr.first, _1))
+ .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, fp_gpio, attr.first, _1))
.set(0);
}
_tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK")
- .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio));
+ .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, fp_gpio));
////////////////////////////////////////////////////////////////////
// register the time keepers - only one can be the highlander
////////////////////////////////////////////////////////////////////
_tree->create<time_spec_t>(mb_path / "time" / "now")
- .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64))
- .subscribe(boost::bind(&e300_impl::_set_time, this, _1))
+ .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_set_time, this, _1))
.set(0.0);
//re-sync the times when the tick rate changes
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&e300_impl::_sync_times, this));
+ .add_coerced_subscriber(boost::bind(&e300_impl::_sync_times, this));
_tree->create<time_spec_t>(mb_path / "time" / "pps")
- .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64))
- .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1))
- .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1));
+ .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64))
+ .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1))
+ .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1));
//setup time source props
_tree->create<std::string>(mb_path / "time_source" / "value")
- .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_time_source, this, _1))
.set(e300::DEFAULT_TIME_SRC);
#ifdef E300_GPSD
static const std::vector<std::string> time_sources = boost::assign::list_of("none")("internal")("external")("gpsdo");
@@ -554,7 +560,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
_tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources);
//setup reference source props
_tree->create<std::string>(mb_path / "clock_source" / "value")
- .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_clock_source, this, _1))
.set(e300::DEFAULT_CLOCK_SRC);
static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); //external,gpsdo not supported
_tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources);
@@ -565,13 +571,13 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
dboard_eeprom_t db_eeprom;
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom")
.set(_eeprom_manager->get_db_eeprom())
- .subscribe(boost::bind(
+ .add_coerced_subscriber(boost::bind(
&e300_eeprom_manager::write_db_eeprom,
_eeprom_manager, _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom")
.set(_eeprom_manager->get_db_eeprom())
- .subscribe(boost::bind(
+ .add_coerced_subscriber(boost::bind(
&e300_eeprom_manager::write_db_eeprom,
_eeprom_manager, _1));
@@ -604,10 +610,10 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
.set(subdev_spec_t())
- .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
.set(subdev_spec_t())
- .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1));
////////////////////////////////////////////////////////////////////
// do some post-init tasks
@@ -631,37 +637,6 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr)
_tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec);
}
-boost::uint8_t e300_impl::_get_internal_gpio(gpio_core_200::sptr gpio)
-{
- return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX));
-}
-
-void e300_impl::_set_internal_gpio(
- gpio_core_200::sptr gpio,
- const gpio_attr_t attr,
- const boost::uint32_t value)
-{
- switch (attr)
- {
- case GPIO_CTRL:
- return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value);
- case GPIO_DDR:
- return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value);
- case GPIO_OUT:
- return gpio->set_gpio_out(dboard_iface::UNIT_RX, value);
- case GPIO_ATR_0X:
- return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value);
- case GPIO_ATR_RX:
- return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value);
- case GPIO_ATR_TX:
- return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value);
- case GPIO_ATR_XX:
- return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value);
- default:
- UHD_THROW_INVALID_CODE_PATH();
- }
-}
-
uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx)
{
const boost::uint32_t st =
@@ -1001,7 +976,8 @@ void e300_impl::_setup_radio(const size_t dspno)
////////////////////////////////////////////////////////////////////
// Set up peripherals
////////////////////////////////////////////////////////////////////
- perif.atr = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::GPIO));
+ perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::GPIO));
+ perif.atr->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);
@@ -1036,26 +1012,25 @@ void e300_impl::_setup_radio(const size_t dspno)
// connect rx dsp control objects
////////////////////////////////////////////////////////////////////
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
- .subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1));
+ .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1));
const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno);
perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path));
_tree->access<double>(rx_dsp_path / "rate" / "value")
- .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1))
;
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1));
+ .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1));
////////////////////////////////////////////////////////////////////
// create tx dsp control objects
////////////////////////////////////////////////////////////////////
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1))
- .subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1));
+ .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1));
const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno);
perif.duc->populate_subtree(_tree->subtree(tx_dsp_path));
_tree->access<double>(tx_dsp_path / "rate" / "value")
- .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1))
;
////////////////////////////////////////////////////////////////////
@@ -1075,10 +1050,10 @@ void e300_impl::_setup_radio(const size_t dspno)
// This will connect all the e300_impl-specific items
_tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked")
- .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION))
+ .set_publisher(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION))
;
_tree->access<double>(rf_fe_path / "freq" / "value")
- .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1))
;
// Antenna Setup
@@ -1086,7 +1061,7 @@ void e300_impl::_setup_radio(const size_t dspno)
static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2");
_tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants);
_tree->create<std::string>(rf_fe_path / "antenna" / "value")
- .subscribe(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1))
+ .add_coerced_subscriber(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1))
.set("RX2");
}
else if (dir == TX_DIRECTION) {
@@ -1315,11 +1290,11 @@ void e300_impl::_update_atrs(void)
if (enb_tx)
fd_reg |= tx_enables | xx_leds;
- gpio_core_200_32wo::sptr atr = _radio_perifs[instance].atr;
- atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, oo_reg);
- atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rx_reg);
- atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_reg);
- atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd_reg);
+ gpio_atr_3000::sptr atr = _radio_perifs[instance].atr;
+ atr->set_atr_reg(ATR_REG_IDLE, oo_reg);
+ atr->set_atr_reg(ATR_REG_RX_ONLY, rx_reg);
+ atr->set_atr_reg(ATR_REG_TX_ONLY, tx_reg);
+ atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd_reg);
}
}
diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp
index 595b42679..e9a0b4b9a 100644
--- a/host/lib/usrp/e300/e300_impl.hpp
+++ b/host/lib/usrp/e300/e300_impl.hpp
@@ -41,7 +41,7 @@
#include "tx_dsp_core_3000.hpp"
#include "ad9361_ctrl.hpp"
#include "ad936x_manager.hpp"
-#include "gpio_core_200.hpp"
+#include "gpio_atr_3000.hpp"
#include "e300_global_regs.hpp"
#include "e300_i2c.hpp"
@@ -147,7 +147,7 @@ private: // types
struct radio_perifs_t
{
radio_ctrl_core_3000::sptr ctrl;
- gpio_core_200_32wo::sptr atr;
+ gpio_atr::gpio_atr_3000::sptr atr;
time_core_3000::sptr time64;
rx_vita_core_3000::sptr framer;
rx_dsp_core_3000::sptr ddc;
@@ -283,14 +283,6 @@ private: // methods
// get frontend lock sensor
uhd::sensor_value_t _get_fe_pll_lock(const bool is_tx);
- // internal gpios
- boost::uint8_t _get_internal_gpio(gpio_core_200::sptr);
-
- void _set_internal_gpio(
- gpio_core_200::sptr gpio,
- const gpio_attr_t attr,
- const boost::uint32_t value);
-
private: // members
uhd::device_addr_t _device_addr;
xport_t _xport_path;
diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp
index 29d250c8f..c84042e98 100644
--- a/host/lib/usrp/e300/e300_io_impl.cpp
+++ b/host/lib/usrp/e300/e300_io_impl.cpp
@@ -87,7 +87,6 @@ void e300_impl::_update_tick_rate(const double rate)
boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock());
if (my_streamer)
my_streamer->set_tick_rate(rate);
- perif.deframer->set_tick_rate(_tick_rate);
}
}
@@ -158,10 +157,8 @@ void e300_impl::_update_subdev_spec(
const std::string conn = _tree->access<std::string>(
mb_path / "dboards" / spec[i].db_name /
("rx_frontends") / spec[i].sd_name / "connection").get();
-
- const bool fe_swapped = (conn == "QI" or conn == "Q");
- _radio_perifs[i].ddc->set_mux(conn, fe_swapped);
- _radio_perifs[i].rx_fe->set_mux(fe_swapped);
+ _radio_perifs[i].ddc->set_mux(usrp::fe_connection_t(conn));
+ _radio_perifs[i].rx_fe->set_mux(false);
}
}
diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp
index 846c759a4..74e45df00 100644
--- a/host/lib/usrp/e300/e300_regs.hpp
+++ b/host/lib/usrp/e300/e300_regs.hpp
@@ -41,7 +41,7 @@ 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 = 200;
+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 CODEC_IDLE = 250;
diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp
index 17a564f21..cb2583b1b 100644
--- a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp
+++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp
@@ -36,6 +36,9 @@ public:
{
}
+ void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {};
+ void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {};
+
double set_gain(const std::string &which, const double value)
{
_clear();
diff --git a/host/lib/usrp/fe_connection.cpp b/host/lib/usrp/fe_connection.cpp
new file mode 100644
index 000000000..071f5ecf2
--- /dev/null
+++ b/host/lib/usrp/fe_connection.cpp
@@ -0,0 +1,67 @@
+//
+// 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/usrp/fe_connection.hpp>
+#include <uhd/exception.hpp>
+#include <boost/regex.hpp>
+#include <uhd/utils/math.hpp>
+
+using namespace uhd::usrp;
+
+fe_connection_t::fe_connection_t(
+ sampling_t sampling_mode, bool iq_swapped,
+ bool i_inverted, bool q_inverted, double if_freq
+) : _sampling_mode(sampling_mode), _iq_swapped(iq_swapped),
+ _i_inverted(i_inverted), _q_inverted(q_inverted), _if_freq(if_freq)
+{
+}
+
+fe_connection_t::fe_connection_t(const std::string& conn_str, double if_freq) {
+ static const boost::regex conn_regex("([IQ])(b?)(([IQ])(b?))?");
+ boost::cmatch matches;
+ if (boost::regex_match(conn_str.c_str(), matches, conn_regex)) {
+ if (matches[3].length() == 0) {
+ //Connection in {I, Q, Ib, Qb}
+ _sampling_mode = REAL;
+ _iq_swapped = (matches[1].str() == "Q");
+ _i_inverted = (matches[2].length() != 0);
+ _q_inverted = false; //IQ is swapped after inversion
+ } else {
+ //Connection in {I(b?)Q(b?), Q(b?)I(b?), I(b?)I(b?), Q(b?)Q(b?)}
+ _sampling_mode = (matches[1].str() == matches[4].str()) ? HETERODYNE : QUADRATURE;
+ _iq_swapped = (matches[1].str() == "Q");
+ size_t i_idx = _iq_swapped ? 5 : 2, q_idx = _iq_swapped ? 2 : 5;
+ _i_inverted = (matches[i_idx].length() != 0);
+ _q_inverted = (matches[q_idx].length() != 0);
+
+ if (_sampling_mode == HETERODYNE and _i_inverted != _q_inverted) {
+ throw uhd::value_error("Invalid connection string: " + conn_str);
+ }
+ }
+ _if_freq = if_freq;
+ } else {
+ throw uhd::value_error("Invalid connection string: " + conn_str);
+ }
+}
+
+bool uhd::usrp::operator==(const fe_connection_t &lhs, const fe_connection_t &rhs){
+ return ((lhs.get_sampling_mode() == rhs.get_sampling_mode()) and
+ (lhs.is_iq_swapped() == rhs.is_iq_swapped()) and
+ (lhs.is_i_inverted() == rhs.is_i_inverted()) and
+ (lhs.is_q_inverted() == rhs.is_q_inverted()) and
+ uhd::math::frequencies_are_equal(lhs.get_if_freq(), rhs.get_if_freq()));
+}
diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp
index 396237e24..7905a6d32 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>
@@ -39,6 +40,7 @@ using namespace uhd;
using namespace uhd::usrp;
const std::string multi_usrp::ALL_GAINS = "";
+const std::string multi_usrp::ALL_LOS = "all";
UHD_INLINE std::string string_vector_to_string(std::vector<std::string> values, std::string delimiter = std::string(" "))
{
@@ -389,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;
@@ -438,8 +456,10 @@ public:
******************************************************************/
void set_master_clock_rate(double rate, size_t mboard){
if (mboard != ALL_MBOARDS){
- if (_tree->exists(mb_root(mboard) / "auto_tick_rate")) {
+ if (_tree->exists(mb_root(mboard) / "auto_tick_rate")
+ and _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").get()) {
_tree->access<bool>(mb_root(mboard) / "auto_tick_rate").set(false);
+ UHD_MSG(status) << "Setting master clock rate selection to 'manual'." << std::endl;
}
_tree->access<double>(mb_root(mboard) / "tick_rate").set(rate);
return;
@@ -605,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++){
@@ -740,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);
}
@@ -830,6 +858,171 @@ public:
return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get();
}
+ std::vector<std::string> get_rx_lo_names(size_t chan = 0){
+ std::vector<std::string> lo_names;
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ BOOST_FOREACH(const std::string &name, _tree->list(rx_rf_fe_root(chan) / "los")) {
+ lo_names.push_back(name);
+ }
+ }
+ return lo_names;
+ }
+
+ void set_rx_lo_source(const std::string &src, const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) {
+ //Special value ALL_LOS support atomically sets the source for all LOs
+ _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").set(src);
+ } else {
+ BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) {
+ this->set_rx_lo_source(src, n, chan);
+ }
+ }
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").set(src);
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ throw uhd::runtime_error("This device does not support manual configuration of LOs");
+ }
+ }
+
+ const std::string get_rx_lo_source(const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ //Special value ALL_LOS support atomically sets the source for all LOs
+ return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").get();
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ // If the daughterboard doesn't expose it's LO(s) then it can only be internal
+ return "internal";
+ }
+ }
+
+ std::vector<std::string> get_rx_lo_sources(const std::string &name = ALL_LOS, size_t chan = 0) {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) {
+ //Special value ALL_LOS support atomically sets the source for all LOs
+ return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "options").get();
+ } else {
+ return std::vector<std::string>();
+ }
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / name / "source" / "options").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ // If the daughterboard doesn't expose it's LO(s) then it can only be internal
+ return std::vector<std::string>(1, "internal");
+ }
+ }
+
+ void set_rx_lo_export_enabled(bool enabled, const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) {
+ //Special value ALL_LOS support atomically sets the source for all LOs
+ _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").set(enabled);
+ } else {
+ BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) {
+ this->set_rx_lo_export_enabled(enabled, n, chan);
+ }
+ }
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").set(enabled);
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ throw uhd::runtime_error("This device does not support manual configuration of LOs");
+ }
+ }
+
+ bool get_rx_lo_export_enabled(const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ //Special value ALL_LOS support atomically sets the source for all LOs
+ return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").get();
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ // If the daughterboard doesn't expose it's LO(s), assume it cannot export
+ return false;
+ }
+ }
+
+ double set_rx_lo_freq(double freq, const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ throw uhd::runtime_error("LO frequency must be set for each stage individually");
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").set(freq);
+ return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ throw uhd::runtime_error("This device does not support manual configuration of LOs");
+ }
+ }
+
+ double get_rx_lo_freq(const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ throw uhd::runtime_error("LO frequency must be retrieved for each stage individually");
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ // Return actual RF frequency if the daughterboard doesn't expose it's LO(s)
+ return _tree->access<double>(rx_rf_fe_root(chan) / "freq" /" value").get();
+ }
+ }
+
+ freq_range_t get_rx_lo_freq_range(const std::string &name = ALL_LOS, size_t chan = 0){
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ if (name == ALL_LOS) {
+ throw uhd::runtime_error("LO frequency range must be retrieved for each stage individually");
+ } else {
+ if (_tree->exists(rx_rf_fe_root(chan) / "los")) {
+ return _tree->access<freq_range_t>(rx_rf_fe_root(chan) / "los" / name / "freq" / "range").get();
+ } else {
+ throw uhd::runtime_error("Could not find LO stage " + name);
+ }
+ }
+ } else {
+ // Return the actual RF range if the daughterboard doesn't expose it's LO(s)
+ return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get();
+ }
+ }
+
void set_rx_gain(double gain, const std::string &name, size_t chan){
/* Check if any AGC mode is enable and if so warn the user */
if (chan != ALL_CHANS) {
@@ -1100,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);
}
@@ -1346,10 +1542,10 @@ public:
if (attr == "CTRL") iface->set_pin_ctrl(unit, boost::uint16_t(value), boost::uint16_t(mask));
if (attr == "DDR") iface->set_gpio_ddr(unit, boost::uint16_t(value), boost::uint16_t(mask));
if (attr == "OUT") iface->set_gpio_out(unit, boost::uint16_t(value), boost::uint16_t(mask));
- if (attr == "ATR_0X") iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask));
- if (attr == "ATR_RX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask));
- if (attr == "ATR_TX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask));
- if (attr == "ATR_XX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask));
+ if (attr == "ATR_0X") iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask));
+ if (attr == "ATR_RX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask));
+ if (attr == "ATR_TX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask));
+ if (attr == "ATR_XX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask));
}
}
@@ -1367,10 +1563,10 @@ public:
if (attr == "CTRL") return iface->get_pin_ctrl(unit);
if (attr == "DDR") return iface->get_gpio_ddr(unit);
if (attr == "OUT") return iface->get_gpio_out(unit);
- if (attr == "ATR_0X") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_IDLE);
- if (attr == "ATR_RX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY);
- if (attr == "ATR_TX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY);
- if (attr == "ATR_XX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX);
+ if (attr == "ATR_0X") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE);
+ if (attr == "ATR_RX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY);
+ if (attr == "ATR_TX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY);
+ if (attr == "ATR_XX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX);
if (attr == "READBACK") return iface->read_gpio(unit);
}
return 0;
@@ -1456,9 +1652,8 @@ public:
default:
throw uhd::assertion_error("multi_usrp::read_register - register has invalid bitwidth: " + path);
}
- } else {
- throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device");
}
+ throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device");
}
std::vector<std::string> enumerate_registers(const size_t mboard)
@@ -1494,6 +1689,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;
@@ -1546,6 +1743,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);
@@ -1566,6 +1767,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);
@@ -1585,6 +1790,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);
@@ -1599,6 +1807,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/n230/CMakeLists.txt b/host/lib/usrp/n230/CMakeLists.txt
new file mode 100644
index 000000000..9eaccffba
--- /dev/null
+++ b/host/lib/usrp/n230/CMakeLists.txt
@@ -0,0 +1,37 @@
+#
+# Copyright 2013 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# 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
+########################################################################
+
+########################################################################
+# Conditionally configure the N230 support
+########################################################################
+IF(ENABLE_N230)
+ LIBUHD_APPEND_SOURCES(
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_cores.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_impl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_resource_manager.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_eeprom_manager.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_stream_manager.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_clk_pps_ctrl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_frontend_ctrl.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_uart.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/n230_image_loader.cpp
+ )
+ENDIF(ENABLE_N230)
diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp
new file mode 100644
index 000000000..9d704b702
--- /dev/null
+++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp
@@ -0,0 +1,158 @@
+//
+// Copyright 2013-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 "n230_clk_pps_ctrl.hpp"
+
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <boost/cstdint.hpp>
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <stdexcept>
+#include <cmath>
+#include <cstdlib>
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_clk_pps_ctrl_impl : public n230_clk_pps_ctrl
+{
+public:
+ n230_clk_pps_ctrl_impl(
+ ad9361_ctrl::sptr codec_ctrl,
+ n230_ref_pll_ctrl::sptr ref_pll_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ fpga::core_pps_sel_reg_t& core_pps_sel,
+ fpga::core_status_reg_t& core_status_reg,
+ const std::vector<time_core_3000::sptr>& time_cores
+ ): _codec_ctrl(codec_ctrl),
+ _ref_pll_ctrl(ref_pll_ctrl),
+ _core_misc_reg(core_misc_reg),
+ _core_pps_sel_reg(core_pps_sel),
+ _core_status_reg(core_status_reg),
+ _time_cores(time_cores),
+ _tick_rate(0.0),
+ _clock_source("<undefined>"),
+ _time_source("<undefined>")
+ {
+ }
+
+ virtual ~n230_clk_pps_ctrl_impl()
+ {
+ }
+
+ double set_tick_rate(const double rate)
+ {
+ UHD_MSG(status) << "Configuring a tick rate of " << rate/1e6 << " MHz... ";
+ _tick_rate = _codec_ctrl->set_clock_rate(rate);
+ UHD_MSG(status) << "got " << _tick_rate/1e6 << " MHz\n";
+
+ BOOST_FOREACH(time_core_3000::sptr& time_core, _time_cores) {
+ time_core->set_tick_rate(_tick_rate);
+ time_core->self_test();
+ }
+
+ return _tick_rate;
+ }
+
+ double get_tick_rate()
+ {
+ return _tick_rate;
+ }
+
+ void set_clock_source(const std::string &source)
+ {
+ if (_clock_source == source) return;
+
+ if (source == "internal") {
+ _ref_pll_ctrl->set_lock_to_ext_ref(false);
+ } else if (source == "external" || source == "gpsdo") {
+ _ref_pll_ctrl->set_lock_to_ext_ref(true);
+ } else {
+ throw uhd::key_error("set_clock_source: unknown source: " + source);
+ }
+ _core_misc_reg.write(fpga::core_misc_reg_t::REF_SEL, (source == "gpsdo") ? 1 : 0);
+
+ _clock_source = source;
+ }
+
+ const std::string& get_clock_source()
+ {
+ return _clock_source;
+ }
+
+ uhd::sensor_value_t get_ref_locked()
+ {
+ bool locked = false;
+ if (_clock_source == "external" || _clock_source == "gpsdo") {
+ locked = (_core_status_reg.read(fpga::core_status_reg_t::REF_LOCKED) == 1);
+ } else {
+ //If the source is internal, the charge pump on the ADF4001 is tristated which
+ //means that the 40MHz VCTXXO is free running i.e. always "locked"
+ locked = true;
+ }
+ return sensor_value_t("Ref", locked, "locked", "unlocked");
+ }
+
+ void set_pps_source(const std::string &source)
+ {
+ if (_time_source == source) return;
+
+ if (source == "none" or source == "gpsdo") {
+ _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 0);
+ } else if (source == "external") {
+ _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 1);
+ } else {
+ throw uhd::key_error("update_time_source: unknown source: " + source);
+ }
+
+ _time_source = source;
+ }
+
+ const std::string& get_pps_source()
+ {
+ return _time_source;
+ }
+
+private:
+ ad9361_ctrl::sptr _codec_ctrl;
+ n230_ref_pll_ctrl::sptr _ref_pll_ctrl;
+ fpga::core_misc_reg_t& _core_misc_reg;
+ fpga::core_pps_sel_reg_t& _core_pps_sel_reg;
+ fpga::core_status_reg_t& _core_status_reg;
+ std::vector<time_core_3000::sptr> _time_cores;
+ double _tick_rate;
+ std::string _clock_source;
+ std::string _time_source;
+};
+
+}}} //namespace
+
+using namespace uhd::usrp::n230;
+using namespace uhd::usrp;
+
+n230_clk_pps_ctrl::sptr n230_clk_pps_ctrl::make(
+ ad9361_ctrl::sptr codec_ctrl,
+ n230_ref_pll_ctrl::sptr ref_pll_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ fpga::core_pps_sel_reg_t& core_pps_sel_reg,
+ fpga::core_status_reg_t& core_status_reg,
+ const std::vector<time_core_3000::sptr>& time_cores)
+{
+ return sptr(new n230_clk_pps_ctrl_impl(
+ codec_ctrl, ref_pll_ctrl, core_misc_reg, core_pps_sel_reg, core_status_reg, time_cores));
+}
+
diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp
new file mode 100644
index 000000000..3e0a21e04
--- /dev/null
+++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp
@@ -0,0 +1,89 @@
+//
+// Copyright 2013-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_N230_CLK_PPS_CTRL_HPP
+#define INCLUDED_N230_CLK_PPS_CTRL_HPP
+
+#include "time_core_3000.hpp"
+#include "ad9361_ctrl.hpp"
+#include <uhd/types/sensors.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <vector>
+#include "n230_cores.hpp"
+#include "n230_fpga_defs.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_clk_pps_ctrl : boost::noncopyable
+{
+public:
+ typedef boost::shared_ptr<n230_clk_pps_ctrl> sptr;
+
+ static sptr make(
+ ad9361_ctrl::sptr codec_ctrl,
+ n230_ref_pll_ctrl::sptr ref_pll_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ fpga::core_pps_sel_reg_t& core_pps_sel_reg,
+ fpga::core_status_reg_t& core_status_reg,
+ const std::vector<time_core_3000::sptr>& time_cores);
+
+ virtual ~n230_clk_pps_ctrl() {}
+
+ /***********************************************************************
+ * Tick Rate
+ **********************************************************************/
+ /*! Set the master clock rate of the device.
+ * \return the clock frequency in Hz
+ */
+ virtual double set_tick_rate(const double rate) = 0;
+
+ /*! Get the master clock rate of the device.
+ * \return the clock frequency in Hz
+ */
+ virtual double get_tick_rate() = 0;
+
+ /***********************************************************************
+ * Reference clock
+ **********************************************************************/
+ /*! Set the reference clock source of the device.
+ */
+ virtual void set_clock_source(const std::string &source) = 0;
+
+ /*! Get the reference clock source of the device.
+ */
+ virtual const std::string& get_clock_source() = 0;
+
+ /*! Get the reference clock lock status.
+ */
+ virtual uhd::sensor_value_t get_ref_locked() = 0;
+
+ /***********************************************************************
+ * Time source
+ **********************************************************************/
+ /*! Set the time source of the device.
+ */
+ virtual void set_pps_source(const std::string &source) = 0;
+
+ /*! Get the reference clock source of the device.
+ */
+ virtual const std::string& get_pps_source() = 0;
+};
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_CLK_PPS_CTRL_HPP */
diff --git a/host/lib/usrp/n230/n230_cores.cpp b/host/lib/usrp/n230/n230_cores.cpp
new file mode 100644
index 000000000..58c702ec1
--- /dev/null
+++ b/host/lib/usrp/n230/n230_cores.cpp
@@ -0,0 +1,91 @@
+//
+// Copyright 2013-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 "n230_cores.hpp"
+#include "n230_fpga_defs.h"
+#include "n230_fw_defs.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+n230_core_spi_core::n230_core_spi_core(
+ uhd::wb_iface::sptr iface,
+ perif_t default_perif) :
+ _spi_core(spi_core_3000::make(iface,
+ fpga::sr_addr(fpga::SR_CORE_SPI),
+ fpga::rb_addr(fpga::RB_CORE_SPI))),
+ _current_perif(default_perif),
+ _last_perif(default_perif)
+{
+ change_perif(default_perif);
+}
+
+boost::uint32_t n230_core_spi_core::transact_spi(
+ int which_slave,
+ const spi_config_t &config,
+ boost::uint32_t data,
+ size_t num_bits,
+ bool readback)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ return _spi_core->transact_spi(which_slave, config, data, num_bits, readback);
+}
+
+void n230_core_spi_core::change_perif(perif_t perif)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+ _last_perif = _current_perif;
+ _current_perif = perif;
+
+ switch (_current_perif) {
+ case CODEC:
+ _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::CODEC_SPI_CLOCK_FREQ);
+ break;
+ case PLL:
+ _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::ADF4001_SPI_CLOCK_FREQ);
+ break;
+ }
+}
+
+void n230_core_spi_core::restore_perif()
+{
+ change_perif(_last_perif);
+}
+
+n230_ref_pll_ctrl::n230_ref_pll_ctrl(n230_core_spi_core::sptr spi) :
+ adf4001_ctrl(spi, fpga::ADF4001_SPI_SLAVE_NUM),
+ _spi(spi)
+{
+}
+
+void n230_ref_pll_ctrl::set_lock_to_ext_ref(bool external)
+{
+ _spi->change_perif(n230_core_spi_core::PLL);
+ adf4001_ctrl::set_lock_to_ext_ref(external);
+ _spi->restore_perif();
+}
+
+}}} //namespace
+
+using namespace uhd::usrp::n230;
+using namespace uhd::usrp;
+
+n230_core_spi_core::sptr n230_core_spi_core::make(
+ uhd::wb_iface::sptr iface, n230_core_spi_core::perif_t default_perif)
+{
+ return sptr(new n230_core_spi_core(iface, default_perif));
+}
+
diff --git a/host/lib/usrp/n230/n230_cores.hpp b/host/lib/usrp/n230/n230_cores.hpp
new file mode 100644
index 000000000..3f56c1889
--- /dev/null
+++ b/host/lib/usrp/n230/n230_cores.hpp
@@ -0,0 +1,71 @@
+//
+// Copyright 2013-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_N230_CORES_HPP
+#define INCLUDED_N230_CORES_HPP
+
+#include "spi_core_3000.hpp"
+#include "adf4001_ctrl.hpp"
+#include <boost/thread/mutex.hpp>
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_core_spi_core : boost::noncopyable, public uhd::spi_iface {
+
+public:
+ typedef boost::shared_ptr<n230_core_spi_core> sptr;
+
+ enum perif_t {
+ CODEC, PLL
+ };
+
+ n230_core_spi_core(uhd::wb_iface::sptr iface, perif_t default_perif);
+
+ virtual boost::uint32_t transact_spi(
+ int which_slave,
+ const spi_config_t &config,
+ boost::uint32_t data,
+ size_t num_bits,
+ bool readback);
+
+ void change_perif(perif_t perif);
+ void restore_perif();
+
+ static sptr make(uhd::wb_iface::sptr iface, perif_t default_perif = CODEC);
+
+private:
+ spi_core_3000::sptr _spi_core;
+ perif_t _current_perif;
+ perif_t _last_perif;
+ boost::mutex _mutex;
+};
+
+class n230_ref_pll_ctrl : public adf4001_ctrl {
+public:
+ typedef boost::shared_ptr<n230_ref_pll_ctrl> sptr;
+
+ n230_ref_pll_ctrl(n230_core_spi_core::sptr spi);
+ void set_lock_to_ext_ref(bool external);
+
+private:
+ n230_core_spi_core::sptr _spi;
+};
+
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_CORES_HPP */
diff --git a/host/lib/usrp/n230/n230_defaults.h b/host/lib/usrp/n230/n230_defaults.h
new file mode 100644
index 000000000..a25978585
--- /dev/null
+++ b/host/lib/usrp/n230/n230_defaults.h
@@ -0,0 +1,65 @@
+//
+// 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_N230_DEFAULTS_H
+#define INCLUDED_N230_DEFAULTS_H
+
+#include <stdint.h>
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+#include <uhd/transport/udp_constants.hpp>
+
+namespace uhd {
+namespace usrp {
+namespace n230 {
+
+static const double DEFAULT_TICK_RATE = 46.08e6;
+static const double MAX_TICK_RATE = 50e6;
+static const double MIN_TICK_RATE = 1e6;
+
+static const double DEFAULT_TX_SAMP_RATE = 1.0e6;
+static const double DEFAULT_RX_SAMP_RATE = 1.0e6;
+static const double DEFAULT_DDC_FREQ = 0.0;
+static const double DEFAULT_DUC_FREQ = 0.0;
+
+static const double DEFAULT_FE_GAIN = 0.0;
+static const double DEFAULT_FE_FREQ = 1.0e9;
+static const double DEFAULT_FE_BW = 56e6;
+
+static const std::string DEFAULT_TIME_SRC = "none";
+static const std::string DEFAULT_CLOCK_SRC = "internal";
+
+static const size_t DEFAULT_FRAME_SIZE = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header
+static const size_t MAX_FRAME_SIZE = 8000;
+static const size_t MIN_FRAME_SIZE = IP_PROTOCOL_MIN_MTU_SIZE;
+
+static const size_t DEFAULT_NUM_FRAMES = 32;
+
+//A 1MiB SRAM is shared between two radios so we allocate each
+//radio 0.5MiB minus 8 packets worth of buffering to ensure
+//that the FIFO does not overflow
+static const size_t DEFAULT_SEND_BUFF_SIZE = 500*1024;
+#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
+static const size_t DEFAULT_RECV_BUFF_SIZE = 0x100000; //1Mib
+#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32)
+static const size_t DEFAULT_RECV_BUFF_SIZE = 0x2000000;//32MiB
+#endif
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_DEFAULTS_H */
diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp
new file mode 100644
index 000000000..014a6cd14
--- /dev/null
+++ b/host/lib/usrp/n230/n230_device_args.hpp
@@ -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/>.
+//
+
+#ifndef INCLUDED_N230_DEV_ARGS_HPP
+#define INCLUDED_N230_DEV_ARGS_HPP
+
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/transport/udp_simple.hpp>
+#include <boost/thread/mutex.hpp>
+#include "../common/constrained_device_args.hpp"
+#include "n230_defaults.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_device_args_t : public constrained_device_args_t
+{
+public:
+ enum loopback_mode_t { LOOPBACK_OFF=0, LOOPBACK_RADIO=1, LOOPBACK_CODEC=2 };
+
+ n230_device_args_t():
+ _master_clock_rate("master_clock_rate", n230::DEFAULT_TICK_RATE),
+ _send_frame_size("send_frame_size", n230::DEFAULT_FRAME_SIZE),
+ _recv_frame_size("recv_frame_size", n230::DEFAULT_FRAME_SIZE),
+ _num_send_frames("num_send_frames", n230::DEFAULT_NUM_FRAMES),
+ _num_recv_frames("num_recv_frames", n230::DEFAULT_NUM_FRAMES),
+ _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE),
+ _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE),
+ _safe_mode("safe_mode", false),
+ _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec"))
+ {}
+
+ double get_master_clock_rate() const {
+ return _master_clock_rate.get();
+ }
+ size_t get_send_frame_size() const {
+ return _send_frame_size.get();
+ }
+ size_t get_recv_frame_size() const {
+ return _recv_frame_size.get();
+ }
+ size_t get_num_send_frames() const {
+ return _num_send_frames.get();
+ }
+ size_t get_num_recv_frames() const {
+ return _num_recv_frames.get();
+ }
+ size_t get_send_buff_size() const {
+ return _send_buff_size.get();
+ }
+ size_t get_recv_buff_size() const {
+ return _recv_buff_size.get();
+ }
+ bool get_safe_mode() const {
+ return _safe_mode.get();
+ }
+ loopback_mode_t get_loopback_mode() const {
+ return _loopback_mode.get();
+ }
+
+ inline virtual std::string to_string() const {
+ return _master_clock_rate.to_string() + ", " +
+ _send_frame_size.to_string() + ", " +
+ _recv_frame_size.to_string() + ", " +
+ _num_send_frames.to_string() + ", " +
+ _num_recv_frames.to_string() + ", " +
+ _send_buff_size.to_string() + ", " +
+ _recv_buff_size.to_string() + ", " +
+ _safe_mode.to_string() + ", " +
+ _loopback_mode.to_string();
+ }
+private:
+ virtual void _parse(const device_addr_t& dev_args) {
+ //Extract parameters from dev_args
+ if (dev_args.has_key(_master_clock_rate.key()))
+ _master_clock_rate.parse(dev_args[_master_clock_rate.key()]);
+ if (dev_args.has_key(_send_frame_size.key()))
+ _send_frame_size.parse(dev_args[_send_frame_size.key()]);
+ if (dev_args.has_key(_recv_frame_size.key()))
+ _recv_frame_size.parse(dev_args[_recv_frame_size.key()]);
+ if (dev_args.has_key(_num_send_frames.key()))
+ _num_send_frames.parse(dev_args[_num_send_frames.key()]);
+ if (dev_args.has_key(_num_recv_frames.key()))
+ _num_recv_frames.parse(dev_args[_num_recv_frames.key()]);
+ if (dev_args.has_key(_send_buff_size.key()))
+ _send_buff_size.parse(dev_args[_send_buff_size.key()]);
+ if (dev_args.has_key(_recv_buff_size.key()))
+ _recv_buff_size.parse(dev_args[_recv_buff_size.key()]);
+ if (dev_args.has_key(_safe_mode.key()))
+ _safe_mode.parse(dev_args[_safe_mode.key()]);
+ if (dev_args.has_key(_loopback_mode.key()))
+ _loopback_mode.parse(dev_args[_loopback_mode.key()], false /* assert invalid */);
+
+ //Sanity check params
+ _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE);
+ _enforce_range(_send_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE);
+ _enforce_range(_recv_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE);
+ _enforce_range(_num_send_frames, (size_t)2, (size_t)UINT_MAX);
+ _enforce_range(_num_recv_frames, (size_t)2, (size_t)UINT_MAX);
+ }
+
+ constrained_device_args_t::num_arg<double> _master_clock_rate;
+ constrained_device_args_t::num_arg<size_t> _send_frame_size;
+ constrained_device_args_t::num_arg<size_t> _recv_frame_size;
+ constrained_device_args_t::num_arg<size_t> _num_send_frames;
+ constrained_device_args_t::num_arg<size_t> _num_recv_frames;
+ constrained_device_args_t::num_arg<size_t> _send_buff_size;
+ constrained_device_args_t::num_arg<size_t> _recv_buff_size;
+ constrained_device_args_t::bool_arg _safe_mode;
+ constrained_device_args_t::enum_arg<loopback_mode_t> _loopback_mode;
+};
+
+}}} //namespace
+
+#endif //INCLUDED_N230_DEV_ARGS_HPP
diff --git a/host/lib/usrp/n230/n230_eeprom.h b/host/lib/usrp/n230/n230_eeprom.h
new file mode 100644
index 000000000..b6c2a0c76
--- /dev/null
+++ b/host/lib/usrp/n230/n230_eeprom.h
@@ -0,0 +1,124 @@
+//
+// 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_N230_EEPROM_H
+#define INCLUDED_N230_EEPROM_H
+
+#include <stdint.h>
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define N230_NUM_ETH_PORTS 2
+#define N230_MAX_NUM_ETH_PORTS 2
+
+#if (N230_NUM_ETH_PORTS > N230_MAX_NUM_ETH_PORTS)
+#error
+#endif
+
+#define N230_EEPROM_VER_MAJOR 1
+#define N230_EEPROM_VER_MINOR 1
+#define N230_EEPROM_SERIAL_LEN 9
+#define N230_EEPROM_NAME_LEN 32
+
+typedef struct
+{
+ uint8_t mac_addr[6];
+ uint8_t _pad[2];
+ uint32_t subnet;
+ uint32_t ip_addr;
+} n230_eth_eeprom_map_t;
+
+typedef struct
+{
+ //Data format version
+ uint16_t data_version_major;
+ uint16_t data_version_minor;
+
+ //HW identification info
+ uint16_t hw_revision;
+ uint16_t hw_product;
+ uint8_t serial[N230_EEPROM_SERIAL_LEN];
+ uint8_t _pad_serial;
+ uint16_t hw_revision_compat;
+ uint8_t _pad0[18 - (N230_EEPROM_SERIAL_LEN + 1)];
+
+ //Ethernet specific
+ uint32_t gateway;
+ n230_eth_eeprom_map_t eth_info[N230_MAX_NUM_ETH_PORTS];
+
+ //User specific
+ uint8_t user_name[N230_EEPROM_NAME_LEN];
+} n230_eeprom_map_t;
+
+#ifdef __cplusplus
+} //extern "C"
+#endif
+
+// The following definitions are only useful in firmware. Exclude in host code.
+#ifndef __cplusplus
+
+/*!
+ * Read the eeprom and update caches.
+ * Returns true if read was successful.
+ * If the read was not successful then the cache is initialized with
+ * default values and marked as dirty.
+ */
+bool read_n230_eeprom();
+
+/*!
+ * Write the contents of the cache to the eeprom.
+ * Returns true if write was successful.
+ */
+bool write_n230_eeprom();
+
+/*!
+ * Returns the dirty state of the cache.
+ */
+bool is_n230_eeprom_cache_dirty();
+
+/*!
+ * Returns a const pointer to the EEPROM map.
+ */
+const n230_eeprom_map_t* get_n230_const_eeprom_map();
+
+/*!
+ * Returns the settings for the the 'iface'th ethernet interface
+ */
+const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface);
+
+/*!
+ * Returns a non-const pointer to the EEPROM map. Will mark the cache as dirty.
+ */
+n230_eeprom_map_t* get_n230_eeprom_map();
+
+/*!
+ * FPGA Image operations
+ */
+inline void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes);
+
+inline bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes);
+
+inline bool erase_n230_fpga_image_sector(uint32_t offset);
+
+#endif //ifdef __cplusplus
+
+#endif /* INCLUDED_N230_EEPROM_H */
diff --git a/host/lib/usrp/n230/n230_eeprom_manager.cpp b/host/lib/usrp/n230/n230_eeprom_manager.cpp
new file mode 100644
index 000000000..b19deb23a
--- /dev/null
+++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp
@@ -0,0 +1,207 @@
+//
+// Copyright 2013-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 "n230_eeprom.h"
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/types/mac_addr.hpp>
+#include <boost/format.hpp>
+#include <boost/asio.hpp> //used for htonl and ntohl
+#include "n230_eeprom_manager.hpp"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+const double n230_eeprom_manager::UDP_TIMEOUT_IN_SEC = 2.0;
+
+n230_eeprom_manager::n230_eeprom_manager(const std::string& addr):
+ _seq_num(0)
+{
+ _udp_xport = transport::udp_simple::make_connected(
+ addr, BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT));
+ read_mb_eeprom();
+}
+
+static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len)
+{
+ std::string out;
+ for (size_t i = 0; i < max_len; i++) {
+ if (bytes[i] < 32 or bytes[i] > 127) return out;
+ out += bytes[i];
+ }
+ return out;
+}
+
+static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer)
+{
+ byte_vector_t bytes;
+ const size_t len = std::min(string.size(), max_len);
+ for (size_t i = 0; i < len; i++){
+ buffer[i] = string[i];
+ }
+ if (len < max_len - 1) buffer[len] = '\0';
+}
+
+const mboard_eeprom_t& n230_eeprom_manager::read_mb_eeprom()
+{
+ boost::mutex::scoped_lock lock(_mutex);
+
+ //Read EEPROM from device
+ _transact(N230_FLASH_COMM_CMD_READ_NV_DATA);
+ const n230_eeprom_map_t* map_ptr = reinterpret_cast<const n230_eeprom_map_t*>(_response.data);
+ const n230_eeprom_map_t& map = *map_ptr;
+
+ uint16_t ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major);
+ uint16_t ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor);
+
+ _mb_eeprom["product"] = boost::lexical_cast<std::string>(
+ uhd::htonx<boost::uint16_t>(map.hw_product));
+ _mb_eeprom["revision"] = boost::lexical_cast<std::string>(
+ uhd::htonx<boost::uint16_t>(map.hw_revision));
+ //The revision_compat field does not exist in version 1.0
+ //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set
+ //revision_compat = revision
+ if (ver_major == 1 and ver_minor == 0) {
+ _mb_eeprom["revision_compat"] = _mb_eeprom["revision"];
+ } else {
+ _mb_eeprom["revision_compat"] = boost::lexical_cast<std::string>(
+ uhd::htonx<boost::uint16_t>(map.hw_revision_compat));
+ }
+ _mb_eeprom["serial"] = _bytes_to_string(
+ map.serial, N230_EEPROM_SERIAL_LEN);
+
+ //Extract ethernet info
+ _mb_eeprom["gateway"] = boost::asio::ip::address_v4(
+ uhd::htonx<boost::uint32_t>(map.gateway)).to_string();
+ for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) {
+ const std::string n(1, i+'0');
+ _mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4(
+ uhd::htonx<boost::uint32_t>(map.eth_info[i].ip_addr)).to_string();
+ _mb_eeprom["subnet"+n] = boost::asio::ip::address_v4(
+ uhd::htonx<boost::uint32_t>(map.eth_info[i].subnet)).to_string();
+ byte_vector_t mac_addr(map.eth_info[i].mac_addr, map.eth_info[i].mac_addr + 6);
+ _mb_eeprom["mac-addr"+n] = mac_addr_t::from_bytes(mac_addr).to_string();
+ }
+
+ _mb_eeprom["name"] = _bytes_to_string(
+ map.user_name, N230_EEPROM_NAME_LEN);
+
+ return _mb_eeprom;
+}
+
+void n230_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom)
+{
+ boost::mutex::scoped_lock lock(_mutex);
+
+ _mb_eeprom = eeprom;
+
+ n230_eeprom_map_t* map_ptr = reinterpret_cast<n230_eeprom_map_t*>(_request.data);
+ memset(map_ptr, 0xff, sizeof(n230_eeprom_map_t)); //Initialize to erased state
+ //Read EEPROM from device
+ _transact(N230_FLASH_COMM_CMD_READ_NV_DATA);
+ memcpy(map_ptr, _response.data, sizeof(n230_eeprom_map_t));
+ n230_eeprom_map_t& map = *map_ptr;
+
+ // Automatic version upgrade handling
+ uint16_t old_ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major);
+ uint16_t old_ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor);
+
+ //The revision_compat field does not exist for version 1.0 so force write it
+ //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set
+ //revision_compat = revision for the upgrade
+ bool force_write_version_compat = (old_ver_major == 1 and old_ver_minor == 0);
+
+ map.data_version_major = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MAJOR);
+ map.data_version_minor = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MINOR);
+
+ if (_mb_eeprom.has_key("product")) {
+ map.hw_product = uhd::htonx<boost::uint16_t>(
+ boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"]));
+ }
+ if (_mb_eeprom.has_key("revision")) {
+ map.hw_revision = uhd::htonx<boost::uint16_t>(
+ boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"]));
+ }
+ if (_mb_eeprom.has_key("revision_compat")) {
+ map.hw_revision_compat = uhd::htonx<boost::uint16_t>(
+ boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision_compat"]));
+ } else if (force_write_version_compat) {
+ map.hw_revision_compat = map.hw_revision;
+ }
+ if (_mb_eeprom.has_key("serial")) {
+ _string_to_bytes(_mb_eeprom["serial"], N230_EEPROM_SERIAL_LEN, map.serial);
+ }
+
+ //Push ethernet info
+ if (_mb_eeprom.has_key("gateway")){
+ map.gateway = uhd::htonx<boost::uint32_t>(
+ boost::asio::ip::address_v4::from_string(_mb_eeprom["gateway"]).to_ulong());
+ }
+ for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) {
+ const std::string n(1, i+'0');
+ if (_mb_eeprom.has_key("ip-addr"+n)){
+ map.eth_info[i].ip_addr = uhd::htonx<boost::uint32_t>(
+ boost::asio::ip::address_v4::from_string(_mb_eeprom["ip-addr"+n]).to_ulong());
+ }
+ if (_mb_eeprom.has_key("subnet"+n)){
+ map.eth_info[i].subnet = uhd::htonx<boost::uint32_t>(
+ boost::asio::ip::address_v4::from_string(_mb_eeprom["subnet"+n]).to_ulong());
+ }
+ if (_mb_eeprom.has_key("mac-addr"+n)) {
+ byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"+n]).to_bytes();
+ std::copy(mac_addr.begin(), mac_addr.end(), map.eth_info[i].mac_addr);
+ }
+ }
+ //store the name
+ if (_mb_eeprom.has_key("name")) {
+ _string_to_bytes(_mb_eeprom["name"], N230_EEPROM_NAME_LEN, map.user_name);
+ }
+
+ //Write EEPROM to device
+ _transact(N230_FLASH_COMM_CMD_WRITE_NV_DATA);
+}
+
+void n230_eeprom_manager::_transact(const boost::uint32_t command)
+{
+ //Load request struct
+ _request.flags = uhd::htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK | command);
+ _request.seq = uhd::htonx<boost::uint32_t>(_seq_num++);
+
+ //Send request
+ _flush_xport();
+ _udp_xport->send(boost::asio::buffer(&_request, sizeof(_request)));
+
+ //Recv reply
+ const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&_response, sizeof(_response)), UDP_TIMEOUT_IN_SEC);
+ if (nbytes == 0) throw uhd::io_error("n230_eeprom_manager::_transact failure");
+
+ //Sanity checks
+ const size_t flags = uhd::ntohx<boost::uint32_t>(_response.flags);
+ UHD_ASSERT_THROW(nbytes == sizeof(_response));
+ UHD_ASSERT_THROW(_response.seq == _request.seq);
+ UHD_ASSERT_THROW(flags & command);
+}
+
+void n230_eeprom_manager::_flush_xport()
+{
+ char buff[sizeof(n230_flash_prog_t)] = {};
+ while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) {
+ /*NOP*/
+ }
+}
+
+}}}; //namespace
diff --git a/host/lib/usrp/n230/n230_eeprom_manager.hpp b/host/lib/usrp/n230/n230_eeprom_manager.hpp
new file mode 100644
index 000000000..cc5aee9f3
--- /dev/null
+++ b/host/lib/usrp/n230/n230_eeprom_manager.hpp
@@ -0,0 +1,58 @@
+//
+// Copyright 2013-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_N230_EEPROM_MANAGER_HPP
+#define INCLUDED_N230_EEPROM_MANAGER_HPP
+
+#include <boost/thread/mutex.hpp>
+#include <uhd/transport/udp_simple.hpp>
+#include <uhd/types/dict.hpp>
+#include <uhd/usrp/mboard_eeprom.hpp>
+#include "n230_fw_host_iface.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_eeprom_manager : boost::noncopyable
+{
+public:
+ n230_eeprom_manager(const std::string& addr);
+
+ const mboard_eeprom_t& read_mb_eeprom();
+ void write_mb_eeprom(const mboard_eeprom_t& eeprom);
+
+ inline const mboard_eeprom_t& get_mb_eeprom() {
+ return _mb_eeprom;
+ }
+
+private: //Functions
+ void _transact(const boost::uint32_t command);
+ void _flush_xport();
+
+private: //Members
+ mboard_eeprom_t _mb_eeprom;
+ transport::udp_simple::sptr _udp_xport;
+ n230_flash_prog_t _request;
+ n230_flash_prog_t _response;
+ boost::uint32_t _seq_num;
+ boost::mutex _mutex;
+
+ static const double UDP_TIMEOUT_IN_SEC;
+};
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_EEPROM_MANAGER_HPP */
diff --git a/host/lib/usrp/n230/n230_fpga_defs.h b/host/lib/usrp/n230/n230_fpga_defs.h
new file mode 100644
index 000000000..3aa96643f
--- /dev/null
+++ b/host/lib/usrp/n230/n230_fpga_defs.h
@@ -0,0 +1,207 @@
+//
+// 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_N230_FPGA_DEFS_H
+#define INCLUDED_N230_FPGA_DEFS_H
+
+#include <stdint.h>
+#ifndef __cplusplus
+#include <stdbool.h>
+#endif
+#include <uhd/utils/soft_register.hpp>
+
+namespace uhd {
+namespace usrp {
+namespace n230 {
+namespace fpga {
+
+static inline uint32_t sr_addr(uint32_t offset) {
+ return (offset*4);
+}
+
+static inline uint32_t rb_addr(uint32_t offset) {
+ return (offset*8);
+}
+
+static const size_t NUM_RADIOS = 2;
+static const double BUS_CLK_RATE = 80e6;
+
+/*******************************************************************
+ * CVITA Routing
+ *******************************************************************/
+static const uint32_t CVITA_UDP_PORT = 49153;
+static const bool CVITA_BIG_ENDIAN = true;
+
+enum xb_endpoint_t {
+ N230_XB_DST_E0 = 0,
+ N230_XB_DST_E1 = 1,
+ N230_XB_DST_R0 = 2,
+ N230_XB_DST_R1 = 3,
+ N230_XB_DST_GCTRL = 4,
+ N230_XB_DST_UART = 5
+};
+
+static const boost::uint8_t RADIO_CTRL_SUFFIX = 0x00;
+static const boost::uint8_t RADIO_FC_SUFFIX = 0x01;
+static const boost::uint8_t RADIO_DATA_SUFFIX = 0x02;
+
+/*******************************************************************
+ * Seting Register Base addresses
+ *******************************************************************/
+static const uint32_t SR_CORE_RADIO_CONTROL = 3;
+static const uint32_t SR_CORE_LOOPBACK = 4;
+static const uint32_t SR_CORE_BIST1 = 5;
+static const uint32_t SR_CORE_BIST2 = 6;
+static const uint32_t SR_CORE_SPI = 8;
+static const uint32_t SR_CORE_MISC = 16;
+static const uint32_t SR_CORE_DATA_DELAY = 17;
+static const uint32_t SR_CORE_CLK_DELAY = 18;
+static const uint32_t SR_CORE_COMPAT = 24;
+static const uint32_t SR_CORE_READBACK = 32;
+static const uint32_t SR_CORE_GPSDO_ST = 40;
+static const uint32_t SR_CORE_PPS_SEL = 48;
+static const uint32_t SR_CORE_MS0_GPIO = 50;
+static const uint32_t SR_CORE_MS1_GPIO = 58;
+
+static const uint32_t RB_CORE_SIGNATUE = 0;
+static const uint32_t RB_CORE_SPI = 1;
+static const uint32_t RB_CORE_STATUS = 2;
+static const uint32_t RB_CORE_BIST = 3;
+static const uint32_t RB_CORE_VERSION_HASH = 4;
+static const uint32_t RB_CORE_MS0_GPIO = 5;
+static const uint32_t RB_CORE_MS1_GPIO = 6;
+
+/*******************************************************************
+ * Seting Register Base addresses
+ *******************************************************************/
+static const uint32_t SR_RADIO_SPI = 8;
+static const uint32_t SR_RADIO_ATR = 12;
+static const uint32_t SR_RADIO_SW_RST = 20;
+static const uint32_t SR_RADIO_TEST = 21;
+static const uint32_t SR_RADIO_CODEC_IDLE = 22;
+static const uint32_t SR_RADIO_READBACK = 32;
+static const uint32_t SR_RADIO_TX_CTRL = 64;
+static const uint32_t SR_RADIO_RX_CTRL = 96;
+static const uint32_t SR_RADIO_RX_DSP = 144;
+static const uint32_t SR_RADIO_TX_DSP = 184;
+static const uint32_t SR_RADIO_TIME = 128;
+static const uint32_t SR_RADIO_RX_FMT = 136;
+static const uint32_t SR_RADIO_TX_FMT = 138;
+static const uint32_t SR_RADIO_USER_SR = 253;
+
+static const uint32_t RB_RADIO_TEST = 0;
+static const uint32_t RB_RADIO_TIME_NOW = 1;
+static const uint32_t RB_RADIO_TIME_PPS = 2;
+static const uint32_t RB_RADIO_CODEC_DATA = 3;
+static const uint32_t RB_RADIO_DEBUG = 4;
+static const uint32_t RB_RADIO_FRAMER = 5;
+static const uint32_t SR_RADIO_USER_RB = 7;
+
+static const uint32_t AD9361_SPI_SLAVE_NUM = 0x1;
+static const uint32_t ADF4001_SPI_SLAVE_NUM = 0x2;
+
+static const uint32_t RB_N230_PRODUCT_ID = 1;
+static const uint32_t RB_N230_COMPAT_MAJOR = 0x20;
+static const uint32_t RB_N230_COMPAT_SAFE = 0xC0;
+
+/*******************************************************************
+ * Codec Interface Specific
+ *******************************************************************/
+
+// Matches delay setting of 0x00 in AD9361 register 0x006
+static const uint32_t CODEC_DATA_DELAY = 0;
+static const uint32_t CODEC_CLK_DELAY = 16;
+
+//This number must be < 46.08MHz to make sure we don't
+//violate timing for radio_clk. It is only used during
+//initialization so the exact value does not matter.
+static const double CODEC_DEFAULT_CLK_RATE = 40e6;
+
+/*******************************************************************
+ * Link Specific
+ *******************************************************************/
+static const double N230_LINK_RATE_BPS = 1e9/8;
+
+/*******************************************************************
+ * GPSDO
+ *******************************************************************/
+static const uint32_t GPSDO_UART_BAUDRATE = 115200;
+static const uint32_t GPSDO_ST_ABSENT = 0x83;
+/*******************************************************************
+ * Register Objects
+ *******************************************************************/
+class core_radio_ctrl_reg_t : public soft_reg32_wo_t {
+public:
+ UHD_DEFINE_SOFT_REG_FIELD(MIMO, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(CODEC_ARST, /*width*/ 1, /*shift*/ 1); //[1]
+
+ core_radio_ctrl_reg_t():
+ soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_RADIO_CONTROL))
+ {
+ //Initial values
+ set(CODEC_ARST, 0);
+ set(MIMO, 1); //MIMO always ON for now
+ }
+};
+
+class core_misc_reg_t : public soft_reg32_wo_t {
+public:
+ UHD_DEFINE_SOFT_REG_FIELD(REF_SEL, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_C, /*width*/ 1, /*shift*/ 1); //[1]
+ UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_B, /*width*/ 1, /*shift*/ 2); //[2]
+ UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_A, /*width*/ 1, /*shift*/ 3); //[3]
+ UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_B, /*width*/ 1, /*shift*/ 4); //[4]
+ UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_A, /*width*/ 1, /*shift*/ 5); //[5]
+
+ core_misc_reg_t():
+ soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_MISC))
+ {
+ //Initial values
+ set(REF_SEL, 0);
+ set(RX_BANDSEL_C, 0);
+ set(RX_BANDSEL_B, 0);
+ set(RX_BANDSEL_A, 0);
+ set(TX_BANDSEL_B, 0);
+ set(TX_BANDSEL_A, 0);
+ }
+};
+
+class core_pps_sel_reg_t : public soft_reg32_wo_t {
+public:
+ UHD_DEFINE_SOFT_REG_FIELD(EXT_PPS_EN, /*width*/ 1, /*shift*/ 0); //[0]
+
+ core_pps_sel_reg_t():
+ soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_PPS_SEL))
+ {
+ //Initial values
+ set(EXT_PPS_EN, 0);
+ }
+};
+
+class core_status_reg_t : public soft_reg64_ro_t {
+public:
+ UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED, /*width*/ 1, /*shift*/ 0); //[0]
+ UHD_DEFINE_SOFT_REG_FIELD(GPSDO_STATUS, /*width*/ 8, /*shift*/ 32); //[32:39]
+
+ core_status_reg_t():
+ soft_reg64_ro_t(fpga::rb_addr(fpga::RB_CORE_STATUS))
+ { }
+};
+
+}}}} //namespace
+
+#endif /* INCLUDED_N230_FPGA_DEFS_H */
diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.cpp b/host/lib/usrp/n230/n230_frontend_ctrl.cpp
new file mode 100644
index 000000000..e0820d9b2
--- /dev/null
+++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp
@@ -0,0 +1,243 @@
+//
+// Copyright 2013-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 "n230_frontend_ctrl.hpp"
+
+#include <uhd/utils/msg.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/types/dict.hpp>
+#include <boost/format.hpp>
+#include <boost/thread.hpp>
+#include "n230_fpga_defs.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+/* ATR Control Bits */
+static const boost::uint32_t TX_ENABLE = (1 << 7);
+static const boost::uint32_t SFDX_RX = (1 << 6);
+static const boost::uint32_t SFDX_TX = (1 << 5);
+static const boost::uint32_t SRX_RX = (1 << 4);
+static const boost::uint32_t SRX_TX = (1 << 3);
+static const boost::uint32_t LED_RX = (1 << 2);
+static const boost::uint32_t LED_TXRX_RX = (1 << 1);
+static const boost::uint32_t LED_TXRX_TX = (1 << 0);
+
+/* ATR State Definitions. */
+static const boost::uint32_t STATE_OFF = 0x00;
+static const boost::uint32_t STATE_RX_RX2 = (SFDX_RX
+ | SFDX_TX
+ | LED_RX);
+static const boost::uint32_t STATE_RX_TXRX = (SRX_RX
+ | SRX_TX
+ | LED_TXRX_RX);
+static const boost::uint32_t STATE_FDX_TXRX = (TX_ENABLE
+ | SFDX_RX
+ | SFDX_TX
+ | LED_TXRX_TX
+ | LED_RX);
+static const boost::uint32_t STATE_TX_TXRX = (TX_ENABLE
+ | SFDX_RX
+ | SFDX_TX
+ | LED_TXRX_TX);
+
+using namespace uhd::usrp;
+
+class n230_frontend_ctrl_impl : public n230_frontend_ctrl
+{
+public:
+ n230_frontend_ctrl_impl(
+ radio_ctrl_core_3000::sptr core_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ ad9361_ctrl::sptr codec_ctrl,
+ const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores
+ ): _core_ctrl(core_ctrl),
+ _codec_ctrl(codec_ctrl),
+ _gpio_cores(gpio_cores),
+ _core_misc_reg(core_misc_reg)
+ {
+ }
+
+ virtual ~n230_frontend_ctrl_impl()
+ {
+ }
+
+ void set_antenna_sel(const size_t which, const std::string &ant)
+ {
+ if (ant != "TX/RX" and ant != "RX2")
+ throw uhd::value_error("n230: unknown RX antenna option: " + ant);
+
+ _fe_states[which].rx_ant = ant;
+ _flush_atr_state();
+ }
+
+ void set_stream_state(const fe_state_t fe0_state_, const fe_state_t fe1_state_)
+ {
+ //Update soft-state
+ _fe_states[0].state = fe0_state_;
+ _fe_states[1].state = fe1_state_;
+
+ const fe_state_t fe0_state = _fe_states[0].state;
+ const fe_state_t fe1_state = (_gpio_cores.size() > 1) ? _fe_states[1].state : NONE_STREAMING;
+
+ const size_t num_tx = (_is_tx(fe0_state) ? 1 : 0) + (_is_tx(fe1_state) ? 1 : 0);
+ const size_t num_rx = (_is_rx(fe0_state) ? 1 : 0) + (_is_rx(fe1_state) ? 1 : 0);
+
+ //setup the active chains in the codec
+ if ((num_rx + num_tx) == 0) {
+ _codec_ctrl->set_active_chains(
+ true, false,
+ true, false); //enable something
+ } else {
+ _codec_ctrl->set_active_chains(
+ _is_tx(fe0_state), _is_tx(fe1_state),
+ _is_rx(fe0_state), _is_rx(fe1_state));
+ }
+
+ _core_misc_reg.flush();
+ //atrs change based on enables
+ _flush_atr_state();
+ }
+
+
+ void set_stream_state(const size_t which, const fe_state_t state)
+ {
+ if (which == 0) {
+ set_stream_state(state, _fe_states[1].state);
+ } else if (which == 1) {
+ set_stream_state(_fe_states[0].state, state);
+ } else {
+ throw uhd::value_error(
+ str(boost::format("n230: unknown stream index option: %d") % which)
+ );
+ }
+ }
+
+ void set_bandsel(const std::string& which, double freq)
+ {
+ using namespace n230::fpga;
+
+ if(which[0] == 'R') {
+ if(freq < 2.2e9) {
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 1);
+ } else if((freq >= 2.2e9) && (freq < 4e9)) {
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 1);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0);
+ } else if((freq >= 4e9) && (freq <= 6e9)) {
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 1);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0);
+ _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0);
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ } else if(which[0] == 'T') {
+ if(freq < 2.5e9) {
+ _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 0);
+ _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 1);
+ } else if((freq >= 2.5e9) && (freq <= 6e9)) {
+ _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 1);
+ _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 0);
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+ } else {
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ _core_misc_reg.flush();
+ }
+
+ void set_self_test_mode(self_test_mode_t mode)
+ {
+ switch (mode) {
+ case LOOPBACK_RADIO: {
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x1);
+ } break;
+ case LOOPBACK_CODEC: {
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0);
+ _codec_ctrl->data_port_loopback(true);
+ } break;
+ //Default = disable
+ default:
+ case LOOPBACK_DISABLED: {
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0);
+ _codec_ctrl->data_port_loopback(false);
+ } break;
+ }
+ }
+
+private:
+ void _flush_atr_state()
+ {
+ for (size_t i = 0; i < _gpio_cores.size(); i++) {
+ const fe_state_cache_t& fe_state_cache = _fe_states[i];
+ const bool enb_rx = _is_rx(fe_state_cache.state);
+ const bool enb_tx = _is_tx(fe_state_cache.state);
+ const bool is_rx2 = (fe_state_cache.rx_ant == "RX2");
+ const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX_RX2 : STATE_RX_TXRX) : STATE_OFF;
+ const size_t txonly = (enb_tx)? (STATE_TX_TXRX) : STATE_OFF;
+ size_t fd = STATE_OFF;
+ if (enb_rx and enb_tx) fd = STATE_FDX_TXRX;
+ if (enb_rx and not enb_tx) fd = rxonly;
+ if (not enb_rx and enb_tx) fd = txonly;
+ _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_IDLE, STATE_OFF);
+ _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, rxonly);
+ _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, txonly);
+ _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, fd);
+ }
+ }
+
+ inline static bool _is_tx(const fe_state_t state)
+ {
+ return state == TX_STREAMING || state == TXRX_STREAMING;
+ }
+
+ inline static bool _is_rx(const fe_state_t state)
+ {
+ return state == RX_STREAMING || state == TXRX_STREAMING;
+ }
+
+private:
+ struct fe_state_cache_t {
+ fe_state_cache_t() : state(NONE_STREAMING), rx_ant("RX2")
+ {}
+ fe_state_t state;
+ std::string rx_ant;
+ };
+
+ radio_ctrl_core_3000::sptr _core_ctrl;
+ ad9361_ctrl::sptr _codec_ctrl;
+ std::vector<gpio_atr::gpio_atr_3000::sptr> _gpio_cores;
+ fpga::core_misc_reg_t& _core_misc_reg;
+ uhd::dict<size_t, fe_state_cache_t> _fe_states;
+};
+
+}}} //namespace
+
+using namespace uhd::usrp::n230;
+
+n230_frontend_ctrl::sptr n230_frontend_ctrl::make(
+ radio_ctrl_core_3000::sptr core_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ ad9361_ctrl::sptr codec_ctrl,
+ const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores)
+{
+ return sptr(new n230_frontend_ctrl_impl(core_ctrl, core_misc_reg, codec_ctrl, gpio_cores));
+}
+
diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.hpp b/host/lib/usrp/n230/n230_frontend_ctrl.hpp
new file mode 100644
index 000000000..377d23ba8
--- /dev/null
+++ b/host/lib/usrp/n230/n230_frontend_ctrl.hpp
@@ -0,0 +1,76 @@
+//
+// Copyright 2013-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_N230_FRONTEND_CTRL_HPP
+#define INCLUDED_N230_FRONTEND_CTRL_HPP
+
+#include "radio_ctrl_core_3000.hpp"
+#include "ad9361_ctrl.hpp"
+#include "gpio_atr_3000.hpp"
+#include <uhd/types/sensors.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+#include <vector>
+#include "n230_fpga_defs.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+enum fe_state_t {
+ NONE_STREAMING, TX_STREAMING, RX_STREAMING, TXRX_STREAMING
+};
+
+enum self_test_mode_t {
+ LOOPBACK_DISABLED, LOOPBACK_RADIO, LOOPBACK_CODEC
+};
+
+
+class n230_frontend_ctrl : boost::noncopyable
+{
+public:
+ typedef boost::shared_ptr<n230_frontend_ctrl> sptr;
+
+ static sptr make(
+ radio_ctrl_core_3000::sptr core_ctrl,
+ fpga::core_misc_reg_t& core_misc_reg,
+ ad9361_ctrl::sptr codec_ctrl,
+ const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores);
+
+ virtual ~n230_frontend_ctrl() {}
+
+ virtual void set_antenna_sel(
+ const size_t which,
+ const std::string &ant) = 0;
+
+ virtual void set_stream_state(
+ const size_t which,
+ const fe_state_t state) = 0;
+
+ virtual void set_stream_state(
+ const fe_state_t fe0_state,
+ const fe_state_t fe1_state) = 0;
+
+ virtual void set_bandsel(
+ const std::string& which,
+ double freq) = 0;
+
+ virtual void set_self_test_mode(
+ self_test_mode_t mode) = 0;
+};
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_FRONTEND_CTRL_HPP */
diff --git a/host/lib/usrp/n230/n230_fw_defs.h b/host/lib/usrp/n230/n230_fw_defs.h
new file mode 100644
index 000000000..fbdc67ebb
--- /dev/null
+++ b/host/lib/usrp/n230/n230_fw_defs.h
@@ -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_N230_FW_DEFS_H
+#define INCLUDED_N230_FW_DEFS_H
+
+#include <stdint.h>
+
+/*!
+ * Constants specific to N230 firmware.
+ * This header is shared by the firmware and host code.
+ * Therefore, this header may only contain valid C code.
+ * However, if it is included from within the host code,
+ * it will be namespaced appropriately
+ */
+#ifdef __cplusplus
+namespace uhd {
+namespace usrp {
+namespace n230 {
+namespace fw {
+#endif
+
+static inline uint32_t reg_addr(uint32_t base, uint32_t offset) {
+ return ((base) + (offset)*4);
+}
+
+/*******************************************************************
+ * Global
+ *******************************************************************/
+static const uint32_t CPU_CLOCK_FREQ = 80000000;
+static const uint32_t PER_MILLISEC_CRON_JOBID = 0;
+static const uint32_t PER_SECOND_CRON_JOBID = 1;
+
+/*******************************************************************
+ * Wishbone slave addresses
+ *******************************************************************/
+static const uint32_t WB_MAIN_RAM_BASE = 0x0000;
+static const uint32_t WB_PKT_RAM_BASE = 0x8000;
+static const uint32_t WB_SBRB_BASE = 0xa000;
+static const uint32_t WB_SPI_FLASH_BASE = 0xb000;
+static const uint32_t WB_ETH0_MAC_BASE = 0xc000;
+static const uint32_t WB_ETH1_MAC_BASE = 0xd000;
+static const uint32_t WB_XB_SBRB_BASE = 0xe000;
+static const uint32_t WB_ETH0_I2C_BASE = 0xf600;
+static const uint32_t WB_ETH1_I2C_BASE = 0xf700;
+static const uint32_t WB_DBG_UART_BASE = 0xf900;
+
+/*******************************************************************
+ * Seting Register Base addresses
+ *******************************************************************/
+static const uint32_t SR_ZPU_SW_RST = 0;
+static const uint32_t SR_ZPU_BOOT_DONE = 1;
+static const uint32_t SR_ZPU_LEDS = 2;
+static const uint32_t SR_ZPU_XB_LOCAL = 4;
+static const uint32_t SR_ZPU_SFP_CTRL0 = 16;
+static const uint32_t SR_ZPU_SFP_CTRL1 = 17;
+static const uint32_t SR_ZPU_ETHINT0 = 64;
+static const uint32_t SR_ZPU_ETHINT1 = 80;
+
+static const uint32_t SR_ZPU_SW_RST_NONE = 0x0;
+static const uint32_t SR_ZPU_SW_RST_PHY = 0x1;
+static const uint32_t SR_ZPU_SW_RST_RADIO = 0x2;
+
+/*******************************************************************
+ * Readback addresses
+ *******************************************************************/
+static const uint32_t RB_ZPU_COMPAT = 0;
+static const uint32_t RB_ZPU_COUNTER = 1;
+static const uint32_t RB_ZPU_SFP_STATUS0 = 2;
+static const uint32_t RB_ZPU_SFP_STATUS1 = 3;
+static const uint32_t RB_ZPU_ETH0_PKT_CNT = 6;
+static const uint32_t RB_ZPU_ETH1_PKT_CNT = 7;
+
+/*******************************************************************
+ * Ethernet
+ *******************************************************************/
+static const uint32_t WB_PKT_RAM_CTRL_OFFSET = 0x1FFC;
+
+static const uint32_t SR_ZPU_ETHINT_FRAMER_BASE = 0;
+static const uint32_t SR_ZPU_ETHINT_DISPATCHER_BASE = 8;
+
+//Eth framer constants
+static const uint32_t ETH_FRAMER_SRC_MAC_HI = 0;
+static const uint32_t ETH_FRAMER_SRC_MAC_LO = 1;
+static const uint32_t ETH_FRAMER_SRC_IP_ADDR = 2;
+static const uint32_t ETH_FRAMER_SRC_UDP_PORT = 3;
+static const uint32_t ETH_FRAMER_DST_RAM_ADDR = 4;
+static const uint32_t ETH_FRAMER_DST_IP_ADDR = 5;
+static const uint32_t ETH_FRAMER_DST_UDP_MAC = 6;
+static const uint32_t ETH_FRAMER_DST_MAC_LO = 7;
+
+/*******************************************************************
+ * CODEC
+ *******************************************************************/
+static const uint32_t CODEC_SPI_CLOCK_FREQ = 4000000; //4MHz
+static const uint32_t ADF4001_SPI_CLOCK_FREQ = 200000; //200kHz
+
+/*******************************************************************
+ * UART
+ *******************************************************************/
+static const uint32_t DBG_UART_BAUD = 115200;
+
+/*******************************************************************
+ * Build Compatability Numbers
+ *******************************************************************/
+static const uint8_t PRODUCT_NUM = 0x01;
+static const uint8_t COMPAT_MAJOR = 0x00;
+static const uint16_t COMPAT_MINOR = 0x0000;
+
+static inline uint8_t get_prod_num(uint32_t compat_reg) {
+ return (compat_reg >> 24) & 0xFF;
+}
+static inline uint8_t get_compat_major(uint32_t compat_reg) {
+ return (compat_reg >> 16) & 0xFF;
+}
+static inline uint8_t get_compat_minor(uint32_t compat_reg) {
+ return compat_reg & 0xFFFF;
+}
+
+#ifdef __cplusplus
+}}}} //namespace
+#endif
+#endif /* INCLUDED_N230_FW_DEFS_H */
diff --git a/host/lib/usrp/n230/n230_fw_host_iface.h b/host/lib/usrp/n230/n230_fw_host_iface.h
new file mode 100644
index 000000000..0391af0d9
--- /dev/null
+++ b/host/lib/usrp/n230/n230_fw_host_iface.h
@@ -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/>.
+//
+
+#ifndef INCLUDED_N230_FW_HOST_IFACE_H
+#define INCLUDED_N230_FW_HOST_IFACE_H
+
+#include <stdint.h>
+
+/*!
+ * Structs and constants for N230 communication between firmware and host.
+ * This header is shared by the firmware and host code.
+ * Therefore, this header may only contain valid C code.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------
+// Ethernet related
+//
+#define N230_DEFAULT_ETH0_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff}
+#define N230_DEFAULT_ETH1_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33}
+#define N230_DEFAULT_ETH0_IP (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0)
+#define N230_DEFAULT_ETH1_IP (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0)
+#define N230_DEFAULT_ETH0_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0)
+#define N230_DEFAULT_ETH1_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0)
+#define N230_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0)
+
+#define N230_FW_COMMS_UDP_PORT 49152
+#define N230_FW_COMMS_CVITA_PORT 49153
+#define N230_FW_COMMS_FLASH_PROG_PORT 49154
+//
+//--------------------------------------------------
+
+//--------------------------------------------------
+// Memory shared with host
+//
+#define N230_FW_HOST_SHMEM_BASE_ADDR 0x10000
+#define N230_FW_HOST_SHMEM_RW_BASE_ADDR 0x1000C
+#define N230_FW_HOST_SHMEM_NUM_WORDS (sizeof(n230_host_shared_mem_data_t)/sizeof(uint32_t))
+
+#define N230_FW_HOST_SHMEM_MAX_ADDR \
+ (N230_FW_HOST_SHMEM_BASE_ADDR + ((N230_FW_HOST_SHMEM_NUM_WORDS - 1) * sizeof(uint32_t)))
+
+#define N230_FW_HOST_SHMEM_OFFSET(member) \
+ (N230_FW_HOST_SHMEM_BASE_ADDR + ((uint32_t)offsetof(n230_host_shared_mem_data_t, member)))
+
+//The shared memory block can only be accessed on 32-bit boundaries
+typedef struct { //All fields must be 32-bit wide to avoid packing directives
+ //Read-Only fields (N230_FW_HOST_SHMEM_BASE_ADDR)
+ uint32_t fw_compat_num; //Compat number must be at offset 0
+ uint32_t fw_version_hash;
+ uint32_t claim_status;
+
+ //Read-Write fields (N230_FW_HOST_SHMEM_RW_BASE_ADDR)
+ uint32_t scratch;
+ uint32_t claim_time;
+ uint32_t claim_src;
+} n230_host_shared_mem_data_t;
+
+typedef union
+{
+ uint32_t buff[N230_FW_HOST_SHMEM_NUM_WORDS];
+ n230_host_shared_mem_data_t data;
+} n230_host_shared_mem_t;
+
+#define N230_FW_PRODUCT_ID 1
+#define N230_FW_COMPAT_NUM_MAJOR 32
+#define N230_FW_COMPAT_NUM_MINOR 0
+#define N230_FW_COMPAT_NUM (((N230_FW_COMPAT_NUM_MAJOR & 0xFF) << 16) | (N230_FW_COMPAT_NUM_MINOR & 0xFFFF))
+//
+//--------------------------------------------------
+
+//--------------------------------------------------
+// Flash read-write interface for host
+//
+#define N230_FLASH_COMM_FLAGS_ACK 0x00000001
+#define N230_FLASH_COMM_FLAGS_CMD_MASK 0x00000FF0
+#define N230_FLASH_COMM_FLAGS_ERROR_MASK 0xFF000000
+
+#define N230_FLASH_COMM_CMD_READ_NV_DATA 0x00000010
+#define N230_FLASH_COMM_CMD_WRITE_NV_DATA 0x00000020
+#define N230_FLASH_COMM_CMD_READ_FPGA 0x00000030
+#define N230_FLASH_COMM_CMD_WRITE_FPGA 0x00000040
+#define N230_FLASH_COMM_CMD_ERASE_FPGA 0x00000050
+
+#define N230_FLASH_COMM_ERR_PKT_ERROR 0x80000000
+#define N230_FLASH_COMM_ERR_CMD_ERROR 0x40000000
+#define N230_FLASH_COMM_ERR_SIZE_ERROR 0x20000000
+
+#define N230_FLASH_COMM_MAX_PAYLOAD_SIZE 128
+
+typedef struct
+{
+ uint32_t flags;
+ uint32_t seq;
+ uint32_t offset;
+ uint32_t size;
+ uint8_t data[N230_FLASH_COMM_MAX_PAYLOAD_SIZE];
+} n230_flash_prog_t;
+//
+//--------------------------------------------------
+
+#define N230_HW_REVISION_COMPAT 1
+#define N230_HW_REVISION_MIN 1
+
+
+#define N230_CLAIMER_TIMEOUT_IN_MS 2000
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* INCLUDED_N230_FW_HOST_IFACE_H */
diff --git a/host/lib/usrp/n230/n230_image_loader.cpp b/host/lib/usrp/n230/n230_image_loader.cpp
new file mode 100644
index 000000000..9dd4a252d
--- /dev/null
+++ b/host/lib/usrp/n230/n230_image_loader.cpp
@@ -0,0 +1,209 @@
+//
+// 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 <fstream>
+#include <algorithm>
+#include <uhd/image_loader.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/utils/static.hpp>
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/paths.hpp>
+#include <uhd/transport/udp_simple.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/format.hpp>
+#include "n230_fw_host_iface.h"
+#include "n230_impl.hpp"
+
+using namespace uhd;
+using namespace uhd::usrp;
+using namespace uhd::transport;
+
+struct xil_bitfile_hdr_t {
+ xil_bitfile_hdr_t():
+ valid(false), userid(0), product(""),
+ fpga(""), timestamp(""), filesize(0)
+ {}
+
+ bool valid;
+ boost::uint32_t userid;
+ std::string product;
+ std::string fpga;
+ std::string timestamp;
+ boost::uint32_t filesize;
+};
+
+static inline boost::uint16_t _to_uint16(boost::uint8_t* buf) {
+ return (static_cast<boost::uint16_t>(buf[0]) << 8) |
+ (static_cast<boost::uint16_t>(buf[1]) << 0);
+}
+
+static inline boost::uint32_t _to_uint32(boost::uint8_t* buf) {
+ return (static_cast<boost::uint32_t>(buf[0]) << 24) |
+ (static_cast<boost::uint32_t>(buf[1]) << 16) |
+ (static_cast<boost::uint32_t>(buf[2]) << 8) |
+ (static_cast<boost::uint32_t>(buf[3]) << 0);
+}
+
+static void _parse_bitfile_header(const std::string& filepath, xil_bitfile_hdr_t& hdr) {
+ // Read header into memory
+ std::ifstream img_file(filepath.c_str(), std::ios::binary);
+ static const size_t MAX_HDR_SIZE = 1024;
+ boost::scoped_array<char> hdr_buf(new char[MAX_HDR_SIZE]);
+ img_file.seekg(0, std::ios::beg);
+ img_file.read(hdr_buf.get(), MAX_HDR_SIZE);
+ img_file.close();
+
+ //Parse header
+ size_t ptr = 0;
+ boost::uint8_t* buf = reinterpret_cast<boost::uint8_t*>(hdr_buf.get()); //Shortcut
+
+ boost::uint8_t signature[10] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0};
+ if (memcmp(buf, signature, 10) == 0) { //Validate signature
+ ptr += _to_uint16(buf + ptr) + 2;
+ ptr += _to_uint16(buf + ptr) + 1;
+
+ std::string fields[4];
+ for (size_t i = 0; i < 4; i++) {
+ size_t key = buf[ptr++] - 'a';
+ boost::uint16_t len = _to_uint16(buf + ptr); ptr += 2;
+ fields[key] = std::string(reinterpret_cast<char*>(buf + ptr), size_t(len)); ptr += len;
+ }
+
+ hdr.filesize = _to_uint32(buf + ++ptr); ptr += 4;
+ hdr.fpga = fields[1];
+ hdr.timestamp = fields[2] + std::string(" ") + fields[3];
+
+ std::vector<std::string> tokens;
+ boost::split(tokens, fields[0], boost::is_any_of(";"));
+ if (tokens.size() == 3) {
+ hdr.product = tokens[0];
+ std::vector<std::string> uidtokens;
+ boost::split(uidtokens, tokens[1], boost::is_any_of("="));
+ if (uidtokens.size() == 2 and uidtokens[0] == "UserID") {
+ std::stringstream stream;
+ stream << uidtokens[1];
+ stream >> std::hex >> hdr.userid;
+ hdr.valid = true;
+ }
+ }
+ }
+}
+
+static size_t _send_and_recv(
+ udp_simple::sptr xport,
+ n230_flash_prog_t& out, n230_flash_prog_t& in)
+{
+ static boost::uint32_t seqno = 0;
+ out.seq = htonx<boost::uint32_t>(++seqno);
+ xport->send(boost::asio::buffer(&out, sizeof(n230_flash_prog_t)));
+ size_t len = xport->recv(boost::asio::buffer(&in, udp_simple::mtu), 0.5);
+ if (len != sizeof(n230_flash_prog_t) or ntohx<boost::uint32_t>(in.seq) != seqno) {
+ throw uhd::io_error("Error communicating with the device.");
+ }
+ return len;
+}
+
+
+static bool n230_image_loader(const image_loader::image_loader_args_t &loader_args){
+ // Run discovery routine and ensure that exactly one N230 is specified
+ device_addrs_t devs = usrp::n230::n230_impl::n230_find(loader_args.args);
+ if (devs.size() == 0 or !loader_args.load_fpga) return false;
+ if (devs.size() > 1) {
+ throw uhd::runtime_error("Multiple devices match the specified args. To avoid accidentally updating the "
+ "wrong device, please narrow the search by specifying a unique \"addr\" argument.");
+ }
+ device_addr_t dev = devs[0];
+
+ // Sanity check the specified bitfile
+ std::string fpga_img_path = loader_args.fpga_path;
+ bool fpga_path_specified = !loader_args.fpga_path.empty();
+ if (not fpga_path_specified) {
+ fpga_img_path = (
+ fs::path(uhd::get_pkg_path()) / "share" / "uhd" / "images" / "usrp_n230_fpga.bit"
+ ).string();
+ }
+
+ if (not boost::filesystem::exists(fpga_img_path)) {
+ if (fpga_path_specified) {
+ throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % fpga_img_path));
+ } else {
+ throw uhd::runtime_error(str(boost::format(
+ "Could not find the default FPGA image: %s.\n"
+ "Either specify the --fpga-path argument or download the latest prebuilt images:\n"
+ "%s\n")
+ % fpga_img_path % print_utility_error("uhd_images_downloader.py")));
+ }
+ }
+ xil_bitfile_hdr_t hdr;
+ _parse_bitfile_header(fpga_img_path, hdr);
+
+ // Create a UDP communication link
+ udp_simple::sptr udp_xport =
+ udp_simple::make_connected(dev["addr"],BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT));
+
+ if (hdr.valid and hdr.product == "n230") {
+ if (hdr.userid != 0x5AFE0000) {
+ std::cout << boost::format("Unit: USRP N230 (%s, %s)\n-- FPGA Image: %s\n")
+ % dev["addr"] % dev["serial"] % fpga_img_path;
+
+ // Write image
+ std::ifstream image(fpga_img_path.c_str(), std::ios::binary);
+ size_t image_size = boost::filesystem::file_size(fpga_img_path);
+
+ static const size_t SECTOR_SIZE = 65536;
+ static const size_t IMAGE_BASE = 0x400000;
+
+ n230_flash_prog_t out, in;
+ size_t bytes_written = 0;
+ while (bytes_written < image_size) {
+ size_t payload_size = std::min<size_t>(image_size - bytes_written, N230_FLASH_COMM_MAX_PAYLOAD_SIZE);
+ if (bytes_written % SECTOR_SIZE == 0) {
+ out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA);
+ out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE);
+ out.size = htonx<boost::uint32_t>(payload_size);
+ _send_and_recv(udp_xport, out, in);
+ }
+ out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA);
+ out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE);
+ out.size = htonx<boost::uint32_t>(payload_size);
+ image.read((char*)out.data, payload_size);
+ _send_and_recv(udp_xport, out, in);
+ bytes_written += ntohx<boost::uint32_t>(in.size);
+ std::cout << boost::format("\r-- Loading FPGA image: %d%%")
+ % (int(double(bytes_written) / double(image_size) * 100.0))
+ << std::flush;
+ }
+ std::cout << std::endl << "FPGA image loaded successfully." << std::endl;
+ std::cout << std::endl << "Power-cycle the device to run the image." << std::endl;
+ return true;
+ } else {
+ throw uhd::runtime_error("This utility cannot burn a failsafe image!");
+ }
+ } else {
+ throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid USRP N230 FPGA image.")
+ % fpga_img_path));
+ }
+}
+
+UHD_STATIC_BLOCK(register_n230_image_loader){
+ std::string recovery_instructions = "Aborting. Your USRP N230 device will likely boot in safe mode.\n"
+ "Please re-run this command with the additional \"safe_mode\" device argument\n"
+ "to recover your device.";
+
+ image_loader::register_image_loader("n230", n230_image_loader, recovery_instructions);
+}
diff --git a/host/lib/usrp/n230/n230_impl.cpp b/host/lib/usrp/n230/n230_impl.cpp
new file mode 100644
index 000000000..5e8aa37b7
--- /dev/null
+++ b/host/lib/usrp/n230/n230_impl.cpp
@@ -0,0 +1,591 @@
+//
+// 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 "n230_impl.hpp"
+
+#include "usrp3_fw_ctrl_iface.hpp"
+#include "validate_subdev_spec.hpp"
+#include <uhd/utils/static.hpp>
+#include <uhd/transport/if_addrs.hpp>
+#include <uhd/transport/udp_zero_copy.hpp>
+#include <uhd/usrp/subdev_spec.hpp>
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/sensors.hpp>
+#include <uhd/types/ranges.hpp>
+#include <uhd/types/direction.hpp>
+#include <uhd/usrp/mboard_eeprom.hpp>
+#include <uhd/usrp/dboard_eeprom.hpp>
+#include <uhd/usrp/gps_ctrl.hpp>
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/asio/ip/address_v4.hpp>
+#include <boost/asio.hpp> //used for htonl and ntohl
+#include <boost/make_shared.hpp>
+
+#include "../common/fw_comm_protocol.h"
+#include "n230_defaults.h"
+#include "n230_fpga_defs.h"
+#include "n230_fw_defs.h"
+#include "n230_fw_host_iface.h"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+using namespace uhd::transport;
+namespace asio = boost::asio;
+
+//----------------------------------------------------------
+// Static device registration with framework
+//----------------------------------------------------------
+UHD_STATIC_BLOCK(register_n230_device)
+{
+ device::register_device(&n230_impl::n230_find, &n230_impl::n230_make, device::USRP);
+}
+
+//----------------------------------------------------------
+// Device discovery
+//----------------------------------------------------------
+uhd::device_addrs_t n230_impl::n230_find(const uhd::device_addr_t &multi_dev_hint)
+{
+ //handle the multi-device discovery
+ device_addrs_t hints = separate_device_addr(multi_dev_hint);
+ if (hints.size() > 1){
+ device_addrs_t found_devices;
+ std::string error_msg;
+ BOOST_FOREACH(const device_addr_t &hint_i, hints){
+ device_addrs_t found_devices_i = n230_find(hint_i);
+ if (found_devices_i.size() != 1) error_msg += str(boost::format(
+ "Could not resolve device hint \"%s\" to a single device."
+ ) % hint_i.to_string());
+ else found_devices.push_back(found_devices_i[0]);
+ }
+ if (found_devices.empty()) return device_addrs_t();
+ if (not error_msg.empty()) throw uhd::value_error(error_msg);
+ return device_addrs_t(1, combine_device_addrs(found_devices));
+ }
+
+ //initialize the hint for a single device case
+ UHD_ASSERT_THROW(hints.size() <= 1);
+ hints.resize(1); //in case it was empty
+ device_addr_t hint = hints[0];
+ device_addrs_t n230_addrs;
+
+ //return an empty list of addresses when type is set to non-n230
+ if (hint.has_key("type") and hint["type"] != "n230") return n230_addrs;
+
+ //Return an empty list of addresses when a resource is specified,
+ //since a resource is intended for a different, non-networked, device.
+ if (hint.has_key("resource")) return n230_addrs;
+
+ //if no address was specified, send a broadcast on each interface
+ if (not hint.has_key("addr")) {
+ BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) {
+ //avoid the loopback device
+ if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue;
+
+ //create a new hint with this broadcast address
+ device_addr_t new_hint = hint;
+ new_hint["addr"] = if_addrs.bcast;
+
+ //call discover with the new hint and append results
+ device_addrs_t new_n230_addrs = n230_find(new_hint);
+ n230_addrs.insert(n230_addrs.begin(),
+ new_n230_addrs.begin(), new_n230_addrs.end()
+ );
+ }
+ return n230_addrs;
+ }
+
+ std::vector<std::string> discovered_addrs =
+ usrp3::usrp3_fw_ctrl_iface::discover_devices(
+ hint["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID);
+
+ BOOST_FOREACH(const std::string& addr, discovered_addrs)
+ {
+ device_addr_t new_addr;
+ new_addr["type"] = "n230";
+ new_addr["addr"] = addr;
+
+ //Attempt a simple 2-way communication with a connected socket.
+ //Reason: Although the USRP will respond the broadcast above,
+ //we may not be able to communicate directly (non-broadcast).
+ udp_simple::sptr ctrl_xport = udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT));
+
+ //Corner case: If two devices have the same IP but different MAC
+ //addresses and are used back-to-back it takes a while for ARP tables
+ //on the host to update in which period brodcasts will respond but
+ //connected communication can fail. Retry the following call to allow
+ //the stack to update
+ size_t first_conn_retries = 10;
+ usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl;
+ while (first_conn_retries > 0) {
+ try {
+ fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID, false /*verbose*/);
+ break;
+ } catch (uhd::io_error& ex) {
+ boost::this_thread::sleep(boost::posix_time::milliseconds(500));
+ first_conn_retries--;
+ }
+ }
+ if (first_conn_retries > 0) {
+ uint32_t compat_reg = fw_ctrl->peek32(fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT));
+ if (fw::get_prod_num(compat_reg) == fw::PRODUCT_NUM) {
+ if (!n230_resource_manager::is_device_claimed(fw_ctrl)) {
+ //Not claimed by another process or host
+ try {
+ //Try to read the EEPROM to get the name and serial
+ n230_eeprom_manager eeprom_mgr(new_addr["addr"]);
+ const mboard_eeprom_t& eeprom = eeprom_mgr.get_mb_eeprom();
+ new_addr["name"] = eeprom["name"];
+ new_addr["serial"] = eeprom["serial"];
+ }
+ catch(const std::exception &)
+ {
+ //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
+ new_addr["name"] = "";
+ new_addr["serial"] = "";
+ }
+ //filter the discovered device below by matching optional keys
+ if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) and
+ (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]))
+ {
+ n230_addrs.push_back(new_addr);
+ }
+ }
+ }
+ }
+ }
+
+ return n230_addrs;
+}
+
+/***********************************************************************
+ * Make
+ **********************************************************************/
+device::sptr n230_impl::n230_make(const device_addr_t &device_addr)
+{
+ return device::sptr(new n230_impl(device_addr));
+}
+
+/***********************************************************************
+ * n230_impl::ctor
+ **********************************************************************/
+n230_impl::n230_impl(const uhd::device_addr_t& dev_addr)
+{
+ UHD_MSG(status) << "N230 initialization sequence..." << std::endl;
+ _dev_args.parse(dev_addr);
+ _tree = uhd::property_tree::make();
+
+ //TODO: Only supports one motherboard per device class.
+ const fs_path mb_path = "/mboards/0";
+
+ //Initialize addresses
+ std::vector<std::string> ip_addrs(1, dev_addr["addr"]);
+ if (dev_addr.has_key("secondary-addr")) {
+ ip_addrs.push_back(dev_addr["secondary-addr"]);
+ }
+
+ //Read EEPROM and perform version checks before talking to HW
+ _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]);
+ const mboard_eeprom_t& mb_eeprom = _eeprom_mgr->get_mb_eeprom();
+ bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom");
+ if (recover_mb_eeprom) {
+ UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version "
+ "checks.\nOperating in this mode may cause hardware damage and unstable "
+ "radio performance!"<< std::endl;
+ }
+ boost::uint16_t hw_rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision"]);
+ boost::uint16_t hw_rev_compat = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision_compat"]);
+ if (not recover_mb_eeprom) {
+ if (hw_rev_compat > N230_HW_REVISION_COMPAT) {
+ throw uhd::runtime_error(str(boost::format(
+ "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.")
+ % hw_rev));
+ }
+ }
+
+ //Initialize all subsystems
+ _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode());
+ _stream_mgr = boost::make_shared<n230_stream_manager>(_dev_args, _resource_mgr, _tree);
+
+ //Build property tree
+ _initialize_property_tree(mb_path);
+
+ //Debug loopback mode
+ switch(_dev_args.get_loopback_mode()) {
+ case n230_device_args_t::LOOPBACK_RADIO:
+ UHD_MSG(status) << "DEBUG: Running in TX->RX Radio loopback mode.\n";
+ _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_RADIO);
+ break;
+ case n230_device_args_t::LOOPBACK_CODEC:
+ UHD_MSG(status) << "DEBUG: Running in TX->RX CODEC loopback mode.\n";
+ _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_CODEC);
+ break;
+ default:
+ _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_DISABLED);
+ break;
+ }
+}
+
+/***********************************************************************
+ * n230_impl::dtor
+ **********************************************************************/
+n230_impl::~n230_impl()
+{
+ _stream_mgr.reset();
+ _eeprom_mgr.reset();
+ _resource_mgr.reset();
+ _tree.reset();
+}
+
+/***********************************************************************
+ * n230_impl::get_rx_stream
+ **********************************************************************/
+rx_streamer::sptr n230_impl::get_rx_stream(const uhd::stream_args_t &args)
+{
+ return _stream_mgr->get_rx_stream(args);
+}
+
+/***********************************************************************
+ * n230_impl::get_tx_stream
+ **********************************************************************/
+tx_streamer::sptr n230_impl::get_tx_stream(const uhd::stream_args_t &args)
+{
+ return _stream_mgr->get_tx_stream(args);
+}
+
+/***********************************************************************
+ * n230_impl::recv_async_msg
+ **********************************************************************/
+bool n230_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout)
+{
+ return _stream_mgr->recv_async_msg(async_metadata, timeout);
+}
+
+/***********************************************************************
+ * _initialize_property_tree
+ **********************************************************************/
+void n230_impl::_initialize_property_tree(const fs_path& mb_path)
+{
+ //------------------------------------------------------------------
+ // General info
+ //------------------------------------------------------------------
+ _tree->create<std::string>("/name").set("N230 Device");
+
+ _tree->create<std::string>(mb_path / "name").set("N230");
+ _tree->create<std::string>(mb_path / "codename").set("N230");
+ _tree->create<std::string>(mb_path / "dboards").set("none"); //No dboards.
+
+ _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u")
+ % _resource_mgr->get_version(FIRMWARE, COMPAT_MAJOR)
+ % _resource_mgr->get_version(FIRMWARE, COMPAT_MINOR)));
+ _tree->create<std::string>(mb_path / "fw_version_hash").set(str(boost::format("%s")
+ % _resource_mgr->get_version_hash(FIRMWARE)));
+ _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u")
+ % _resource_mgr->get_version(FPGA, COMPAT_MAJOR)
+ % _resource_mgr->get_version(FPGA, COMPAT_MINOR)));
+ _tree->create<std::string>(mb_path / "fpga_version_hash").set(str(boost::format("%s")
+ % _resource_mgr->get_version_hash(FPGA)));
+
+ _tree->create<double>(mb_path / "link_max_rate").set(_resource_mgr->get_max_link_rate());
+
+ //------------------------------------------------------------------
+ // EEPROM
+ //------------------------------------------------------------------
+ _tree->create<mboard_eeprom_t>(mb_path / "eeprom")
+ .set(_eeprom_mgr->get_mb_eeprom()) //Set first...
+ .add_coerced_subscriber(boost::bind(&n230_eeprom_manager::write_mb_eeprom, _eeprom_mgr, _1)); //..then enable writer
+
+ //------------------------------------------------------------------
+ // Create codec nodes
+ //------------------------------------------------------------------
+ const fs_path rx_codec_path = mb_path / ("rx_codecs") / "A";
+ _tree->create<std::string>(rx_codec_path / "name")
+ .set("N230 RX dual ADC");
+ _tree->create<int>(rx_codec_path / "gains"); //Empty because gains are in frontend
+
+ const fs_path tx_codec_path = mb_path / ("tx_codecs") / "A";
+ _tree->create<std::string>(tx_codec_path / "name")
+ .set("N230 TX dual DAC");
+ _tree->create<int>(tx_codec_path / "gains"); //Empty because gains are in frontend
+
+ //------------------------------------------------------------------
+ // Create clock and time control nodes
+ //------------------------------------------------------------------
+ _tree->create<double>(mb_path / "tick_rate")
+ .set_coercer(boost::bind(&n230_clk_pps_ctrl::set_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr(), _1))
+ .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr()))
+ .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tick_rate, _stream_mgr, _1));
+
+ //Register time now and pps onto available radio cores
+ //radio0 is the master
+ _tree->create<time_spec_t>(mb_path / "time" / "cmd");
+ _tree->create<time_spec_t>(mb_path / "time" / "now")
+ .set_publisher(boost::bind(&time_core_3000::get_time_now, _resource_mgr->get_radio(0).time));
+ _tree->create<time_spec_t>(mb_path / "time" / "pps")
+ .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _resource_mgr->get_radio(0).time));
+
+ //Setup time source props
+ _tree->create<std::string>(mb_path / "time_source" / "value")
+ .add_coerced_subscriber(boost::bind(&n230_impl::_check_time_source, this, _1))
+ .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_pps_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1))
+ .set(n230::DEFAULT_TIME_SRC);
+ static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo");
+ _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options")
+ .set(time_sources);
+
+ //Setup reference source props
+ _tree->create<std::string>(mb_path / "clock_source" / "value")
+ .add_coerced_subscriber(boost::bind(&n230_impl::_check_clock_source, this, _1))
+ .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_clock_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1))
+ .set(n230::DEFAULT_CLOCK_SRC);
+ static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("gpsdo");
+ _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options")
+ .set(clock_sources);
+ _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked")
+ .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_ref_locked, _resource_mgr->get_clk_pps_ctrl_sptr()));
+
+ //------------------------------------------------------------------
+ // Create frontend mapping
+ //------------------------------------------------------------------
+ _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
+ .set(subdev_spec_t())
+ .add_coerced_subscriber(boost::bind(&n230_impl::_update_rx_subdev_spec, this, _1));
+ _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
+ .set(subdev_spec_t())
+ .add_coerced_subscriber(boost::bind(&n230_impl::_update_tx_subdev_spec, this, _1));
+
+ //------------------------------------------------------------------
+ // Create a fake dboard to put frontends in
+ //------------------------------------------------------------------
+ //For completeness we give it a fake EEPROM as well
+ dboard_eeprom_t db_eeprom; //Default state: ID is 0xffff, Version and serial empty
+ _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom").set(db_eeprom);
+ _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom").set(db_eeprom);
+ _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom);
+
+ //------------------------------------------------------------------
+ // Create radio specific nodes
+ //------------------------------------------------------------------
+ for (size_t radio_instance = 0; radio_instance < fpga::NUM_RADIOS; radio_instance++) {
+ _initialize_radio_properties(mb_path, radio_instance);
+ }
+ //Update tick rate on newly created radio objects
+ _tree->access<double>(mb_path / "tick_rate").set(_dev_args.get_master_clock_rate());
+
+ //------------------------------------------------------------------
+ // Initialize subdev specs
+ //------------------------------------------------------------------
+ subdev_spec_t rx_spec, tx_spec;
+ BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends"))
+ {
+ rx_spec.push_back(subdev_spec_pair_t("A", fe));
+ }
+ BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends"))
+ {
+ tx_spec.push_back(subdev_spec_pair_t("A", fe));
+ }
+ _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec);
+ _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec);
+
+ //------------------------------------------------------------------
+ // MiniSAS GPIO
+ //------------------------------------------------------------------
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "DDR")
+ .set(0)
+ .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr,
+ _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_DDR, _1));
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "DDR")
+ .set(0)
+ .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr,
+ _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_DDR, _1));
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "OUT")
+ .set(0)
+ .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr,
+ _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_OUT, _1));
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "OUT")
+ .set(0)
+ .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr,
+ _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_OUT, _1));
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK")
+ .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(0)));
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "READBACK")
+ .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(1)));
+
+ //------------------------------------------------------------------
+ // GPSDO sensors
+ //------------------------------------------------------------------
+ if (_resource_mgr->is_gpsdo_present()) {
+ uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl();
+ BOOST_FOREACH(const std::string &name, gps_ctrl->get_sensors())
+ {
+ _tree->create<sensor_value_t>(mb_path / "sensors" / name)
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, gps_ctrl, name));
+ }
+ }
+}
+
+/***********************************************************************
+ * _initialize_radio_properties
+ **********************************************************************/
+void n230_impl::_initialize_radio_properties(const fs_path& mb_path, size_t instance)
+{
+ radio_resource_t& perif = _resource_mgr->get_radio(instance);
+
+ //Time
+ _tree->access<time_spec_t>(mb_path / "time" / "cmd")
+ .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1));
+ _tree->access<double>(mb_path / "tick_rate")
+ .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1));
+ _tree->access<time_spec_t>(mb_path / "time" / "now")
+ .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_now, perif.time, _1));
+ _tree->access<time_spec_t>(mb_path / "time" / "pps")
+ .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time, _1));
+
+ //RX DSP
+ _tree->access<double>(mb_path / "tick_rate")
+ .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1));
+ const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % instance);
+ _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range")
+ .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc));
+ _tree->create<double>(rx_dsp_path / "rate" / "value")
+ .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1))
+ .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_rx_samp_rate, _stream_mgr, instance, _1))
+ .set(n230::DEFAULT_RX_SAMP_RATE);
+ _tree->create<double>(rx_dsp_path / "freq" / "value")
+ .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1))
+ .set(n230::DEFAULT_DDC_FREQ);
+ _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range")
+ .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc));
+ _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));
+
+ //TX DSP
+ _tree->access<double>(mb_path / "tick_rate")
+ .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1));
+ const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % instance);
+ _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range")
+ .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc));
+ _tree->create<double>(tx_dsp_path / "rate" / "value")
+ .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1))
+ .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tx_samp_rate, _stream_mgr, instance, _1))
+ .set(n230::DEFAULT_TX_SAMP_RATE);
+ _tree->create<double>(tx_dsp_path / "freq" / "value")
+ .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1))
+ .set(n230::DEFAULT_DUC_FREQ);
+ _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range")
+ .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc));
+
+ //RF Frontend Interfacing
+ static const std::vector<direction_t> data_directions = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION);
+ BOOST_FOREACH(direction_t direction, data_directions) {
+ const std::string dir_str = (direction == RX_DIRECTION) ? "rx" : "tx";
+ const std::string key = boost::to_upper_copy(dir_str) + str(boost::format("%u") % (instance + 1));
+ const fs_path rf_fe_path = mb_path / "dboards" / "A" / (dir_str + "_frontends") / ((instance==0)?"A":"B");
+
+ //CODEC subtree
+ _resource_mgr->get_codec_mgr().populate_frontend_subtree(_tree->subtree(rf_fe_path), key, direction);
+
+ //User settings
+ _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings" / "iface")
+ .set(perif.user_settings);
+
+ //Setup antenna stuff
+ if (key[0] == 'R') {
+ static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2");
+ _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options")
+ .set(ants);
+ _tree->create<std::string>(rf_fe_path / "antenna" / "value")
+ .add_coerced_subscriber(boost::bind(&n230_frontend_ctrl::set_antenna_sel, _resource_mgr->get_frontend_ctrl_sptr(), instance, _1))
+ .set("RX2");
+ }
+ if (key[0] == 'T') {
+ static const std::vector<std::string> ants(1, "TX/RX");
+ _tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options")
+ .set(ants);
+ _tree->create<std::string>(rf_fe_path / "antenna" / "value")
+ .set("TX/RX");
+ }
+ }
+}
+
+void n230_impl::_update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec)
+{
+ //sanity checking
+ if (spec.size()) validate_subdev_spec(_tree, spec, "rx");
+ UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS);
+
+ if (spec.size() > 0) {
+ UHD_ASSERT_THROW(spec[0].db_name == "A");
+ UHD_ASSERT_THROW(spec[0].sd_name == "A");
+ }
+ if (spec.size() > 1) {
+ //TODO we can support swapping at a later date, only this combo is supported
+ UHD_ASSERT_THROW(spec[1].db_name == "A");
+ UHD_ASSERT_THROW(spec[1].sd_name == "B");
+ }
+
+ _stream_mgr->update_stream_states();
+}
+
+void n230_impl::_update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec)
+{
+ //sanity checking
+ if (spec.size()) validate_subdev_spec(_tree, spec, "tx");
+ UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS);
+
+ if (spec.size() > 0) {
+ UHD_ASSERT_THROW(spec[0].db_name == "A");
+ UHD_ASSERT_THROW(spec[0].sd_name == "A");
+ }
+ if (spec.size() > 1) {
+ //TODO we can support swapping at a later date, only this combo is supported
+ UHD_ASSERT_THROW(spec[1].db_name == "A");
+ UHD_ASSERT_THROW(spec[1].sd_name == "B");
+ }
+
+ _stream_mgr->update_stream_states();
+}
+
+void n230_impl::_check_time_source(std::string source)
+{
+ if (source == "gpsdo")
+ {
+ uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl();
+ if (not (gps_ctrl and gps_ctrl->gps_detected()))
+ throw uhd::runtime_error("GPSDO time source not available");
+ }
+}
+
+void n230_impl::_check_clock_source(std::string source)
+{
+ if (source == "gpsdo")
+ {
+ uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl();
+ if (not (gps_ctrl and gps_ctrl->gps_detected()))
+ throw uhd::runtime_error("GPSDO clock source not available");
+ }
+}
+
+}}} //namespace
diff --git a/host/lib/usrp/n230/n230_impl.hpp b/host/lib/usrp/n230/n230_impl.hpp
new file mode 100644
index 000000000..b644dd8a3
--- /dev/null
+++ b/host/lib/usrp/n230/n230_impl.hpp
@@ -0,0 +1,81 @@
+//
+// 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_N230_IMPL_HPP
+#define INCLUDED_N230_IMPL_HPP
+
+#include <uhd/property_tree.hpp>
+#include <uhd/device.hpp>
+#include <uhd/usrp/subdev_spec.hpp>
+
+#include "n230_device_args.hpp"
+#include "n230_eeprom_manager.hpp"
+#include "n230_resource_manager.hpp"
+#include "n230_stream_manager.hpp"
+#include "recv_packet_demuxer_3000.hpp"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_impl : public uhd::device
+{
+public: //Functions
+ // ctor and dtor
+ n230_impl(const uhd::device_addr_t& device_addr);
+ virtual ~n230_impl(void);
+
+ //---------------------------------------------------------------------
+ // uhd::device interface
+ //
+ static sptr make(const uhd::device_addr_t &hint, size_t which = 0);
+
+ //! Make a new receive streamer from the streamer arguments
+ virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args);
+
+ //! Make a new transmit streamer from the streamer arguments
+ virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args);
+
+ //!Receive and asynchronous message from the device.
+ virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout = 0.1);
+
+ //!Registration methods the discovery and factory system.
+ //[static void register_device(const find_t &find, const make_t &make)]
+ static uhd::device_addrs_t n230_find(const uhd::device_addr_t &hint);
+ static uhd::device::sptr n230_make(const uhd::device_addr_t &device_addr);
+ //
+ //---------------------------------------------------------------------
+
+ typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type;
+
+private: //Functions
+ void _initialize_property_tree(const fs_path& mb_path);
+ void _initialize_radio_properties(const fs_path& mb_path, size_t instance);
+
+ void _update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &);
+ void _update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &);
+ void _check_time_source(std::string);
+ void _check_clock_source(std::string);
+
+private: //Classes and Members
+ n230_device_args_t _dev_args;
+ boost::shared_ptr<n230_resource_manager> _resource_mgr;
+ boost::shared_ptr<n230_eeprom_manager> _eeprom_mgr;
+ boost::shared_ptr<n230_stream_manager> _stream_mgr;
+};
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_IMPL_HPP */
diff --git a/host/lib/usrp/n230/n230_resource_manager.cpp b/host/lib/usrp/n230/n230_resource_manager.cpp
new file mode 100644
index 000000000..f13dd0b33
--- /dev/null
+++ b/host/lib/usrp/n230/n230_resource_manager.cpp
@@ -0,0 +1,569 @@
+//
+// Copyright 2013 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "n230_resource_manager.hpp"
+
+#include "usrp3_fw_ctrl_iface.hpp"
+#include <uhd/transport/if_addrs.hpp>
+#include <uhd/transport/udp_zero_copy.hpp>
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/utils/platform.hpp>
+#include <uhd/utils/paths.hpp>
+#include <boost/format.hpp>
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/functional/hash.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/make_shared.hpp>
+#include "n230_fw_defs.h"
+#include "n230_fw_host_iface.h"
+
+#define IF_DATA_I_MASK 0xFFF00000
+#define IF_DATA_Q_MASK 0x0000FFF0
+
+namespace uhd { namespace usrp { namespace n230 {
+
+//Constants
+static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0;
+static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1;
+static const uint8_t N230_HOST_DEST_ADDR = 2;
+
+static const uint8_t N230_ETH0_IFACE_ID = 0;
+static const uint8_t N230_ETH1_IFACE_ID = 1;
+
+class n230_ad9361_client_t : public ad9361_params {
+public:
+ ~n230_ad9361_client_t() {}
+ double get_band_edge(frequency_band_t band) {
+ switch (band) {
+ case AD9361_RX_BAND0: return 2.2e9;
+ case AD9361_RX_BAND1: return 4.0e9;
+ case AD9361_TX_BAND0: return 2.5e9;
+ default: return 0;
+ }
+ }
+ clocking_mode_t get_clocking_mode() {
+ return AD9361_XTAL_N_CLK_PATH;
+ }
+ digital_interface_mode_t get_digital_interface_mode() {
+ return AD9361_DDR_FDD_LVDS;
+ }
+ digital_interface_delays_t get_digital_interface_timing() {
+ digital_interface_delays_t delays;
+ delays.rx_clk_delay = 0;
+ delays.rx_data_delay = 0;
+ delays.tx_clk_delay = 0;
+ delays.tx_data_delay = 2;
+ return delays;
+ }
+};
+
+n230_resource_manager::n230_resource_manager(
+ const std::vector<std::string> ip_addrs,
+ const bool safe_mode
+) :
+ _safe_mode(safe_mode),
+ _last_host_enpoint(0)
+{
+ if (_safe_mode) UHD_MSG(warning) << "Initializing device in safe mode\n";
+ UHD_MSG(status) << "Setup basic communication...\n";
+
+ //Discover ethernet interfaces
+ bool dual_eth_expected = (ip_addrs.size() > 1);
+ BOOST_FOREACH(const std::string& addr, ip_addrs) {
+ n230_eth_conn_t conn_iface;
+ conn_iface.ip_addr = addr;
+
+ boost::uint32_t iface_id = 0xFFFFFFFF;
+ try {
+ iface_id = usrp3::usrp3_fw_ctrl_iface::get_iface_id(
+ conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID);
+ } catch (uhd::io_error&) {
+ throw uhd::io_error(str(boost::format(
+ "Could not communicate with the device over address %s") %
+ conn_iface.ip_addr));
+ }
+ switch (iface_id) {
+ case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break;
+ case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break;
+ default: {
+ if (dual_eth_expected) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number.");
+ } else {
+ //For backwards compatibility, if only one port is specified, assume that a detection
+ //failure means that the device does not support dual-ethernet behavior.
+ conn_iface.type = ETH0; break;
+ }
+ }
+ }
+ _eth_conns.push_back(conn_iface);
+ }
+ if (_eth_conns.size() < 1) {
+ throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)");
+ }
+
+ //Create firmware communication interface
+ _fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(
+ transport::udp_simple::make_connected(
+ _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID);
+ if (_fw_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)");
+ }
+ _check_fw_compat();
+
+ //Start the device claimer
+ _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this));
+
+ //Create common settings interface
+ const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type);
+
+ transport::udp_zero_copy::buff_params dummy_out_params;
+ transport::zero_copy_if::sptr core_xport =
+ _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params);
+ if (core_xport.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)");
+ }
+ _core_ctrl = radio_ctrl_core_3000::make(
+ fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get());
+ if (_core_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)");
+ }
+ _check_fpga_compat();
+
+ UHD_MSG(status) << boost::format("Version signatures... Firmware:%s FPGA:%s...\n")
+ % _fw_version.get_hash_str() % _fpga_version.get_hash_str();
+
+ _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/);
+ _core_misc_reg.initialize(*_core_ctrl, true /*flush*/);
+ _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/);
+ _core_status_reg.initialize(*_core_ctrl);
+
+ //Create common SPI interface
+ _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl);
+ if (_core_spi_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)");
+ }
+
+ //Create AD9361 interface
+ UHD_MSG(status) << "Initializing CODEC...\n";
+ _codec_ctrl = ad9361_ctrl::make_spi(
+ boost::make_shared<n230_ad9361_client_t>(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM);
+ if (_codec_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)");
+ }
+ _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE);
+ _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS);
+ _codec_mgr->init_codec();
+
+ //Create AD4001 interface
+ _ref_pll_ctrl = boost::make_shared<n230_ref_pll_ctrl>(_core_spi_ctrl);
+ if (_ref_pll_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)");
+ }
+
+ //Reset SERDES interface and synchronize to frame sync from AD9361
+ _reset_codec_digital_interface();
+
+ std::vector<time_core_3000::sptr> time_cores;
+ std::vector<gpio_atr::gpio_atr_3000::sptr> gpio_cores;
+ for (size_t i = 0; i < fpga::NUM_RADIOS; i++) {
+ _initialize_radio(i);
+ time_cores.push_back(_radios[i].time);
+ gpio_cores.push_back(_radios[i].gpio_atr);
+ }
+
+ //Create clock and PPS control interface
+ _clk_pps_ctrl = n230_clk_pps_ctrl::make(
+ _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores);
+ if (_clk_pps_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)");
+ }
+
+ //Create front-end control interface
+ _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores);
+ if (_frontend_ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)");
+ }
+
+ //Create miniSAS GPIO interfaces
+ _ms0_gpio = gpio_atr::gpio_atr_3000::make(
+ _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO));
+ _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL);
+ _ms1_gpio = gpio_atr::gpio_atr_3000::make(
+ _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO));
+ _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL);
+
+ //Create GPSDO interface
+ if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) {
+ UHD_MSG(status) << "Detecting GPSDO.... " << std::flush;
+ try {
+ const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type);
+ transport::zero_copy_if::sptr gps_uart_xport =
+ _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params);
+ _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get()));
+ _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/fpga::GPSDO_UART_BAUDRATE);
+ _gps_uart->write_uart("\n"); //cause the baud and response to be setup
+ boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation
+ _gps_ctrl = gps_ctrl::make(_gps_uart);
+ } catch(std::exception &e) {
+ UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl;
+ }
+ if (not is_gpsdo_present()) {
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT);
+ }
+ }
+
+ //Perform data self-tests
+ _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING);
+ for (size_t i = 0; i < fpga::NUM_RADIOS; i++) {
+ _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO);
+ bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl);
+ if (!radio_selftest_pass) {
+ throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)");
+ }
+
+ _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC);
+ bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl);
+ if (!codec_selftest_pass) {
+ throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)");
+ }
+ }
+ _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED);
+ _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING);
+}
+
+n230_resource_manager::~n230_resource_manager()
+{
+ _claimer_task.reset();
+ { //Critical section
+ boost::mutex::scoped_lock(_claimer_mutex);
+ _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0);
+ _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0);
+ }
+}
+
+transport::zero_copy_if::sptr n230_resource_manager::create_transport(
+ n230_data_dir_t direction,
+ size_t radio_instance,
+ const device_addr_t &params,
+ sid_t& sid_pair,
+ transport::udp_zero_copy::buff_params& buff_out_params)
+{
+ const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH);
+ const sid_t temp_sid_pair =
+ _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance);
+ transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params);
+ if (xport.get() == NULL) {
+ throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)");
+ } else {
+ sid_pair = temp_sid_pair;
+ }
+ return xport;
+}
+
+bool n230_resource_manager::is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl)
+{
+ boost::mutex::scoped_lock(_claimer_mutex);
+
+ //If timed out then device is definitely unclaimed
+ if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0)
+ return false;
+
+ //otherwise check claim src to determine if another thread with the same src has claimed the device
+ return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash();
+}
+
+void n230_resource_manager::_claimer_loop()
+{
+ { //Critical section
+ boost::mutex::scoped_lock(_claimer_mutex);
+ _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL));
+ _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash());
+ }
+ boost::this_thread::sleep(boost::posix_time::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2));
+}
+
+void n230_resource_manager::_initialize_radio(size_t instance)
+{
+ radio_resource_t& radio = _radios[instance];
+
+ //Create common settings interface
+ const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance);
+ transport::udp_zero_copy::buff_params buff_out_params;
+ transport::zero_copy_if::sptr ctrl_xport =
+ _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params);
+ if (ctrl_xport.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)");
+ }
+ radio.ctrl = radio_ctrl_core_3000::make(
+ fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get());
+ if (radio.ctrl.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)");
+ }
+
+ //Perform register loopback test to verify the radio clock
+ bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl);
+ if (!reg_selftest_pass) {
+ throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)");
+ }
+
+ //Write-only ATR interface
+ radio.gpio_atr = gpio_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR));
+ radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL);
+
+ //Core VITA time interface
+ time_core_3000::readback_bases_type time_bases;
+ time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW);
+ time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS);
+ radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases);
+ if (radio.time.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)");
+ }
+
+ //RX DSP
+ radio.framer = rx_vita_core_3000::make(
+ radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL));
+ radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/);
+ if (radio.framer.get() == NULL || radio.ddc.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)");
+ }
+ radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS);
+
+ //TX DSP
+ radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL));
+ radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP));
+ if (radio.deframer.get() == NULL || radio.duc.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)");
+ }
+ radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS);
+
+ //User settings
+ radio.user_settings = user_settings_core_3000::make(radio.ctrl,
+ fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB));
+ if (radio.user_settings.get() == NULL) {
+ throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)");
+ }
+}
+
+boost::uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) {
+ return static_cast<boost::uint8_t>(ep) << 4;
+}
+
+const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance)
+{
+ fpga::xb_endpoint_t xb_dest_ep;
+ boost::uint8_t sid_dest_ep = 0;
+ fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0;
+ boost::uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0;
+
+ if (type == CORE or type == GPS_UART) {
+ //Non-radio endpoints
+ xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART;
+ sid_dest_ep = xb_ep_to_sid(xb_dest_ep);
+ } else {
+ //Radio endpoints
+ xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0;
+ sid_dest_ep = xb_ep_to_sid(xb_dest_ep);
+ switch (type) {
+ case RADIO_TX_DATA:
+ sid_dest_ep |= fpga::RADIO_DATA_SUFFIX;
+ break;
+ case RADIO_RX_DATA:
+ sid_dest_ep |= fpga::RADIO_FC_SUFFIX;
+ break;
+ default:
+ sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX;
+ break;
+ }
+ }
+
+ //Increment last host logical endpoint
+ sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep);
+
+ //Program the crossbar addr
+ _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr());
+ // Program CAM entry for returning packets to us
+ // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM
+ _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast<boost::uint32_t>(xb_ret_ep));
+ // Program CAM entry for outgoing packets matching a N230 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
+ _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast<boost::uint32_t>(xb_dest_ep));
+
+ return sid;
+}
+
+transport::zero_copy_if::sptr n230_resource_manager::_create_transport(
+ const n230_eth_conn_t& eth_conn,
+ const sid_t& sid, const device_addr_t &buff_params,
+ transport::udp_zero_copy::buff_params& buff_params_out)
+{
+ transport::zero_copy_xport_params default_buff_args;
+ default_buff_args.recv_frame_size = transport::udp_simple::mtu;
+ default_buff_args.send_frame_size = transport::udp_simple::mtu;
+ default_buff_args.num_recv_frames = 32;
+ default_buff_args.num_send_frames = 32;
+
+ transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make(
+ eth_conn.ip_addr, boost::lexical_cast<std::string>(fpga::CVITA_UDP_PORT),
+ default_buff_args, buff_params_out, buff_params);
+
+ if (xport.get()) {
+ _program_dispatcher(*xport, eth_conn.type, sid);
+ }
+ return xport;
+}
+
+void n230_resource_manager::_program_dispatcher(
+ transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid)
+{
+ //Send a mini packet with SID into the ZPU
+ //ZPU will reprogram the ethernet framer
+ transport::managed_send_buffer::sptr buff = xport.get_send_buff();
+ buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0
+ buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid.get());
+ buff->commit(8);
+ buff.reset();
+
+ //reprogram the ethernet dispatcher's udp port (should be safe to always set)
+ uint32_t disp_base_offset =
+ ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE;
+ _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT);
+
+ //Do a peek to an arbitrary address to guarantee that the
+ //ethernet framer has been programmed before we return.
+ _fw_ctrl->peek32(0);
+}
+
+void n230_resource_manager::_reset_codec_digital_interface()
+{
+ //Set timing registers
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY);
+ _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY);
+
+ _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1);
+ boost::this_thread::sleep(boost::posix_time::milliseconds(10));
+ _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0);
+}
+
+bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface)
+{
+ bool test_fail = false;
+ size_t hash = static_cast<size_t>(time(NULL));
+ for (size_t i = 0; i < 100; i++) {
+ boost::hash_combine(hash, i);
+ iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), boost::uint32_t(hash));
+ test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != boost::uint32_t(hash);
+ if (test_fail) break; //exit loop on any failure
+ }
+ return !test_fail;
+}
+
+bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface)
+{
+ bool test_fail = false;
+ size_t hash = size_t(time(NULL));
+ for (size_t i = 0; i < 100; i++) {
+ boost::hash_combine(hash, i);
+ const boost::uint32_t word32 = boost::uint32_t(hash) & (IF_DATA_I_MASK | IF_DATA_Q_MASK);
+ iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32);
+ iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes
+ boost::this_thread::sleep(boost::posix_time::microseconds(100)); //wait for loopback to propagate through codec
+ const boost::uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA));
+ const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32);
+ const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff);
+ test_fail = word32 != rb_tx or word32 != rb_rx;
+ if (test_fail)
+ UHD_MSG(fastpath) << boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx;
+ break; //exit loop on any failure
+ }
+
+ /* Zero out the idle data. */
+ iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0);
+ return !test_fail;
+}
+
+std::string n230_resource_manager::_get_fpga_upgrade_msg() {
+ std::string img_loader_path =
+ (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string();
+
+ return str(boost::format(
+ "\nDownload the appropriate FPGA images for this version of UHD.\n"
+ "%s\n\n"
+ "Then burn a new image to the on-board flash storage of your\n"
+ "USRP N230 device using the image loader utility. Use this command:\n"
+ "\n \"%s\" --args=\"type=n230,addr=%s\"\n")
+ % print_utility_error("uhd_images_downloader.py")
+ % img_loader_path % _get_conn(PRI_ETH).ip_addr);
+
+}
+
+void n230_resource_manager::_check_fw_compat()
+{
+ boost::uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num));
+ _fw_version.compat_major = compat_num >> 16;
+ _fw_version.compat_minor = compat_num;
+ _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash));
+
+ if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){
+ throw uhd::runtime_error(str(boost::format(
+ "Expected firmware compatibility number %d.x, but got %d.%d\n"
+ "The firmware build is not compatible with the host code build.\n"
+ "%s"
+ ) % static_cast<boost::uint32_t>(N230_FW_COMPAT_NUM_MAJOR)
+ % static_cast<boost::uint32_t>(_fw_version.compat_major)
+ % static_cast<boost::uint32_t>(_fw_version.compat_minor)
+ % _get_fpga_upgrade_msg()));
+ }
+}
+
+void n230_resource_manager::_check_fpga_compat()
+{
+ const boost::uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE));
+ const boost::uint32_t signature = boost::uint32_t(compat >> 32);
+ const boost::uint16_t product_id = boost::uint8_t(compat >> 24);
+ _fpga_version.compat_major = static_cast<boost::uint8_t>(compat >> 16);
+ _fpga_version.compat_minor = static_cast<boost::uint16_t>(compat);
+
+ const boost::uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH));
+ _fpga_version.version_hash = boost::uint32_t(version_hash);
+
+ if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID)
+ throw uhd::runtime_error("Signature check failed. Please contact support.");
+
+ bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE);
+
+ if (is_safe_image && !_safe_mode) {
+ throw uhd::runtime_error(
+ "The device appears to have the failsafe FPGA image loaded\n"
+ "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n"
+ "To remedy this error, please burn a valid FPGA image to the flash.\n"
+ "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n"
+ "Radio functionality/performance not guaranteed when operating in safe mode.\n");
+ } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) {
+ throw uhd::runtime_error(str(boost::format(
+ "Expected FPGA compatibility number %d.x, but got %d.%d:\n"
+ "The FPGA build is not compatible with the host code build.\n"
+ "%s"
+ ) % static_cast<boost::uint32_t>(fpga::RB_N230_COMPAT_MAJOR)
+ % static_cast<boost::uint32_t>(_fpga_version.compat_major)
+ % static_cast<boost::uint32_t>(_fpga_version.compat_minor)
+ % _get_fpga_upgrade_msg()));
+ }
+}
+
+}}} //namespace
diff --git a/host/lib/usrp/n230/n230_resource_manager.hpp b/host/lib/usrp/n230/n230_resource_manager.hpp
new file mode 100644
index 000000000..0a1178bd2
--- /dev/null
+++ b/host/lib/usrp/n230/n230_resource_manager.hpp
@@ -0,0 +1,318 @@
+//
+// 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_N230_RESOURCE_MANAGER_HPP
+#define INCLUDED_N230_RESOURCE_MANAGER_HPP
+
+#include "radio_ctrl_core_3000.hpp"
+#include "spi_core_3000.hpp"
+#include "gpio_atr_3000.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 "user_settings_core_3000.hpp"
+#include "ad9361_ctrl.hpp"
+#include "ad936x_manager.hpp"
+#include <uhd/utils/tasks.hpp>
+#include <uhd/types/sid.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/utils/soft_register.hpp>
+#include <uhd/transport/udp_zero_copy.hpp>
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/usrp/gps_ctrl.hpp>
+
+#include "usrp3_fw_ctrl_iface.hpp"
+#include "n230_clk_pps_ctrl.hpp"
+#include "n230_cores.hpp"
+#include "n230_fpga_defs.h"
+#include "n230_frontend_ctrl.hpp"
+#include "n230_uart.hpp"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+enum n230_eth_port_t {
+ ETH0,
+ ETH1
+};
+
+enum n230_eth_pref_t {
+ PRI_ETH,
+ SEC_ETH
+};
+
+enum n230_endpoint_t {
+ RADIO_TX_DATA,
+ RADIO_RX_DATA,
+ RADIO_CONTROL,
+ CORE,
+ GPS_UART
+};
+
+enum n230_ver_src_t {
+ SOFTWARE,
+ FIRMWARE,
+ FPGA
+};
+
+enum n230_version_t {
+ COMPAT_MAJOR,
+ COMPAT_MINOR
+};
+
+enum n230_data_dir_t {
+ RX_DATA, TX_DATA
+};
+
+//Radio resources
+class radio_resource_t : public boost::noncopyable {
+public:
+ radio_ctrl_core_3000::sptr ctrl;
+ gpio_atr::gpio_atr_3000::sptr gpio_atr;
+ time_core_3000::sptr time;
+ rx_vita_core_3000::sptr framer;
+ rx_dsp_core_3000::sptr ddc;
+ tx_vita_core_3000::sptr deframer;
+ tx_dsp_core_3000::sptr duc;
+ user_settings_core_3000::sptr user_settings;
+};
+
+class n230_resource_manager : public boost::noncopyable
+{
+public: //Methods
+ n230_resource_manager(const std::vector<std::string> ip_addrs, const bool safe_mode);
+ virtual ~n230_resource_manager();
+
+ static bool is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl);
+
+ inline bool is_device_claimed() {
+ if (_fw_ctrl.get()) {
+ return is_device_claimed(_fw_ctrl);
+ } else {
+ return false;
+ }
+ }
+
+ inline boost::uint32_t get_version(n230_ver_src_t src, n230_version_t type) {
+ switch (src) {
+ case FPGA: return _fpga_version.get(type);
+ case FIRMWARE: return _fw_version.get(type);
+ default: return 0;
+ }
+ }
+
+ inline const std::string get_version_hash(n230_ver_src_t src) {
+ switch (src) {
+ case FPGA: return _fpga_version.get_hash_str();
+ case FIRMWARE: return _fw_version.get_hash_str();
+ default: return "";
+ }
+ }
+
+ //Firmware control interface
+ inline wb_iface& get_fw_ctrl() const {
+ return *_fw_ctrl;
+ }
+ inline wb_iface::sptr get_fw_ctrl_sptr() {
+ return _fw_ctrl;
+ }
+
+ //Core settings control interface
+ inline radio_ctrl_core_3000& get_core_ctrl() const {
+ return *_core_ctrl;
+ }
+ inline radio_ctrl_core_3000::sptr get_core_ctrl_sptr() {
+ return _core_ctrl;
+ }
+
+ //AD931 control interface
+ inline ad9361_ctrl& get_codec_ctrl() const {
+ return *_codec_ctrl;
+ }
+ inline ad9361_ctrl::sptr get_codec_ctrl_sptr() {
+ return _codec_ctrl;
+ }
+ inline uhd::usrp::ad936x_manager& get_codec_mgr() const {
+ return *_codec_mgr;
+ }
+
+ //Clock PPS controls
+ inline n230_ref_pll_ctrl& get_ref_pll_ctrl() const {
+ return *_ref_pll_ctrl;
+ }
+ inline n230_ref_pll_ctrl::sptr get_ref_pll_ctrl_sptr() {
+ return _ref_pll_ctrl;
+ }
+
+ //Clock PPS controls
+ inline n230_clk_pps_ctrl& get_clk_pps_ctrl() const {
+ return *_clk_pps_ctrl;
+ }
+ inline n230_clk_pps_ctrl::sptr get_clk_pps_ctrl_sptr() {
+ return _clk_pps_ctrl;
+ }
+
+ //Front-end control
+ inline n230_frontend_ctrl& get_frontend_ctrl() const {
+ return *_frontend_ctrl;
+ }
+ inline n230_frontend_ctrl::sptr get_frontend_ctrl_sptr() {
+ return _frontend_ctrl;
+ }
+
+ //MiniSAS GPIO control
+ inline gpio_atr::gpio_atr_3000::sptr get_minisas_gpio_ctrl_sptr(size_t idx) {
+ return idx == 0 ? _ms0_gpio : _ms1_gpio;
+ }
+
+ inline gpio_atr::gpio_atr_3000& get_minisas_gpio_ctrl(size_t idx) {
+ return *get_minisas_gpio_ctrl_sptr(idx);
+ }
+
+ //GPSDO control
+ inline bool is_gpsdo_present() {
+ return _gps_ctrl.get() and _gps_ctrl->gps_detected();
+ }
+
+ inline uhd::gps_ctrl::sptr get_gps_ctrl(void) {
+ return _gps_ctrl;
+ }
+
+ inline radio_resource_t& get_radio(size_t instance) {
+ return _radios[instance];
+ }
+
+ //Transport to stream data
+ transport::zero_copy_if::sptr create_transport(
+ n230_data_dir_t direction, size_t radio_instance,
+ const device_addr_t &params, sid_t& sid,
+ transport::udp_zero_copy::buff_params& buff_out_params);
+
+ //Misc
+ inline double get_max_link_rate() {
+ return fpga::N230_LINK_RATE_BPS * _eth_conns.size();
+ }
+
+private:
+ struct ver_info_t {
+ boost::uint8_t compat_major;
+ boost::uint16_t compat_minor;
+ boost::uint32_t version_hash;
+
+ boost::uint32_t get(n230_version_t type) {
+ switch (type) {
+ case COMPAT_MAJOR: return compat_major;
+ case COMPAT_MINOR: return compat_minor;
+ default: return 0;
+ }
+ }
+
+ const std::string get_hash_str() {
+ return (str(boost::format("%07x%s")
+ % (version_hash & 0x0FFFFFFF)
+ % ((version_hash & 0xF0000000) ? "(modified)" : "")));
+ }
+ };
+
+ struct n230_eth_conn_t {
+ std::string ip_addr;
+ n230_eth_port_t type;
+ };
+
+ //-- Functions --
+
+ void _claimer_loop();
+
+ void _initialize_radio(size_t instance);
+
+ std::string _get_fpga_upgrade_msg();
+ void _check_fw_compat();
+ void _check_fpga_compat();
+
+ const sid_t _generate_sid(
+ const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance = 0);
+
+ transport::zero_copy_if::sptr _create_transport(
+ const n230_eth_conn_t& eth_conn,
+ const sid_t& sid, const device_addr_t &buff_params,
+ transport::udp_zero_copy::buff_params& buff_params_out);
+
+ void _program_dispatcher(
+ transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid);
+
+ void _reset_codec_digital_interface();
+
+ bool _radio_register_loopback_self_test(wb_iface::sptr iface);
+
+ bool _radio_data_loopback_self_test(wb_iface::sptr iface);
+
+ inline const n230_eth_conn_t& _get_conn(const n230_eth_pref_t pref) {
+ if (_eth_conns.size() == 1)
+ return _eth_conns[0];
+ else
+ return _eth_conns[(pref==PRI_ETH)?0:1];
+ }
+
+ //-- Members --
+
+ std::vector<n230_eth_conn_t> _eth_conns;
+ const bool _safe_mode;
+ ver_info_t _fw_version;
+ ver_info_t _fpga_version;
+
+ //Firmware register interface
+ uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr _fw_ctrl;
+ uhd::task::sptr _claimer_task;
+ static boost::mutex _claimer_mutex; //All claims and checks in this process are serialized
+
+ //Transport
+ boost::uint8_t _last_host_enpoint;
+
+ //Radio settings interface
+ radio_ctrl_core_3000::sptr _core_ctrl;
+ n230_core_spi_core::sptr _core_spi_ctrl;
+ ad9361_ctrl::sptr _codec_ctrl;
+ uhd::usrp::ad936x_manager::sptr _codec_mgr;
+
+ //Core Registers
+ fpga::core_radio_ctrl_reg_t _core_radio_ctrl_reg;
+ fpga::core_misc_reg_t _core_misc_reg;
+ fpga::core_pps_sel_reg_t _core_pps_sel_reg;
+ fpga::core_status_reg_t _core_status_reg;
+
+ //Radio peripherals
+ radio_resource_t _radios[fpga::NUM_RADIOS];
+
+ //Misc IO peripherals
+ n230_ref_pll_ctrl::sptr _ref_pll_ctrl;
+ n230_clk_pps_ctrl::sptr _clk_pps_ctrl;
+ n230_frontend_ctrl::sptr _frontend_ctrl;
+
+ //miniSAS GPIO
+ gpio_atr::gpio_atr_3000::sptr _ms0_gpio;
+ gpio_atr::gpio_atr_3000::sptr _ms1_gpio;
+
+ //GPSDO
+ n230_uart::sptr _gps_uart;
+ uhd::gps_ctrl::sptr _gps_ctrl;
+
+};
+
+}}} //namespace
+
+#endif //INCLUDED_N230_RESOURCE_MANAGER_HPP
diff --git a/host/lib/usrp/n230/n230_stream_manager.cpp b/host/lib/usrp/n230/n230_stream_manager.cpp
new file mode 100644
index 000000000..e7624ecd6
--- /dev/null
+++ b/host/lib/usrp/n230/n230_stream_manager.cpp
@@ -0,0 +1,562 @@
+//
+// Copyright 2013 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "n230_stream_manager.hpp"
+
+#include "../../transport/super_recv_packet_handler.hpp"
+#include "../../transport/super_send_packet_handler.hpp"
+#include "async_packet_handler.hpp"
+#include <uhd/transport/bounded_buffer.hpp>
+#include <boost/bind.hpp>
+#include <uhd/utils/tasks.hpp>
+#include <uhd/utils/log.hpp>
+#include <boost/foreach.hpp>
+#include <boost/make_shared.hpp>
+
+static const double N230_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full.
+static const size_t N230_RX_FC_REQUEST_FREQ = 32; //per flow-control window
+static const size_t N230_TX_MAX_ASYNC_MESSAGES = 1000;
+static const size_t N230_TX_MAX_SPP = 4092;
+static const size_t N230_TX_FC_RESPONSE_FREQ = 10; //per flow-control window
+
+static const boost::uint32_t N230_EVENT_CODE_FLOW_CTRL = 0;
+
+namespace uhd { namespace usrp { namespace n230 {
+
+using namespace uhd::transport;
+
+n230_stream_manager::~n230_stream_manager()
+{
+}
+
+/***********************************************************************
+ * Receive streamer
+ **********************************************************************/
+n230_stream_manager::n230_stream_manager(
+ const n230_device_args_t& dev_args,
+ boost::shared_ptr<n230_resource_manager> resource_mgr,
+ boost::weak_ptr<property_tree> prop_tree
+) :
+ _dev_args(dev_args),
+ _resource_mgr(resource_mgr),
+ _tree(prop_tree)
+{
+ _async_md_queue.reset(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES));
+}
+
+/***********************************************************************
+ * Receive streamer
+ **********************************************************************/
+rx_streamer::sptr n230_stream_manager::get_rx_stream(const uhd::stream_args_t &args_)
+{
+ boost::mutex::scoped_lock lock(_stream_setup_mutex);
+
+ stream_args_t args = args_;
+
+ //setup defaults for unspecified values
+ if (args.otw_format.empty()) 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++)
+ {
+ const size_t chan = args.channels[stream_i];
+ radio_resource_t& perif = _resource_mgr->get_radio(chan);
+
+ //setup transport hints (default to a large recv buff)
+ //TODO: Propagate the device_args class into streamer in the future
+ device_addr_t device_addr = args.args;
+ if (not device_addr.has_key("recv_buff_size")) {
+ device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_buff_size());
+ }
+ if (not device_addr.has_key("recv_frame_size")) {
+ device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_frame_size());
+ }
+ if (not device_addr.has_key("num_recv_frames")) {
+ device_addr["num_recv_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_recv_frames());
+ }
+
+ transport::udp_zero_copy::buff_params buff_params_out;
+ sid_t sid;
+ zero_copy_if::sptr xport = _resource_mgr->create_transport(
+ RX_DATA, chan, device_addr, sid, buff_params_out);
+
+ //calculate packet size
+ static const size_t hdr_size = 0
+ + vrt::max_if_hdr_words32*sizeof(boost::uint32_t)
+ //+ sizeof(vrt::if_packet_info_t().tlr) //no longer using trailer
+ - sizeof(vrt::if_packet_info_t().cid) //no class id ever used
+ - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used
+ ;
+ const size_t bpp = xport->get_recv_frame_size() - hdr_size;
+ const size_t bpi = convert::get_bytes_per_item(args.otw_format);
+ size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi));
+ spp = std::min<size_t>(N230_TX_MAX_SPP, spp); //FPGA FIFO maximum for framing at full rate
+
+ //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
+ my_streamer->set_vrt_unpacker(&n230_stream_manager::_cvita_hdr_unpack);
+
+ //set the converter
+ uhd::convert::id_type id;
+ id.input_format = args.otw_format + "_item32_be";
+ 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);
+ perif.framer->set_sid(sid.reversed().get());
+ perif.framer->setup(args);
+ perif.ddc->setup(args);
+
+ //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, _1),
+ true /*flush*/
+ );
+
+ my_streamer->set_overflow_handler(stream_i, boost::bind(
+ &n230_stream_manager::_handle_overflow, this, chan
+ ));
+
+ my_streamer->set_issue_stream_cmd(stream_i, boost::bind(
+ &rx_vita_core_3000::issue_stream_command, perif.framer, _1
+ ));
+
+ const size_t fc_window = _get_rx_flow_control_window(
+ xport->get_recv_frame_size(), buff_params_out.recv_buff_size);
+ const size_t fc_handle_window = std::max<size_t>(1, fc_window / N230_RX_FC_REQUEST_FREQ);
+
+ perif.framer->configure_flow_control(fc_window);
+
+ //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(&n230_stream_manager::_handle_rx_flowctrl, sid.get(), xport, fc_cache, _1),
+ fc_handle_window,
+ true/*init*/
+ );
+
+ //Store a weak pointer to prevent a streamer->manager->streamer circular dependency
+ _rx_streamers[chan] = my_streamer; //store weak pointer
+ _rx_stream_cached_args[chan] = args;
+
+ //Sets tick and samp rates on all streamer
+ update_tick_rate(_get_tick_rate());
+
+ //TODO: Find a way to remove this dependency
+ property_tree::sptr prop_tree = _tree.lock();
+ if (prop_tree) {
+ //TODO: Update this to support multiple motherboards
+ const fs_path mb_path = "/mboards/0";
+ prop_tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update();
+ }
+ }
+ update_stream_states();
+
+ return my_streamer;
+}
+
+/***********************************************************************
+ * Transmit streamer
+ **********************************************************************/
+tx_streamer::sptr n230_stream_manager::get_tx_stream(const uhd::stream_args_t &args_)
+{
+ boost::mutex::scoped_lock lock(_stream_setup_mutex);
+
+ uhd::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("n230_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_queue_t> async_md(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES));
+
+ boost::shared_ptr<sph::send_packet_streamer> my_streamer;
+ for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++)
+ {
+ const size_t chan = args.channels[stream_i];
+ radio_resource_t& perif = _resource_mgr->get_radio(chan);
+
+ //setup transport hints (default to a large recv buff)
+ //TODO: Propagate the device_args class into streamer in the future
+ device_addr_t device_addr = args.args;
+ if (not device_addr.has_key("send_buff_size")) {
+ device_addr["send_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_buff_size());
+ }
+ if (not device_addr.has_key("send_frame_size")) {
+ device_addr["send_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_frame_size());
+ }
+ if (not device_addr.has_key("num_send_frames")) {
+ device_addr["num_send_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_send_frames());
+ }
+
+ transport::udp_zero_copy::buff_params buff_params_out;
+ sid_t sid;
+ zero_copy_if::sptr xport = _resource_mgr->create_transport(
+ TX_DATA, chan, device_addr, sid, buff_params_out);
+
+ //calculate packet size
+ static const size_t hdr_size = 0
+ + vrt::num_vrl_words32*sizeof(boost::uint32_t)
+ + vrt::max_if_hdr_words32*sizeof(boost::uint32_t)
+ //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer
+ - sizeof(vrt::if_packet_info_t().cid) //no class id ever used
+ - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used
+ ;
+ const size_t bpp = xport->get_send_frame_size() - hdr_size;
+ 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());
+ my_streamer->set_vrt_packer(&n230_stream_manager::_cvita_hdr_pack);
+
+ //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_be";
+ 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(
+ bpp, device_addr.cast<size_t>("send_buff_size", _dev_args.get_send_buff_size()));
+ //In packets
+ const size_t fc_handle_window = (fc_window / N230_TX_FC_RESPONSE_FREQ);
+
+ perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window);
+ boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t());
+ fc_cache->stream_channel = stream_i;
+ fc_cache->device_channel = chan;
+ fc_cache->async_queue = async_md;
+ fc_cache->old_async_queue = _async_md_queue;
+
+ tick_rate_retriever_t get_tick_rate_fn = boost::bind(&n230_stream_manager::_get_tick_rate, this);
+ task::sptr task = task::make(
+ boost::bind(&n230_stream_manager::_handle_tx_async_msgs,
+ fc_cache, xport, get_tick_rate_fn));
+
+ //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(&n230_stream_manager::_get_tx_buff_with_flowctrl, task, fc_cache, xport, fc_window, _1)
+ );
+ //Give the streamer a functor handled received async messages
+ my_streamer->set_async_receiver(
+ boost::bind(&async_md_queue_t::pop_with_timed_wait, async_md, _1, _2)
+ );
+ my_streamer->set_xport_chan_sid(stream_i, true, sid.get());
+ my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet
+
+ //Store a weak pointer to prevent a streamer->manager->streamer circular dependency
+ _tx_streamers[chan] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer);
+ _tx_stream_cached_args[chan] = args;
+
+ //Sets tick and samp rates on all streamer
+ update_tick_rate(_get_tick_rate());
+
+ //TODO: Find a way to remove this dependency
+ property_tree::sptr prop_tree = _tree.lock();
+ if (prop_tree) {
+ //TODO: Update this to support multiple motherboards
+ const fs_path mb_path = "/mboards/0";
+ prop_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update();
+ }
+ }
+ update_stream_states();
+
+ return my_streamer;
+}
+
+/***********************************************************************
+ * Async Message Receiver
+ **********************************************************************/
+bool n230_stream_manager::recv_async_msg(async_metadata_t &async_metadata, double timeout)
+{
+ return _async_md_queue->pop_with_timed_wait(async_metadata, timeout);
+}
+
+/***********************************************************************
+ * Sample Rate Updaters
+ **********************************************************************/
+void n230_stream_manager::update_rx_samp_rate(const size_t dspno, const double rate)
+{
+ boost::shared_ptr<sph::recv_packet_streamer> my_streamer =
+ boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock());
+ if (not my_streamer) return;
+ my_streamer->set_samp_rate(rate);
+ const double adj = _resource_mgr->get_radio(dspno).ddc->get_scaling_adjustment();
+ my_streamer->set_scale_factor(adj);
+}
+
+void n230_stream_manager::update_tx_samp_rate(const size_t dspno, const double rate)
+{
+ boost::shared_ptr<sph::send_packet_streamer> my_streamer =
+ boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock());
+ if (not my_streamer) return;
+ my_streamer->set_samp_rate(rate);
+ const double adj = _resource_mgr->get_radio(dspno).duc->get_scaling_adjustment();
+ my_streamer->set_scale_factor(adj);
+}
+
+/***********************************************************************
+ * Tick Rate Updater
+ **********************************************************************/
+void n230_stream_manager::update_tick_rate(const double rate)
+{
+ for (size_t i = 0; i < fpga::NUM_RADIOS; i++) {
+ radio_resource_t& perif = _resource_mgr->get_radio(i);
+
+ boost::shared_ptr<sph::recv_packet_streamer> my_rx_streamer =
+ boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock());
+ if (my_rx_streamer) my_rx_streamer->set_tick_rate(rate);
+ perif.framer->set_tick_rate(rate);
+
+ boost::shared_ptr<sph::send_packet_streamer> my_tx_streamer =
+ boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock());
+ if (my_tx_streamer) my_tx_streamer->set_tick_rate(rate);
+ }
+}
+
+/***********************************************************************
+ * Stream State Updater
+ **********************************************************************/
+void n230_stream_manager::update_stream_states()
+{
+ //extract settings from state variables
+ const bool enb_tx0 = bool(_tx_streamers[0].lock());
+ const bool enb_rx0 = bool(_rx_streamers[0].lock());
+ const bool enb_tx1 = bool(_tx_streamers[1].lock());
+ const bool enb_rx1 = bool(_rx_streamers[1].lock());
+
+ fe_state_t fe0_state = NONE_STREAMING;
+ if (enb_tx0 && enb_rx0) fe0_state = TXRX_STREAMING;
+ else if (enb_tx0) fe0_state = TX_STREAMING;
+ else if (enb_rx0) fe0_state = RX_STREAMING;
+
+ fe_state_t fe1_state = NONE_STREAMING;
+ if (enb_tx1 && enb_rx1) fe1_state = TXRX_STREAMING;
+ else if (enb_tx1) fe1_state = TX_STREAMING;
+ else if (enb_rx1) fe1_state = RX_STREAMING;
+
+ _resource_mgr->get_frontend_ctrl().set_stream_state(fe0_state, fe1_state);
+}
+
+size_t n230_stream_manager::_get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size)
+{
+ double sw_buff_max = sw_buff_size * N230_RX_SW_BUFF_FULL_FACTOR;
+ size_t window_in_pkts = (static_cast<size_t>(sw_buff_max) / 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;
+}
+
+void n230_stream_manager::_handle_overflow(const size_t i)
+{
+ boost::shared_ptr<sph::recv_packet_streamer> my_streamer =
+ boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock());
+ if (my_streamer->get_num_channels() == 2) {
+ //MIMO
+ //find out if we were in continuous mode before stopping
+ const bool in_continuous_streaming_mode = _resource_mgr->get_radio(i).framer->in_continuous_streaming_mode();
+ //stop streaming
+ my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS);
+ //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 = _resource_mgr->get_radio(i).time->get_time_now() + time_spec_t(0.01);
+ my_streamer->issue_stream_cmd(stream_cmd);
+ }
+ } else {
+ _resource_mgr->get_radio(i).framer->handle_overflow();
+ }
+}
+
+void n230_stream_manager::_handle_rx_flowctrl(
+ const sid_t& sid,
+ zero_copy_if::sptr xport,
+ 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 seq32
+ size_t& seq_sw = fc_cache->last_seq_in;
+ const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK;
+ if (last_seq < seq_hw) seq_sw += (HW_SEQ_NUM_MASK + 1);
+ seq_sw &= ~HW_SEQ_NUM_MASK;
+ seq_sw |= 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 = RXFC_PACKET_LEN_IN_WORDS;
+ packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t);
+ packet_info.packet_count = seq_sw;
+ 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;
+
+ //load header
+ _cvita_hdr_pack(pkt, packet_info);
+
+ //load payload
+ pkt[packet_info.num_header_words32 + RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(N230_EVENT_CODE_FLOW_CTRL);
+ pkt[packet_info.num_header_words32 + RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq_sw);
+
+ //send the buffer over the interface
+ buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32));
+}
+
+void n230_stream_manager::_handle_tx_async_msgs(
+ boost::shared_ptr<tx_fc_cache_t> fc_cache,
+ zero_copy_if::sptr xport,
+ tick_rate_retriever_t 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
+ uint32_t (*endian_conv)(uint32_t) = uhd::ntohx;
+ try {
+ _cvita_hdr_unpack(packet_buff, if_packet_info);
+ endian_conv = uhd::ntohx;
+ } catch(const std::exception &ex) {
+ UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl;
+ return;
+ }
+
+ //fill in the async metadata
+ async_metadata_t metadata;
+ load_metadata_from_buff(
+ endian_conv, metadata, if_packet_info, packet_buff,
+ get_tick_rate(), fc_cache->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 == N230_EVENT_CODE_FLOW_CTRL or
+ metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK
+ ) {
+ 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 != N230_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);
+ }
+}
+
+managed_send_buffer::sptr n230_stream_manager::_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_pkt_window,
+ const double timeout)
+{
+ while (true)
+ {
+ const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK);
+ if ((delta & HW_SEQ_NUM_MASK) <= fc_pkt_window) break;
+
+ 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;
+}
+
+size_t n230_stream_manager::_get_tx_flow_control_window(
+ size_t payload_size,
+ size_t hw_buff_size)
+{
+ size_t window_in_pkts = hw_buff_size / payload_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;
+}
+
+double n230_stream_manager::_get_tick_rate()
+{
+ return _resource_mgr->get_clk_pps_ctrl().get_tick_rate();
+}
+
+void n230_stream_manager::_cvita_hdr_unpack(
+ const boost::uint32_t *packet_buff,
+ vrt::if_packet_info_t &if_packet_info)
+{
+ if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR;
+ return vrt::if_hdr_unpack_be(packet_buff, if_packet_info);
+}
+
+void n230_stream_manager::_cvita_hdr_pack(
+ boost::uint32_t *packet_buff,
+ vrt::if_packet_info_t &if_packet_info)
+{
+ if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR;
+ return vrt::if_hdr_pack_be(packet_buff, if_packet_info);
+}
+
+}}} //namespace
diff --git a/host/lib/usrp/n230/n230_stream_manager.hpp b/host/lib/usrp/n230/n230_stream_manager.hpp
new file mode 100644
index 000000000..7a496c4e9
--- /dev/null
+++ b/host/lib/usrp/n230/n230_stream_manager.hpp
@@ -0,0 +1,151 @@
+//
+// 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_N230_STREAM_MANAGER_HPP
+#define INCLUDED_N230_STREAM_MANAGER_HPP
+
+#include "time_core_3000.hpp"
+#include "rx_vita_core_3000.hpp"
+#include <uhd/types/sid.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/metadata.hpp>
+#include <uhd/transport/zero_copy.hpp>
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/transport/vrt_if_packet.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/utils/tasks.hpp>
+#include <boost/smart_ptr.hpp>
+#include "n230_device_args.hpp"
+#include "n230_resource_manager.hpp"
+
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_stream_manager : public boost::noncopyable
+{
+public: //Methods
+ n230_stream_manager(
+ const n230_device_args_t& dev_args,
+ boost::shared_ptr<n230_resource_manager> resource_mgr,
+ boost::weak_ptr<property_tree> prop_tree);
+ virtual ~n230_stream_manager();
+
+ rx_streamer::sptr get_rx_stream(
+ const uhd::stream_args_t &args);
+
+ tx_streamer::sptr get_tx_stream(
+ const uhd::stream_args_t &args_);
+
+ bool recv_async_msg(
+ async_metadata_t &async_metadata,
+ double timeout);
+
+ void update_stream_states();
+
+ void update_rx_samp_rate(
+ const size_t dspno, const double rate);
+
+ void update_tx_samp_rate(
+ const size_t dspno, const double rate);
+
+ void update_tick_rate(
+ const double rate);
+
+private:
+ typedef transport::bounded_buffer<async_metadata_t> async_md_queue_t;
+
+ struct rx_fc_cache_t
+ {
+ rx_fc_cache_t():
+ last_seq_in(0){}
+ size_t last_seq_in;
+ };
+
+ struct tx_fc_cache_t
+ {
+ tx_fc_cache_t():
+ 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;
+ transport::bounded_buffer<size_t> seq_queue;
+ boost::shared_ptr<async_md_queue_t> async_queue;
+ boost::shared_ptr<async_md_queue_t> old_async_queue;
+ };
+
+ typedef boost::function<double(void)> tick_rate_retriever_t;
+
+ void _handle_overflow(const size_t i);
+
+ double _get_tick_rate();
+
+ static size_t _get_rx_flow_control_window(
+ size_t frame_size, size_t sw_buff_size);
+
+ static void _handle_rx_flowctrl(
+ const sid_t& sid,
+ transport::zero_copy_if::sptr xport,
+ boost::shared_ptr<rx_fc_cache_t> fc_cache,
+ const size_t last_seq);
+
+ static void _handle_tx_async_msgs(
+ boost::shared_ptr<tx_fc_cache_t> guts,
+ transport::zero_copy_if::sptr xport,
+ tick_rate_retriever_t get_tick_rate);
+
+ static transport::managed_send_buffer::sptr _get_tx_buff_with_flowctrl(
+ task::sptr /*holds ref*/,
+ boost::shared_ptr<tx_fc_cache_t> guts,
+ transport::zero_copy_if::sptr xport,
+ size_t fc_pkt_window,
+ const double timeout);
+
+ static size_t _get_tx_flow_control_window(
+ size_t payload_size,
+ size_t hw_buff_size);
+
+ static void _cvita_hdr_unpack(
+ const boost::uint32_t *packet_buff,
+ transport::vrt::if_packet_info_t &if_packet_info);
+
+ static void _cvita_hdr_pack(
+ boost::uint32_t *packet_buff,
+ transport::vrt::if_packet_info_t &if_packet_info);
+
+ const n230_device_args_t _dev_args;
+ boost::shared_ptr<n230_resource_manager> _resource_mgr;
+ //TODO: Find a way to remove this dependency
+ boost::weak_ptr<property_tree> _tree;
+
+ boost::mutex _stream_setup_mutex;
+ uhd::msg_task::sptr _async_task;
+ boost::shared_ptr<async_md_queue_t> _async_md_queue;
+ boost::weak_ptr<uhd::tx_streamer> _tx_streamers[fpga::NUM_RADIOS];
+ boost::weak_ptr<uhd::rx_streamer> _rx_streamers[fpga::NUM_RADIOS];
+ stream_args_t _tx_stream_cached_args[fpga::NUM_RADIOS];
+ stream_args_t _rx_stream_cached_args[fpga::NUM_RADIOS];
+
+ static const boost::uint32_t HW_SEQ_NUM_MASK = 0xFFF;
+};
+
+}}} //namespace
+
+#endif //INCLUDED_N230_STREAM_MANAGER_HPP
diff --git a/host/lib/usrp/n230/n230_uart.cpp b/host/lib/usrp/n230/n230_uart.cpp
new file mode 100644
index 000000000..20936c303
--- /dev/null
+++ b/host/lib/usrp/n230/n230_uart.cpp
@@ -0,0 +1,131 @@
+//
+// Copyright 2013 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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 "n230_uart.hpp"
+
+#include <uhd/transport/bounded_buffer.hpp>
+#include <uhd/transport/vrt_if_packet.hpp>
+#include <uhd/utils/byteswap.hpp>
+#include <uhd/utils/msg.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <uhd/exception.hpp>
+
+using namespace uhd;
+using namespace uhd::transport;
+
+namespace uhd { namespace usrp { namespace n230 {
+
+struct n230_uart_impl : n230_uart
+{
+ n230_uart_impl(zero_copy_if::sptr xport, const boost::uint32_t sid):
+ _xport(xport),
+ _sid(sid),
+ _count(0),
+ _char_queue(4096)
+ {
+ //this default baud divider is over 9000
+ this->set_baud_divider(9001);
+
+ //create a task to handle incoming packets
+ _recv_task = uhd::task::make(boost::bind(&n230_uart_impl::handle_recv, this));
+ }
+
+ void send_char(const char ch)
+ {
+ managed_send_buffer::sptr buff = _xport->get_send_buff();
+ UHD_ASSERT_THROW(bool(buff));
+
+ vrt::if_packet_info_t packet_info;
+ packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR;
+ 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 = _count++;
+ 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;
+
+ boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>();
+ vrt::if_hdr_pack_le(packet_buff, packet_info);
+ packet_buff[packet_info.num_header_words32+0] = uhd::htonx(boost::uint32_t(_baud_div));
+ packet_buff[packet_info.num_header_words32+1] = uhd::htonx(boost::uint32_t(ch));
+ buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t));
+ }
+
+ void write_uart(const std::string &buff)
+ {
+ static bool r_sent = false;
+ for (size_t i = 0; i < buff.size(); i++)
+ {
+ if (buff[i] == '\n' and not r_sent) this->send_char('\r');
+ this->send_char(buff[i]);
+ r_sent = (buff[i] == '\r');
+ }
+ }
+
+ std::string read_uart(double timeout)
+ {
+ std::string line;
+ char ch = '\0';
+ while (_char_queue.pop_with_timed_wait(ch, timeout))
+ {
+ if (ch == '\r') continue;
+ line += std::string(&ch, 1);
+ if (ch == '\n') return line;
+ }
+ return line;
+ }
+
+ void handle_recv(void)
+ {
+ managed_recv_buffer::sptr buff = _xport->get_recv_buff();
+ if (not buff)
+ return;
+ const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>();
+ vrt::if_packet_info_t packet_info;
+ packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR;
+ packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t);
+ vrt::if_hdr_unpack_be(packet_buff, packet_info);
+ const char ch = char(uhd::ntohx(packet_buff[packet_info.num_header_words32+1]));
+ _char_queue.push_with_pop_on_full(ch);
+ }
+
+ void set_baud_divider(const double baud_div)
+ {
+ _baud_div = size_t(baud_div + 0.5);
+ }
+
+ const zero_copy_if::sptr _xport;
+ const boost::uint32_t _sid;
+ size_t _count;
+ size_t _baud_div;
+ bounded_buffer<char> _char_queue;
+ uhd::task::sptr _recv_task;
+};
+
+
+n230_uart::sptr n230_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid)
+{
+ return n230_uart::sptr(new n230_uart_impl(xport, sid));
+}
+
+}}} //namespace
diff --git a/host/lib/usrp/n230/n230_uart.hpp b/host/lib/usrp/n230/n230_uart.hpp
new file mode 100644
index 000000000..0bde12ab2
--- /dev/null
+++ b/host/lib/usrp/n230/n230_uart.hpp
@@ -0,0 +1,38 @@
+//
+// Copyright 2013 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// 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_N230_UART_HPP
+#define INCLUDED_N230_UART_HPP
+
+#include <uhd/transport/zero_copy.hpp>
+#include <uhd/types/serial.hpp> //uart iface
+#include <uhd/utils/tasks.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/utility.hpp>
+namespace uhd { namespace usrp { namespace n230 {
+
+class n230_uart: boost::noncopyable, public uhd::uart_iface
+{
+public:
+ typedef boost::shared_ptr<n230_uart> sptr;
+ static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid);
+ virtual void set_baud_divider(const double baud_div) = 0;
+};
+
+}}} //namespace
+
+#endif /* INCLUDED_N230_UART_HPP */
diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt
index 47344e841..6924ba3b0 100644
--- a/host/lib/usrp/usrp1/CMakeLists.txt
+++ b/host/lib/usrp/usrp1/CMakeLists.txt
@@ -22,8 +22,6 @@
########################################################################
# Conditionally configure the USRP1 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF)
-
IF(ENABLE_USRP1)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp
diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp
index 4c3141d9e..5640e8dae 100644
--- a/host/lib/usrp/usrp1/dboard_iface.cpp
+++ b/host/lib/usrp/usrp1/dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2010-2012 Ettus Research LLC
+// Copyright 2010-2012,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
@@ -63,6 +63,7 @@
using namespace uhd;
using namespace uhd::usrp;
+using namespace uhd::usrp::gpio_atr;
using namespace boost::assign;
static const dboard_id_t tvrx_id(0x0040);
@@ -106,12 +107,23 @@ public:
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_pin_ctrl(unit_t, boost::uint16_t);
void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t);
void _set_gpio_ddr(unit_t, boost::uint16_t);
void _set_gpio_out(unit_t, boost::uint16_t);
- void set_gpio_debug(unit_t, int);
- boost::uint16_t read_gpio(unit_t);
+
+ 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);
@@ -131,6 +143,7 @@ public:
double get_clock_rate(unit_t);
void set_clock_enabled(unit_t, bool);
double get_codec_rate(unit_t);
+ void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn);
private:
usrp1_iface::sptr _iface;
@@ -139,6 +152,8 @@ private:
const usrp1_impl::dboard_slot_t _dboard_slot;
const double &_master_clock_rate;
const dboard_id_t _rx_dboard_id;
+ uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr;
+ uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs;
};
/***********************************************************************
@@ -217,6 +232,65 @@ double usrp1_dboard_iface::get_codec_rate(unit_t){
/***********************************************************************
* GPIO
**********************************************************************/
+template <typename T>
+static T shadow_it(T &shadow, const T &value, const T &mask){
+ shadow = (shadow & ~mask) | (value & mask);
+ return shadow;
+}
+
+void usrp1_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _set_pin_ctrl(unit, shadow_it(_pin_ctrl[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)));
+}
+
+boost::uint32_t usrp1_dboard_iface::get_pin_ctrl(unit_t unit){
+ return _pin_ctrl[unit];
+}
+
+void usrp1_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){
+ _set_atr_reg(unit, reg, shadow_it(_atr_regs[unit][reg], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)));
+}
+
+boost::uint32_t usrp1_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){
+ return _atr_regs[unit][reg];
+}
+
+void usrp1_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _set_gpio_ddr(unit, shadow_it(_gpio_ddr[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)));
+}
+
+boost::uint32_t usrp1_dboard_iface::get_gpio_ddr(unit_t unit){
+ return _gpio_ddr[unit];
+}
+
+void usrp1_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _set_gpio_out(unit, shadow_it(_gpio_out[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)));
+}
+
+boost::uint32_t usrp1_dboard_iface::get_gpio_out(unit_t unit){
+ return _gpio_out[unit];
+}
+
+boost::uint32_t usrp1_dboard_iface::read_gpio(unit_t unit)
+{
+ boost::uint32_t out_value;
+
+ if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A)
+ out_value = _iface->peek32(1);
+ else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
+ out_value = _iface->peek32(2);
+ else
+ UHD_THROW_INVALID_CODE_PATH();
+
+ switch(unit) {
+ case UNIT_RX:
+ return (boost::uint32_t)((out_value >> 16) & 0x0000ffff);
+ case UNIT_TX:
+ return (boost::uint32_t)((out_value >> 0) & 0x0000ffff);
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+ UHD_ASSERT_THROW(false);
+}
+
void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value)
{
switch(unit) {
@@ -232,6 +306,7 @@ void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value)
else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
_iface->poke32(FR_ATR_MASK_2, value);
break;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
}
@@ -250,6 +325,7 @@ void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value)
else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
_iface->poke32(FR_OE_2, 0xffff0000 | value);
break;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
}
@@ -268,34 +344,10 @@ void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value)
else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
_iface->poke32(FR_IO_2, 0xffff0000 | value);
break;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
}
-void usrp1_dboard_iface::set_gpio_debug(unit_t, int)
-{
- /* NOP */
-}
-
-boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit)
-{
- boost::uint32_t out_value;
-
- if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A)
- out_value = _iface->peek32(1);
- else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
- out_value = _iface->peek32(2);
- else
- UHD_THROW_INVALID_CODE_PATH();
-
- switch(unit) {
- case UNIT_RX:
- return (boost::uint16_t)((out_value >> 16) & 0x0000ffff);
- case UNIT_TX:
- return (boost::uint16_t)((out_value >> 0) & 0x0000ffff);
- }
- UHD_ASSERT_THROW(false);
-}
-
void usrp1_dboard_iface::_set_atr_reg(unit_t unit,
atr_reg_t atr, boost::uint16_t value)
{
@@ -316,6 +368,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit,
else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
_iface->poke32(FR_ATR_RXVAL_2, value);
break;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
} else if (atr == ATR_REG_FULL_DUPLEX) {
switch(unit) {
@@ -331,6 +384,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit,
else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B)
_iface->poke32(FR_ATR_TXVAL_2, value);
break;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
}
}
@@ -361,6 +415,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit,
return SPI_ENABLE_RX_B;
else
break;
+ default:
+ break;
}
UHD_THROW_INVALID_CODE_PATH();
}
@@ -429,3 +485,23 @@ double usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit,
return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]);
}
+
+/***********************************************************************
+ * Unsupported
+ **********************************************************************/
+
+void usrp1_dboard_iface::set_command_time(const uhd::time_spec_t&)
+{
+ throw uhd::not_implemented_error("timed command support not implemented");
+}
+
+uhd::time_spec_t usrp1_dboard_iface::get_command_time()
+{
+ throw uhd::not_implemented_error("timed command support not implemented");
+}
+
+void usrp1_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&)
+{
+ throw uhd::not_implemented_error("fe connection configuration support not implemented");
+}
+
diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp
index dbd5408e8..5e1a70a8f 100644
--- a/host/lib/usrp/usrp1/usrp1_impl.cpp
+++ b/host/lib/usrp/usrp1/usrp1_impl.cpp
@@ -209,13 +209,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
const fs_path mb_path = "/mboards/0";
_tree->create<std::string>(mb_path / "name").set("USRP1");
_tree->create<std::string>(mb_path / "load_eeprom")
- .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1));
////////////////////////////////////////////////////////////////////
// create user-defined control objects
////////////////////////////////////////////////////////////////////
_tree->create<std::pair<boost::uint8_t, boost::uint32_t> >(mb_path / "user" / "regs")
- .subscribe(boost::bind(&usrp1_impl::set_reg, this, _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_reg, this, _1));
////////////////////////////////////////////////////////////////////
// setup the mboard eeprom
@@ -223,7 +223,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, USRP1_EEPROM_MAP_KEY);
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(mb_eeprom)
- .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1));
////////////////////////////////////////////////////////////////////
// create clock control objects
@@ -247,7 +247,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
}
UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl;
_tree->create<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&usrp1_impl::update_tick_rate, this, _1))
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tick_rate, this, _1))
.set(_master_clock_rate);
////////////////////////////////////////////////////////////////////
@@ -260,13 +260,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
_tree->create<std::string>(rx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range);
_tree->create<double>(rx_codec_path / "gains/pga/value")
- .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1))
+ .set_coercer(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1))
.set(0.0);
_tree->create<std::string>(tx_codec_path / "name").set("ad9522");
_tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range);
_tree->create<double>(tx_codec_path / "gains/pga/value")
- .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1))
- .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec))
+ .add_coerced_subscriber(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1))
+ .set_publisher(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec))
.set(0.0);
}
@@ -281,18 +281,18 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
////////////////////////////////////////////////////////////////////
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
.set(subdev_spec_t())
- .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
.set(subdev_spec_t())
- .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1));
BOOST_FOREACH(const std::string &db, _dbc.keys()){
const fs_path rx_fe_path = mb_path / "rx_frontends" / db;
_tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1))
+ .set_coercer(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
- .subscribe(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1))
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1))
.set(true);
}
@@ -303,19 +303,19 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){
fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
_tree->create<meta_range_t>(rx_dsp_path / "rate/range")
- .publish(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this));
+ .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this));
_tree->create<double>(rx_dsp_path / "rate/value")
.set(1e6) //some default rate
- .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1));
+ .set_coercer(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1));
_tree->create<double>(rx_dsp_path / "freq/value")
- .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1));
+ .set_coercer(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1));
_tree->create<meta_range_t>(rx_dsp_path / "freq/range")
- .publish(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this));
+ .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd");
if (dspno == 0){
- //only subscribe the callback for dspno 0 since it will stream all dsps
+ //only add_coerced_subscriber the callback for dspno 0 since it will stream all dsps
_tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1));
}
}
@@ -326,22 +326,22 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){
fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno);
_tree->create<meta_range_t>(tx_dsp_path / "rate/range")
- .publish(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this));
+ .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this));
_tree->create<double>(tx_dsp_path / "rate/value")
.set(1e6) //some default rate
- .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1));
+ .set_coercer(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1));
_tree->create<double>(tx_dsp_path / "freq/value")
- .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1));
+ .set_coercer(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1));
_tree->create<meta_range_t>(tx_dsp_path / "freq/range")
- .publish(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this));
+ .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this));
}
////////////////////////////////////////////////////////////////////
// create time control objects
////////////////////////////////////////////////////////////////////
_tree->create<time_spec_t>(mb_path / "time/now")
- .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl))
- .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1));
+ .set_publisher(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl))
+ .add_coerced_subscriber(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1));
_tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal"));
_tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none"));
@@ -365,24 +365,23 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){
//create the properties and register subscribers
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom")
.set(rx_db_eeprom)
- .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom")
.set(tx_db_eeprom)
- .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom")
.set(gdb_eeprom)
- .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1));
+ .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1));
//create a new dboard interface and manager
- _dbc[db].dboard_iface = make_dboard_iface(
+ dboard_iface::sptr dboard_iface = make_dboard_iface(
_iface, _dbc[db].codec,
(db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B,
_master_clock_rate, rx_db_eeprom.id
);
- _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface);
_dbc[db].dboard_manager = dboard_manager::make(
rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id,
- _dbc[db].dboard_iface, _tree->subtree(mb_path / "dboards" / db)
+ dboard_iface, _tree->subtree(mb_path / "dboards" / db)
);
//init the subdev specs if we have a dboard (wont leave this loop empty)
diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp
index 012bc0794..da901bd6c 100644
--- a/host/lib/usrp/usrp1/usrp1_impl.hpp
+++ b/host/lib/usrp/usrp1/usrp1_impl.hpp
@@ -92,7 +92,6 @@ private:
uhd::transport::usb_zero_copy::sptr _data_transport;
struct db_container_type{
usrp1_codec_ctrl::sptr codec;
- uhd::usrp::dboard_iface::sptr dboard_iface;
uhd::usrp::dboard_manager::sptr dboard_manager;
};
uhd::dict<std::string, db_container_type> _dbc;
diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt
index d9894adaf..edf77a654 100644
--- a/host/lib/usrp/usrp2/CMakeLists.txt
+++ b/host/lib/usrp/usrp2/CMakeLists.txt
@@ -22,8 +22,6 @@
########################################################################
# Conditionally configure the USRP2 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF)
-
IF(ENABLE_USRP2)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp
diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp
index 7bb69c7b7..a6ba1e0b7 100644
--- a/host/lib/usrp/usrp2/dboard_iface.cpp
+++ b/host/lib/usrp/usrp2/dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2010-2012,2015 Ettus Research LLC
+// Copyright 2010-2012,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
@@ -54,12 +54,16 @@ public:
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, boost::uint16_t);
- void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t);
- void _set_gpio_ddr(unit_t, boost::uint16_t);
- void _set_gpio_out(unit_t, boost::uint16_t);
- void set_gpio_debug(unit_t, int);
- boost::uint16_t read_gpio(unit_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);
@@ -71,6 +75,7 @@ public:
std::vector<double> get_clock_rates(unit_t);
void set_clock_enabled(unit_t, bool);
double get_codec_rate(unit_t);
+ void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn);
void write_spi(
unit_t unit,
@@ -149,18 +154,22 @@ usrp2_dboard_iface::~usrp2_dboard_iface(void){
* Clocks
**********************************************************************/
void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
_clock_rates[unit] = rate; //set to shadow
switch(unit){
case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return;
case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return;
+ default: UHD_THROW_INVALID_CODE_PATH();
}
}
double usrp2_dboard_iface::get_clock_rate(unit_t unit){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
return _clock_rates[unit]; //get from shadow
}
std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
switch(unit){
case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock();
case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock();
@@ -169,40 +178,56 @@ std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){
}
void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
switch(unit){
- case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return;
- case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return;
+ case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return;
+ case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return;
+ case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return;
}
}
-double usrp2_dboard_iface::get_codec_rate(unit_t){
+double usrp2_dboard_iface::get_codec_rate(unit_t unit){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
return _clock_ctrl->get_master_clock_rate();
}
+
/***********************************************************************
* GPIO
**********************************************************************/
-void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){
- return _gpio->set_pin_ctrl(unit, value);
+void usrp2_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_ddr(unit, value);
+boost::uint32_t usrp2_dboard_iface::get_pin_ctrl(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit));
}
-void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){
- return _gpio->set_gpio_out(unit, value);
+void usrp2_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){
- return _gpio->read_gpio(unit);
+boost::uint32_t usrp2_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){
+ return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg));
+}
+
+void usrp2_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
+}
+
+boost::uint32_t usrp2_dboard_iface::get_gpio_ddr(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit));
}
-void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){
- return _gpio->set_atr_reg(unit, atr, value);
+void usrp2_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){
+ _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask));
}
-void usrp2_dboard_iface::set_gpio_debug(unit_t, int){
- throw uhd::not_implemented_error("no set_gpio_debug implemented");
+boost::uint32_t usrp2_dboard_iface::get_gpio_out(unit_t unit){
+ return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit));
+}
+
+boost::uint32_t usrp2_dboard_iface::read_gpio(unit_t unit){
+ return _gpio->read_gpio(unit);
}
/***********************************************************************
@@ -219,6 +244,7 @@ void usrp2_dboard_iface::write_spi(
boost::uint32_t data,
size_t num_bits
){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
_spi_iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits);
}
@@ -228,6 +254,7 @@ boost::uint32_t usrp2_dboard_iface::read_write_spi(
boost::uint32_t data,
size_t num_bits
){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
return _spi_iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits);
}
@@ -250,6 +277,7 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){
(UNIT_RX, SPI_SS_RX_DAC)
(UNIT_TX, SPI_SS_TX_DAC)
;
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
_spi_iface->write_spi(
unit_to_spi_dac[unit], spi_config_t::EDGE_FALL,
_dac_regs[unit].get_reg(), 24
@@ -257,6 +285,8 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){
}
void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+
_dac_regs[unit].data = boost::math::iround(4095*value/3.3);
_dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N;
@@ -285,6 +315,8 @@ double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){
(UNIT_TX, SPI_SS_TX_ADC)
;
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+
//setup spi config args
spi_config_t config;
config.mosi_edge = spi_config_t::EDGE_FALL;
@@ -320,3 +352,8 @@ void usrp2_dboard_iface::set_command_time(const uhd::time_spec_t& t)
{
_wb_iface->set_time(t);
}
+
+void usrp2_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&)
+{
+ throw uhd::not_implemented_error("fe connection configuration support not implemented");
+}
diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp
index 7b59dfaf1..b0c29392c 100644
--- a/host/lib/usrp/usrp2/usrp2_impl.cpp
+++ b/host/lib/usrp/usrp2/usrp2_impl.cpp
@@ -474,15 +474,15 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
////////////////////////////////////////////////////////////////
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(_mbc[mb].iface->mb_eeprom)
- .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1));
////////////////////////////////////////////////////////////////
// create clock control objects
////////////////////////////////////////////////////////////////
_mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface);
_tree->create<double>(mb_path / "tick_rate")
- .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock))
- .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1));
+ .set_publisher(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock))
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tick_rate, this, _1));
////////////////////////////////////////////////////////////////
// create codec control objects
@@ -500,10 +500,10 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
_tree->create<std::string>(rx_codec_path / "name").set("ads62p44");
_tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5));
_tree->create<double>(rx_codec_path / "gains/digital/value")
- .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0);
+ .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0);
_tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05));
_tree->create<double>(rx_codec_path / "gains/fine/value")
- .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0);
+ .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0);
}break;
case usrp2_iface::USRP2_REV3:
@@ -550,7 +550,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name));
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name));
}
}
else
@@ -563,9 +563,9 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
// and do the misc mboard sensors
////////////////////////////////////////////////////////////////
_tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked")
- .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb));
+ .set_publisher(boost::bind(&usrp2_impl::get_mimo_locked, this, mb));
_tree->create<sensor_value_t>(mb_path / "sensors/ref_locked")
- .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb));
+ .set_publisher(boost::bind(&usrp2_impl::get_ref_locked, this, mb));
////////////////////////////////////////////////////////////////
// create frontend control objects
@@ -578,27 +578,27 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
);
_tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
- .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1));
_tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
- .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1));
const fs_path rx_fe_path = mb_path / "rx_frontends" / "A";
const fs_path tx_fe_path = mb_path / "tx_frontends" / "A";
_tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1))
+ .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
- .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1))
.set(true);
_tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value")
- .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1))
+ .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
_tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value")
- .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1))
+ .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1))
.set(std::complex<double>(0.0, 0.0));
////////////////////////////////////////////////////////////////
@@ -613,20 +613,20 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){
_mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1));
fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
_tree->create<meta_range_t>(rx_dsp_path / "rate/range")
- .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno]));
_tree->create<double>(rx_dsp_path / "rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1))
- .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1))
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1));
_tree->create<double>(rx_dsp_path / "freq/value")
- .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1));
+ .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1));
_tree->create<meta_range_t>(rx_dsp_path / "freq/range")
- .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno]));
+ .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno]));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
- .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1));
+ .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1));
}
////////////////////////////////////////////////////////////////
@@ -637,17 +637,17 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
);
_mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1));
+ .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range")
- .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp));
_tree->create<double>(mb_path / "tx_dsps/0/rate/value")
.set(1e6) //some default
- .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1))
- .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1))
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1));
_tree->create<double>(mb_path / "tx_dsps/0/freq/value")
- .coerce(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1));
+ .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1));
_tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range")
- .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp));
+ .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp));
//setup dsp flow control
const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20);
@@ -670,22 +670,22 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles
);
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1));
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1));
_tree->create<time_spec_t>(mb_path / "time/now")
- .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64))
- .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1));
_tree->create<time_spec_t>(mb_path / "time/pps")
- .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64))
- .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1));
+ .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1));
//setup time source props
_tree->create<std::string>(mb_path / "time_source/value")
- .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1))
+ .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1))
.set("none");
_tree->create<std::vector<std::string> >(mb_path / "time_source/options")
- .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64));
+ .set_publisher(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64));
//setup reference source props
_tree->create<std::string>(mb_path / "clock_source/value")
- .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1))
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1))
.set("internal");
std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo");
if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()) clock_sources.push_back("gpsdo");
@@ -697,18 +697,18 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
case usrp2_iface::USRP_N200_R4:
case usrp2_iface::USRP_N210_R4:
_tree->create<time_spec_t>(mb_path / "time/cmd")
- .subscribe(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1));
default: break; //otherwise, do not register
}
_tree->access<double>(mb_path / "tick_rate")
- .subscribe(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1));
////////////////////////////////////////////////////////////////////
// create user-defined control objects
////////////////////////////////////////////////////////////////////
_mbc[mb].user = user_settings_core_200::make(_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_USER_REGS));
_tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs")
- .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1));
+ .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1));
////////////////////////////////////////////////////////////////
// create dboard control objects
@@ -726,32 +726,31 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) :
//create the properties and register subscribers
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")
.set(rx_db_eeprom)
- .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom")
.set(tx_db_eeprom)
- .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1));
_tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom")
.set(gdb_eeprom)
- .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1));
//create a new dboard interface and manager
- _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock);
- _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface);
_mbc[mb].dboard_manager = dboard_manager::make(
rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id,
- _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A")
+ make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock),
+ _tree->subtree(mb_path / "dboards/A")
);
//bind frontend corrections to the dboard freq props
const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){
_tree->access<double>(db_tx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1));
}
const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends";
BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){
_tree->access<double>(db_rx_fe_path / name / "freq" / "value")
- .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1));
+ .add_coerced_subscriber(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1));
}
}
diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp
index 07cd98b4c..47fcec657 100644
--- a/host/lib/usrp/usrp2/usrp2_impl.hpp
+++ b/host/lib/usrp/usrp2/usrp2_impl.hpp
@@ -102,7 +102,6 @@ private:
uhd::transport::zero_copy_if::sptr tx_dsp_xport;
uhd::transport::zero_copy_if::sptr fifo_ctrl_xport;
uhd::usrp::dboard_manager::sptr dboard_manager;
- uhd::usrp::dboard_iface::sptr dboard_iface;
size_t rx_chan_occ, tx_chan_occ;
mb_container_type(void): rx_chan_occ(0), tx_chan_occ(0){}
};
diff --git a/host/lib/usrp/usrp_c.cpp b/host/lib/usrp/usrp_c.cpp
index 69f2bd5e5..943f96db0 100644
--- a/host/lib/usrp/usrp_c.cpp
+++ b/host/lib/usrp/usrp_c.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Ettus Research LLC
+ * 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
@@ -837,6 +837,95 @@ uhd_error uhd_usrp_get_fe_rx_freq_range(
)
}
+UHD_API uhd_error uhd_usrp_get_rx_lo_names(
+ uhd_usrp_handle h,
+ size_t chan,
+ uhd_string_vector_handle rx_lo_names_out
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ rx_lo_names_out->string_vector_cpp = USRP(h)->get_rx_lo_names(chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_set_rx_lo_source(
+ uhd_usrp_handle h,
+ const char* src,
+ const char* name,
+ size_t chan
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ USRP(h)->set_rx_lo_source(src, name, chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_get_rx_lo_source(
+ uhd_usrp_handle h,
+ const char* name,
+ size_t chan,
+ char* rx_lo_source_out,
+ size_t strbuffer_len
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ strncpy(rx_lo_source_out, USRP(h)->get_rx_lo_source(name, chan).c_str(), strbuffer_len);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_get_rx_lo_sources(
+ uhd_usrp_handle h,
+ const char* name,
+ size_t chan,
+ uhd_string_vector_handle rx_lo_sources_out
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ rx_lo_sources_out->string_vector_cpp = USRP(h)->get_rx_lo_sources(name, chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_set_rx_lo_export_enabled(
+ uhd_usrp_handle h,
+ bool enabled,
+ const char* name,
+ size_t chan
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ USRP(h)->set_rx_lo_export_enabled(enabled, name, chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_get_rx_lo_export_enabled(
+ uhd_usrp_handle h,
+ const char* name,
+ size_t chan,
+ bool* result_out
+) {
+ UHD_SAFE_C_SAVE_ERROR(h,
+ *result_out = USRP(h)->get_rx_lo_export_enabled(name, chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_set_rx_lo_freq(
+ uhd_usrp_handle h,
+ double freq,
+ const char* name,
+ size_t chan,
+ double* coerced_freq_out
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ *coerced_freq_out = USRP(h)->set_rx_lo_freq(freq, name, chan);
+ )
+}
+
+UHD_API uhd_error uhd_usrp_get_rx_lo_freq(
+ uhd_usrp_handle h,
+ const char* name,
+ size_t chan,
+ double* rx_lo_freq_out
+){
+ UHD_SAFE_C_SAVE_ERROR(h,
+ *rx_lo_freq_out = USRP(h)->get_rx_lo_freq(name, chan);
+ )
+}
+
uhd_error uhd_usrp_set_rx_gain(
uhd_usrp_handle h,
double gain,
diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt
index 3d6348eec..ea237b008 100644
--- a/host/lib/usrp/x300/CMakeLists.txt
+++ b/host/lib/usrp/x300/CMakeLists.txt
@@ -22,10 +22,9 @@
########################################################################
# Conditionally configure the X300 support
########################################################################
-LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF)
-
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
@@ -35,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 502630109..b28768f90 100644
--- a/host/lib/usrp/x300/x300_dboard_iface.cpp
+++ b/host/lib/usrp/x300/x300_dboard_iface.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2013,2015 Ettus Research LLC
+// Copyright 2013,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
@@ -15,85 +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, boost::uint16_t);
- void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t);
- void _set_gpio_ddr(unit_t, boost::uint16_t);
- void _set_gpio_out(unit_t, boost::uint16_t);
-
- void set_command_time(const uhd::time_spec_t& t);
- uhd::time_spec_t get_command_time(void);
-
- void set_gpio_debug(unit_t, int);
- boost::uint16_t read_gpio(unit_t);
-
- 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
- );
-
- 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
**********************************************************************/
@@ -116,27 +47,6 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config):
this->set_clock_enabled(UNIT_RX, false);
this->set_clock_enabled(UNIT_TX, false);
-
-
- //some test code
- /*
- {
-
- this->write_aux_dac(UNIT_TX, AUX_DAC_A, .1);
- this->write_aux_dac(UNIT_TX, AUX_DAC_B, 1);
- this->write_aux_dac(UNIT_RX, AUX_DAC_A, 2);
- this->write_aux_dac(UNIT_RX, AUX_DAC_B, 3);
- while (1)
- {
- UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_A));
- UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_B));
- UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_A));
- UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_B));
- sleep(1);
- }
- }
- */
-
}
x300_dboard_iface::~x300_dboard_iface(void)
@@ -153,6 +63,8 @@ x300_dboard_iface::~x300_dboard_iface(void)
**********************************************************************/
void x300_dboard_iface::set_clock_rate(unit_t unit, double rate)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+
// Just return if the requested rate is already set
if (std::fabs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon())
return;
@@ -165,17 +77,21 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate)
case UNIT_TX:
_config.clock->set_dboard_rate(_config.which_tx_clk, rate);
break;
+ default:
+ UHD_THROW_INVALID_CODE_PATH();
}
_clock_rates[unit] = rate; //set to shadow
}
double x300_dboard_iface::get_clock_rate(unit_t unit)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
return _clock_rates[unit]; //get from shadow
}
std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
switch(unit)
{
case UNIT_RX:
@@ -189,6 +105,7 @@ std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit)
void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
switch(unit)
{
case UNIT_RX:
@@ -200,57 +117,74 @@ void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb)
}
}
-double x300_dboard_iface::get_codec_rate(unit_t)
+double x300_dboard_iface::get_codec_rate(unit_t unit)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
return _config.clock->get_master_clock_rate();
}
/***********************************************************************
* GPIO
**********************************************************************/
-void x300_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value)
+void x300_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask)
{
- return _config.gpio->set_pin_ctrl(unit, value);
+ _config.gpio->set_pin_ctrl(unit, value, mask);
}
-void x300_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value)
+boost::uint32_t x300_dboard_iface::get_pin_ctrl(unit_t unit)
{
- return _config.gpio->set_gpio_ddr(unit, value);
+ return _config.gpio->get_pin_ctrl(unit);
}
-void x300_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value)
+void x300_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask)
{
- return _config.gpio->set_gpio_out(unit, value);
+ _config.gpio->set_atr_reg(unit, reg, value, mask);
}
-boost::uint16_t x300_dboard_iface::read_gpio(unit_t unit)
+boost::uint32_t x300_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg)
{
- return _config.gpio->read_gpio(unit);
+ return _config.gpio->get_atr_reg(unit, reg);
+}
+
+void x300_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask)
+{
+ _config.gpio->set_gpio_ddr(unit, value, mask);
+}
+
+boost::uint32_t x300_dboard_iface::get_gpio_ddr(unit_t unit)
+{
+ return _config.gpio->get_gpio_ddr(unit);
}
-void x300_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value)
+void x300_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask)
{
- return _config.gpio->set_atr_reg(unit, atr, value);
+ _config.gpio->set_gpio_out(unit, value, mask);
}
-void x300_dboard_iface::set_gpio_debug(unit_t, int)
+boost::uint32_t x300_dboard_iface::get_gpio_out(unit_t unit)
{
- throw uhd::not_implemented_error("no set_gpio_debug implemented");
+ return _config.gpio->get_gpio_out(unit);
+}
+
+boost::uint32_t x300_dboard_iface::read_gpio(unit_t unit)
+{
+ return _config.gpio->read_gpio(unit);
}
/***********************************************************************
* SPI
**********************************************************************/
-#define toslaveno(unit) \
- (((unit) == dboard_iface::UNIT_TX)? _config.tx_spi_slaveno : _config.rx_spi_slaveno)
-
void x300_dboard_iface::write_spi(
unit_t unit,
const spi_config_t &config,
boost::uint32_t data,
size_t num_bits
){
- _config.spi->write_spi(toslaveno(unit), config, data, num_bits);
+ boost::uint32_t slave = 0;
+ if (unit == UNIT_TX) slave |= _config.tx_spi_slaveno;
+ if (unit == UNIT_RX) slave |= _config.rx_spi_slaveno;
+
+ _config.spi->write_spi(int(slave), config, data, num_bits);
}
boost::uint32_t x300_dboard_iface::read_write_spi(
@@ -259,7 +193,10 @@ boost::uint32_t x300_dboard_iface::read_write_spi(
boost::uint32_t data,
size_t num_bits
){
- return _config.spi->read_spi(toslaveno(unit), config, data, num_bits);
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+ return _config.spi->read_spi(
+ (unit==dboard_iface::UNIT_TX)?_config.tx_spi_slaveno:_config.rx_spi_slaveno,
+ config, data, num_bits);
}
/***********************************************************************
@@ -284,6 +221,7 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit)
(UNIT_RX, DB_RX_LSDAC_SEN)
(UNIT_TX, DB_TX_LSDAC_SEN)
;
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
_config.spi->write_spi(
unit_to_spi_dac[unit], spi_config_t::EDGE_FALL,
_dac_regs[unit].get_reg(), 24
@@ -292,6 +230,8 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit)
void x300_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value)
{
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+
_dac_regs[unit].data = boost::math::iround(4095*value/3.3);
_dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N;
@@ -321,6 +261,8 @@ double x300_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which)
(UNIT_TX, DB_TX_LSADC_SEN)
;
+ if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported.");
+
//setup spi config args
spi_config_t config;
config.mosi_edge = spi_config_t::EDGE_FALL;
@@ -356,3 +298,25 @@ 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,
+ const fe_connection_t& fe_conn)
+{
+ if (unit == UNIT_RX) {
+ 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 549fc9dfa..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 19
+#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_fw_ctrl.cpp b/host/lib/usrp/x300/x300_fw_ctrl.cpp
index 3a8d984fb..25960ede0 100644
--- a/host/lib/usrp/x300/x300_fw_ctrl.cpp
+++ b/host/lib/usrp/x300/x300_fw_ctrl.cpp
@@ -37,6 +37,11 @@ class x300_ctrl_iface : public wb_iface
public:
enum {num_retries = 3};
+ x300_ctrl_iface(bool enable_errors = true) : errors(enable_errors)
+ {
+ /* NOP */
+ }
+
void flush(void)
{
boost::mutex::scoped_lock lock(reg_access);
@@ -52,11 +57,11 @@ public:
{
return this->__poke32(addr, data);
}
- catch(const std::exception &ex)
+ catch(const uhd::io_error &ex)
{
- const std::string error_msg = str(boost::format(
+ std::string error_msg = str(boost::format(
"x300 fw communication failure #%u\n%s") % i % ex.what());
- UHD_MSG(error) << error_msg << std::endl;
+ if (errors) UHD_MSG(error) << error_msg << std::endl;
if (i == num_retries) throw uhd::io_error(error_msg);
}
}
@@ -72,11 +77,11 @@ public:
boost::uint32_t data = this->__peek32(addr);
return data;
}
- catch(const std::exception &ex)
+ catch(const uhd::io_error &ex)
{
- const std::string error_msg = str(boost::format(
+ std::string error_msg = str(boost::format(
"x300 fw communication failure #%u\n%s") % i % ex.what());
- UHD_MSG(error) << error_msg << std::endl;
+ if (errors) UHD_MSG(error) << error_msg << std::endl;
if (i == num_retries) throw uhd::io_error(error_msg);
}
}
@@ -84,6 +89,8 @@ public:
}
protected:
+ bool errors;
+
virtual void __poke32(const wb_addr_type addr, const boost::uint32_t data) = 0;
virtual boost::uint32_t __peek32(const wb_addr_type addr) = 0;
virtual void __flush() = 0;
@@ -98,8 +105,8 @@ protected:
class x300_ctrl_iface_enet : public x300_ctrl_iface
{
public:
- x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp):
- udp(udp), seq(0)
+ x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true):
+ x300_ctrl_iface(enable_errors), udp(udp), seq(0)
{
try
{
@@ -187,8 +194,8 @@ private:
class x300_ctrl_iface_pcie : public x300_ctrl_iface
{
public:
- x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy):
- _drv_proxy(drv_proxy)
+ x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true):
+ x300_ctrl_iface(enable_errors), _drv_proxy(drv_proxy)
{
nirio_status status = 0;
nirio_status_chain(_drv_proxy->set_attribute(RIO_ADDRESS_SPACE, BUS_INTERFACE), status);
@@ -289,12 +296,12 @@ private:
static const boost::uint32_t INIT_TIMEOUT_IN_MS = 5000;
};
-wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp)
+wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true)
{
- return wb_iface::sptr(new x300_ctrl_iface_enet(udp));
+ return wb_iface::sptr(new x300_ctrl_iface_enet(udp, enable_errors));
}
-wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy)
+wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true)
{
- return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy));
+ return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy, enable_errors));
}
diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp
index ce81d5f1f..43ccd26f5 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
@@ -18,9 +18,9 @@
#include "x300_impl.hpp"
#include "x300_lvbitx.hpp"
#include "x310_lvbitx.hpp"
+#include "apply_corrections.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
-#include "apply_corrections.hpp"
#include <uhd/utils/static.hpp>
#include <uhd/utils/msg.hpp>
#include <uhd/utils/paths.hpp>
@@ -32,12 +32,14 @@
#include <boost/make_shared.hpp>
#include <boost/functional/hash.hpp>
#include <boost/assign/list_of.hpp>
-#include <fstream>
#include <uhd/transport/udp_zero_copy.hpp>
#include <uhd/transport/udp_constants.hpp>
+#include <uhd/transport/zero_copy_recv_offload.hpp>
#include <uhd/transport/nirio_zero_copy.hpp>
#include <uhd/transport/nirio/niusrprio_session.h>
#include <uhd/utils/platform.hpp>
+#include <uhd/types/sid.hpp>
+#include <fstream>
#define NIUSRPRIO_DEFAULT_RPC_PORT "5444"
@@ -45,24 +47,41 @@
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 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
+ //HA = {0:1G, 1:Aurora} w/ DRAM, XA = {0:10G, 1:Aurora} w/ DRAM
+
+ std::string option;
+ uint32_t sfp0_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP0_TYPE));
+ uint32_t sfp1_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP1_TYPE));
+
+ if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_1G_ETH) {
+ option = "1G";
+ } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_10G_ETH) {
+ option = "HG";
+ } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_10G_ETH) {
+ option = "XG";
+ } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_AURORA) {
+ option = "HA";
+ } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_AURORA) {
+ option = "XA";
+ } else {
+ option = "HG"; //Default
+ }
+ return option;
+}
+
/***********************************************************************
* Discovery over the udp and pcie transport
**********************************************************************/
-static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) {
- //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM
- //HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM
-
- //In the default configuration, UHD does not support the HG and XG images so
- //they are never autodetected.
- bool eth0XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE0)) == 0x1);
- bool eth1XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE1)) == 0x1);
- return (eth0XG && eth1XG) ? "XGS" : (eth1XG ? "HGS" : "1G");
-}
//@TODO: Refactor the find functions to collapse common code for ethernet and PCIe
static device_addrs_t x300_find_with_addr(const device_addr_t &hint)
@@ -96,7 +115,12 @@ static device_addrs_t x300_find_with_addr(const device_addr_t &hint)
//This operation can throw due to compatibility mismatch.
try
{
- wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)));
+ wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet(
+ udp_simple::make_connected(new_addr["addr"],
+ BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)),
+ false /* Suppress timeout errors */
+ );
+
if (x300_impl::is_claimed(zpu_ctrl)) continue; //claimed by another process
new_addr["fpga"] = get_fpga_option(zpu_ctrl);
@@ -193,7 +217,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu
if (get_pcie_zpu_iface_registry().has_key(resource_d)) {
zpu_ctrl = get_pcie_zpu_iface_registry()[resource_d].lock();
} else {
- zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy);
+ zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy, false /* suppress timeout errors */);
//We don't put this zpu_ctrl in the registry because we need
//a persistent niriok_proxy associated with the object
}
@@ -215,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"] = "";
@@ -348,18 +372,18 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam
if ((i & 0x1fff) == 0) UHD_MSG(status) << "." << std::flush;
}
+ //Wait for fimrware to reboot. 3s is an upper bound
+ boost::this_thread::sleep(boost::posix_time::milliseconds(3000));
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());
@@ -369,13 +393,134 @@ x300_impl::x300_impl(const uhd::device_addr_t &dev_addr)
}
}
+void x300_impl::mboard_members_t::discover_eth(
+ const mboard_eeprom_t mb_eeprom,
+ const std::vector<std::string> &ip_addrs)
+{
+ // Clear any previous addresses added
+ eth_conns.clear();
+
+ // Index the MB EEPROM addresses
+ std::vector<std::string> mb_eeprom_addrs;
+ const size_t num_mb_eeprom_addrs = 4;
+ for (size_t i = 0; i < num_mb_eeprom_addrs; i++) {
+ const std::string key = "ip-addr" + boost::to_string(i);
+
+ // Show a warning if there exists duplicate addresses in the mboard eeprom
+ if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) != mb_eeprom_addrs.end()) {
+ UHD_MSG(warning) << str(boost::format(
+ "Duplicate IP address %s found in mboard EEPROM. "
+ "Device may not function properly.\nView and reprogram the values "
+ "using the usrp_burn_mb_eeprom utility.\n") % mb_eeprom[key]);
+ }
+ mb_eeprom_addrs.push_back(mb_eeprom[key]);
+ }
+
+ BOOST_FOREACH(const std::string& addr, ip_addrs) {
+ x300_eth_conn_t conn_iface;
+ conn_iface.addr = addr;
+ conn_iface.type = X300_IFACE_NONE;
+
+ // Decide from the mboard eeprom what IP corresponds
+ // to an interface
+ for (size_t i = 0; i < mb_eeprom_addrs.size(); i++) {
+ if (addr == mb_eeprom_addrs[i]) {
+ // Choose the interface based on the index parity
+ if (i % 2 == 0) {
+ conn_iface.type = X300_IFACE_ETH0;
+ } else {
+ conn_iface.type = X300_IFACE_ETH1;
+ }
+ break;
+ }
+ }
+
+ // Check default IP addresses if we couldn't
+ // determine the IP from the mboard eeprom
+ if (conn_iface.type == X300_IFACE_NONE) {
+ UHD_MSG(warning) << str(boost::format(
+ "Address %s not found in mboard EEPROM. Address may be wrong or "
+ "the EEPROM may be corrupt.\n Attempting to continue with default "
+ "IP addresses.\n") % conn_iface.addr
+ );
+
+ if (addr == boost::asio::ip::address_v4(
+ boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) {
+ conn_iface.type = X300_IFACE_ETH0;
+ } else if (addr == boost::asio::ip::address_v4(
+ boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) {
+ conn_iface.type = X300_IFACE_ETH1;
+ } else if (addr == boost::asio::ip::address_v4(
+ boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) {
+ conn_iface.type = X300_IFACE_ETH0;
+ } else if (addr == boost::asio::ip::address_v4(
+ boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) {
+ conn_iface.type = X300_IFACE_ETH1;
+ } else {
+ throw uhd::assertion_error(str(boost::format(
+ "X300 Initialization Error: Failed to match address %s with "
+ "any addresses for the device. Please check the address.")
+ % conn_iface.addr
+ ));
+ }
+ }
+
+ // Save to a vector of connections
+ if (conn_iface.type != X300_IFACE_NONE) {
+ // Check the address before we add it
+ try
+ {
+ wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet(
+ udp_simple::make_connected(conn_iface.addr,
+ BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)),
+ false /* Suppress timeout errors */
+ );
+
+ // Peek the ZPU ctrl to make sure this connection works
+ zpu_ctrl->peek32(0);
+ }
+
+ // If the address does not work, throw an error
+ catch(std::exception &)
+ {
+ throw uhd::io_error(str(boost::format(
+ "X300 Initialization Error: Invalid address %s")
+ % conn_iface.addr));
+ }
+ eth_conns.push_back(conn_iface);
+ }
+ }
+
+ if (eth_conns.size() == 0)
+ throw uhd::assertion_error("X300 Initialization Error: No ethernet interfaces specified.");
+}
+
void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
{
const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i);
mboard_members_t &mb = _mb[mb_i];
mb.initialization_done = false;
- mb.addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"];
+ std::vector<std::string> eth_addrs;
+ // Not choosing eth0 based on resource might cause user issues
+ 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"];
+
+ // Ensure we do not have duplicate addresses
+ if (eth1_addr != eth0_addr)
+ eth_addrs.push_back(eth1_addr);
+ }
+
+ // Initially store the first address provided to setup communication
+ // Once we read the eeprom, we use it to map IP to its interface
+ x300_eth_conn_t init;
+ init.addr = eth_addrs[0];
+ mb.eth_conns.push_back(init);
+
mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth";
mb.if_pkt_is_big_endian = mb.xport_path != "nirio";
@@ -413,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);
}
@@ -452,7 +599,28 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
// Detect the frame size on the path to the USRP
try {
- _max_frame_sizes = determine_max_frame_size(mb.addr, req_max_frame_size);
+ frame_size_t pri_frame_sizes = determine_max_frame_size(
+ eth_addrs.at(0), req_max_frame_size
+ );
+
+ _max_frame_sizes = pri_frame_sizes;
+ if (eth_addrs.size() > 1) {
+ frame_size_t sec_frame_sizes = determine_max_frame_size(
+ eth_addrs.at(1), req_max_frame_size
+ );
+
+ // Choose the minimum of the max frame sizes
+ // to ensure we don't exceed any one of the links' MTU
+ _max_frame_sizes.recv_frame_size = std::min(
+ pri_frame_sizes.recv_frame_size,
+ sec_frame_sizes.recv_frame_size
+ );
+
+ _max_frame_sizes.send_frame_size = std::min(
+ pri_frame_sizes.send_frame_size,
+ sec_frame_sizes.send_frame_size
+ );
+ }
} catch(std::exception &e) {
UHD_MSG(error) << e.what() << std::endl;
}
@@ -479,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(std::min(_max_frame_sizes.send_frame_size, X300_1GE_DATA_FRAME_MAX_SIZE));
_tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_10GIGE);
}
@@ -486,15 +656,15 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
UHD_MSG(status) << "Setup basic communication..." << std::endl;
if (mb.xport_path == "nirio") {
boost::mutex::scoped_lock(pcie_zpu_iface_registry_mutex);
- if (get_pcie_zpu_iface_registry().has_key(mb.addr)) {
+ if (get_pcie_zpu_iface_registry().has_key(mb.get_pri_eth().addr)) {
throw uhd::assertion_error("Someone else has a ZPU transport to the device open. Internal error!");
} else {
mb.zpu_ctrl = x300_make_ctrl_iface_pcie(mb.rio_fpga_interface->get_kernel_proxy());
- get_pcie_zpu_iface_registry()[mb.addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl);
+ get_pcie_zpu_iface_registry()[mb.get_pri_eth().addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl);
}
} else {
- mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(mb.addr,
- BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)));
+ mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(
+ mb.get_pri_eth().addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)));
}
mb.claimer_task = uhd::task::make(boost::bind(&x300_impl::claimer_loop, this, mb.zpu_ctrl));
@@ -561,7 +731,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
const mboard_eeprom_t mb_eeprom(*eeprom16, "X300");
_tree->create<mboard_eeprom_t>(mb_path / "eeprom")
.set(mb_eeprom)
- .subscribe(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1));
+ .add_coerced_subscriber(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1));
bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom");
if (recover_mb_eeprom) {
@@ -593,27 +763,9 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
////////////////////////////////////////////////////////////////////
// determine routing based on address match
////////////////////////////////////////////////////////////////////
- mb.router_dst_here = X300_XB_DST_E0; //some default if eeprom not match
- if (mb.xport_path == "nirio") {
- mb.router_dst_here = X300_XB_DST_PCI;
- } else {
- if (mb.addr == mb_eeprom["ip-addr0"]) mb.router_dst_here = X300_XB_DST_E0;
- else if (mb.addr == mb_eeprom["ip-addr1"]) mb.router_dst_here = X300_XB_DST_E1;
- else if (mb.addr == mb_eeprom["ip-addr2"]) mb.router_dst_here = X300_XB_DST_E0;
- else if (mb.addr == mb_eeprom["ip-addr3"]) mb.router_dst_here = X300_XB_DST_E1;
- else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E0;
- else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E1;
- else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E0;
- else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E1;
- }
-
- ////////////////////////////////////////////////////////////////////
- // 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);
+ if (mb.xport_path != "nirio") {
+ // Discover ethernet interfaces
+ mb.discover_eth(mb_eeprom, eth_addrs);
}
////////////////////////////////////////////////////////////////////
@@ -678,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")
- .publish(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;
@@ -713,7 +864,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
BOOST_FOREACH(const std::string &name, mb.gps->get_sensors())
{
_tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, mb.gps, name));
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, mb.gps, name));
}
}
else
@@ -729,69 +880,27 @@ 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);
}
- ////////////////////////////////////////////////////////////////////
- // 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_core_200::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)
- .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr.first, _1));
- }
- _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK")
- .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio));
-
- ////////////////////////////////////////////////////////////////////
- // register the time keepers - only one can be the highlander
- ////////////////////////////////////////////////////////////////////
- _tree->create<time_spec_t>(mb_path / "time" / "now")
- .publish(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64))
- .subscribe(boost::bind(&x300_impl::sync_times, this, mb, _1))
- .set(0.0);
- _tree->create<time_spec_t>(mb_path / "time" / "pps")
- .publish(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64))
- .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1))
- .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1));
////////////////////////////////////////////////////////////////////
// setup time sources and properties
////////////////////////////////////////////////////////////////////
_tree->create<std::string>(mb_path / "time_source" / "value")
.set("internal")
- .subscribe(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1));
+ .add_coerced_subscriber(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1));
static const std::vector<std::string> time_sources = boost::assign::list_of("internal")("external")("gpsdo");
_tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources);
//setup the time output, default to ON
_tree->create<bool>(mb_path / "time_source" / "output")
- .subscribe(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1))
+ .add_coerced_subscriber(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1))
.set(true);
////////////////////////////////////////////////////////////////////
// setup clock sources and properties
////////////////////////////////////////////////////////////////////
_tree->create<std::string>(mb_path / "clock_source" / "value")
- .set("internal")
- .subscribe(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1));
+ .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");
_tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_source_options);
@@ -807,54 +916,70 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr)
//setup the clock output, default to ON
_tree->create<bool>(mb_path / "clock_source" / "output")
- .subscribe(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1));
+ .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")
- .subscribe(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1))
- .subscribe(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")
- .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1));
- _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
- .subscribe(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
////////////////////////////////////////////////////////////////////
_tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked")
- .publish(boost::bind(&x300_impl::get_ref_locked, this, mb));
+ .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb));
+
+ //////////////// 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);
+ }
- ////////////////////////////////////////////////////////////////////
- // 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);
+ 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;
}
@@ -865,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
@@ -881,7 +998,7 @@ x300_impl::~x300_impl(void)
mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC), 0);
//If the process is killed, the entire registry will disappear so we
//don't need to worry about unclean shutdowns here.
- get_pcie_zpu_iface_registry().pop(mb.addr);
+ get_pcie_zpu_iface_registry().pop(mb.get_pri_eth().addr);
}
}
}
@@ -891,270 +1008,144 @@ 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_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::LEDS));
- 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.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL));
- perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP));
- perif.duc->set_link_rate(10e9/8); //whatever
-
- ////////////////////////////////////////////////////////////////////
- // 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")
- .subscribe(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")
- .subscribe(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")
- .subscribe(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")
- .subscribe(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")
- .subscribe(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])
- .subscribe(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])
- .subscribe(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])
- .subscribe(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 = gpio_core_200::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.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;
- _dboard_ifaces[db_path] = x300_make_dboard_iface(db_config);
-
- //create a new dboard manager
- _tree->create<dboard_iface::sptr>(db_path / "iface").set(_dboard_ifaces[db_path]);
- _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,
- _dboard_ifaces[db_path],
- _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")
- .subscribe(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")
- .subscribe(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")
- .subscribe(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;
- config.router_dst_here = mb.router_dst_here;
- 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.*/
size_t eth_data_rec_frame_size = 0;
- if (mb.loaded_fpga_image == "HGS") {
- if (mb.router_dst_here == X300_XB_DST_E0) {
+ fs_path mboard_path = fs_path("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate");
+
+ if (mb.loaded_fpga_image == "HG") {
+ size_t max_link_rate = 0;
+ if (xbar_src_dst == X300_XB_DST_E0) {
eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE;
- _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_1GIGE);
- } else if (mb.router_dst_here == X300_XB_DST_E1) {
+ max_link_rate += X300_MAX_RATE_1GIGE;
+ } else if (xbar_src_dst == X300_XB_DST_E1) {
eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE;
- _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE);
+ max_link_rate += X300_MAX_RATE_10GIGE;
}
- } else if (mb.loaded_fpga_image == "XGS") {
+ _tree->access<double>(mboard_path).set(max_link_rate);
+ } else if (mb.loaded_fpga_image == "XG" or mb.loaded_fpga_image == "XA") {
eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE;
- _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE);
+ 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 == "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();
+ _tree->access<double>(mboard_path).set(max_link_rate);
}
if (eth_data_rec_frame_size == 0) {
@@ -1188,33 +1179,43 @@ 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;
//make a new transport - fpga has no idea how to talk to us on this yet
udp_zero_copy::buff_params buff_params;
- xports.recv = udp_zero_copy::make(mb.addr,
+
+ xports.recv = udp_zero_copy::make(
+ interface_addr,
BOOST_STRINGIZE(X300_VITA_UDP_PORT),
default_buff_args,
buff_params,
xport_args);
+ // Create a threaded transport for the receive chain only
+ // Note that this shouldn't affect PCIe
+ if (xport_type == RX_DATA) {
+ xports.recv = zero_copy_recv_offload::make(
+ xports.recv,
+ X300_THREAD_BUFFER_TIMEOUT
+ );
+ }
xports.send = xports.recv;
//For the UDP transport the buffer size if the size of the socket buffer
@@ -1229,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.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();
@@ -1247,48 +1248,31 @@ x300_impl::both_xports_t x300_impl::make_transport(
//ethernet framer has been programmed before we return.
mb.zpu_ctrl->peek32(0);
}
-
return xports;
}
-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;
-
- const boost::uint32_t sid = 0
- | (X300_DEVICE_HERE << 24)
- | (_sid_framer << 16)
- | (config.router_addr_there << 8)
- | (stream << 0)
- ;
- UHD_LOG << std::hex
- << " sid 0x" << sid
- << " 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 + (X300_DEVICE_HERE)), config.router_dst_here);
-
- 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++;
@@ -1296,57 +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_core_200_32wo::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(dboard_iface::ATR_REG_IDLE, 0);
- leds->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led);
- leds->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_led);
- leds->set_atr_reg(dboard_iface::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.deframer->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);
@@ -1419,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();
}
}
@@ -1460,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);
}
@@ -1518,30 +1448,6 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep
}
/***********************************************************************
- * front-panel GPIO
- **********************************************************************/
-
-boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio)
-{
- return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX));
-}
-
-void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value)
-{
- switch (attr)
- {
- case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value);
- case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value);
- case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value);
- case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value);
- case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value);
- case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value);
- case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value);
- default: UHD_THROW_INVALID_CODE_PATH();
- }
-}
-
-/***********************************************************************
* claimer logic
**********************************************************************/
@@ -1682,9 +1588,12 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t
% image_loader_path
% (members.xport_path == "eth" ? "addr"
: "resource")
- % members.addr);
+ % 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"
@@ -1696,9 +1605,17 @@ 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));
+
+ const boost::uint32_t git_hash = members.zpu_ctrl->peek32(SR_ADDR(SET0_BASE,
+ ZPU_RB_GIT_HASH));
+ _tree->create<std::string>(mb_path / "fpga_version_hash").set(
+ str(boost::format("%07x%s")
+ % (git_hash & 0x0FFFFFFF)
+ % ((git_hash & 0xF000000) ? "-dirty" : "")));
}
x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port)
diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp
index e04b06d65..d491e2bc0 100644
--- a/host/lib/usrp/x300/x300_impl.hpp
+++ b/host/lib/usrp/x300/x300_impl.hpp
@@ -19,51 +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_core_200.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 = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO
-static const size_t X300_TX_FC_RESPONSE_FREQ = 8; //per flow-control window
+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_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.
@@ -73,93 +58,66 @@ 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
static const size_t X300_ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; //bytes
+static const double X300_THREAD_BUFFER_TIMEOUT = 0.1; // Time in seconds
+
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_DEVICE_HERE 0
-
-//eeprom addrs for various boards
-enum
+
+#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
{
- 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,
+ X300_IFACE_NONE = 0,
+ X300_IFACE_ETH0 = 1,
+ X300_IFACE_ETH1 = 2,
};
-struct x300_dboard_iface_config_t
+struct x300_eth_conn_t
{
- gpio_core_200::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;
- 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;
+ std::string addr;
+ x300_eth_iface_t type;
};
-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);
-uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy);
+uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true);
+uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy, bool enable_errors = true);
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);
@@ -170,43 +128,37 @@ 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;
-
- //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;
- gpio_core_200_32wo::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;
- };
+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
+ );
- //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 addr;
std::string xport_path;
- int router_dst_here;
+
+ 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,
+ const std::vector<std::string> &ip_addrs);
+
+ // Get the primary ethernet connection
+ inline const x300_eth_conn_t& get_pri_eth() const
+ {
+ return eth_conns[0];
+ }
+
uhd::device_addr_t send_args;
uhd::device_addr_t recv_args;
bool if_pkt_is_big_endian;
@@ -217,20 +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;
- }
-
//other perifs on mboard
x300_clock_ctrl::sptr clock;
uhd::gps_ctrl::sptr gps;
- gpio_core_200::sptr fp_gpio;
uhd::usrp::x300::fw_regmap_t::sptr fw_regmap;
@@ -240,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;
- boost::uint8_t router_dst_here;
- };
- 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
{
@@ -306,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.
@@ -328,28 +246,9 @@ private:
////////////////////////////////////////////////////////////////////
uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers;
- uhd::dict<std::string, uhd::usrp::dboard_iface::sptr> _dboard_ifaces;
- 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);
@@ -367,21 +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(gpio_core_200_32wo::sptr, const std::string &ant);
- boost::uint32_t get_fp_gpio(gpio_core_200::sptr);
- void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t);
-
- 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 e3515af0c..614a98590 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,588 +36,62 @@ 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 std::string conn = _tree->access<std::string>(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name / "connection").get();
-
- if (tx_rx == "tx") {
- //swap condition
- _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn);
- } else {
- //swap condition
- const bool fe_swapped = (conn == "QI" or conn == "Q");
- _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(conn, fe_swapped);
- //see usrp/io_impl.cpp if multiple DSPs share the frontend:
- _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(fe_swapped);
+ 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);
-}
-
-
-/***********************************************************************
- * 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)
-{
- 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%)");
- }
-
- 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 rx_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)
-{
- 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 device_addr_t& tx_args)
+device_addr_t x300_impl::get_tx_hints(size_t mb_index)
{
- double hw_buff_size = tx_args.cast<double>("send_buff_size", X300_TX_HW_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;
+ device_addr_t tx_hints = _mb[mb_index].send_args;
+ return tx_hints;
}
-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)
+void x300_impl::post_streamer_hooks(direction_t dir)
{
- 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(), device_addr); //In packets
- const size_t fc_handle_window = std::max<size_t>(1, fc_window/X300_TX_FC_RESPONSE_FREQ);
-
- 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..e1b724db6
--- /dev/null
+++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp
@@ -0,0 +1,894 @@
+//
+// 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_FE_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, db_path, _root_path / "tx_fe_corrections" / 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_radio_ctrl_impl::set_rx_fe_corrections, this, db_path, _root_path / "rx_fe_corrections" / name,_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 fs_path &db_path,
+ const fs_path &rx_fe_corr_path,
+ const double lo_freq
+) {
+ if (not _ignore_cal_file) {
+ apply_rx_fe_corrections(_tree, db_path, rx_fe_corr_path, lo_freq);
+ }
+}
+
+void x300_radio_ctrl_impl::set_tx_fe_corrections(
+ const fs_path &db_path,
+ const fs_path &tx_fe_corr_path,
+ const double lo_freq
+) {
+ if (not _ignore_cal_file) {
+ apply_tx_fe_corrections(_tree, db_path, tx_fe_corr_path, 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(uhd::RX_DIRECTION, chan);
+ if (chan_active) {
+ _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 / _tx_fe_map.at(chan).db_fe_name / "enabled")) {
+ const bool chan_active = _is_streamer_active(uhd::TX_DIRECTION, chan);
+ if (chan_active) {
+ _tree->access<bool>(tx_fe_path / _tx_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..770519eba
--- /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_FE_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 uhd::fs_path &db_path, const uhd::fs_path &rx_fe_corr_path, const double lo_freq);
+ void set_tx_fe_corrections(const uhd::fs_path &db_path, const uhd::fs_path &tx_fe_corr_path, 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 3e0966c83..c5ed1460b 100644
--- a/host/lib/usrp/x300/x300_regs.hpp
+++ b/host/lib/usrp/x300/x300_regs.hpp
@@ -22,45 +22,8 @@
#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 = 200;
-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
-
-#define localparam static const int
-
-localparam BL_ADDRESS = 0;
-localparam BL_DATA = 1;
+static const int BL_ADDRESS = 0;
+static const int BL_DATA = 1;
//wishbone settings map - relevant to host code
#define SET0_BASE 0xa000
@@ -70,13 +33,15 @@ localparam BL_DATA = 1;
#define I2C1_BASE 0xff00
#define SR_ADDR(base, offset) ((base) + (offset)*4)
-localparam ZPU_SR_LEDS = 00;
-localparam ZPU_SR_SW_RST = 01;
-localparam ZPU_SR_CLOCK_CTRL = 02;
-localparam ZPU_SR_XB_LOCAL = 03;
-localparam ZPU_SR_SPI = 32;
-localparam ZPU_SR_ETHINT0 = 40;
-localparam ZPU_SR_ETHINT1 = 56;
+static const int ZPU_SR_LEDS = 00;
+static const int ZPU_SR_SW_RST = 01;
+static const int ZPU_SR_CLOCK_CTRL = 02;
+static const int ZPU_SR_XB_LOCAL = 03;
+static const int ZPU_SR_SPI = 32;
+static const int ZPU_SR_ETHINT0 = 40;
+static const int ZPU_SR_ETHINT1 = 56;
+static const int ZPU_SR_DRAM_FIFO0 = 72;
+static const int ZPU_SR_DRAM_FIFO1 = 80;
//reset bits
#define ZPU_SR_SW_RST_ETH_PHY (1<<0)
@@ -84,11 +49,17 @@ localparam ZPU_SR_ETHINT1 = 56;
#define ZPU_SR_SW_RST_RADIO_CLK_PLL (1<<2)
#define ZPU_SR_SW_RST_ADC_IDELAYCTRL (1<<3)
-localparam ZPU_RB_SPI = 2;
-localparam ZPU_RB_CLK_STATUS = 3;
-localparam ZPU_RB_COMPAT_NUM = 6;
-localparam ZPU_RB_ETH_TYPE0 = 4;
-localparam ZPU_RB_ETH_TYPE1 = 5;
+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_GIT_HASH = 10;
+static const int ZPU_RB_SFP0_TYPE = 4;
+static const int ZPU_RB_SFP1_TYPE = 5;
+
+static const uint32_t RB_SFP_1G_ETH = 0;
+static const uint32_t RB_SFP_10G_ETH = 1;
+static const uint32_t RB_SFP_AURORA = 2;
//spi slaves on radio
#define DB_DAC_SEN (1 << 7)
@@ -244,49 +215,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/lib/usrp_clock/octoclock/CMakeLists.txt b/host/lib/usrp_clock/octoclock/CMakeLists.txt
index a54d27c52..d2b70e356 100644
--- a/host/lib/usrp_clock/octoclock/CMakeLists.txt
+++ b/host/lib/usrp_clock/octoclock/CMakeLists.txt
@@ -18,8 +18,6 @@
########################################################################
# Conditionally configure the OctoClock support
########################################################################
-LIBUHD_REGISTER_COMPONENT("OctoClock" ENABLE_OCTOCLOCK ON "ENABLE_LIBUHD" OFF OFF)
-
IF(ENABLE_OCTOCLOCK)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/octoclock_eeprom.cpp
diff --git a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp
index 15d919272..297983c15 100644
--- a/host/lib/usrp_clock/octoclock/octoclock_impl.cpp
+++ b/host/lib/usrp_clock/octoclock/octoclock_impl.cpp
@@ -243,21 +243,21 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){
_oc_dict[oc].eeprom = octoclock_eeprom_t(_oc_dict[oc].ctrl_xport, _proto_ver);
_tree->create<octoclock_eeprom_t>(oc_path / "eeprom")
.set(_oc_dict[oc].eeprom)
- .subscribe(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1));
+ .add_coerced_subscriber(boost::bind(&octoclock_impl::_set_eeprom, this, oc, _1));
////////////////////////////////////////////////////////////////////
// Initialize non-GPSDO sensors
////////////////////////////////////////////////////////////////////
_tree->create<boost::uint32_t>(oc_path / "time")
- .publish(boost::bind(&octoclock_impl::_get_time, this, oc));
+ .set_publisher(boost::bind(&octoclock_impl::_get_time, this, oc));
_tree->create<sensor_value_t>(oc_path / "sensors/ext_ref_detected")
- .publish(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc));
+ .set_publisher(boost::bind(&octoclock_impl::_ext_ref_detected, this, oc));
_tree->create<sensor_value_t>(oc_path / "sensors/gps_detected")
- .publish(boost::bind(&octoclock_impl::_gps_detected, this, oc));
+ .set_publisher(boost::bind(&octoclock_impl::_gps_detected, this, oc));
_tree->create<sensor_value_t>(oc_path / "sensors/using_ref")
- .publish(boost::bind(&octoclock_impl::_which_ref, this, oc));
+ .set_publisher(boost::bind(&octoclock_impl::_which_ref, this, oc));
_tree->create<sensor_value_t>(oc_path / "sensors/switch_pos")
- .publish(boost::bind(&octoclock_impl::_switch_pos, this, oc));
+ .set_publisher(boost::bind(&octoclock_impl::_switch_pos, this, oc));
////////////////////////////////////////////////////////////////////
// Check reference and GPSDO
@@ -277,7 +277,7 @@ octoclock_impl::octoclock_impl(const device_addr_t &_device_addr){
if(_oc_dict[oc].gps and _oc_dict[oc].gps->gps_detected()){
BOOST_FOREACH(const std::string &name, _oc_dict[oc].gps->get_sensors()){
_tree->create<sensor_value_t>(oc_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name));
+ .set_publisher(boost::bind(&gps_ctrl::get_sensor, _oc_dict[oc].gps, name));
}
}
else{
diff --git a/host/lib/utils/log.cpp b/host/lib/utils/log.cpp
index 8d42af9c4..4e58ce894 100644
--- a/host/lib/utils/log.cpp
+++ b/host/lib/utils/log.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2012,2014 Ettus Research LLC
+// Copyright 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
@@ -24,19 +24,7 @@
#include <boost/thread/mutex.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread/locks.hpp>
-#ifdef BOOST_MSVC
-//whoops! https://svn.boost.org/trac/boost/ticket/5287
-//enjoy this useless dummy class instead
-namespace boost{ namespace interprocess{
- struct file_lock{
- file_lock(const char * = NULL){}
- void lock(void){}
- void unlock(void){}
- };
-}} //namespace
-#else
#include <boost/interprocess/sync/file_lock.hpp>
-#endif
#include <fstream>
#include <cctype>
diff --git a/host/lib/utils/msg.cpp b/host/lib/utils/msg.cpp
index de98ada64..95879a116 100644
--- a/host/lib/utils/msg.cpp
+++ b/host/lib/utils/msg.cpp
@@ -79,6 +79,8 @@ void uhd::msg::register_handler(const handler_t &handler){
}
static void default_msg_handler(uhd::msg::type_t type, const std::string &msg){
+ static boost::mutex msg_mutex;
+ boost::mutex::scoped_lock lock(msg_mutex);
switch(type){
case uhd::msg::fastpath:
std::cerr << msg << std::flush;
diff --git a/host/lib/utils/paths.cpp b/host/lib/utils/paths.cpp
index 9cbc83062..38839c8d4 100644
--- a/host/lib/utils/paths.cpp
+++ b/host/lib/utils/paths.cpp
@@ -17,7 +17,6 @@
#include <uhd/config.hpp>
#include <uhd/exception.hpp>
-#include <uhd/transport/nirio/nifpga_lvbitx.h>
#include <uhd/utils/paths.hpp>
#include <boost/algorithm/string.hpp>