diff options
Diffstat (limited to 'host')
183 files changed, 16592 insertions, 1405 deletions
diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index ded4bb54c..45d0cd940 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -137,6 +137,7 @@ SET(BOOST_REQUIRED_COMPONENTS system thread unit_test_framework + serialization ) IF(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64") @@ -153,15 +154,11 @@ IF(MSVC) ENDIF(MSVC) SET(Boost_ADDITIONAL_VERSIONS - "1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39" - "1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44" - "1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49" - "1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" - "1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59" - "1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64" - "1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69" + "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44" "1.45.0" "1.45" + "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.48.0" "1.49" "1.50.0" "1.50" + "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54" "1.55.0" "1.55" ) -FIND_PACKAGE(Boost 1.36 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) +FIND_PACKAGE(Boost 1.41 COMPONENTS ${BOOST_REQUIRED_COMPONENTS}) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) @@ -213,8 +210,8 @@ UHD_INSTALL(FILES # Images download directory for utils/uhd_images_downloader.py ######################################################################## -SET(UHD_IMAGES_MD5SUM "0bab468817baa28024c4bf966d923b31") -SET(UHD_IMAGES_DOWNLOAD_SRC "http://files.ettus.com/binaries/maint_images/archive/uhd-images_003.006.002-release.zip") +SET(UHD_IMAGES_MD5SUM "00784ebb5243b0abb15db305f557e230") +SET(UHD_IMAGES_DOWNLOAD_SRC "http://files.ettus.com/binaries/maint_images/archive/uhd-images_003.007.000-48-ge1c32905.zip") ######################################################################## # Register top level components diff --git a/host/cmake/Modules/FindUDev.cmake b/host/cmake/Modules/FindUDev.cmake new file mode 100644 index 000000000..4c8390db6 --- /dev/null +++ b/host/cmake/Modules/FindUDev.cmake @@ -0,0 +1,19 @@ +# - Try to find UDev +# Once done this will define +# +# UDEV_FOUND - system has UDev +# UDEV_INCLUDE_DIR - the libudev include directory +# UDEV_LIBS - The libudev libraries + +# Copyright (c) 2010, Rafael Fernández López, <ereslibre@kde.org> +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +find_path(UDEV_INCLUDE_DIR libudev.h) +find_library(UDEV_LIBS udev) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(UDev DEFAULT_MSG UDEV_INCLUDE_DIR UDEV_LIBS) + +mark_as_advanced(UDEV_INCLUDE_DIR UDEV_LIBS) diff --git a/host/cmake/Modules/UHDPackage.cmake b/host/cmake/Modules/UHDPackage.cmake index 4968571b5..a98a52fc7 100644 --- a/host/cmake/Modules/UHDPackage.cmake +++ b/host/cmake/Modules/UHDPackage.cmake @@ -1,5 +1,5 @@ # -# Copyright 2010-2013 Ettus Research LLC +# 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 @@ -149,7 +149,7 @@ SET(CPACK_COMPONENTS_ALL libraries headers utilities examples manual doxygen rea ######################################################################## # Setup CPack Debian ######################################################################## -SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libusb-1.0-0, libboost-all-dev") +SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-all-dev") SET(CPACK_DEBIAN_PACKAGE_RECOMMENDS "python, python-tk") FOREACH(filename preinst postinst prerm postrm) LIST(APPEND CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA ${CMAKE_BINARY_DIR}/debian/${filename}) @@ -167,7 +167,7 @@ CONFIGURE_FILE( ######################################################################## # Setup CPack RPM ######################################################################## -SET(CPACK_RPM_PACKAGE_REQUIRES "boost-devel, libusb1") +SET(CPACK_RPM_PACKAGE_REQUIRES "boost-devel") FOREACH(filename post_install post_uninstall pre_install pre_uninstall) STRING(TOUPPER ${filename} filename_upper) @@ -200,4 +200,7 @@ IF(MSVC) ENDIF(MSVC) ######################################################################## +IF(NOT ${CPACK_GENERATOR} STREQUAL NSIS) + SET(CPACK_SET_DESTDIR "ON") +ENDIF(NOT ${CPACK_GENERATOR} STREQUAL NSIS) INCLUDE(CPack) #include after setting vars diff --git a/host/cmake/Modules/UHDVersion.cmake b/host/cmake/Modules/UHDVersion.cmake index b0d04fe70..41c3ac20b 100644 --- a/host/cmake/Modules/UHDVersion.cmake +++ b/host/cmake/Modules/UHDVersion.cmake @@ -1,5 +1,5 @@ # -# Copyright 2010-2013 Ettus Research LLC +# 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 @@ -26,8 +26,8 @@ FIND_PACKAGE(Git QUIET) # - increment patch on for bug fixes and docs ######################################################################## SET(UHD_VERSION_MAJOR 003) -SET(UHD_VERSION_MINOR 006) -SET(UHD_VERSION_PATCH 002) +SET(UHD_VERSION_MINOR 007) +SET(UHD_VERSION_PATCH 000) ######################################################################## # Set up trimmed version numbers for DLL resource files and packages diff --git a/host/docs/CMakeLists.txt b/host/docs/CMakeLists.txt index fa6e98918..1ee0f1ade 100644 --- a/host/docs/CMakeLists.txt +++ b/host/docs/CMakeLists.txt @@ -25,8 +25,10 @@ SET(manual_sources calibration.rst coding.rst dboards.rst + gpio_api.rst gpsdo.rst gpsdo_b2x0.rst + gpsdo_x3x0.rst general.rst images.rst stream.rst @@ -37,6 +39,8 @@ SET(manual_sources usrp_b100.rst usrp_b200.rst usrp_e1x0.rst + usrp_x3x0.rst + usrp_x3x0_config.rst ) ######################################################################## @@ -81,6 +85,14 @@ IF(ENABLE_MANUAL) #make the html manual a build-time dependency ADD_CUSTOM_TARGET(manual_html ALL DEPENDS ${manual_html_files}) UHD_INSTALL(FILES ${manual_sources} DESTINATION ${PKG_DOC_DIR}/manual/rst COMPONENT manual) + + #resources for html manual + ADD_CUSTOM_COMMAND( + TARGET manual_html POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/res ${CMAKE_CURRENT_BINARY_DIR}/res + ) + UHD_INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/res DESTINATION ${PKG_DOC_DIR}/manual/html COMPONENT manual) + ENDIF(ENABLE_MANUAL) ######################################################################## @@ -128,6 +140,7 @@ SET(man_page_sources uhd_images_downloader.1 uhd_usrp_probe.1 usrp_n2xx_simple_net_burner.1 + usrp_x3xx_fpga_burner.1 usrp2_card_burner.1 ) @@ -137,7 +150,12 @@ SET(man_page_sources MESSAGE(STATUS "") FIND_PACKAGE(GZip) -LIBUHD_REGISTER_COMPONENT("Man Pages" ENABLE_MAN_PAGES ON "GZIP_FOUND;LINUX" OFF) +# No elegant way in CMake to reverse a boolean +IF(NOT WIN32) + SET(NOT_WIN32 TRUE) +ENDIF(NOT WIN32) + +LIBUHD_REGISTER_COMPONENT("Man Pages" ENABLE_MAN_PAGES ON "GZIP_FOUND;NOT_WIN32" OFF) IF(ENABLE_MAN_PAGES) #Generate man pages diff --git a/host/docs/build.rst b/host/docs/build.rst index 5512e71ae..f53a56d9b 100644 --- a/host/docs/build.rst +++ b/host/docs/build.rst @@ -14,9 +14,11 @@ the dependencies should be available in the package repositories for your package manager. **Mac OS X Notes:** -Install the "Xcode Developer Tools" to get the build tools (GCC and Make). +Install the Xcode app to get the build tools (GCC and Make). Use MacPorts to get the Boost and Cheetah dependencies. -Other dependencies can be downloaded as DMG installers from the web. +Other dependencies can be downloaded as DMG installers from the web +or installed via MacPorts. +See the UHD OS X page for more information: http://code.ettus.com/redmine/ettus/projects/uhd/wiki/UHD_OS_X **Windows Notes:** The dependencies can be acquired through installable EXE files. @@ -148,11 +150,6 @@ or add it to **/etc/ld.so.conf** and make sure to run: sudo ldconfig -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Setup the library path (Mac OS X) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Make sure that **libuhd.dylib** is in your **DYLD_LIBRARY_PATH**. - ------------------------------------------------------------------------ Build Instructions (Windows) ------------------------------------------------------------------------ @@ -220,3 +217,12 @@ Post-Install Tasks For USB-based devices, see the `USB Transport Application Notes <./transport.html#usb-transport-libusb>`_ for platform-specific post-installation tasks. + +------------------------------------------------------------------------ +Post-Install Tasks (Mac OS X) +------------------------------------------------------------------------ +Make sure that the value of **CMAKE_INSTALL_PREFIX** is at or near the +front of the shell **PATH** environment variable. Do **NOT** set +DYLD_LIBRARY_PATH or any related DYLD environment variable +permanently; these work differently than under Linux and should be +used for testing / temporary purposes only. diff --git a/host/docs/calibration.rst b/host/docs/calibration.rst index 8ba3477b8..23bef01b7 100644 --- a/host/docs/calibration.rst +++ b/host/docs/calibration.rst @@ -5,14 +5,15 @@ UHD - Calibration Application Notes .. contents:: Table of Contents ------------------------------------------------------------------------ -Self-calibration +Self-Calibration ------------------------------------------------------------------------ -UHD software comes with several self-calibration utilities for minimizing IQ imbalance and DC offset. -These utilities perform calibration sweeps using transmit leakage into the receive path -(special equipment is not required). -The results from a calibration are written to a CSV file in the user's home directory. -UHD software will automatically apply corrections at runtime when the user re-tunes the daughterboard LO. -Calibration results are specific to an individual RF board. +UHD software comes with several self-calibration utilities for minimizing IQ +imbalance and DC offset. These utilities perform calibration sweeps using +transmit leakage into the receive path (special equipment is not required). +The results from a calibration are written to a CSV file in the user's home +directory. UHD software will automatically apply corrections at runtime when +the user re-tunes the daughterboard LO. Calibration results are specific to an +individual RF board. **Note:** When a calibration table is present, @@ -27,10 +28,10 @@ UHD software comes with the following calibration utilities: The following RF frontends are supported by the self-calibration utilities: - * RFX transceiver board - * WBX transceiver board - * SBX transceiver board - * CBX transceiver board + * RFX Series transceiver boards + * WBX Series transceiver boards + * SBX Series transceiver boards + * CBX Series transceiver boards ******************************************** Calibration Utilities @@ -54,7 +55,7 @@ may not have a serial number. If this is the case, run the following command to into the daughterboard's EEPROM: :: - <install dir>/share/uhd/utils/usrp_burn_db_eeprom --ser=<desired serial> --args=<optional device args> + <install dir>/lib/uhd/utils/usrp_burn_db_eeprom --ser=<desired serial> --args=<optional device args> ******************************************** Calibration Data diff --git a/host/docs/dboards.rst b/host/docs/dboards.rst index 4b5a074a8..d6cbc6151 100644 --- a/host/docs/dboards.rst +++ b/host/docs/dboards.rst @@ -26,15 +26,15 @@ The boards have no tunable elements or programmable gains. Through the magic of aliasing, you can down-convert signals greater than the Nyquist rate of the ADC. -BasicRX Bandwidth (Hz): +BasicRX Bandwidth: -* **For Real-Mode (A or B frontend)**: 250M -* **For Complex (AB or BA frontend)**: 500M +* **For Real-Mode (A or B frontend)**: 250 MHz +* **For Complex (AB or BA frontend)**: 500 MHz -LFRX Bandwidth (Hz): +LFRX Bandwidth: -* **For Real-Mode (A or B frontend)**: 33M -* **For Complex (AB or BA frontend)**: 66M +* **For Real-Mode (A or B frontend)**: 33 MHz +* **For Complex (AB or BA frontend)**: 66 MHz ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Basic TX and LFTX @@ -50,15 +50,15 @@ The boards have no tunable elements or programmable gains. Through the magic of aliasing, you can up-convert signals greater than the Nyquist rate of the DAC. -BasicTX Bandwidth (Hz): 250M +BasicTX Bandwidth (Hz): -* **For Real-Mode (A or B frontend**): 250M -* **For Complex (AB or BA frontend)**: 500M +* **For Real-Mode (A or B frontend**): 250 MHz +* **For Complex (AB or BA frontend)**: 500 MHz -LFTX Bandwidth (Hz): 33M +LFTX Bandwidth (Hz): -* **For Real-Mode (A or B frontend)**: 33M -* **For Complex (AB or BA frontend)**: 66M +* **For Real-Mode (A or B frontend)**: 33 MHz +* **For Complex (AB or BA frontend)**: 66 MHz ^^^^^^^^^^^^^^^^^^^^^^^^^^^ DBSRX @@ -77,7 +77,7 @@ Receive Gains: * **GC1**, Range: 0-56dB * **GC2**, Range: 0-24dB -Bandwidth (Hz): 8M-66M +Bandwidth: 8 MHz - 66 MHz Sensors: @@ -100,7 +100,7 @@ Receive Gains: * **GC1**, Range: 0-73dB * **BBG**, Range: 0-15dB -Bandwidth (Hz): 8M-80M +Bandwidth (Hz): 8 MHz -80 MHz Sensors: @@ -128,10 +128,10 @@ the receive antenna will always be set to RX2, regardless of the settings. Receive Gains: **PGA0**, Range: 0-70dB (except RFX400 range is 0-45dB) -Bandwidths (Hz): +Bandwidth: -* **RX**: 40M -* **TX**: 40M +* **RX**: 40 MHz +* **TX**: 40 MHz Sensors: @@ -142,10 +142,10 @@ XCVR 2450 ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The XCVR2450 has 2 quadrature frontends, one transmit, one receive. Transmit and Receive default to direct conversion but -can be used in low IF mode through lo_offset in uhd::tune_request_t +can be used in low IF mode through lo_offset in uhd::tune_request_t. The XCVR2450 has a non-contiguous tuning range consisting of a -high band (4.9-6.0GHz) and a low band (2.4-2.5GHz). +high band (4.9-6.0 GHz) and a low band (2.4-2.5 GHz). Transmit Antennas: **J1** or **J2** @@ -170,10 +170,10 @@ Receive Gains: * **LNA**, Range: 0-30.5dB * **VGA**, Range: 0-62dB -Bandwidths (Hz): +Bandwidths: -* **RX**: 15M, 19M, 28M, 36M; (each +-0, 5, or 10%) -* **TX**: 24M, 36M, 48M +* **RX**: 15 MHz, 19 MHz, 28 MHz, 36 MHz; (each +-0, 5, or 10%) +* **TX**: 24 MHz, 36 MHz, 48 MHz Sensors: @@ -183,12 +183,18 @@ Sensors: ^^^^^^^^^^^^^^^^^^^^^^^^^^^ WBX Series ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The WBX Series boards have 2 quadrature frontends, one transmit, one receive. -Transmit and Receive default to direct conversion but -can be used in low IF mode through lo_offset in **uhd::tune_request_t**. -The WBX Series boards have independent receive and transmit LO's and synthesizers -allowing full-duplex operation on different transmit and receive frequencies. +Features: + +* 2 quadrature frontends (1 transmit, 1 receive) + + * Defaults to direct conversion + * Can be used in low IF mode through lo_offset with **uhd::tune_request_t** + +* Independent recieve and transmit LO's and synthesizers + + * Allows for full-duplex operation on different transmit and receive frequencies + * Can be set to use Integer-N tuning for better spur performance with **uhd::tune_request_t** Transmit Antennas: **TX/RX** @@ -196,18 +202,16 @@ Receive Antennas: **TX/RX** or **RX2** * **Frontend 0:** Complex baseband signal for selected antenna -The user may set the receive antenna to be TX/RX or RX2. -However, when using an WBX board in full-duplex mode, -the receive antenna will always be set to RX2, regardless of the settings. +* **Note:** The user may set the receive antenna to be TX/RX or RX2. However, when using a WBX board in full-duplex mode, the receive antenna will always be set to RX2, regardless of the settings. Transmit Gains: **PGA0**, Range: 0-25dB Receive Gains: **PGA0**, Range: 0-31.5dB -Bandwidths (Hz): +Bandwidths: -* **RX**: 40M -* **TX**: 40M +* **WBX**: 40 MHz, RX & TX +* **WBX-120**: 120 MHz, RX & TX Sensors: @@ -216,12 +220,18 @@ Sensors: ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SBX Series ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The SBX Series boards have 2 quadrature frontends, one transmit, one receive. -Transmit and Receive default to direct conversion but -can be used in low IF mode through lo_offset in **uhd::tune_request_t**. -The SBX Series boards have independent receive and transmit LO's and synthesizers -allowing full-duplex operation on different transmit and receive frequencies. +Features: + +* 2 quadrature frontends (1 transmit, 1 receive) + + * Defaults to direct conversion + * Can be used in low IF mode through lo_offset with **uhd::tune_request_t** + +* Independent recieve and transmit LO's and synthesizers + + * Allows for full-duplex operation on different transmit and receive frequencies + * Can be set to use Integer-N tuning for better spur performance with **uhd::tune_request_t** Transmit Antennas: **TX/RX** @@ -229,18 +239,16 @@ Receive Antennas: **TX/RX** or **RX2** * **Frontend 0:** Complex baseband signal for selected antenna -The user may set the receive antenna to be TX/RX or RX2. -However, when using an SBX board in full-duplex mode, -the receive antenna will always be set to RX2, regardless of the settings. +* **Note:** The user may set the receive antenna to be TX/RX or RX2. However, when using an SBX board in full-duplex mode, the receive antenna will always be set to RX2, regardless of the settings. Transmit Gains: **PGA0**, Range: 0-31.5dB Receive Gains: **PGA0**, Range: 0-31.5dB -Bandwidths (Hz): +Bandwidths: -* **RX**: 40M -* **TX**: 40M +* **SBX**: 40 MHz, RX & TX +* **SBX-120**: 120 MHz, RX & TX Sensors: @@ -248,16 +256,56 @@ Sensors: LEDs: -* All LEDs flash when dboard control is initialized +* All LEDs flash when daughterboard control is initialized * **TX LD**: Transmit Synthesizer Lock Detect * **TX/RX**: Receiver on TX/RX antenna port (No TX) * **RX LD**: Receive Synthesizer Lock Detect * **RX1/RX2**: Receiver on RX2 antenna port ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -CBX +CBX Series ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -See SBX Series for more details. + +Features: + +* 2 quadrature frontends (1 transmit, 1 receive) + + * Defaults to direct conversion + * Can be used in low IF mode through lo_offset with **uhd::tune_request_t** + +* Independent recieve and transmit LO's and synthesizers + + * Allows for full-duplex operation on different transmit and receive frequencies + * Can be set to use Integer-N tuning for better spur performance with **uhd::tune_request_t** + +Transmit Antennas: **TX/RX** + +Receive Antennas: **TX/RX** or **RX2** + +* **Frontend 0:** Complex baseband signal for selected antenna + +* **Note:** The user may set the receive antenna to be TX/RX or RX2. However, when using a CBX board in full-duplex mode, the receive antenna will always be set to RX2, regardless of the settings. + +Transmit Gains: **PGA0**, Range: 0-31.5dB + +Receive Gains: **PGA0**, Range: 0-31.5dB + +Bandwidths: + +* **CBX**: 40 MHz, RX & TX +* **CBX-120**: 120 MHz, RX & TX + +Sensors: + +* **lo_locked**: boolean for LO lock state + +LEDs: + +* All LEDs flash when daughterboard control is initialized +* **TX LD**: Transmit Synthesizer Lock Detect +* **TX/RX**: Receiver on TX/RX antenna port (No TX) +* **RX LD**: Receive Synthesizer Lock Detect +* **RX1/RX2**: Receiver on RX2 antenna port ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TVRX @@ -274,7 +322,7 @@ Receive Gains: * **RF**, Range: -13.3-50.3dB (frequency-dependent) * **IF**, Range: -1.5-32.5dB -Bandwidth: 6MHz +Bandwidth: 6 MHz ^^^^^^^^^^^^^^^^^^^^^^^^^^^ TVRX2 @@ -294,7 +342,7 @@ Receive Gains: * **IF**, Range: 0.0-30.0dB -Bandwidth: 1.7MHz, 6MHz, 7MHz, 8MHz, 10MHz +Bandwidth: 1.7 MHz, 6 MHz, 7 MHz, 8 MHz, 10 MHz Sensors: @@ -302,15 +350,6 @@ Sensors: * **rssi**: float for measured RSSI in dBm * **temperature**: float for measured temperature in degC ------------------------------------------------------------------------- -Daughterboard Modifications ------------------------------------------------------------------------- - -Sometimes, daughterboards will require modification -to work on certain frequencies or to work with certain hardware. -Modification usually involves moving/removing an SMT component -and burning a new daughterboard ID into the EEPROM. - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ DBSRX - Mod ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -324,20 +363,20 @@ over the standard daughterboard clock lines. **Step 1: Move the clock configuration resistor** -Remove **R193** (which is 10 ohms, 0603 size), and put it on **R194**, which is empty. +Remove **R193** (which is 10 Ohms, 0603 size), and put it on **R194**, which is empty. This is made somewhat more complicated by the fact that the silkscreen is not clear in that area. **R193** is on the back, immediately below the large beige connector, **J2**. **R194** is just below, and to the left of **R193**. The silkscreen for **R193** is ok, but for **R194**, it is upside down, and partially cut off. -If you lose **R193**, you can use anything from 0 to 10 ohms there. +If you lose **R193**, you can use anything from 0 to 10 Ohms there. **Step 2: Burn a new daughterboard id into the EEPROM** With the daughterboard plugged-in, run the following commands: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_db_eeprom --id=0x000d --unit=RX --args=<args> --slot=<slot> * **<args>** are device address arguments (optional if only one USRP device is on your machine) @@ -357,14 +396,14 @@ Move **R64** to **R84**. Move **R142** to **R153**. **Step 2: Connect the motherboard blocks** Move **R35** to **R36**. Move **R117** to **R115**. -These are all 0-ohm, so if you lose one, just short across the appropriate pads. +These are all 0-Ohm, so if you lose one, just short across the appropriate pads. **Step 3: Burn the appropriate daughterboard ID into the EEPROM** With the daughterboard plugged-in, run the following commands: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_db_eeprom --id=<rx_id> --unit=RX --args=<args> --slot=<slot> ./usrp_burn_db_eeprom --id=<tx_id> --unit=TX --args=<args> --slot=<slot> diff --git a/host/docs/general.rst b/host/docs/general.rst index b116ba816..930c18188 100644 --- a/host/docs/general.rst +++ b/host/docs/general.rst @@ -1,16 +1,16 @@ -======================================================================== +=============================== UHD - General Application Notes -======================================================================== +=============================== .. contents:: Table of Contents ------------------------------------------------------------------------- +------------ Tuning Notes ------------------------------------------------------------------------- +------------ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^ Two-stage tuning process -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^ A USRP device has two stages of tuning: * RF front-end: translates bewteen RF and IF @@ -28,8 +28,19 @@ easy to move the DC component out of your band-of-interest. This can be done by passing your desired LO offset to the **tune_request_t** object, and letting the UHD software handle the rest. +The **tune_request_t** object can also be used with certain daughterboards to use +Integer-N tuning instead of the default fractional tuning, allowing for better spur +performance. The daughterboards that support this functionality are: + +* WBX (all revisions) +* WBX-120 +* SBX (all revisions) +* SBX-120 +* CBX +* CBX-120 + Tuning the receive chain: -:: +::::::::::::::::::::::::: //tuning to a desired center frequency usrp->set_rx_freq(target_frequency_in_hz); @@ -38,14 +49,15 @@ Tuning the receive chain: //advanced tuning with tune_request_t uhd::tune_request_t tune_req(target_frequency_in_hz, desired_lo_offset); + tune_req.args = uhd::device_addr_t("mode_n=integer"); //to use Int-N tuning //fill in any additional/optional tune request fields... usrp->set_rx_freq(tune_req); -More information can be fonud in *tune_request.hpp*. +More information can be found in `tune_request.hpp <./../../doxygen/html/structuhd_1_1tune__request__t.html>`_. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ RF front-end settling time -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ After tuning, the RF front-end will need time to settle into a usable state. Typically, this means that the local oscillators must be given time to lock before streaming begins. Lock time is not consistent; it varies depending upon @@ -54,6 +66,8 @@ should wait for the **lo_locked** sensor to become true or sleep for a conservative amount of time (perhaps a second). Pseudo-code for dealing with settling time after tuning on receive: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + :: usrp->set_rx_freq(...); @@ -68,9 +82,10 @@ Pseudo-code for dealing with settling time after tuning on receive: } usrp->issue_stream_command(...); ------------------------------------------------------------------------- + +------------------------------- Specifying the Subdevice to Use ------------------------------------------------------------------------- +------------------------------- A subdevice specification string for USRP family devices is composed of: :: @@ -97,16 +112,16 @@ Ex: The subdev spec markup string to select a BasicRX on slot B. B:B -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ USRP Family Motherboard Slot Names -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ All USRP family motherboards have a first slot named **A:**. The USRP1 has two daughterboard subdevice slots, known as **A:** and **B:**. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Daughterboard Frontend Names -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Daughterboard frontend names can be used to specify which signal path is used from a daughterboard. Most daughterboards have only one frontend **:0**. A few @@ -114,15 +129,15 @@ daughterboards (Basic, LF and TVRX2) have multiple frontend names available. The frontend names are documented in the `Daughterboard Application Notes <./dboards.html>`_ ------------------------------------------------------------------------- +------------------------ Overflow/Underflow Notes ------------------------------------------------------------------------- +------------------------ **Note:** The following overflow/underflow notes do not apply to USRP1, which does not support the advanced features available in newer products. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ Overflow notes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ When receiving, the device produces samples at a constant rate. Overflows occurs when the host does not consume data fast enough. When UHD software detects the overflow, it prints an "O" or "D" to stdout, @@ -142,23 +157,24 @@ When the device's internal buffers become full, streaming is shut off, and an inline message packet is sent to the host. In this case the character "O" is printed to stdout as an indication. If the device was in continuous streaming mode, -the UHD software will automatically restart streaming. +the UHD software will automatically restart streaming when the buffer has +space again. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^ Underflow notes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^ When transmitting, the device consumes samples at a constant rate. Underflow occurs when the host does not produce data fast enough. When UHD software detects the underflow, it prints a "U" to stdout, and pushes a message packet into the async message stream. ------------------------------------------------------------------------- +--------------- Threading Notes ------------------------------------------------------------------------- +--------------- -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^ Thread safety notes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^ For the most part, UHD software is thread-safe. Please observe the following limitations: @@ -175,9 +191,9 @@ This is because changing one setting could have an impact on how a call affects Example: setting the channel mapping affects how the antennas are set. It is recommended to use at most one thread context for manipulating device settings. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ Thread priority scheduling -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ When UHD software spawns a new thread it may try to boost the thread's scheduling priority. When setting the priority fails, the UHD software prints out an error. @@ -187,29 +203,29 @@ This error is harmless; it simply means that the thread will have a normal sched Non-privileged users need special permission to change the scheduling priority. Add the following line to **/etc/security/limits.conf**: -:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::: @<my_group> - rtprio 99 Replace **<my_group>** with a group to which your user belongs. Settings will not take effect until the user is in a different login session. ------------------------------------------------------------------------- +------------------- Miscellaneous Notes ------------------------------------------------------------------------- +------------------- -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Support for dynamically loadable modules -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For a module to be loaded at runtime, it must be: * found in the **UHD_MODULE_PATH** environment variable, * installed into the **<install-path>/share/uhd/modules** directory, * or installed into **/usr/share/uhd/modules** directory (UNIX only). -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Disabling or redirecting prints to stdout -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The user can disable the UHD library from printing directly to stdout by registering a custom message handler. The handler will intercept all messages, which can be dropped or redirected. Only one handler can be registered at a time. diff --git a/host/docs/gpio_api.rst b/host/docs/gpio_api.rst new file mode 100644 index 000000000..9fd86d081 --- /dev/null +++ b/host/docs/gpio_api.rst @@ -0,0 +1,129 @@ +======================================================================== +UHD - X3x0 GPIO API +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +The X3x0 Front Panel GPIO +------------------------------------------------------------------------ +The X3x0 is the first USRP device to offer an auxiliary GPIO connection on the +motherboard itself (independent of the daughterboards). These GPIO pins are +controlled directly by the FPGA, where they are controlled by an ATR (Automatic +Transmit / Receive). This allows them to be toggled simultaneously with other +radio-level changes (e.g., enabling or disabling a TX or RX mixer). + + +^^^^^^^^^^^^^^^^ +Front Panel GPIO +^^^^^^^^^^^^^^^^ + +Connector +::::::::: + +.. image:: ./res/x3x0_gpio_conn.png + :scale: 75% + :align: left + +Pin Mapping +::::::::::: + +* Pin 1: +3.3V +* Pin 2: Data[0] +* Pin 3: Data[1] +* Pin 4: Data[2] +* Pin 5: Data[3] +* Pin 6: Data[4] +* Pin 7: Data[5] +* Pin 8: Data[6] +* Pin 9: Data[7] +* Pin 10: Data[8] +* Pin 11: Data[9] +* Pin 12: Data[10] +* Pin 13: Data[11] +* Pin 14: 0V +* Pin 15: 0V + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Explaining ATR +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +ATR works by defining the value of the GPIO pins for certain states of the +radio. This is the "automatic" part of it. For example, you can tell UHD that +when the radio is transmitting and receiving (full duplex), GPIO6 should be +high, but when it is only transmitting, GPI06 should be low. This state machine +is set up using a series of GPIO attributes, with paired values and a mask, +which you will want to define for the GPIO pins you intend to use. To set up +the ATR, you use the **multi_usrp** function *set_gpio_attr*. + +* **CTRL**: Is this pin controlled by ATR (automatic), or by manual control + only? +* **DDR**: "Data Direction Register" - defines whether or not a GPIO is an + output or an input. +* **OUT**: Manually set the value of a pin (only to be used in non-ATR mode). +* **ATR_0X**: The status of the pins when the radio is **idle**. +* **ATR_RX**: The status of the pins when the radio is only **receiving**. +* **ATR_TX**: The status of the pins when the radio is only **transmitting**. +* **ATR_XX**: The status of the pins when the radio is in **full-duplex** mode. + +The counterpart to setting the ATR (the "getter"), is called *get_gpio_attr*. +It has the exact same attributes as above, and has one more: + +* **READBACK**: Readback the GPIOs marked as inputs. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +An Example +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The front panel X3x0 GPIO bank is enumerated in the motherboard property tree +("*<mb_path>/gpio/FP0/*"), and so is easily accessible through the standard +**multi_usrp** UHD interface. + +You can discover this using the *get_gpio_banks* function in **multi_usrp**. +This will tell you that there is a GPIO bank on your X3x0 called "FP0". This is +the bank we want to set-up. + +Let's say we want to use GPIO6 for an external amp. We want it to be +automatically controlled by ATR as an output, and we want it to be high when we +are transmitting, and low in all other cases. We are also using GPIO4, which +we want to control manually, as an output. We can set this up with the following +code: + +:: + + // set up our masks, defining the pin numbers + #define AMP_GPIO_MASK (1 << 6) + #define MAN_GPIO_MASK (1 << 4) + + #define ATR_MASKS (AMP_GPIO_MASK | MAN_GPIO_MASK) + + // set up our values for ATR control: 1 for ATR, 0 for manual + #define ATR_CONTROL (AMP_GPIO_MASK & ~MAN_GPIO_MASK) + + // set up the GPIO directions: 1 for output, 0 for input + #define GPIO_DDR (AMP_GPIO_MASK & ~MAN_GPIO_MASK) + + // assume an existing USRP device handle, called "usrp_x300" + + // now, let's do the basic ATR setup + usrp_x300->set_gpio_attr("FP0", "CTRL", ATR_CONTROL, ATR_MASKS); + usrp_x300->set_gpio_attr("FP0", "DDR", GPIO_DDR, ATR_MASKS); + + // let's manually set GPIO4 high + usrp_x300->set_gpio_attr("FP0", "OUT", 1, MAN_GPIO_MASK); + + // finally, let's set up GPIO6 as we described above + usrp_x300->set_gpio_attr("FP0", "ATR_0X", 0, AMP_GPIO_MASK); + usrp_x300->set_gpio_attr("FP0", "ATR_RX", 0, AMP_GPIO_MASK); + usrp_x300->set_gpio_attr("FP0", "ATR_TX", 0, AMP_GPIO_MASK); + usrp_x300->set_gpio_attr("FP0", "ATR_XX", 0, AMP_GPIO_MASK); + +After the above code is run, the ATR in the FPGA will automatically control +GPIO6, as we have described, based on the radio state, and we have direct +manual control over GPIO4. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Further Information +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +For more information, see the Doxygen API documentation: + +* `multi_usrp API <./../../doxygen/html/classuhd_1_1usrp_1_1multi__usrp.html>`_ diff --git a/host/docs/gpsdo.rst b/host/docs/gpsdo.rst index 3b718fc20..8ffff8672 100644 --- a/host/docs/gpsdo.rst +++ b/host/docs/gpsdo.rst @@ -4,8 +4,13 @@ UHD - Internal GPSDO Application Notes (USRP-N2x0/E1X0 Models) .. contents:: Table of Contents -This application note describes the use of integrated GPS-disciplined -oscillators with Ettus Research USRP devices. +This application note describes the use of integrated GPS-disciplined oscillators (GPSDOs) for +the USRP N-Series and E1xx. For information regarding the GPSDO that is compatible with +the USRP X-Series, please see: + +`USRP-X3x0 Internal GPSDO Device Manual <./gpsdo_x3x0.html>`_ + +======= ------------------------------------------------------------------------ Specifications @@ -16,11 +21,11 @@ Specifications * **Holdover**: <11us over 3h * **Phase noise**: - * **1Hz:** -80dBc/Hz - * **10Hz:** -110dBc/Hz - * **100Hz:** -135dBc/Hz - * **1kHz:** -145dBc/Hz - * **10kHz:** <-145dBc/Hz + * **1Hz:** -80 dBc/Hz + * **10Hz:** -110 dBc/Hz + * **100Hz:** -135 dBc/Hz + * **1kHz:** -145 dBc/Hz + * **10kHz:** <-145 dBc/Hz **Antenna Types:** @@ -29,7 +34,7 @@ The GPSDO is capable of supplying a 3V for active GPS antennas or supporting pas ------------------------------------------------------------------------ Installation Instructions ------------------------------------------------------------------------ -Installation instructions can be found here: +Instructions for mounting the GPSDO kit onto your USRP device can be found here: `http://www.ettus.com/content/files/gpsdo-kit_2.pdf <http://www.ettus.com/content/files/gpsdo-kit_2.pdf>`_ ******************************************** @@ -40,7 +45,7 @@ Post-installation Task (N-Series only) This is necessary if you require absolute GPS time in your application or need to communicate with the GPSDO to obtain location, satellite info, etc. -If you only require 10MHz and PPS signals for reference or MIMO use +If you only require 10 MHz and PPS signals for reference or MIMO use (see the `Synchronization Application Notes <./sync.html>`_), it is not necessary to perform this step. @@ -49,7 +54,7 @@ To configure the USRP to communicate with the GPSDO, use the :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_mb_eeprom --args=<optional device args> --key=gpsdo --val=internal -- restore original setting -- @@ -61,7 +66,7 @@ Using the GPSDO in Your Application By default, if a GPSDO is detected at startup, the USRP will be configured to use it as a frequency and time reference. The internal VITA timestamp will be initialized to the GPS time, and the internal oscillator will be -phase-locked to the 10MHz GPSDO reference. If the GPSDO is not locked to +phase-locked to the 10 MHz GPSDO reference. If the GPSDO is not locked to satellites, the VITA time will not be initialized. GPS data is obtained through the **mboard_sensors** interface. To retrieve diff --git a/host/docs/gpsdo_b2x0.rst b/host/docs/gpsdo_b2x0.rst index 8bc363e97..a0815d23a 100644 --- a/host/docs/gpsdo_b2x0.rst +++ b/host/docs/gpsdo_b2x0.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - Internal GPSDO Application Notes (USRP-B2X0 Models) +UHD - Internal GPSDO Application Notes (USRP-B2x0 Models) ======================================================================== .. contents:: Table of Contents @@ -17,21 +17,21 @@ Specifications **Phase noise**: -+------------+-------------+------------+ -| | TCXO | OXCO | -+============+=============+============+ -| **1Hz** | -65dBc/Hz | -75dBc/Hz | -+------------+-------------+------------+ -| **10Hz** | -102dBc/Hz | -110dBc/Hz | -+------------+-------------+------------+ -| **100Hz** | -132dBc/Hz | -132dBc/Hz | -+------------+-------------+------------+ -| **1kHz** | -148dBc/Hz | -142dBc/Hz | -+------------+-------------+------------+ -| **10kHz** | -152dBc/Hz | -145dBc/Hz | -+------------+-------------+------------+ -| **100kHz** | <-155dBc/Hz | -150dBc/Hz | -+------------+-------------+------------+ ++------------+-------------+ +| | TCXO | ++============+=============+ +| **1Hz** | -65dBc/Hz | ++------------+-------------+ +| **10Hz** | -102dBc/Hz | ++------------+-------------+ +| **100Hz** | -132dBc/Hz | ++------------+-------------+ +| **1kHz** | -148dBc/Hz | ++------------+-------------+ +| **10kHz** | -152dBc/Hz | ++------------+-------------+ +| **100kHz** | <-155dBc/Hz | ++------------+-------------+ **Antenna Types:** diff --git a/host/docs/gpsdo_x3x0.rst b/host/docs/gpsdo_x3x0.rst new file mode 100644 index 000000000..0d4d31f3b --- /dev/null +++ b/host/docs/gpsdo_x3x0.rst @@ -0,0 +1,82 @@ +======================================================================== +UHD - Internal GPSDO Application Notes (USRP-X3x0 Models) +======================================================================== + +.. contents:: Table of Contents + +This application note describes the use of the board-mounted GPS Disciplined OCXO, +as used with the USRP X300/X310. For information regarding the GPSDO that is +compatible with the USRP N2xx or E1xx, please see: + +`USRP-N2x0/E1x0 Internal GPSDO Device Manual <./gpsdo.html>`_ + +------------------------------------------------------------------------ +Specifications +------------------------------------------------------------------------ +* **Receiver type**: 50 channel with WAAS, EGNOS, MSAS +* **10 MHz ADEV**: 5e-11 over >24h +* **1PPS RMS jitter**: <50ns 1-sigma +* **Holdover**: <20us over 3h + +**Phase noise**: + ++------------+------------+ +| | OCXO | ++============+============+ +| **1Hz** | -75dBc/Hz | ++------------+------------+ +| **10Hz** | -110dBc/Hz | ++------------+------------+ +| **100Hz** | -132dBc/Hz | ++------------+------------+ +| **1kHz** | -142dBc/Hz | ++------------+------------+ +| **10kHz** | -145dBc/Hz | ++------------+------------+ +| **100kHz** | -150dBc/Hz | ++------------+------------+ + +**Antenna Types:** + +The GPSDO is capable of supplying a 3V for active GPS antennas or supporting passive antennas. + +------------------------------------------------------------------------ +Installation Instructions +------------------------------------------------------------------------ +To install the GPSDO, you must insert it into the slot on the board +near the 10 MHz Reference SMA. Keep in mind that the two sides of the +GPSDO have a different number of pins. When inserting the GPSDO, make +sure to press down firmly and evenly. When turning on the USRP B2X0 device, +a green LED should illuminate on the GPSDO. This signifies that the unit +has successfully been placed. + +**NOTE: The pins on the GPSDO are very fragile. Be sure to press down +evenly, or the pins may bend or break. Once the GPSDO is in place, +we very highly discourage further removal, as this also risks damaging +the pins.** + +------------------------------------------------------------------------ +Using the GPSDO in Your Application +------------------------------------------------------------------------ +By default, if a GPSDO is detected at startup, the USRP will be configured +to use it as a frequency and time reference. The internal VITA timestamp +will be initialized to the GPS time, and the internal oscillator will be +phase-locked to the 10MHz GPSDO reference. If the GPSDO is not locked to +satellites, the VITA time will not be initialized. + +GPS data is obtained through the **mboard_sensors** interface. To retrieve +the current GPS time, use the **gps_time** sensor: + +:: + + usrp->get_mboard_sensor("gps_time"); + +The returned value will be the current epoch time, in seconds since +January 1, 1970. This value is readily converted into human-readable +format using the **time.h** library in C, **boost::posix_time** in C++, etc. + +Other information can be fetched as well. You can query the lock status +with the **gps_locked** sensor, as well as obtain raw NMEA sentences using +the **gps_gprmc**, and **gps_gpgga** sensors. Location +information can be parsed out of the **gps_gpgga** sensor by using **gpsd** or +another NMEA parser. diff --git a/host/docs/identification.rst b/host/docs/identification.rst index ba9b30898..cbae25082 100644 --- a/host/docs/identification.rst +++ b/host/docs/identification.rst @@ -1,38 +1,40 @@ -======================================================================== +================================= UHD - Device Identification Notes -======================================================================== +================================= .. contents:: Table of Contents ------------------------------------------------------------------------- +------------------------ Identifying USRP Devices ------------------------------------------------------------------------- +------------------------ Devices are addressed through key/value string pairs. These string pairs can be used to narrow down the search for a specific device or group of devices. Most UHD utility applications and examples have an **--args** parameter that takes a device address, which is expressed as a delimited string. See the documentation in **types/device_addr.hpp** for reference. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^ Common device identifiers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^ Every device has several ways of identifying it on the host system: -+------------+------------+--------------------------------------------+ -| Identifier | Key | Notes | -+============+============+============================================+ -| Serial | serial | globally unique identifier | -+------------+------------+--------------------------------------------+ -| Address | addr | unique identifier on a network | -+------------+------------+--------------------------------------------+ -| Name | name | optional user-set identifier | -+------------+------------+--------------------------------------------+ -| Type | type | hardware series identifier | -+------------+------------+--------------------------------------------+ - -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ++------------+----------+-----------------------------------------------------------+------------------------------- +| Identifier | Key | Notes | Example ++============+==========+===========================================================+=============================== +| Serial | serial | globally unique identifier | 12345678 ++------------+----------+-----------------------------------------------------------+---------------------------- +| Address | addr | unique identifier on a network | 192.168.10.2 ++------------+----------+-----------------------------------------------------------+------------------------------- +| Resource | resource | unique identifier for USRP RIO devices (over PCI Express) | RIO0 ++------------+----------+-----------------------------------------------------------+------------------------------- +| Name | name | optional user-set identifier | my_usrp1 (User-defined value) ++------------+----------+-----------------------------------------------------------+---------------------------- +| Type | type | hardware series identifier | usrp1, usrp2, ++------------+----------+-----------------------------------------------------------+---------------------------- + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Device discovery via command line -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Devices attached to your system can be discovered using the **uhd_find_devices** program. This program scans your system for supported devices and prints out an enumerated list of discovered devices and their addresses. @@ -52,9 +54,9 @@ Device address arguments can be supplied to narrow the scope of the search. uhd_find_devices --args="serial=12345678" -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Device discovery through the API -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The **device::find()** API call searches for devices and returns a list of discovered devices. :: @@ -76,21 +78,22 @@ The **hint** argument can be populated to narrow the scope of the search. hint["serial"] = "12345678"; uhd::device_addrs_t dev_addrs = uhd::device::find(hint); -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ Device properties -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ Properties of devices attached to your system can be probed with the **uhd_usrp_probe** program. This program constructs an instance of the device and prints out its properties, such as detected daughterboards, frequency range, gain ranges, etc... **Usage:** + :: uhd_usrp_probe --args <device-specific-address-args> ------------------------------------------------------------------------- +-------------------- Naming a USRP Device ------------------------------------------------------------------------- +-------------------- For convenience purposes, users may assign a custom name to their USRP device. The USRP device can then be identified via name, rather than a difficult to remember serial or address. @@ -100,22 +103,24 @@ A name has the following properties: * is 0-20 characters * is not required to be unique -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ Set a custom name -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ Run the following commands: + :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_mb_eeprom --args=<optional device args> --key=name --val=lab1_xcvr -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ Discovery via name -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ The keyword **name** can be used to narrow the scope of the search. Example with the find devices utility: + :: uhd_find_devices --args="name=lab1_xcvr" diff --git a/host/docs/images.rst b/host/docs/images.rst index 95e2d96b0..37fbabf4b 100644 --- a/host/docs/images.rst +++ b/host/docs/images.rst @@ -12,9 +12,12 @@ The methods of loading images into the device vary among devices: * **USRP1:** The host code will automatically load the firmware and FPGA at runtime. * **USRP2:** The user must manually write the images onto the USRP2 SD card. -* **USRP-N Series:** The user must manually transfer the images over ethernet. +* **USRP-N Series:** The user programs an image into on-board storage, which + then is automatically loaded at runtime. * **USRP-E Series:** The host code will automatically load the FPGA at runtime. * **USRP-B Series:** The host code will automatically load the FPGA at runtime. +* **USRP-X Series:** The user programs an image into on-board storage, which + then is automatically loaded at runtime. ------------------------------------------------------------------------ Pre-built Images @@ -25,8 +28,6 @@ Pre-built images are available for download. * `Master Branch images <http://files.ettus.com/binaries/master_images/>`_ * `Maint Branch images <http://files.ettus.com/binaries/maint_images/>`_ -See the UHD wiki for the download link. - The pre-built images come in two forms: * bundled with UHD software in a platform-specific installer @@ -36,11 +37,10 @@ The pre-built images come in two forms: UHD Images Downloader ^^^^^^^^^^^^^^^^^^^^^^ -The UHD Images Downloader is a new feature in UHD 003.005.000. This script downloads UHD images that -are guaranteed to be compatible with the host code and places them in the default images -directory. +The UHD images downloader downloads UHD images compatible with the host code +and places them in the default images directory. -By default, it can be found at: **<install-path>/share/uhd/utils/uhd_images_downloader.py** +By default, it can be found at: **<install-path>/lib/uhd/utils/uhd_images_downloader.py** By default, it installs images to: **<install-path>/share/uhd/images** @@ -79,10 +79,25 @@ The build commands for a particular image can be found in **<uhd-repo-path>/imag ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Xilinx FPGA builds ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Xilinx ISE 12.x and up is required to build the Xilinx FPGA images. +USRP Xilinx FPGA images are built with two different versions of ISE, depending +on the device. + The build requires that you have a UNIX-like environment with **Make**. Make sure that **xtclsh** from the Xilinx ISE bin directory is in your **$PATH**. + +**Xilinx ISE 14.4** +* USRP X3x0 Series + +See **<uhd-repo-path>/fpga/usrp3/top/**. + +**Xilinx ISE 12.2** +* USRP N2x0 +* USRP B2x0 +* USRP B1x0 +* USRP E1x0 +* USRP2 + See **<uhd-repo-path>/fpga/usrp2/top/**. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -97,7 +112,7 @@ See **<uhd-repo-path>/firmware/zpu**. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Altera FPGA builds ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Quartus is required to build the Altera FPGA images. +Quartus is required to build the Altera FPGA image for the USRP1. Pre-built images can also be found in **<uhd-repo-path>/fpga/usrp1/rbf**. See **<uhd-repo-path>/fpga/usrp1/toplevel/***. diff --git a/host/docs/index.rst b/host/docs/index.rst index 65069059f..ffad1488d 100644 --- a/host/docs/index.rst +++ b/host/docs/index.rst @@ -18,25 +18,49 @@ Building and Installing UHD Software * `Installation Guide (Windows) <http://code.ettus.com/redmine/ettus/projects/uhd/wiki/UHD_Windows>`_ ^^^^^^^^^^^^^^^^^^^^^ -Application Notes +General UHD Manuals ^^^^^^^^^^^^^^^^^^^^^ * `General Application Notes <./general.html>`_ * `Device Identification Notes <./identification.html>`_ * `Firmware and FPGA Image Notes <./images.html>`_ -* `USRP1 Application Notes <./usrp1.html>`_ -* `USRP2 Application Notes <./usrp2.html>`_ -* `USRP-N2X0 Series Application Notes <./usrp2.html>`_ -* `USRP-B100 Series Application Notes <./usrp_b100.html>`_ -* `USRP-B2X0 Series Application Notes <./usrp_b200.html>`_ -* `USRP-E1X0 Series Application Notes <./usrp_e1x0.html>`_ * `Daughterboard Application Notes <./dboards.html>`_ * `Transport Application Notes <./transport.html>`_ * `Synchronization Application Notes <./sync.html>`_ -* `Internal GPSDO Application Notes (USRP-N2X0/E1X0 Models) <./gpsdo.html>`_ -* `Internal GPSDO Application Notes (USRP-B2X0 Models) <./gpsdo_b2x0.html>`_ * `Calibration Application Notes <./calibration.html>`_ ^^^^^^^^^^^^^^^^^^^^^ +USRP N-Series Devices +^^^^^^^^^^^^^^^^^^^^^ +* `USRP-N2x0 Series Device Manual <./usrp2.html>`_ +* `USRP-N2x0 Internal GPSDO Device Manual <./gpsdo.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +USRP B-Series Devices +^^^^^^^^^^^^^^^^^^^^^ +* `USRP-B100 Series Device Manual <./usrp_b100.html>`_ +* `USRP-B2x0 Series Device Manual <./usrp_b200.html>`_ +* `USRP1 Device Manual <./usrp1.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +USRP E-Series Devices +^^^^^^^^^^^^^^^^^^^^^ +* `USRP-E1x0 Series Device Manual <./usrp_e1x0.html>`_ +* `USRP-E1x0 Internal GPSDO Device Manual <./gpsdo.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +USRP X-Series Devices +^^^^^^^^^^^^^^^^^^^^^ +* `USRP-X3x0 Series Device Manual <./usrp_x3x0.html>`_ +* `USRP-X3x0 Internal GPSDO Device Manual <./gpsdo_x3x0.html>`_ +* `USRP-X3x0 Front Panel GPIO API <./gpio_api.html>`_ +* `USRP-X3x0 System Configuration <./usrp_x3x0_config.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ +USRP Legacy Series +^^^^^^^^^^^^^^^^^^^^^ +* `USRP2 Device Manual <./usrp2.html>`_ + +^^^^^^^^^^^^^^^^^^^^^ API Documentation ^^^^^^^^^^^^^^^^^^^^^ * `Doxygen <./../../doxygen/html/index.html>`_ diff --git a/host/docs/res/x3x0_fp_overlay.png b/host/docs/res/x3x0_fp_overlay.png Binary files differnew file mode 100644 index 000000000..8ab88d4a3 --- /dev/null +++ b/host/docs/res/x3x0_fp_overlay.png diff --git a/host/docs/res/x3x0_gpio_conn.png b/host/docs/res/x3x0_gpio_conn.png Binary files differnew file mode 100644 index 000000000..0147bf422 --- /dev/null +++ b/host/docs/res/x3x0_gpio_conn.png diff --git a/host/docs/res/x3x0_rp_overlay.png b/host/docs/res/x3x0_rp_overlay.png Binary files differnew file mode 100644 index 000000000..c34936ac3 --- /dev/null +++ b/host/docs/res/x3x0_rp_overlay.png diff --git a/host/docs/stream.rst b/host/docs/stream.rst index a00ef88ee..337d7ca40 100644 --- a/host/docs/stream.rst +++ b/host/docs/stream.rst @@ -16,7 +16,7 @@ A TX stream allows the user to transmit samples to the device. Link Layer Encapsulation ------------------------------------------------------------------------ The VITA49 standard provides encapsulation for sample data across a link layer. -On all generation2 hardware, samples are encapsulated into VRT IF data packets. +On all second generation hardware (and later), samples are encapsulated into VRT IF data packets. These packets also provide sample decoration such as stream time and burst flags. Sample decoration is exposed to the user in the form of RX and TX metadata structs. @@ -54,6 +54,6 @@ Conversion The user may request arbitrary combinations of host and link data types; however, not all combinations are supported. The user may register custom data type formats and conversion routines. -See **uhd/convert.hpp** for futher documentation. +See **uhd/convert.hpp** for further documentation. TODO: provide example of convert API diff --git a/host/docs/sync.rst b/host/docs/sync.rst index a1f6cb7df..21be60a20 100644 --- a/host/docs/sync.rst +++ b/host/docs/sync.rst @@ -153,7 +153,7 @@ For transmit, a burst is started when the user calls send(). The metadata should size_t num_tx_samps = tx_streamer->send(buffs, samps_to_send, md); ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Align LOs in the front-end (SBX/WBX + N-Series) +Align LOs in the front-end (SBX, WBX, CBX) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Using timed commands, multiple frontends can be tuned at a specific time. This timed-tuning ensures that the phase offsets between VCO/PLL chains @@ -163,7 +163,7 @@ will remain constant after each re-tune. See notes below: * This phase offset is different for different LO frequencies * This phase offset remains constant after retuning - * Due to divider, WBX phase offset will be randomly +/- 180 deg after re-tune + * Due to a divider, WBX phase offset will be randomly +/- 180 deg after re-tune * This phase offset will drift over time due to thermal and other characteristics * Periodic calibration will be necessary for phase-coherent applications diff --git a/host/docs/transport.rst b/host/docs/transport.rst index 79d2743a4..88f787893 100644 --- a/host/docs/transport.rst +++ b/host/docs/transport.rst @@ -7,14 +7,14 @@ UHD - Transport Application Notes ------------------------------------------------------------------------ Introduction ------------------------------------------------------------------------ -A transport is the layer between the packet interface and a device IO interface. -The advanced user can pass optional parameters -into the underlying transport layer through the device address. -These optional parameters control how the transport object allocates memory, -resizes kernel buffers, spawns threads, etc. -When not spcified, the transport layer will use values for these parameters -that are known to perform well on a variety of systems. -The transport parameters are defined below for the various transports in the UHD software: +A transport is the layer between the packet interface and a device IO +interface. The advanced user can pass optional parameters into the underlying +transport layer through the device address. These optional parameters control +how the transport object allocates memory, resizes kernel buffers, spawns +threads, etc. When not spcified, the transport layer will use values for these +parameters that are known to perform well on a variety of systems. The +transport parameters are defined below for the various transports in the UHD +software: ------------------------------------------------------------------------ UDP Transport (Sockets) @@ -147,7 +147,7 @@ so that non-root users may access the device: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils sudo cp uhd-usrp.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger diff --git a/host/docs/uhd_cal_rx_iq_balance.1 b/host/docs/uhd_cal_rx_iq_balance.1 index 073d8ce63..8c2c87ece 100644 --- a/host/docs/uhd_cal_rx_iq_balance.1 +++ b/host/docs/uhd_cal_rx_iq_balance.1 @@ -1,4 +1,4 @@ -.TH "uhd_cal_rx_iq_balance" "1" "3.5.1" "UHD" "User Commands" +.TH "uhd_cal_rx_iq_balance" "1" "3.7.0" "UHD" "User Commands" .SH NAME uhd_cal_rx_iq_balance \- Generate RX IQ Balance Calibration Table for a UHD Device .SH DESCRIPTION diff --git a/host/docs/uhd_cal_tx_dc_offset.1 b/host/docs/uhd_cal_tx_dc_offset.1 index d64120242..f0479d7ec 100644 --- a/host/docs/uhd_cal_tx_dc_offset.1 +++ b/host/docs/uhd_cal_tx_dc_offset.1 @@ -1,4 +1,4 @@ -.TH "uhd_cal_tx_dc_offset" "1" "3.5.1" "UHD" "User Commands" +.TH "uhd_cal_tx_dc_offset" "1" "3.7.0" "UHD" "User Commands" .SH NAME uhd_cal_tx_dc_offset \- Generate TX DC Offset Calibration Table for a UHD Device .SH DESCRIPTION diff --git a/host/docs/uhd_cal_tx_iq_balance.1 b/host/docs/uhd_cal_tx_iq_balance.1 index f4fdbd12f..bb8970f4c 100644 --- a/host/docs/uhd_cal_tx_iq_balance.1 +++ b/host/docs/uhd_cal_tx_iq_balance.1 @@ -1,4 +1,4 @@ -.TH "uhd_cal_tx_iq_balance" "1" "3.5.1" "UHD" "User Commands" +.TH "uhd_cal_tx_iq_balance" "1" "3.7.0" "UHD" "User Commands" .SH NAME uhd_cal_tx_iq_balance \- Generate TX IQ Balance Calibration Table for a UHD Device .SH DESCRIPTION diff --git a/host/docs/uhd_find_devices.1 b/host/docs/uhd_find_devices.1 index 08f9a789c..416f807b9 100644 --- a/host/docs/uhd_find_devices.1 +++ b/host/docs/uhd_find_devices.1 @@ -1,4 +1,4 @@ -.TH "uhd_find_devices" 1 "3.5.1" UHD "User Commands" +.TH "uhd_find_devices" 1 "3.7.0" UHD "User Commands" .SH NAME uhd_find_devices \- USRP Hardware Driver Discovery Utility .SH DESCRIPTION diff --git a/host/docs/uhd_images_downloader.1 b/host/docs/uhd_images_downloader.1 index 28310dd37..dd0fe208d 100644 --- a/host/docs/uhd_images_downloader.1 +++ b/host/docs/uhd_images_downloader.1 @@ -1,4 +1,4 @@ -.TH "uhd_images_downloader" 1 "3.5.1" UHD "User Commands" +.TH "uhd_images_downloader" 1 "3.7.0" UHD "User Commands" .SH NAME uhd_images_downloader \- USRP Hardware Driver firmware/FPGA downloader .SH DESCRIPTION @@ -12,7 +12,7 @@ matching this UHD driver release onto the host system. If the uhd-images package is installed, there is no need to run this installer. .SH SYNOPSIS .B uhd_images_downloader -Usage: uhd_images_downloader.py [options] +Usage: uhd_images_downloader [options] .SH OPTIONS This program works best without any arguments. .PP @@ -38,7 +38,7 @@ usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) This manual page was written by Maitland Bottoms and Nicholas Corgan for the Debian project (but may be used by others). .SH COPYRIGHT -Copyright (c) 2012 Ettus Research LLC +Copyright (c) 2012,2014 Ettus Research LLC .LP This 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/docs/uhd_usrp_probe.1 b/host/docs/uhd_usrp_probe.1 index a6c7150ee..ee30b2350 100644 --- a/host/docs/uhd_usrp_probe.1 +++ b/host/docs/uhd_usrp_probe.1 @@ -1,4 +1,4 @@ -.TH "uhd_usrp_probe" 1 "3.5.1" UHD "User Commands" +.TH "uhd_usrp_probe" 1 "3.7.0" UHD "User Commands" .SH NAME uhd_usrp_probe \- USRP Hardware Driver Peripheral Report Utility .SH DESCRIPTION diff --git a/host/docs/usrp1.rst b/host/docs/usrp1.rst index b6fd24b09..be74fe00a 100644 --- a/host/docs/usrp1.rst +++ b/host/docs/usrp1.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - USRP1 Application Notes +UHD - USRP1 Device Manual ======================================================================== .. contents:: Table of Contents @@ -8,12 +8,20 @@ UHD - USRP1 Application Notes Comparative features list ------------------------------------------------------------------------ -* 2 transceiver card slots -* 2 RX DDC chains in FPGA -* 2 TX DUC chains in FPGA (no TX CORDIC -> uses DAC) -* 64 MHz fixed clock rate -* sc16 sample modes -* sc8 sample mode - RX only +**Hardware Capabilities:** + * 2 transceiver card slots + * 64 MHz fixed clock rate + +**FPGA Capabilities:** + * 2 RX DDC chains in FPGA + * 2 TX DUC chains in FPGA (no TX CORDIC -> uses DAC) + * sc16 sample modes - RX & TX + + - Up to 8 MHz of RF BW with 16-bit samples + + * sc8 sample mode - RX only + + - Up to 16 MHz of RF BW with 8-bit samples ------------------------------------------------------------------------ Specify a Non-standard Image @@ -93,7 +101,7 @@ so UHD software can initialize with the correct clock rate. Run the following commands to record the setting into the EEPROM: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_mb_eeprom --args=<optional device args> --key=mcr --val=<rate> The user may override the clock rate specified in the EEPROM by using a device address: diff --git a/host/docs/usrp2.rst b/host/docs/usrp2.rst index 8ef31af1a..361d98f01 100644 --- a/host/docs/usrp2.rst +++ b/host/docs/usrp2.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - USRP2 and N2X0 Series Application Notes +UHD - USRP2 and N2x0 Series Device Manual ======================================================================== .. contents:: Table of Contents @@ -8,23 +8,29 @@ UHD - USRP2 and N2X0 Series Application Notes Comparative features list ------------------------------------------------------------------------ -* 1 transceiver card slot -* 2 RX DDC chains in FPGA -* 1 TX DUC chain in FPGA -* Timed commands in FPGA (N2x0 only) -* Timed sampling in FPGA -* External PPS reference -* External 10MHz reference -* MIMO cable shared reference -* Fixed 100 MHz clock rate -* Internal GPSDO option (N2x0 only) -* sc8 and sc16 sample modes +**Hardware Capabilities:** + * 1 transceiver card slot + * External PPS reference input + * External 10 MHz reference input + * MIMO cable shared reference + * Fixed 100 MHz clock rate + * Internal GPSDO option (N2x0 only) + +**FPGA Capabilities:** + * 2 RX DDC chains in FPGA + * 1 TX DUC chain in FPGA + * Timed commands in FPGA (N2x0 only) + * Timed sampling in FPGA + * 16-bit and 8-bit sample modes (sc8 and sc16) + + * Up to 25 MHz of RF BW with 16-bit samples + * Up to 50 MHz of RF BW with 8-bit samples ------------------------------------------------------------------------ Load the Images onto the SD card (USRP2 only) ------------------------------------------------------------------------ **Warning!** -Use **usrp2_card_burner.py** with caution. If you specify the wrong device node, +Use **usrp2_card_burner** with caution. If you specify the wrong device node, you could overwrite your hard drive. Make sure that **--dev=** specifies the SD card. **Warning!** @@ -41,11 +47,11 @@ Use the card burner tool (UNIX) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - sudo <install-path>/share/uhd/utils/usrp2_card_burner_gui.py + sudo <install-path>/lib/uhd/utils/usrp2_card_burner_gui.py -- OR -- - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils sudo ./usrp2_card_burner.py --dev=/dev/sd<XXX> --fpga=<path_to_fpga_image> sudo ./usrp2_card_burner.py --dev=/dev/sd<XXX> --fw=<path_to_firmware_image> @@ -58,51 +64,46 @@ Use the card burner tool (Windows) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - <path_to_python.exe> <install-path>/share/uhd/utils/usrp2_card_burner_gui.py + <path_to_python.exe> <install-path>/lib/uhd/utils/usrp2_card_burner_gui.py ------------------------------------------------------------------------ Load the Images onto the On-board Flash (USRP-N Series only) ------------------------------------------------------------------------ -The USRP-N Series can be reprogrammed over the network -to update or change the firmware and FPGA images. +The USRP-N Series can be reprogrammed over the network to update or change the firmware and FPGA images. When updating images, always burn both the FPGA and firmware images before power cycling. This ensures that when the device reboots, it has a compatible set of images to boot into. -**Note:** -Different hardware revisions require different FPGA images. -Determine the revision number from the sticker on the rear of the chassis. -Use this number to select the correct FPGA image for your device. - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use the net burner tool (UNIX) +Use the net burner tool ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - <install-path>/share/uhd/utils/usrp_n2xx_net_burner_gui.py + Use default images: + usrp_n2xx_simple_net_burner --addr=<IP address> - -- OR -- + Use custom-built images: + usrp_n2xx_simple_net_burner --addr=<IP address> --fw=<firmware path> --fpga=<FPGA path> + +**Note:** +Different hardware revisions require different FPGA images. +Determine the revision number from the sticker on the rear of the chassis. +Use this number to select the correct FPGA image for your device. - cd <install-path>/share/uhd/utils - ./usrp_n2xx_net_burner.py --addr=<ip address> --fw=<path for firmware image> - ./usrp_n2xx_net_burner.py --addr=<ip address> --fpga=<path to FPGA image> +For users who would prefer a graphical utility, a Python-based alternative exists. -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Use the net burner tool (Windows) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the graphical net burner tool (Linux) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - <path_to_python.exe> <install-path>/share/uhd/utils/usrp_n2xx_net_burner_gui.py + <install-path>/lib/uhd/utils/usrp_n2xx_net_burner_gui.py -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Burning images without Python -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -For users who do not wish to install Python, a new script is available in UHD 003.005.000: -the USRP N2XX Simple Net Burner. It provides the same functionality as its Python -counterpart, but by default, it automatically installs the default images without the user needing -to specify their location on the command line. +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the graphical net burner tool (Windows) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: -The utility can be found at: **<install-path>/share/uhd/utils/usrp_n2xx_simple_net_burner** + <path_to_python.exe> <install-path>/lib/uhd/utils/usrp_n2xx_net_burner_gui.py ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Device recovery and bricking @@ -190,7 +191,7 @@ and the network must be setup properly as described above. Run the following commands: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_mb_eeprom --args=<optional device args> --key=ip-addr --val=192.168.10.3 **Method 2 (Linux Only):** @@ -199,7 +200,7 @@ It uses raw Ethernet packets to bypass the IP/UDP layer to communicate with the Run the following commands: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils sudo ./usrp2_recovery.py --ifc=eth0 --new-ip=192.168.10.3 ------------------------------------------------------------------------ @@ -271,11 +272,11 @@ Addressing the Device ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Single device configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In a single-device configuration, -the USRP device must have a unique IPv4 address on the host computer. -The USRP can be identified through its IPv4 address, resolvable hostname, or by other means. -See the application notes on `device identification <./identification.html>`_. -Use this addressing scheme with the **single_usrp** interface. +In a single-device configuration, the USRP device must have a unique IPv4 +address on the host computer. The USRP can be identified through its IPv4 +address, resolvable hostname, or by other means. See the application notes on +`device identification <./identification.html>`_. Please note that this +addressing scheme should also be used with the **multi_usrp** interface. Example device address string representation for a USRP2 with IPv4 address **192.168.10.2**: @@ -313,7 +314,7 @@ This device will be referred to as the slave, and the other device, the master. * External clocking is optional and should only be supplied to the master device. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Shared ethernet mode +Shared Ethernet mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In shared Ethernet mode, only one device in the configuration can be attached to the Ethernet. @@ -322,7 +323,7 @@ only one device in the configuration can be attached to the Ethernet. * Master and slave must have different IPv4 addresses in the same subnet. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Dual ethernet mode +Dual Ethernet mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In dual Ethernet mode, both devices in the configuration must be attached to the Ethernet. @@ -358,7 +359,7 @@ have been programmed into the device's EEPROM. Run the following commands: :: - cd <install-path>/share/uhd/utils + cd <install-path>/lib/uhd/utils ./usrp_burn_mb_eeprom --args=<optional device args> --key=subnet --val=255.255.255.0 ./usrp_burn_mb_eeprom --args=<optional device args> --key=gateway --val=192.168.10.1 @@ -400,7 +401,7 @@ The LEDs on the front panel can be useful in debugging hardware and software iss The LEDs reveal the following about the state of the device: * **LED A:** transmitting -* **LED B:** mimo cable link +* **LED B:** MIMO cable link * **LED C:** receiving * **LED D:** firmware loaded * **LED E:** reference lock @@ -408,13 +409,13 @@ The LEDs reveal the following about the state of the device: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Ref Clock - 10MHz +Ref Clock - 10 MHz ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Using an external 10MHz reference clock, a square wave will offer the best phase +Using an external 10 MHz reference clock, a square wave will offer the best phase noise performance, but a sinusoid is acceptable. The reference clock requires the following power level: -* **USRP2** 5 to 15dBm -* **N2XX** 0 to 15dBm +* **USRP2** 5 to 15 dBm +* **N2XX** 0 to 15 dBm ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -431,7 +432,7 @@ Test the PPS input with the following app: :: - cd <install-path>/share/uhd/examples + cd <install-path>/lib/uhd/examples ./test_pps_input --args=<args> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -466,7 +467,9 @@ which has been aptly named slot **A**. In the following example, a TVRX2 is installed. Channel 0 is sourced from subdevice **RX1**, -and channel 1 is sourced from subdevice **RX2**: +and channel 1 is sourced from subdevice **RX2** (**RX1** and **RX2** +are the antenna ports on the TVRX2 daughterboard): + :: usrp->set_rx_subdev_spec("A:RX1 A:RX2"); diff --git a/host/docs/usrp2_card_burner.1 b/host/docs/usrp2_card_burner.1 index d6a12b073..b6e1954d8 100644 --- a/host/docs/usrp2_card_burner.1 +++ b/host/docs/usrp2_card_burner.1 @@ -1,4 +1,4 @@ -.TH "usrp2_card_burner" 1 "3.5.1" UHD "User Commands" +.TH "usrp2_card_burner" 1 "3.7.0" UHD "User Commands" .SH NAME usrp2_card_burner - USRP N-Series FPGA/Firmware Burner .SH DESCRIPTION @@ -34,12 +34,12 @@ GR-UHD documentation: .LP Other UHD programs: .sp -uhd_images_downloader(1) usrp_n2xx_simple_net_burner(1) +uhd_images_downloader(1) usrp_n2xx_simple_net_burner(1) usrp_x3xx_fpga_burner(1) .SH AUTHOR This manual page was written by Nicholas Corgan for the Debian project (but may be used by others). .SH COPYRIGHT -Copyright (c) 2012 Ettus Research LLC +Copyright (c) 2012,2014 Ettus Research LLC .LP This 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/docs/usrp_b100.rst b/host/docs/usrp_b100.rst index ac0942f5c..223ad5a90 100644 --- a/host/docs/usrp_b100.rst +++ b/host/docs/usrp_b100.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - USRP-B100 Series Application Notes +UHD - USRP-B100 Series Device Manual ======================================================================== .. contents:: Table of Contents @@ -8,15 +8,21 @@ UHD - USRP-B100 Series Application Notes Comparative features list ------------------------------------------------------------------------ -* 1 transceiver card slot -* 1 RX DDC chain in FPGA -* 1 TX DUC chain in FPGA -* Timed commands in FPGA -* Timed sampling in FPGA -* External PPS reference -* External 10MHz reference -* Configurable clock rate (defaults 64 MHz) -* sc8 and sc16 sample modes +**Hardware Capabilities:** + * 1 transceiver card slot + * External PPS reference input + * External 10 MHz reference input + * Configurable clock rate (defaults 64 MHz) + +**FPGA Capabilities:** + * 1 RX DDC chain in FPGA + * 1 TX DUC chain in FPGA + * Timed commands in FPGA + * Timed sampling in FPGA + * sc8 and sc16 sample modes + + * Up to 8 MHz of RF BW with 16-bit samples + * Up to 16 MHz of RF BW with 8-bit samples ------------------------------------------------------------------------ Specify a Non-standard Image @@ -38,17 +44,17 @@ Example device address string representations to specify non-standard images: Changing the Master Clock Rate ------------------------------------------------------------------------ The master clock rate of the B100 feeds both the FPGA DSP and the codec chip. -Hundreds of rates between 32MHz and 64MHz are available. +Hundreds of rates between 32 MHz and 64 MHz are available. A few notable rates are: -* **64MHz:** maximum rate of the codec chip -* **61.44MHz:** good for UMTS/WCDMA applications -* **52Mhz:** good for GSM applications +* **64 MHz:** maximum rate of the codec chip +* **61.44 MHz:** good for UMTS/WCDMA applications +* **52 MHz:** good for GSM applications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Set 61.44MHz - uses external VCXO +Set 61.44 MHz - uses external VCXO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To use the 61.44MHz clock rate, the USRP embedded will require one jumper to be moved, +To use the 61.44 MHz clock rate, the USRP embedded will require one jumper to be moved, and X4 must be populated with a 61.44 MHz oscillator. * **J15** is a three pin header, move the jumper to (pin1, pin2) @@ -64,7 +70,7 @@ To use other clock rates, the jumper will need to be in the default position. * **J15** is a three pin header, move the jumper to (pin2, pin3) To communicate the desired clock rate into UHD software, -specify the a special device address argument, +specify the special device address argument, where the key is **master_clock_rate** and the value is a rate in Hz. Example: :: @@ -82,9 +88,9 @@ The LEDs on the front panel can be useful in debugging hardware and software iss The LEDs reveal the following about the state of the device: * **LED A:** transmitting -* **LED B:** fpga loaded +* **LED B:** FPGA loaded * **LED C:** receiving -* **LED D:** fpga loaded +* **LED D:** FPGA loaded * **LED E:** reference lock * **LED F:** board power diff --git a/host/docs/usrp_b200.rst b/host/docs/usrp_b200.rst index 7986e3b73..1b0d7b719 100644 --- a/host/docs/usrp_b200.rst +++ b/host/docs/usrp_b200.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - USRP-B2X0 Series Application Notes +UHD - USRP-B2x0 Series Device Manual ======================================================================== .. contents:: Table of Contents @@ -8,35 +8,26 @@ UHD - USRP-B2X0 Series Application Notes Comparative features list - B200 ------------------------------------------------------------------------ -* integrated RF frontend (RF coverage from 70 MHz - 6 GHz) -* 1 RX DDC chain in FPGA -* 1 TX DUC chain in FPGA -* Timed commands in FPGA -* Timed sampling in FPGA -* External PPS reference -* External 10MHz reference -* Configurable clock rate -* Internal GPSDO option +**Hardware Capabilities:** + * Integrated RF frontend (70 MHz - 6 GHz) + * External PPS reference input + * External 10 MHz reference input + * Configurable clock rate + * Internal GPSDO option + * B210 Only: ------------------------------------------------------------------------- -Comparative features list - B210 ------------------------------------------------------------------------- + * MICTOR Debug Connector + * JTAG Connector -* integrated MIMO frontend (RF coverage from 70 MHz - 6 GHz) -* 2 RX DDC chains in FPGA -* 2 TX DUC chains in FPGA -* Timed commands in FPGA -* Timed sampling in FPGA -* External PPS reference -* External 10MHz reference -* Configurable clock rate -* Internal GPSDO option +**FPGA Capabilities:** + * Timed commands in FPGA + * Timed sampling in FPGA ------------------------------------------------------------------------ Specify a Non-standard Image ------------------------------------------------------------------------ UHD software will automatically select the USRP B2X0 images from the installed images package. -The image selection can be overridden with the **--fpga=** and **--fw=** device address parameters. +The image selection can be overridden with the **fpga** and **fw** device address parameters. Example device address string representations to specify non-standard images: @@ -81,9 +72,9 @@ Frontend gain ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ All frontends have individual analog gain controls. The receive frontends have 73 dB of available gain; -and the transmit frontends have 89 dB of available gain. +and the transmit frontends have 89.5 dB of available gain. Gain settings are application specific, -but its recommended that users consider using at least +but it is recommended that users consider using at least half of the available gain to get reasonable dynamic range. ------------------------------------------------------------------------ diff --git a/host/docs/usrp_e1x0.rst b/host/docs/usrp_e1x0.rst index fc929e639..ea2d05a3c 100644 --- a/host/docs/usrp_e1x0.rst +++ b/host/docs/usrp_e1x0.rst @@ -1,5 +1,5 @@ ======================================================================== -UHD - USRP-E1X0 Series Application Notes +UHD - USRP-E1x0 Series Device Manual ======================================================================== .. contents:: Table of Contents @@ -8,23 +8,29 @@ UHD - USRP-E1X0 Series Application Notes Comparative features list ------------------------------------------------------------------------ -* 1 transceiver card slot -* 2 RX DDC chains in FPGA -* 1 TX DUC chain in FPGA -* Timed commands in FPGA -* Timed sampling in FPGA -* Internal PPS reference -* Internal 10MHz reference -* Configurable clock rate (defaults 64 MHz) -* Internal GPSDO option -* sc8 and sc16 sample modes +**Hardware Capabilities:** + * 1 transceiver card slot + * Internal PPS reference input + * Internal 10 MHz reference input + * Configurable clock rate (defaults to 64 MHz) + * Internal GPSDO option + +**FPGA Capabilities:** + * 2 RX DDC chains in FPGA + * 1 TX DUC chain in FPGA + * Timed commands in FPGA + * Timed sampling in FPGA + * sc8 and sc16 sample modes + + * Up to 8 MHz of RF BW with 16-bit samples + * Up to 16 MHz of RF BW with 8-bit samples ------------------------------------------------------------------------ Specify a Non-standard Image ------------------------------------------------------------------------ UHD software will automatically select the USRP-Embedded FPGA image from the installed images package. The FPGA image selection can be overridden with the -**--fpga=** device address parameter. +**fpga** device address parameter. Example device address string representations to specify non-standard FPGA image: @@ -37,17 +43,17 @@ image: Changing the Master Clock Rate ------------------------------------------------------------------------ The master clock rate of the USRP-Embedded feeds both the FPGA DSP and the codec -chip. Hundreds of rates between 32MHz and 64MHz are available. A few notable +chip. Hundreds of rates between 32 MHz and 64 MHz are available. A few notable rates are: -* **64MHz:** maximum rate of the codec chip -* **61.44MHz:** good for UMTS/WCDMA applications -* **52Mhz:** good for GSM applications +* **64 MHz:** maximum rate of the codec chip +* **61.44 MHz:** good for UMTS/WCDMA applications +* **52 MHz:** good for GSM applications ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Set 61.44MHz - uses external VCXO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To use the 61.44MHz clock rate with the USRP-Embedded, two jumpers must be moved +To use the 61.44 MHz clock rate with the USRP-Embedded, two jumpers must be moved on the device. * **J16** is a two pin header; remove the jumper (or leave it on pin1 only). @@ -94,22 +100,22 @@ a connector. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PPS - Pulse Per Second ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -An exteral PPS signal for timestamp synchronization can be supplied by soldering +An external PPS signal for timestamp synchronization can be supplied by soldering a connector. * Connector **J13** (PPS) needs MCX connector **WM5541-ND** or similar. * Requires a square wave signal. -* **Amplitude:** 3.3 to 5Vpp +* **Amplitude:** 3.3 to 5 Vpp -Test the PPS input with the following app: - -* **<args** are device address arguments (optional if only one USRP device is on your machine). +Test the PPS input with the following app (**<args>** are device address +arguments, optional if only one USRP device is on your machine): :: - cd <install-path>/share/uhd/examples + cd <install-path>/lib/uhd/examples ./test_pps_input --args=<args> + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Internal GPSDO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,7 +123,7 @@ Please see the `Internal GPSDO Application Notes <./gpsdo.html>`_ for information on configuring and using the internal GPSDO. UHD software will always try to detect an installed GPSDO at runtime. -There is not a special EEPROM value to burn for GPSDO detection. +It is not necessary to burn a special EEPROM value for GPSDO detection. ------------------------------------------------------------------------ Hardware Setup Notes @@ -132,7 +138,7 @@ issues. The LEDs reveal the following about the state of the device: * **LED A:** transmitting * **LED B:** PPS signal * **LED C:** receiving -* **LED D:** fpga loaded +* **LED D:** FPGA loaded * **LED E:** reference lock * **LED F:** board power diff --git a/host/docs/usrp_n2xx_simple_net_burner.1 b/host/docs/usrp_n2xx_simple_net_burner.1 index 6e8e38fae..8df38b7bd 100644 --- a/host/docs/usrp_n2xx_simple_net_burner.1 +++ b/host/docs/usrp_n2xx_simple_net_burner.1 @@ -1,4 +1,4 @@ -.TH "usrp_n2xx_simple_net_burner" 1 "3.5.1" UHD "User Commands" +.TH "usrp_n2xx_simple_net_burner" 1 "3.7.0" UHD "User Commands" .SH NAME usrp_n2xx_simple_net_burner - USRP N-Series FPGA/Firmware Burner .SH DESCRIPTION @@ -41,12 +41,12 @@ GR-UHD documentation: .LP Other UHD programs: .sp -uhd_images_downloader(1) usrp2_card_burner(1) +uhd_images_downloader(1) usrp2_card_burner(1) usrp_x3xx_fpga_burner(1) .SH AUTHOR This manual page was written by Nicholas Corgan for the Debian project (but may be used by others). .SH COPYRIGHT -Copyright (c) 2012 Ettus Research LLC +Copyright (c) 2012,2014 Ettus Research LLC .LP This 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/docs/usrp_x3x0.rst b/host/docs/usrp_x3x0.rst new file mode 100644 index 000000000..3b7e9914e --- /dev/null +++ b/host/docs/usrp_x3x0.rst @@ -0,0 +1,784 @@ +=============================== +UHD - X3x0 Series Device Manual +=============================== + +.. contents:: Table of Contents + +------------------------- +Comparative features list +------------------------- + +**Hardware Capabilities:** + * 2 transceiver card slots (can do 2x2 MIMO out of the box) + * Dual SFP+ Transceivers (can be used with 1 GigE, 10 GigE) + * PCI Express over cable (MXI) gen1 x4 + * External PPS input & output + * External 10 MHz input & output + * Expandable via 2nd SFP+ interface + * Supported master clock rates: 200 MHz, 184.32 MHz + * External GPIO Connector with UHD API control + * External USB Connection for built-in JTAG debugger + * Internal GPSDO option + * Kintex-7 FPGA (X310: XC7K410T, X300: XC7K325T) + +**FPGA Capabilities:** + * 2 RX DDC chains in FPGA + * 2 TX DUC chain in FPGA + * Timed commands in FPGA + * Timed sampling in FPGA + * 16-bit and 8-bit sample modes (sc8 and sc16) + * Up to 120 MHz of RF bandwidth with 16-bit samples + +--------------- +Getting started +--------------- + +This will run you through the first steps relevant to get your USRP X300/X310 +up and running. Here, we assume you will connect your USRP using Gigabit Ethernet (1GigE), +as this interface is readily available in most computers. For 10 Gigabit Ethernet (10GigE) or +PCI Express (PCIe), see the corresponding sections in this manual page. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Assembling the X300/X310 kit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Before you can start using your USRP, you might have to assemble the hardware, +if this has not yet happened. Make sure you are grounded (e.g. by touching a radiator) +in order not to damage sensitive electronics through static discharge! + +1. Unscrew the top of your X300/X310 (there are 2 screws which can be easily loosened + using a small Phillips screwdriver). +2. Insert the daughterboards by inserting them into the slots and optionally screwing + them onto the motherboard. +3. Connect the RF connectors on the daughterboards to the front panel. In order to avoid + confusion, make sure the internal connections match the labels on the front panel (i.e. + TX/RX is connected to TX/RX). +4. If you have purchased an internal GPSDO, follow the instructions on + `the internal GPSDO manual page <./gpsdo_x3x0.html>`_ to insert the GPSDO. Note that you + will need an external GPS antenna connected to the rear GPS ANT connector in order to + make use of GPS, although your USRP will still be usable without. +5. Connect the 1 GigE SFP+ transceiver into the Ethernet port 0 and connect the X300/X310 with + your computer. +6. Connect the power supply and switch on the USRP. + +^^^^^^^^^^^^^^^^^^^^ +Network Connectivity +^^^^^^^^^^^^^^^^^^^^ + +The next step is to make sure your computer can talk to the USRP. An otherwise unconfigured +USRP device will have the IP address 192.168.10.2 when using 1GigE. +It is recommended to directly connect your USRP to the computer at first, +and to set the IP address on your machine to 192.168.10.1. +See Section `Setup the host interface`_ on details how to change your machine's IP address. + +**Note**: If you are running an automatic IP configuration service such as Network Manager, make +sure it is either deactivated or configured to not change the network device! This can, in extreme cases, +lead to you bricking the USRP! + +If your network configuration is correct, running ``uhd_find_devices`` will find your USRP +and print some information about it. You will also be able to ping the USRP by running:: + + ping 192.168.10.2 + +on the command line. At this point, you should also run:: + + uhd_usrp_probe --args addr=192.168.10.2 + +to make sure all of your components (daughterboards, GPSDO) are correctly detected and usable. + +^^^^^^^^^^^^^^^^^^^^^ +Updating the firmware +^^^^^^^^^^^^^^^^^^^^^ + +If the output from ``uhd_find_devices`` and ``uhd_usrp_probe`` didn't show any warnings, you +can skip this step. However, if there were warnings regarding version incompatibility, you will +have to upate the FPGA image before you can start using your USRP. + +1. Download the current UHD images. You can use the ``uhd_images_downloader`` script provided + with UHD (see also `FPGA Image Flavors`_). +2. Use the ``usrp_x3xx_fpga_burner`` utility to update the FPGA image. On the command line, run:: + + usrp_x3xx_fpga_burner --addr=192.168.10.2 --type=HGS # Since we are using 1GigE, type is HGS + + If you have installed the images to a non-standard location, you might need to run (change the filename according to your device):: + + usrp_x3xx_fpga_burner --addr=192.168.10.2 --fpga-path <path_to_images>/usrp_x310_fpga_HGS.bit + + The process of updating the firmware will take several minutes. Make sure the process of flashing the image does not get interrupted. + +See `Load the Images onto the On-board Flash`_ for more details. + +When your firmware is up to date, power-cycle the device and re-run ``uhd_usrp_probe``. There should +be no more warnings at this point, and all components should be correctly detected. Your USRP is now +ready for development! + +-------------- +Hardware Setup +-------------- + +^^^^^^^^^^^^^^^^^^^^^^^^^ +Gigabit Ethernet (1 GigE) +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Installing the USRP X300/X310 +::::::::::::::::::::::::::::: +* Prior to installing the module, the host PC can remain powered on. +* Plug a 1 Gigabit SFP Transceiver into Ethernet Port 0 on the USRP X300/X310 device. +* Use the Ethernet cable to connect the SFP+ transciever on the device to the host computer. For maximum throughput, Ettus Research recommends that you connect each device to its own dedicated Gigabit Ethernet interface on the host computer. +* Connect the AC/DC power supply to the device and plug the supply into a wall outlet. +* The OS will automatically recognize the device (e.g. when running uhd_find_devices). + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Ten Gigabit Ethernet (10 GigE) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Installing the Host Ethernet Interface +:::::::::::::::::::::::::::::::::::::: +Ettus Research recommends the Intel Ethernet Converged Network Adapter X520-DA2 interface for communication with the USRP X300/X310 device. +Installation instructions for this interface are available on the official Intel website. + +Installing the USRP X300/X310 +::::::::::::::::::::::::::::: +* Prior to installing the module, the host PC can remain powered on. +* Use a 10 Gigabit SFP+ cable to connect Ethernet Port 1 on the USRP X300/X310 device to the host computer. For maximum throughput, Ettus Research recommends that you connect the device to its own dedicated Ten Gigabit, Ettus Research recommended Ethernet interface on the host computer. +* Connect the AC/DC power supply to the device and plug the supply into a wall outlet. +* The OS will automatically recognize the device (e.g. when running uhd_find_devices). + +The LEDs on the front panel can be useful in debugging hardware and software issues (see Section "Front Panel") + +^^^^^^^^^^^^^^^^^^^^^ +PCI Express (Desktop) +^^^^^^^^^^^^^^^^^^^^^ +*Important Note: The USRP X-Series provides PCIe connectivity over MXI cable. +We will use the 'MXI' nomenclature for the rest of this manual.* + +Installing the PCI Express Interface Kit +:::::::::::::::::::::::::::::::::::::::: +Follow the instructions listed in the `Set Up Your MXI-Express x4 System <http://www.ni.com/pdf/manuals/371976c.pdf>`_ +document to setup the NI PCIe-8371 module. + +Installing the USRP X300/X310 +::::::::::::::::::::::::::::: +* Prior to installing the module, make sure that the PC is powered off. +* Using a MXI-Express Cable connect the USRP X300/X310 to the NI PCIe-8371. +* Connect the AC/DC power supply to the device and plug the supply into a wall outlet. +* Power on the USRP X300/X310 device using the power switch located in the bottom-right corner of the front panel. +* Power on the PC (The OS automatically recognizes the new device) + +NOTE: The USRP device is not hot-pluggable over PCI Express. Any connection changes with only be detected by your +computer after a successful reboot. + +Troubleshooting +::::::::::::::: +Two possible failure modes are your computer not booting when connected to your +USRP device through MXI-Express, and Windows not properly discovering your +devices (for example, there is a yellow exclamation point on a PCI to PCI +bridge in Windows Device Manager, despite drivers for all devices being +installed). These situations often are due to programming errors in PCI Express +device configuration of the BIOS. To use this software, you need a MXI-Express +device that supports Mode 1 operation. +Refer to `NI MXI-Express BIOS Compatibility Software Readme <http://download.ni.com/support/softlib//PXI/MXIe%20Compatibility%20Software/1.5.0/readme.html#SupportedHardware>`_ +for more information. + +The BIOS Compatibility Software can be downloaded for Windows from the `MXI-Express BIOS Compatibility Software <http://www.ni.com/download/mxi-express-bios-compatibility-software-1.5/3764/en/>`_ page + +^^^^^^^^^^^^^^^^^^^^ +PCI Express (Laptop) +^^^^^^^^^^^^^^^^^^^^ +*Important Note: The USRP X-Series provides PCIe connectivity over MXI cable. +We will use the 'MXI' nomenclature for the rest of this manual.* + +Installing the PCI Express Card +::::::::::::::::::::::::::::::: +Follow the instructions listed in the “Installing an NI ExpressCard-8360 Host Card” section of the +`Set Up Your MXI-Express x1 System <http://www.ni.com/pdf/manuals/373259d.pdf#page=10>`_ +document to setup the NI ExpressCard-8360B module. + +Installing the USRP X300/X310 +::::::::::::::::::::::::::::: +Because a laptop computer is not grounded, follow this procedure to safely connect a laptop +computer to your USRP device. + +* Connect the AC/DC power supply to the device and plug the supply into a wall outlet. Ensure that the USRP device is powered off. +* Touch the NI ExpressCard-8360B and a metal part of the USRP device simultaneously. Do not install the NI ExpressCard-8360B into the laptop computer yet. +* Connect the cable to the NI ExpressCard-8360B and USRP. +* Plug the NI ExpressCard-8360B into an available ExpressCard slot. If your laptop computer is already running (or hibernating, suspended, etc) when you install an NI ExpressCard-8360B, you must reboot to detect the USRP. Otherwise, the USRP is detected when you start your computer. + +NOTE: The USRP device is not hot-pluggable over PCI Express. Any connection changes will only be detected by your computer after a successful reboot. + +-------------------------------- +On-Board JTAG Programmer +-------------------------------- +The USRP X3x0 includes an on-board JTAG programmer, built into the motherboard. +To connect to this JTAG device, simply connect your computer to the USB JTAG +port on the front of the X3x0 device. You may now use the JTAG programmer in +the same way you would use any other, including: + +* `Xilinx Programming Tools (ISE, iMPACT) <http://www.xilinx.com/support/download/index.htm>`_ +* `Xilinx Chipscope <http://www.xilinx.com/tools/cspro.htm>`_ +* `Digilent ADEPT <https://www.digilentinc.com/Products/Detail.cfm?NavPath=2,66,828&Prod=ADEPT2>`_ + +In order to use the JTAG programmer with the Xilinx tools, the Digilent drivers and plugin have to be installed first. +Although recent versions of ISE ship with the driver, it has to still be manually installed. + +Note: Sometimes the ISE shipped versions are newer than the ones available via Digilent's website. It is therefore advisable to +use the ISE provided plugin and drivers. + +To install first locate your ISE installation path (default is /opt/Xilinx/<Version>). + +**LINUX** +:: + + sudo <ise install path>/ISE_DS/common/bin/lin64/digilent/install_digilent.sh + +Afterwards either reboot or force udev to reload its rules by: +:: + + sudo udevadm control --reload + +The USRP-X series device should now be usable with all the tools mentioned above. + +-------------------------------- +Load FPGA Images onto the Device +-------------------------------- +The USRP-X Series device ships with a bitstream pre-programmed in the flash, +which is automatically loaded onto the FPGA during device power-up. However, +a new FPGA image can be configured over the PCI Express interface or the +on-board USB-JTAG programmer. This process can be seen as a "one-time load", in +that if you power-cycle the device, it will not retain the FPGA image. + +Please note that this process is *different* than replacing the FPGA image +stored in the flash, which will then be automatically loaded the next time the +device is reset. + +^^^^^^^^^^^^^^^^^^ +FPGA Image Flavors +^^^^^^^^^^^^^^^^^^ +The USRP-X Series devices contains two SFP+ ports for the two Ethernet channels. +Because the SFP+ ports support both 1 Gigabit (SFP) and 10 Gigabit (SFP+) +transceivers, several FPGA images are shipped with UHD to determine the +behavior of the above interfaces. + ++---------------------+------------------------+------------------------+ +| FPGA Image Flavor | SFP+ Port 0 Interface | SFP+ Port 1 Interface | ++=====================+========================+========================+ +| HGS (Default) | 1 Gigabit Ethernet | 10 Gigabit Ethernet | ++---------------------+------------------------+------------------------+ +| XGS | 10 Gigabit Ethernet | 10 Gigabit Ethernet | ++---------------------+------------------------+------------------------+ + +FPGA images are shipped in 2 formats: + +* **LVBITX**: LabVIEW FPGA configuration bitstream format (for use over PCI Express and Ethernet) +* **BIT**: Xilinx configuration bitstream format (for use over Ethernet and JTAG) + +To get the latest images, simply use the uhd_images_downloader script: + +**UNIX:** + +:: + + sudo uhd_images_downloader + +**Windows:** + +:: + + <path_to_python.exe> <install-path>/bin/uhd_images_downloader.py + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use PCI Express to load FPGA images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +UHD requires a valid LabVIEW FPGA configuration bitstream file (LVBITX) to use the USRP-X Series +device over the PCI Express bus. LabVIEW FPGA is **NOT** required to use UHD with a USRP-X Series device. +Because FPGA configuration is a part of normal operation over PCI Express, there is no setup required +before running UHD. + +The **fpga** tag can be set in the optional device args passed to indicate the FPGA image flavor to UHD. +If the above tag is specified, UHD will attempt to load the FPGA image with the requested flavor from the +UHD images directory. If the tag is not specified, UHD will automatically detect the flavor of the image +and attempt to load the corresponding configuration bitstream onto the device. Note that if UHD detects +that the requested image is already loaded onto the FPGA then it will not reload it. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use JTAG to load FPGA images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +The USRP-X Series device features an on-board USB-JTAG programmer that can be accessed on the front-panel +of the device. The iMPACT tool in the `Xilinx Programming Tools <http://www.xilinx.com/support/download/index.htm>`_ package can be used to load an image over +the JTAG interface. This can be useful for unbricking devices. + +If you have iMPACT installed, you can use the impact_jtag_programmer.sh tool to install images. Make sure your X3x0 is powered on and connected to your computer using the front panel USB JTAG connector (USB 2.0 is fine for this). Then run the tool: + +:: + + <path_to_uhd_tools>/impact_jtag_programmer.sh --fpga-path=<fpga_image_path> + +--------------------------------------- +Load the Images onto the On-board Flash +--------------------------------------- +To change the FPGA image stored in the on-board flash, the USRP-X Series device +can be reprogrammed over the network or PCI Express. Once you have programmed an +image into the flash, that image will be automatically loaded on the FPGA +during the device boot-up sequence. + +**Note:** +Different hardware revisions require different FPGA images. +Determine the revision number from the sticker on the rear of the device. +If you are manually specifying an FPGA path, the utility will not try to +detect your device information, and you will need to use this number to +select which image to burn. + +**Note:** +The burner utility will default to using the appropriate BIT file if no custom +FPGA image path is specified, but it is compatible with BIN, BIT, and LVBITX +images. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the burner tool over Ethernet +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + Automatic FPGA path, detect image type: + usrp_x3xx_fpga_burner --addr=<IP address> + + Automatic FPGA path, select image type: + usrp_x3xx_fpga_burner --addr=<IP address> --type=<HGS or XGS> + + Manual FPGA path: + usrp_x3xx_fpga_burner --addr=<IP address> --fpga-path=<path to FPGA image> + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use the burner tool over PCI Express +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +:: + + Automatic FPGA path, detect image type: + usrp_x3xx_fpga_burner --resource=<NI-RIO resource> + + Automatic FPGA path, select image type: + usrp_x3xx_fpga_burner --resource=<NI-RIO resource> --type=<HGS or XGS> + + Manual FPGA path: + usrp_x3xx_fpga_burner --resource=<NI-RIO resource> --fpga-path=<path to FPGA image> + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Device recovery and bricking +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +It is possible to put the device into an unusable state by loading bad images ("bricking"). +Fortunately, the USRP-X Series device can be loaded with a good image temporarily using the USB-JTAG interface. +Once booted into the safe image, the user can once again load images onto the device over Ethernet or PCI Express. + +---------------- +Setup Networking +---------------- +The USRP-X Series only supports Gigabit and Ten Gigabit Ethernet and will not work with a 10/100 Mbps interface. + +**Please note that 10 Gigabit Ethernet defines the protocol, not necessary the +medium. For example, you may use 10GigE over optical with optical SFP+ +transceiver modules.** + +^^^^^^^^^^^^^^^^^^^^^^^^ +Setup the host interface +^^^^^^^^^^^^^^^^^^^^^^^^ +The USRP-X Series communicates at the IP/UDP layer over the Gigabit and Ten Gigabit Ethernet. +The default IP address for the USRP X300/X310 device depends on the Ethernet Port and interface used. +You must configure the host Ethernet interface with a static IP address on the same subnet as the connected +device to enable communication, as shown in the following table: + ++---------------+-------------------------+----------------+----------------+---------------+ +| Ethernet | USRP | Default USRP | Host Static | Host Static | +| Interface | Ethernet Port | IP Address | IP Address | Subnet Mask | ++===============+=========================+================+================+===============+ +| Gigabit | Port 0 (HGS Image) | 192.168.10.2 | 192.168.10.1 | 255.255.255.0 | ++---------------+-------------------------+----------------+----------------+---------------+ +| Ten Gigabit | Port 1 (HGS/XGS Image) | 192.168.40.2 | 192.168.40.1 | 255.255.255.0 | ++---------------+-------------------------+----------------+----------------+---------------+ +| Ten Gigabit | Port 0 (XGS Image) | 192.168.30.2 | 192.168.30.1 | 255.255.255.0 | ++---------------+-------------------------+----------------+----------------+---------------+ + + +On a Linux system, you can add a static IP address very easily by using the +'ip' command + +:: + + sudo ip addr add 192.168.10.1/24 dev <interface> + +Note that **<interface>** is usually something like **eth0**. You can discover the +names of the network interfaces in your computer by running: + +:: + + ip addr show + +**Note:** +When using UHD software, if an IP address for the USRP-X Series device is not specified, +the software will use UDP broadcast packets to locate the USRP-X Series device. +On some systems, the firewall will block UDP broadcast packets. +It is recommended that you change or disable your firewall settings. + +On many Linux distributions, NetworkManager or similar tools may control the network interface. +It is important to deactivate these tools for your device before continuing! + +^^^^^^^^^^^^^^^ +Setting the MTU +^^^^^^^^^^^^^^^ +As UHD by default uses receive and transmit frames larger than the standard MTU of 1500 Bytes, +the NIC needs to be configured to use a larger MTU when used with the USRP X series devices. + +:: + + sudo ip link set mtu 8192 dev <interface> + +Upon initialization UHD will probe for the maximum possible path MTU along the path between the USRP X series device +and the host, both in receive and transmit direction. + +If the network hardware does not support MTUs as large as 8000 Bytes, passing the **send_frame_size** and **receive_frame_size** +arguments will make UHD use smaller MTUs: + +:: + + uhd_usrp_probe --args='send_frame_size=<max send MTU>, recv_frame_size=<max receive MTU>' + +**Note:** This will most likely have a severe performance penalty. + + +^^^^^^^^^^^^^^^^^^^^^^^^^ +Multiple devices per host +^^^^^^^^^^^^^^^^^^^^^^^^^ +For maximum throughput, one Ethernet interface per USRP is recommended, +although multiple devices may be connected via an Ethernet switch. +In any case, each Ethernet interface should have its own subnet, +and the corresponding USRP device should be assigned an address in that subnet. +Example: + +**Configuration for USRP-X Series device 0:** + +* Ethernet interface IPv4 address: **192.168.10.1** +* Ethernet interface subnet mask: **255.255.255.0** +* USRP-X Series device IPv4 address: **192.168.10.2** + +**Configuration for USRP-X Series device 1:** + +* Ethernet interface IPv4 address: **192.168.110.1** +* Ethernet interface subnet mask: **255.255.255.0** +* USRP-X Series device IPv4 address: **192.168.110.2** + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Change the USRP's IP address +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +You may need to change the USRP's IP address for several reasons: + +* to satisfy your particular network configuration +* to use multiple USRP-X Series devices on the same host computer +* to set a known IP address into USRP (in case you forgot) + +To change the USRP's IP address, +you must know the current address of the USRP, +and the network must be setup properly as described above. +Run the following commands: + +**UNIX:** + +:: + + cd <install-path>/lib/uhd/utils + ./usrp_burn_mb_eeprom --args=<optional device args> --key=ip-addr --val=192.168.10.3 + +**Windows:** + +:: + + cd <install-path>\lib\uhd\utils + usrp_burn_mb_eeprom.exe --args=<optional device args> --key=ip-addr --val=192.168.10.3 + +--------------------- +Addressing the Device +--------------------- + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Single device configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In a single-device configuration, +the USRP device must have a unique IPv4 address on the host computer. +The USRP can be identified through its IPv4 address, resolvable hostname, NI-RIO resource name or by other means. +See the application notes on `device identification <./identification.html>`_. +Use this addressing scheme with the **multi_usrp** interface (not a typo!). + +Example device address string representation for a USRP-X Series device with IPv4 address **192.168.10.2**: + +:: + + addr=192.168.10.2 + +Example device address string representation for a USRP-X Series device with RIO resource name **RIO0** over PCI Express: + +:: + + resource=RIO0 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Multiple device configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In a multi-device configuration, +each USRP device must have a unique IPv4 address on the host computer. +The device address parameter keys must be suffixed with the device index. +Each parameter key should be of the format <key><index>. +Use this addressing scheme with the **multi_usrp** interface. + +* The order in which devices are indexed corresponds to the indexing of the transmit and receive channels. +* The key indexing provides the same granularity of device identification as in the single device case. + +Example device address string representation for 2 USRPs with IPv4 addresses **192.168.10.2** and **192.168.20.2**: + +:: + + addr0=192.168.10.2, addr1=192.168.20.2 + + +---------------------- +Communication Problems +---------------------- +When setting up a development machine for the first time, +you may have various difficulties communicating with the USRP device. +The following tips are designed to help narrow down and diagnose the problem. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +RuntimeError: no control response +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +This is a common error that occurs when you have set the subnet of your network +interface to a different subnet than the network interface of the USRP device. For +example, if your network interface is set to **192.168.20.1**, and the USRP device is +**192.168.10.2** (note the difference in the third numbers of the IP addresses), you +will likely see a 'no control response' error message. + +Fixing this is simple - just set the your host PC's IP address to the same +subnet as that of your USRP device. Instructions for setting your IP address are in the +previous section of this documentation. + +^^^^^^^^^^^^^^^ +Firewall issues +^^^^^^^^^^^^^^^ +When the IP address is not specified, +the device discovery broadcasts UDP packets from each Ethernet interface. +Many firewalls will block the replies to these broadcast packets. +If disabling your system's firewall +or specifying the IP address yields a discovered device, +then your firewall may be blocking replies to UDP broadcast packets. +If this is the case, we recommend that you disable the firewall +or create a rule to allow all incoming packets with UDP source port **49152**. + +^^^^^^^^^^^^^^^ +Ping the device +^^^^^^^^^^^^^^^ +The USRP device will reply to ICMP echo requests ("ping"). +A successful ping response means that the device has booted properly +and that it is using the expected IP address. + +:: + + ping 192.168.10.2 + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +USRP device not enumerated (Linux) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +UHD requires the RIO device manager service to be running in order to +communicate with an X-Series USRP over PCIe. This service is installed as +a part of the USRP RIO (or NI-USRP) installer. On Linux, the service is not +started at system boot time, and is left to the user to control. To start it, +run the following command: + +:: + + sudo niusrprio_pcie start + +If the device still does not enumerate after starting the device manager, make sure that the host computer +has successfully detected it. You can do so by running the following command: + +:: + + lspci -k -d 1093:c4c4 + +A device similar to the following should be detected: + +:: + + $ lspci -k -d 1093:c4c4 + 04:00.0 Signal processing controller: National Instruments ... + Subsystem: National Instruments Device 76ca + Kernel driver in use: niusrpriok_shipped + +* A USRP X300 should appear with 'Subsystem: National Instruments Device 7736' +* A USRP X310 should appear with 'Subsystem: National Instruments Device 76ca' + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +USRP device not enumerated (Windows) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +UHD requires the RIO device manager service to be running in order to +communicate with an X-Series USRP over PCIe. +This service is installed as a part of the USRP RIO (or NI-USRP) installer. On Windows, it can be found in +the **Services** section in the Control Panel and it is started at system boot time. To ensure that the +service is indeed started, navigate to the Services tag in the Windows Task Manager and ensure that the +status of **niusrpriorpc** is "Running". + +If the device still does not enumerate after starting the device manager, make sure that the host computer +has successfully detected it. You can do so by checking if your device shows up in the Windows Device Manager. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Monitor the host network traffic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Use Wireshark to monitor packets sent to and received from the device. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Observe Ethernet port LEDs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When there is network traffic arriving at the Ethernet port, LEDs will light up. +You can use this to make sure the network connection is correctly set up, e.g. +by pinging the USRP and making sure the LEDs start to blink. + +-------------- +Hardware Notes +-------------- + +^^^^^^^^^^^ +Front Panel +^^^^^^^^^^^ + +.. image:: ./res/x3x0_fp_overlay.png + :scale: 80% + :align: left + +* **JTAG**: USB connector for the on-board USB-JTAG programmer +* **RF A Group** + + * **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard A + * **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard A + +* **REF**: Indicates that the external Reference Clock is locked +* **PPS**: Indicates a valid PPS signal by pulsing once per second +* **AUX I/O**: Front panel GPIO connector. +* **GPS**: Indicates that GPS reference is locked +* **LINK**: Indicates that the host computer is communicating with the device (Activity) + +* **RF B Group** + + * **TX/RX LED**: Indicates that data is streaming on the TX/RX channel on daughterboard B + * **RX2 LED**: Indicates that data is streaming on the RX2 channel on daughterboard B + +* **PWR**: Power switch + +^^^^^^^^^^ +Rear Panel +^^^^^^^^^^ + +.. image:: ./res/x3x0_rp_overlay.png + :scale: 80% + :align: left + + +* **PWR**: Connector for the USRP-X Series power supply +* **1G/10G ETH**: SFP+ ports for Ethernet interfaces +* **REF OUT**: Output port for the exported reference clock +* **REF IN**: Reference clock input +* **PCIe x4**: Connector for Cabled PCI Express link +* **PPS/TRIG OUT**: Output port for the PPS signal +* **PPS/TRIG IN**: Input port for the PPS signal +* **GPS**: Connection for the GPS antenna + +^^^^^^^^^^^^^^^^^^ +Ref Clock - 10 MHz +^^^^^^^^^^^^^^^^^^ +Using an external 10 MHz reference clock, a square wave will offer the best phase +noise performance, but a sinusoid is acceptable. The power level of the reference clock cannot exceed +15 dBm. + +^^^^^^^^^^^^^^^^^^^^^^ +PPS - Pulse Per Second +^^^^^^^^^^^^^^^^^^^^^^ +Using a PPS signal for timestamp synchronization requires a square wave signal with the following a 5Vpp amplitude. + +To test the PPS input, you can use the following tool from the UHD examples: + +* **<args>** are device address arguments (optional if only one USRP device is on your machine) + +:: + + cd <install-path>/lib/uhd/examples + ./test_pps_input --args=<args> + +^^^^^^^^^^^^^^ +Internal GPSDO +^^^^^^^^^^^^^^ +Please see the `Internal GPSDO Application Notes <./gpsdo_x3x0.html>`_ +for information on configuring and using the internal GPSDO. + +^^^^^^^^^^^^^^^^ +Front Panel GPIO +^^^^^^^^^^^^^^^^ + +Connector +::::::::: + +.. image:: ./res/x3x0_gpio_conn.png + :scale: 75% + :align: left + +Pin Mapping +::::::::::: + +* Pin 1: +3.3V +* Pin 2: Data[0] +* Pin 3: Data[1] +* Pin 4: Data[2] +* Pin 5: Data[3] +* Pin 6: Data[4] +* Pin 7: Data[5] +* Pin 8: Data[6] +* Pin 9: Data[7] +* Pin 10: Data[8] +* Pin 11: Data[9] +* Pin 12: Data[10] +* Pin 13: Data[11] +* Pin 14: 0V +* Pin 15: 0V + + +Please see the `GPIO API Notes <./gpio_api.html>`_ for information on configuring and using the GPIO bus. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Debugging custom FPGA designs with Xilinx Chipscope +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Xilinx chipscope allows for debugging custom FPGA designs similar to a logic analyzer. +USRP-X series devices can be used with Xilinx chipscope using the onboard USB JTAG connector. + +Further information on how to use Chipscope can be found in the Xilinx Chipscope Pro Software and Cores User Guide (UG029). + +------------- +Miscellaneous +------------- + +^^^^^^^^^^^^^^^^^^^^ +Multiple RX channels +^^^^^^^^^^^^^^^^^^^^ +There are two complete DDC and DUC DSP chains in the FPGA. In the single channel case, +only one chain is ever used. To receive from both channels, the user must set the **RX** or **TX** +subdevice specification. + +In the following example, a TVRX2 is installed. +Channel 0 is sourced from subdevice **RX1**, +and channel 1 is sourced from subdevice **RX2** (**RX1** and **RX2** are antenna connectors on the TVRX2 daughterboard). + +:: + + usrp->set_rx_subdev_spec("A:RX1 A:RX2"); + + +^^^^^^^^^^^^^^^^^ +Available Sensors +^^^^^^^^^^^^^^^^^ +The following sensors are available for the USRP-X Series motherboards; +they can be queried through the API. + +* **ref_locked** - clock reference locked (internal/external) +* Other sensors are added when the GPSDO is enabled diff --git a/host/docs/usrp_x3x0_config.rst b/host/docs/usrp_x3x0_config.rst new file mode 100644 index 000000000..22ef8c595 --- /dev/null +++ b/host/docs/usrp_x3x0_config.rst @@ -0,0 +1,297 @@ +======================================================================== +UHD - System Configuration for USRP X3x0 Series +======================================================================== + +.. contents:: Table of Contents + +------------------------------------------------------------------------ +Configuring your Host PC +------------------------------------------------------------------------ + +The USRP X3x0 is capable of delivering very fast sample rates to the host PC, +and even high-powered desktops can have trouble keeping up at the higher rates. +You can improve the performance of your host by configuring a number of +settings that affect the performance of your computer. + +These are: + + * Kernel Version + * Network Configuration + * Power Management Configuration + * Real-Time & Priority Scheduling + * Building with ORC & Volk + +These items are covered in more detail, below. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Kernel Version +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Performance issues may be encountered with Linux kernels earlier than 3.11. +Ettus Research strongly recommends using kernel version 3.11 or higher for high +sample rates. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Network Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +When using Ethernet interfaces to communicate with the device, it is necessary +to configure a number of facets regarding your network connection. + +Configuring NetworkManager +------------------------------------- +Fedora and Ubuntu both use NetworkManager to manage network connections. +Unfortunately, NetworkManager often tries to take control of a connection and +will disconnect the interface. + +You should open your NetworkManager configuration and tell it to ignore the +network interface you are using. **This is not the same as simply setting +a static IP address.** You *must* tell NetworkManager to ignore the interface. + +Configuring the Socket Buffers +------------------------------------- +It is necessary to increase the maximum size of the socket buffers to avoid +potential overflows and underruns at high sample rates. Add the following +entries into /etc/sysctl.conf (root privileges required): + +:: + + net.core.rmem_max=33554432 + net.core.wmem_max=33554432 + +Either restart the system or issue the following commands: + +:: + + sudo sysctl -w net.core.rmem_max=33554432 + sudo sysctl -w net.core.wmem_max=33554432 + + +Configuring the MTU +------------------------------------- +In order to achieve maximum performance, we recommend setting the MTU size to +9000 for 10 GigE and 1500 for 1 GigE. It is possible to use smaller MTUs, but this +can affect performance. With some NICs, setting the MTU too high can also cause issues. +To set the MTU to 9000, you can use the following command: + +:: + + sudo ifconfig <interface> mtu 9000 # For 10 GigE + sudo ifconfig <interface> mtu 1500 # For 1 GigE + +Using these MTUs will set the frame sizes for UHD communication to 8000 and 1472, +respectively. + +In some cases, specifying the frame size manually by adding the argument +"<send/recv>_frame_size=1472" can solve issues. Note that a frame size of 1472 will limit +the available sampling rate, although this is not a problem on 1 GigE. + + +Configuring the Firewall +------------------------------------- +Many Linux distributions come installed with a Firewall, by default. The +Firewall will often interfere with your ability to communicate with your USRP. +You should configure your firewall to "trust" the interface you are using. +Setting this properly depends on your OS and firewall configuration method. + +Interface Configuration File (Fedora) +------------------------------------- +On Fedora systems, you can configure the network interface mostly from one +place (with the exception of the socket buffers). Each interface on your system +should have a file in: + +:: + + /etc/sysconfig/network-scripts/ + +As an example, if your 1GigE interface is "em1", your "ifcfg-em1" configuration +file should look something like this, when configured for use with a USRP X3xx: + +:: + + TYPE="Ethernet" + BOOTPROTO="none" + IPADDR0="192.168.10.1" + DEFROUTE="yes" + IPV4_FAILURE_FATAL="no" + IPV6INIT="no" + IPV6_FAILURE_FATAL="no" + NAME="em1" + UUID="<specific to your device>" + ONBOOT="no" + HWADDR"<specific to your device>" + PEERDNS="yes" + PEERROUTES="yes" + ZONE="trusted" + MTU="9000" + NM_MANAGED="no" + +The above file was generated and modified on a "Fedora 20" system. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Power Management +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Power management on the host system attempts to save power by reducing clock +frequencies or even powering off devices while not in use. This can lead to +significant performance issues when trying to operate at high sample rates. +Ettus Research strongly recommends disabling all power management. + + +Setting the CPU Governors +------------------------------------- +In Linux, the CPU governors dictate the frequency at which the CPU operates and +attempt to reduce the CPU frequencies at certain times to save power. When +running at high sample rates, reduction of CPU frequencies can cause +significant performance issues. To prevent those issues, set the governor to +"performance". + +**Ubuntu:** +1. Install cpufrequtils: + +:: + + sudo apt-get install cpufrequtils + +2. Edit /etc/init.d/cpufrequtils and set GOVERNOR="performance" on the appropriate line (run as root): + +:: + + sed s/^GOVERNOR=.*$/GOVERNOR=\"performance\"/g /etc/init.d/cpufrequtils > /etc/init.d/cpufrequtils + +3. Restart cpufrequtils: + +:: + + sudo /etc/init.d/cpufrequtils restart + +**Fedora:** + +:: + + sudo cpupower frequency-set -g performance + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Real-Time & Priority Scheduling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Enabling real-time and priority scheduling can improve the total processing +throughput of your application. Priority scheduling should be enabled for UHD, +and real-time scheduling can be enabled by your application. + +Thread Priority Scheduling with UHD +------------------------------------- +For information regarding how to enable priority scheduling for UHD on your +system, please see the `General UHD Notes <./general.html#threading-notes>`_. + +Real-Time Scheduling in your Application +---------------------------------------- +Please note that turning on real-time scheduling in your application **may lock +up your computer** if the processor cannot keep up with the application. You +should generally avoid using real-time scheduling unless you need to. + +Real-time scheduling is enabled via different methods depending on your +application and operating system. In GNU Radio Companion, it can be turned on in +each individual flowgraph. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Building with ORC & Volk +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Especially when running high-performance applications, processing performance +can be dramatically improved by SIMD instructions. UHD uses ORC to provide SIMD +capability, and GNU Radio includes a SIMD library called "Volk". These should +both be used to guarantee optimum performance. + +Compiling UHD with ORC +------------------------------------- +ORC, the `Oil Runtime Compiler <http://code.entropywave.com/orc/>`_, is +a third-party compiler that UHD uses to create efficient SIMD code for your +particular computer. ORC is generally easily installed from your OS's package +manager. + +On Fedora: + +:: + + $ sudo yum update; sudo yum install orc-compiler orc-devel + +On Ubuntu: + +:: + + $ sudo apt-get update; sudo apt-get install liborc-<version> liborc-<version>-dev + +After installing ORC, when building UHD from source, you should see "ORC" as +one of the configured UHD components. + +:: + + -- ###################################################### + -- # UHD enabled components + -- ###################################################### + -- * LibUHD + <cut for brevity> + -- * ORC + +Compiling GNURadio with Volk +------------------------------------- +If you are using GNURadio to build applications, you should compile GNURadio +with Volk. For instructions on how to do this, `refer to the GNURadio wiki +<http://gnuradio.org/redmine/projects/gnuradio/wiki/Volk>`_. + + +------------------------------------------------------------------------ +Host PC Hardware Selection +------------------------------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Motherboard +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Testing has shown that some motherboards do not provide enough PCIe bus +bandwidth to support higher sample rates. Motherboards with PCIe 3.0 are +required and the PCIe architecture of the motherboard should be carefully +considered. Slots with dedicated PCIe lanes should be used for PCIe or 10GbE +cards that will be connected to the X3x0 device. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +10GbE NIC +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Intel or Myricom 10GbE NICs are recommended. Mellanox, SolarFlare, and Chelsio +10GbE NICs are not currently recommended. The Ethernet card should be plugged +into the slot that has the most direct connection with the CPU (PCIe lanes are +not shared with another slot). Refer to the motherboard manual for more +information on PCIe architecture. + +------------------------------------------------------------------------ +Troubleshooting Performance Issues +------------------------------------------------------------------------ +The output on the host console provides indicators of performance issues in the +form of single upper-case letters. The following table lists the letters, +their meanings, and possible causes: + +========= ====================== ==================================================================== +Indicator Meaning Possible Causes +========= ====================== ==================================================================== +O Overflow on RX - Data is not being consumed by user's application fast enough. + - CPU governor or other power management not configured correctly. +D Dropped packet on RX - Network hardware failure. (Check host NIC, cable, switch, etc...) + - PCIe bus on host cannot sustain throughput. (Check ethtool -S <interface>). + - CPU governor or other power management not configured correctly. + - Frame size might not work with the current NIC's MTU. +U Underflow on TX - Samples are not being produced by user's application fast enough. + - CPU governor or other power management not configured correctly. +L Late packet - Samples are not being produced by user's application fast enough. + (usually on MIMO TX) - CPU governor or other power management not configured correctly. + - Incorrect/invalid time_spec provided. +S Sequence error on TX - Network hardware failure. (Check host NIC, cable, switch, etc...) + - Frame size might not work with the current NIC's MTU. +========= ====================== ==================================================================== + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Troubleshooting Ethernet Issues +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +1. First, check 'ifconfig <interface>' to see if there are any errors reported + on the interface. If there are errors, it is most likely a network hardware + problem. +2. Next, check the output of 'ethtool -S <interface>'. The output is + driver-specific, but may give important clues as to what may be happening. + For example, a high value on rx_missed_errors for an Intel NIC indicates + that the bus (i.e. PCIe) is not keeping up. +3. Finally, Wireshark can be used to validate the traffic between the host and + device and make sure there is no unwanted traffic on the interface. + diff --git a/host/docs/usrp_x3xx_fpga_burner.1 b/host/docs/usrp_x3xx_fpga_burner.1 new file mode 100644 index 000000000..3a8a27e69 --- /dev/null +++ b/host/docs/usrp_x3xx_fpga_burner.1 @@ -0,0 +1,65 @@ +.TH "usrp_x3xx_fpga_burner" 1 "3.7.0" UHD "User Commands" +.SH NAME +usrp_x3xx_fpga_burner - USRP X-Series FPGA Burner +.SH DESCRIPTION +Burn an FPGA image onto a connected USRP X-Series device. The program takes +two main arguments. The first is either an IP address or an NI-RIO resource +to decide the method of loading the FPGA image. The other is either the +type of image to burn, in which case the program will choose the default image +of that type and model, or a custom FPGA image path. +.SH SYNOPSIS +.B usrp_x3xx_fpga_burner [OPTIONS] +.SH OPTIONS +.IP "Device IP Address:" +--addr=\fI"Address"\fR +. IP "Device NI-RIO Resource:" +--resource=\fI"Resource"\fR +. IP "RPC Port:" +--rpc-port=\fI"Port"\fR (default=5444) +. IP "Image Type (1G, HGS, or XGS):" +--type=\fI"Type"\fR +. IP "Custom FPGA path:" +--fpga-path=\fI"Path"\fR +. IP "Initialize USRP with image currently burned onto device (Ethernet only):" +--configure +. IP "Verify image during Ethernet burn (takes longer):" +--verify +. IP "List connected USRP X-Series devices:" +--list +.IP "This help information:" +--help +.SH EXAMPLES +.SS Burning a 1-Gigabit image over Ethernet +.sp +usrp_x3xx_fpga_burner --addr=192.168.10.2 --type=1G +.SS Burning a Hybrid image over PCIe +usrp_x3xx_fpga_burner --resource=RIO0 --type=HGS +.SS Burning a custom FPGA image over Ethernet +usrp_x3xx_fpga_burner --addr=192.168.10.2 --fpga=path="custom_image.bit" +.ft +.fi +.SH SEE ALSO +UHD documentation: +.B http://files.ettus.com/uhd_docs/manual/html/index.html +.LP +GR-UHD documentation: +.B http://gnuradio.org/doc/doxygen/page_uhd.html +.LP +Other UHD programs: +.sp +uhd_images_downloader(1) usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) +.SH AUTHOR +This manual page was written by Nicholas Corgan +for the Debian project (but may be used by others). +.SH COPYRIGHT +Copyright (c) 2014 Ettus Research LLC +.LP +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +.LP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. diff --git a/host/examples/CMakeLists.txt b/host/examples/CMakeLists.txt index 53e913070..4f394bbef 100644 --- a/host/examples/CMakeLists.txt +++ b/host/examples/CMakeLists.txt @@ -36,6 +36,7 @@ SET(example_sources tx_waveforms.cpp txrx_loopback_to_file.cpp latency_test.cpp + fpgpio.cpp ) #for each source: build an executable and install diff --git a/host/examples/benchmark_rate.cpp b/host/examples/benchmark_rate.cpp index 3b2ab8a1f..ea49d48d9 100644 --- a/host/examples/benchmark_rate.cpp +++ b/host/examples/benchmark_rate.cpp @@ -66,8 +66,18 @@ void benchmark_rx_rate(uhd::usrp::multi_usrp::sptr usrp, const std::string &rx_c cmd.time_spec = usrp->get_time_now() + uhd::time_spec_t(0.05); cmd.stream_now = (buffs.size() == 1); rx_stream->issue_stream_cmd(cmd); + while (not boost::this_thread::interruption_requested()){ - num_rx_samps += rx_stream->recv(buffs, max_samps_per_packet, md)*rx_stream->get_num_channels(); + try { + num_rx_samps += rx_stream->recv(buffs, max_samps_per_packet, md)*rx_stream->get_num_channels(); + } + catch (...) { + /* apparently, the boost thread interruption can sometimes result in + throwing exceptions not of type boost::exception, this catch allows + this thread to still attempt to issue the STREAM_MODE_STOP_CONTINUOUS + */ + break; + } //handle the error codes switch(md.error_code){ @@ -78,10 +88,13 @@ void benchmark_rx_rate(uhd::usrp::multi_usrp::sptr usrp, const std::string &rx_c } break; + // ERROR_CODE_OVERFLOW can indicate overflow or sequence error case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: - had_an_overflow = true; last_time = md.time_spec; - num_overflows++; + had_an_overflow = true; + // check out_of_sequence flag to see if it was a sequence error or overflow + if (!md.out_of_sequence) + num_overflows++; break; default: @@ -89,7 +102,6 @@ void benchmark_rx_rate(uhd::usrp::multi_usrp::sptr usrp, const std::string &rx_c std::cerr << "Unexpected error on recv, continuing..." << std::endl; break; } - } rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); } @@ -226,7 +238,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ boost::split(channel_strings, channel_list, boost::is_any_of("\"',")); for(size_t ch = 0; ch < channel_strings.size(); ch++){ size_t chan = boost::lexical_cast<int>(channel_strings[ch]); - if(chan >= usrp->get_tx_num_channels() or chan >= usrp->get_tx_num_channels()){ + if(chan >= usrp->get_tx_num_channels() or chan >= usrp->get_rx_num_channels()){ throw std::runtime_error("Invalid channel(s) specified."); } else channel_nums.push_back(boost::lexical_cast<int>(channel_strings[ch])); @@ -275,6 +287,6 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //finished std::cout << std::endl << "Done!" << std::endl << std::endl; - + return EXIT_SUCCESS; } diff --git a/host/examples/fpgpio.cpp b/host/examples/fpgpio.cpp new file mode 100644 index 000000000..89a6ecec3 --- /dev/null +++ b/host/examples/fpgpio.cpp @@ -0,0 +1,374 @@ +// +// 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/>. +// + +// Example for front panel GPIO. +// Bits are set as follows: +// FPGPIO[0] = ATR output 1 at idle +// FPGPIO[1] = ATR output 1 during RX +// FPGPIO[2] = ATR output 1 during TX +// FPGPIO[3] = ATR output 1 during full duplex +// FPGPIO[4] = output +// FPGPIO[5] = input +// FPGPIO[6] = input +// FPGPIO[7] = input +// FPGPIO[8] = input +// FPGPIO[9] = input +// FPGPIO[10] = input +// The example cycles through idle, TX, RX, and full duplex, spending 2 seconds for each. +// Outputs can be physically looped back to inputs for verification testing. + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/convert.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <csignal> +#include <iostream> + +#define FPGPIO_DEFAULT_CPU_FORMAT "fc32" +#define FPGPIO_DEFAULT_OTW_FORMAT "sc16" +#define FPGPIO_DEFAULT_RX_RATE 1e6 +#define FPGPIO_DEFAULT_TX_RATE 1e6 +#define FPGPIO_DEFAULT_DWELL_TIME 2.0 +#define FPGPIO_NUM_BITS 11 +#define FPGPIO_BIT(x) (1 << x) + +namespace po = boost::program_options; + +static bool stop_signal_called = false; +void sig_int_handler(int){stop_signal_called = true;} + +std::string to_bit_string(boost::uint16_t val) +{ + std::string out; + for (int i = FPGPIO_NUM_BITS - 1; i >= 0; i--) + { + std::string bit = ((val >> i) & 1) ? "1" : "0"; + out += " "; + out += bit; + } + return out; +} + +void output_reg_values(const std::string bank, const uhd::usrp::multi_usrp::sptr &usrp) +{ + std::cout << (boost::format("Bit ")); + for (int i = FPGPIO_NUM_BITS - 1; i >= 0; i--) + std::cout << (boost::format(" %s%d") % (i < 10 ? " " : "") % i); + std::cout << std::endl; + std::cout << "CTRL: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("CTRL")))) << std::endl; + std::cout << "DDR: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("DDR")))) << std::endl; + std::cout << "ATR_0X: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("ATR_0X")))) << std::endl; + std::cout << "ATR_RX: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("ATR_RX")))) << std::endl; + std::cout << "ATR_TX: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("ATR_TX")))) << std::endl; + std::cout << "ATR_XX: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("ATR_XX")))) << std::endl; + std::cout << "OUT: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("OUT")))) << std::endl; + std::cout << "READBACK: " << to_bit_string(uint16_t(usrp->get_gpio_attr(bank, std::string("READBACK")))) << std::endl; +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + uhd::set_thread_priority_safe(); + + //variables to be set by po + std::string args; + std::string cpu, otw; + double rx_rate, tx_rate, dwell; + const std::string fpgpio = "FP0"; + + //setup the program options + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "multi uhd device address args") + ("repeat", "repeat loop until Ctrl-C is pressed") + ("cpu", po::value<std::string>(&cpu)->default_value(FPGPIO_DEFAULT_CPU_FORMAT), "cpu data format") + ("otw", po::value<std::string>(&otw)->default_value(FPGPIO_DEFAULT_OTW_FORMAT), "over the wire data format") + ("rx_rate", po::value<double>(&rx_rate)->default_value(FPGPIO_DEFAULT_RX_RATE), "rx sample rate") + ("tx_rate", po::value<double>(&tx_rate)->default_value(FPGPIO_DEFAULT_TX_RATE), "tx sample rate") + ("dwell", po::value<double>(&dwell)->default_value(FPGPIO_DEFAULT_DWELL_TIME), "dwell time in seconds for each test case") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("Front Panel GPIO %s") % desc << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + + //print out initial unconfigured state of FP GPIO + std::cout << "Unconfigured GPIO values:" << std::endl; + output_reg_values(fpgpio, usrp); + + //configure GPIO registers + uint32_t ctrl = 0; // default all as manual + uint32_t ddr = 0; // default all as input + uint32_t atr_idle = 0; + uint32_t atr_rx = 0; + uint32_t atr_tx = 0; + uint32_t atr_duplex = 0; + uint32_t mask = 0x7ff; + + //set up FPGPIO outputs: + //FPGPIO[0] = ATR output 1 at idle + ctrl |= FPGPIO_BIT(0); + atr_idle |= FPGPIO_BIT(0); + ddr |= FPGPIO_BIT(0); + + //FPGPIO[1] = ATR output 1 during RX + ctrl |= FPGPIO_BIT(1); + ddr |= FPGPIO_BIT(1); + atr_rx |= FPGPIO_BIT(1); + + //FPGPIO[2] = ATR output 1 during TX + ctrl |= FPGPIO_BIT(2); + ddr |= FPGPIO_BIT(2); + atr_tx |= FPGPIO_BIT(2); + + //FPGPIO[3] = ATR output 1 during full duplex + ctrl |= FPGPIO_BIT(3); + ddr |= FPGPIO_BIT(3); + atr_duplex |= FPGPIO_BIT(3); + + //FPGPIO[4] = output + ddr |= FPGPIO_BIT(4); + + //set data direction register (DDR) + usrp->set_gpio_attr(fpgpio, std::string("DDR"), ddr, mask); + + //set ATR registers + usrp->set_gpio_attr(fpgpio, std::string("ATR_0X"), atr_idle, mask); + usrp->set_gpio_attr(fpgpio, std::string("ATR_RX"), atr_rx, mask); + usrp->set_gpio_attr(fpgpio, std::string("ATR_TX"), atr_tx, mask); + usrp->set_gpio_attr(fpgpio, std::string("ATR_XX"), atr_duplex, mask); + + //set control register + usrp->set_gpio_attr(fpgpio, std::string("CTRL"), ctrl, mask); + + //print out initial state of FP GPIO + std::cout << "\nConfigured GPIO values:" << std::endl; + output_reg_values(fpgpio, usrp); + std::cout << std::endl; + + //set up streams + uhd::stream_args_t rx_args(cpu, otw); + uhd::stream_args_t tx_args(cpu, otw); + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(rx_args); + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(tx_args); + uhd::stream_cmd_t rx_cmd(uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + rx_cmd.stream_now = true; + usrp->set_rx_rate(rx_rate); + usrp->set_tx_rate(tx_rate); + + //set up buffers for tx and rx + const size_t max_samps_per_packet = rx_stream->get_max_num_samps(); + const size_t nsamps_per_buff = max_samps_per_packet; + std::vector<char> rx_buff(max_samps_per_packet*uhd::convert::get_bytes_per_item(cpu)); + std::vector<char> tx_buff(max_samps_per_packet*uhd::convert::get_bytes_per_item(cpu)); + std::vector<void *> rx_buffs, tx_buffs; + for (size_t ch = 0; ch < rx_stream->get_num_channels(); ch++) + rx_buffs.push_back(&rx_buff.front()); //same buffer for each channel + for (size_t ch = 0; ch < tx_stream->get_num_channels(); ch++) + tx_buffs.push_back(&tx_buff.front()); //same buffer for each channel + + uhd::rx_metadata_t rx_md; + uhd::tx_metadata_t tx_md; + tx_md.has_time_spec = false; + tx_md.start_of_burst = true; + uhd::time_spec_t stop_time; + double timeout = 0.01; + uhd::time_spec_t dwell_time(dwell); + int loop = 0; + boost::uint32_t rb, expected; + + //register singal handler + std::signal(SIGINT, &sig_int_handler); + + //Test the mask - only need to test once with no dwell time + std::cout << "\nTesting mask..." << std::flush; + //send a value of all 1's to the DDR with a mask for only bit 10 + usrp->set_gpio_attr(fpgpio, std::string("DDR"), ~0, FPGPIO_BIT(10)); + //bit 10 should now be 1, but all the other bits should be unchanged + rb = usrp->get_gpio_attr(fpgpio, std::string("DDR")) & mask; + expected = ddr | FPGPIO_BIT(10); + if (rb == expected) + std::cout << "pass" << std::endl; + else + std::cout << "fail" << std::endl; + std::cout << std::endl; + output_reg_values(fpgpio, usrp); + usrp->set_gpio_attr(fpgpio, std::string("DDR"), ddr, mask); + + while (not stop_signal_called) + { + int failures = 0; + + if (vm.count("repeat")) + std::cout << "Press Ctrl + C to quit..." << std::endl; + + // test user controlled GPIO and ATR idle by setting bit 4 high for 1 second + std::cout << "\nTesting user controlled GPIO and ATR idle output..." << std::flush; + usrp->set_gpio_attr(fpgpio, "OUT", 1 << 4, 1 << 4); + stop_time = usrp->get_time_now() + dwell_time; + while (not stop_signal_called and usrp->get_time_now() < stop_time) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } + rb = usrp->get_gpio_attr(fpgpio, "READBACK"); + expected = FPGPIO_BIT(4) | FPGPIO_BIT(0); + if ((rb & expected) != expected) + { + ++failures; + std::cout << "fail" << std::endl; + if ((rb & FPGPIO_BIT(0)) == 0) + std::cout << "Bit 0 should be set, but is not" << std::endl; + if ((rb & FPGPIO_BIT(4)) == 0) + std::cout << "Bit 4 should be set, but is not" << std::endl; + } else { + std::cout << "pass" << std::endl; + } + std::cout << std::endl; + output_reg_values(fpgpio, usrp); + usrp->set_gpio_attr(fpgpio, "OUT", 0, FPGPIO_BIT(4)); + if (stop_signal_called) + break; + + // test ATR RX by receiving for 1 second + std::cout << "\nTesting ATR RX output..." << std::flush; + rx_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + rx_stream->issue_stream_cmd(rx_cmd); + stop_time = usrp->get_time_now() + dwell_time; + while (not stop_signal_called and usrp->get_time_now() < stop_time) + { + try { + rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout); + } catch(...){} + } + rb = usrp->get_gpio_attr(fpgpio, "READBACK"); + expected = FPGPIO_BIT(1); + if ((rb & expected) != expected) + { + ++failures; + std::cout << "fail" << std::endl; + std::cout << "Bit 1 should be set, but is not" << std::endl; + } else { + std::cout << "pass" << std::endl; + } + std::cout << std::endl; + output_reg_values(fpgpio, usrp); + rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + //clear out any data left in the rx stream + try { + rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout); + } catch(...){} + if (stop_signal_called) + break; + + // test ATR TX by transmitting for 1 second + std::cout << "\nTesting ATR TX output..." << std::flush; + stop_time = usrp->get_time_now() + dwell_time; + tx_md.start_of_burst = true; + tx_md.end_of_burst = false; + while (not stop_signal_called and usrp->get_time_now() < stop_time) + { + try { + tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout); + tx_md.start_of_burst = false; + } catch(...){} + } + rb = usrp->get_gpio_attr(fpgpio, "READBACK"); + expected = FPGPIO_BIT(2); + if ((rb & expected) != expected) + { + ++failures; + std::cout << "fail" << std::endl; + std::cout << "Bit 2 should be set, but is not" << std::endl; + } else { + std::cout << "pass" << std::endl; + } + std::cout << std::endl; + output_reg_values(fpgpio, usrp); + tx_md.end_of_burst = true; + try { + tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout); + } catch(...){} + if (stop_signal_called) + break; + + // test ATR RX by transmitting and receiving for 1 second + std::cout << "\nTesting ATR full duplex output..." << std::flush; + rx_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + rx_stream->issue_stream_cmd(rx_cmd); + tx_md.start_of_burst = true; + tx_md.end_of_burst = false; + stop_time = usrp->get_time_now() + dwell_time; + while (not stop_signal_called and usrp->get_time_now() < stop_time) + { + try { + tx_stream->send(rx_buffs, nsamps_per_buff, tx_md, timeout); + tx_md.start_of_burst = false; + rx_stream->recv(tx_buffs, nsamps_per_buff, rx_md, timeout); + } catch(...){} + } + rb = usrp->get_gpio_attr(fpgpio, "READBACK"); + expected = FPGPIO_BIT(3); + if ((rb & expected) != expected) + { + ++failures; + std::cout << "fail" << std::endl; + std::cout << "Bit 3 should be set, but is not" << std::endl; + } else { + std::cout << "pass" << std::endl; + } + std::cout << std::endl; + output_reg_values(fpgpio, usrp); + rx_stream->issue_stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + tx_md.end_of_burst = true; + try { + tx_stream->send(tx_buffs, nsamps_per_buff, tx_md, timeout); + } catch(...){} + //clear out any data left in the rx stream + try { + rx_stream->recv(rx_buffs, nsamps_per_buff, rx_md, timeout); + } catch(...){} + + std::cout << std::endl; + if (failures) + std::cout << failures << " tests failed" << std::endl; + else + std::cout << "All tests passed!" << std::endl; + + if (!vm.count("repeat")) + break; + + std::cout << (boost::format("\nLoop %d completed") % ++loop) << std::endl; + } + + //finished + std::cout << std::endl << "Done!" << std::endl << std::endl; + + return EXIT_SUCCESS; +} diff --git a/host/examples/rx_ascii_art_dft.cpp b/host/examples/rx_ascii_art_dft.cpp index df3256b09..85cf8b50c 100644 --- a/host/examples/rx_ascii_art_dft.cpp +++ b/host/examples/rx_ascii_art_dft.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-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 @@ -56,6 +56,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("ref-lvl", po::value<float>(&ref_lvl)->default_value(0), "reference level for the display (dB)") ("dyn-rng", po::value<float>(&dyn_rng)->default_value(60), "dynamic range for the display (dB)") ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ("int-n", "tune USRP with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -95,7 +96,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return EXIT_FAILURE; } std::cout << boost::format("Setting RX Freq: %f MHz...") % (freq/1e6) << std::endl; - usrp->set_rx_freq(freq); + uhd::tune_request_t tune_request(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_rx_freq(tune_request); std::cout << boost::format("Actual RX Freq: %f MHz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; //set the rf gain diff --git a/host/examples/rx_samples_to_file.cpp b/host/examples/rx_samples_to_file.cpp index a28d1d628..0d42404d3 100644 --- a/host/examples/rx_samples_to_file.cpp +++ b/host/examples/rx_samples_to_file.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-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 @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <uhd/types/tune_request.hpp> #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/usrp/multi_usrp.hpp> @@ -242,6 +243,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("null", "run without writing to file") ("continue", "don't abort on a bad packet") ("skip-lo", "skip checking LO lock status") + ("int-n", "tune USRP with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -287,7 +289,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //set the center frequency if (vm.count("freq")){ //with default of 0.0 this will always be true std::cout << boost::format("Setting RX Freq: %f MHz...") % (freq/1e6) << std::endl; - usrp->set_rx_freq(freq); + uhd::tune_request_t tune_request(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_rx_freq(tune_request); std::cout << boost::format("Actual RX Freq: %f MHz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; } diff --git a/host/examples/rx_samples_to_udp.cpp b/host/examples/rx_samples_to_udp.cpp index 0b3c6dce3..2f62652e0 100644 --- a/host/examples/rx_samples_to_udp.cpp +++ b/host/examples/rx_samples_to_udp.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,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 @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <uhd/types/tune_request.hpp> #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/usrp/multi_usrp.hpp> @@ -52,6 +53,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("port", po::value<std::string>(&port)->default_value("7124"), "server udp port") ("addr", po::value<std::string>(&addr)->default_value("192.168.1.10"), "resolvable server address") ("ref", po::value<std::string>(&ref)->default_value("internal"), "waveform type (internal, external, mimo)") + ("int-n", "tune USRP with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -79,7 +81,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //set the rx center frequency std::cout << boost::format("Setting RX Freq: %f Mhz...") % (freq/1e6) << std::endl; - usrp->set_rx_freq(freq); + uhd::tune_request_t tune_request(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_rx_freq(tune_request); std::cout << boost::format("Actual RX Freq: %f Mhz...") % (usrp->get_rx_freq()/1e6) << std::endl << std::endl; //set the rx rf gain diff --git a/host/examples/rx_timed_samples.cpp b/host/examples/rx_timed_samples.cpp index 0eea2ffee..cc9216cb7 100644 --- a/host/examples/rx_timed_samples.cpp +++ b/host/examples/rx_timed_samples.cpp @@ -20,6 +20,7 @@ #include <uhd/usrp/multi_usrp.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> +#include <boost/algorithm/string.hpp> #include <iostream> #include <complex> @@ -30,19 +31,24 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args; + std::string wire; double seconds_in_future; size_t total_num_samps; double rate; + std::string channel_list; //setup the program options po::options_description desc("Allowed options"); desc.add_options() ("help", "help message") ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("wire", po::value<std::string>(&wire)->default_value(""), "the over the wire type, sc16, sc8, etc") ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "number of seconds in the future to receive") ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to receive") ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of incoming samples") ("dilv", "specify to disable inner-loop verbose") + ("channels", po::value<std::string>(&channel_list)->default_value("0"), "which channel(s) to use (specify \"0\", \"1\", \"0,1\", etc)") + ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -62,6 +68,18 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; + //detect which channels to use + std::vector<std::string> channel_strings; + std::vector<size_t> channel_nums; + boost::split(channel_strings, channel_list, boost::is_any_of("\"',")); + for(size_t ch = 0; ch < channel_strings.size(); ch++){ + size_t chan = boost::lexical_cast<int>(channel_strings[ch]); + if(chan >= usrp->get_tx_num_channels() or chan >= usrp->get_rx_num_channels()){ + throw std::runtime_error("Invalid channel(s) specified."); + } + else channel_nums.push_back(boost::lexical_cast<int>(channel_strings[ch])); + } + //set the rx sample rate std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate/1e6) << std::endl; usrp->set_rx_rate(rate); @@ -71,7 +89,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ usrp->set_time_now(uhd::time_spec_t(0.0)); //create a receive streamer - uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::stream_args_t stream_args("fc32", wire); //complex floats + stream_args.channels = channel_nums; uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); //setup streaming @@ -90,6 +109,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //allocate buffer to receive with samples std::vector<std::complex<float> > buff(rx_stream->get_max_num_samps()); + std::vector<void *> buffs; + for (size_t ch = 0; ch < rx_stream->get_num_channels(); ch++) + buffs.push_back(&buff.front()); //same buffer for each channel //the first call to recv() will block this many seconds before receiving double timeout = seconds_in_future + 0.1; //timeout (delay before receive + padding) @@ -98,7 +120,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ while(num_acc_samps < total_num_samps){ //receive a single packet size_t num_rx_samps = rx_stream->recv( - &buff.front(), buff.size(), md, timeout, true + buffs, buff.size(), md, timeout, true ); //use a small timeout for subsequent packets diff --git a/host/examples/transport_hammer.cpp b/host/examples/transport_hammer.cpp index a44e81a82..4b949e5bd 100644 --- a/host/examples/transport_hammer.cpp +++ b/host/examples/transport_hammer.cpp @@ -83,7 +83,8 @@ void rx_hammer(uhd::usrp::multi_usrp::sptr usrp, const std::string &rx_cpu, uhd: case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: had_an_overflow = true; last_time = md.time_spec; - num_overflows++; + if (!md.out_of_sequence) + num_overflows++; break; default: diff --git a/host/examples/tx_bursts.cpp b/host/examples/tx_bursts.cpp index eada1a618..fec89a0e4 100644 --- a/host/examples/tx_bursts.cpp +++ b/host/examples/tx_bursts.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-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 @@ -60,6 +60,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("gain", po::value<double>(&gain)->default_value(0), "gain") ("dilv", "specify to disable inner-loop verbose") ("channels", po::value<std::string>(&channel_list)->default_value("0"), "which channel(s) to use (specify \"0\", \"1\", \"0,1\", etc") + ("int-n", "tune USRP with integer-n tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -98,7 +99,11 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format("Actual TX Rate: %f Msps...") % (usrp->get_tx_rate()/1e6) << std::endl << std::endl; std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; - for(size_t i=0; i < channel_nums.size(); i++) usrp->set_tx_freq(freq, channel_nums[i]); + for(size_t i=0; i < channel_nums.size(); i++){ + uhd::tune_request_t tune_request(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_tx_freq(tune_request, channel_nums[i]); + } std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq()/1e6) << std::endl << std::endl; std::cout << boost::format("Setting TX Gain: %f...") % (gain) << std::endl; diff --git a/host/examples/tx_samples_from_file.cpp b/host/examples/tx_samples_from_file.cpp index f9447c25d..f911c446e 100644 --- a/host/examples/tx_samples_from_file.cpp +++ b/host/examples/tx_samples_from_file.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// Copyright 2011-2012,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 @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <uhd/types/tune_request.hpp> #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/usrp/multi_usrp.hpp> @@ -70,7 +71,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args, file, type, ant, subdev, ref, wirefmt; size_t spb; - double rate, freq, gain, bw, delay; + double rate, freq, gain, bw, delay, lo_off; //setup the program options po::options_description desc("Allowed options"); @@ -82,6 +83,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer") ("rate", po::value<double>(&rate), "rate of outgoing samples") ("freq", po::value<double>(&freq), "RF center frequency in Hz") + ("lo_off", po::value<double>(&lo_off), "Offset for frontend LO in Hz (optional)") ("gain", po::value<double>(&gain), "gain for the RF chain") ("ant", po::value<std::string>(&ant), "daughterboard antenna selection") ("subdev", po::value<std::string>(&subdev), "daughterboard subdevice specification") @@ -90,6 +92,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("wirefmt", po::value<std::string>(&wirefmt)->default_value("sc16"), "wire format (sc8 or sc16)") ("delay", po::value<double>(&delay)->default_value(0.0), "specify a delay between repeated transmission of file") ("repeat", "repeatedly transmit file") + ("int-n", "tune USRP with integer-n tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -131,7 +134,11 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return ~0; } std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; - usrp->set_tx_freq(freq); + uhd::tune_request_t tune_request; + if(vm.count("lo_off")) tune_request = uhd::tune_request_t(freq, lo_off); + else tune_request = uhd::tune_request_t(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_tx_freq(tune_request); std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq()/1e6) << std::endl << std::endl; //set the rf gain diff --git a/host/examples/tx_timed_samples.cpp b/host/examples/tx_timed_samples.cpp index 2eef80389..667ae66a9 100644 --- a/host/examples/tx_timed_samples.cpp +++ b/host/examples/tx_timed_samples.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011 Ettus Research LLC +// Copyright 2010-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 @@ -31,6 +31,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ //variables to be set by po std::string args; + std::string wire; double seconds_in_future; size_t total_num_samps; double rate; @@ -41,6 +42,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ desc.add_options() ("help", "help message") ("args", po::value<std::string>(&args)->default_value(""), "single uhd device address args") + ("wire", po::value<std::string>(&wire)->default_value(""), "the over the wire type, sc16, sc8, etc") ("secs", po::value<double>(&seconds_in_future)->default_value(1.5), "number of seconds in the future to transmit") ("nsamps", po::value<size_t>(&total_num_samps)->default_value(10000), "total number of samples to transmit") ("rate", po::value<double>(&rate)->default_value(100e6/16), "rate of outgoing samples") @@ -74,7 +76,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ usrp->set_time_now(uhd::time_spec_t(0.0)); //create a transmit streamer - uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::stream_args_t stream_args("fc32", wire); //complex floats uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); //allocate buffer with data to send diff --git a/host/examples/tx_waveforms.cpp b/host/examples/tx_waveforms.cpp index 9474642df..ba89a8ad5 100644 --- a/host/examples/tx_waveforms.cpp +++ b/host/examples/tx_waveforms.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,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 @@ -116,6 +116,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("ref", po::value<std::string>(&ref)->default_value("internal"), "clock reference (internal, external, mimo)") ("otw", po::value<std::string>(&otw)->default_value("sc16"), "specify the over-the-wire sample mode") ("channels", po::value<std::string>(&channel_list)->default_value("0"), "which channels to use (specify \"0\", \"1\", \"0,1\", etc)") + ("int-n", "tune USRP with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -170,7 +171,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ for(size_t ch = 0; ch < channel_nums.size(); ch++) { std::cout << boost::format("Setting TX Freq: %f MHz...") % (freq/1e6) << std::endl; - usrp->set_tx_freq(freq, channel_nums[ch]); + uhd::tune_request_t tune_request(freq); + if(vm.count("int-n")) tune_request.args = uhd::device_addr_t("mode_n=integer"); + usrp->set_tx_freq(tune_request, channel_nums[ch]); std::cout << boost::format("Actual TX Freq: %f MHz...") % (usrp->get_tx_freq(channel_nums[ch])/1e6) << std::endl << std::endl; //set the rf gain diff --git a/host/examples/txrx_loopback_to_file.cpp b/host/examples/txrx_loopback_to_file.cpp index 666153edb..3d3cf1dfc 100644 --- a/host/examples/txrx_loopback_to_file.cpp +++ b/host/examples/txrx_loopback_to_file.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,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 @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <uhd/types/tune_request.hpp> #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/utils/static.hpp> @@ -240,6 +241,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("otw", po::value<std::string>(&otw)->default_value("sc16"), "specify the over-the-wire sample mode") ("tx-channels", po::value<std::string>(&tx_channels)->default_value("0"), "which TX channel(s) to use (specify \"0\", \"1\", \"0,1\", etc)") ("rx-channels", po::value<std::string>(&rx_channels)->default_value("0"), "which RX channel(s) to use (specify \"0\", \"1\", \"0,1\", etc)") + ("tx-int-n", "tune USRP TX with integer-N tuning") + ("rx-int-n", "tune USRP RX with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); @@ -318,7 +321,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ for(size_t ch = 0; ch < tx_channel_nums.size(); ch++) { std::cout << boost::format("Setting TX Freq: %f MHz...") % (tx_freq/1e6) << std::endl; - tx_usrp->set_tx_freq(tx_freq, tx_channel_nums[ch]); + uhd::tune_request_t tx_tune_request(tx_freq); + if(vm.count("tx-int-n")) tx_tune_request.args = uhd::device_addr_t("mode_n=integer"); + tx_usrp->set_tx_freq(tx_tune_request, tx_channel_nums[ch]); std::cout << boost::format("Actual TX Freq: %f MHz...") % (tx_usrp->get_tx_freq(tx_channel_nums[ch])/1e6) << std::endl << std::endl; //set the rf gain @@ -345,7 +350,9 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ return ~0; } std::cout << boost::format("Setting RX Freq: %f MHz...") % (rx_freq/1e6) << std::endl; - rx_usrp->set_rx_freq(rx_freq); + uhd::tune_request_t rx_tune_request(rx_freq); + if(vm.count("rx-int-n")) rx_tune_request.args = uhd::device_addr_t("mode_n=integer"); + rx_usrp->set_rx_freq(rx_tune_request); std::cout << boost::format("Actual RX Freq: %f MHz...") % (rx_usrp->get_rx_freq()/1e6) << std::endl << std::endl; //set the receive rf gain diff --git a/host/include/uhd/device.hpp b/host/include/uhd/device.hpp index de39383c9..1090c243c 100644 --- a/host/include/uhd/device.hpp +++ b/host/include/uhd/device.hpp @@ -21,6 +21,7 @@ #include <uhd/config.hpp> #include <uhd/stream.hpp> #include <uhd/deprecated.hpp> +#include <uhd/property_tree.hpp> #include <uhd/types/device_addr.hpp> #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> @@ -83,10 +84,12 @@ public: virtual tx_streamer::sptr get_tx_stream(const stream_args_t &args) = 0; //! Get access to the underlying property structure - virtual boost::shared_ptr<property_tree> get_tree(void) const = 0; + uhd::property_tree::sptr get_tree(void) const; #include <uhd/device_deprecated.ipp> +protected: + uhd::property_tree::sptr _tree; }; } //namespace uhd diff --git a/host/include/uhd/transport/CMakeLists.txt b/host/include/uhd/transport/CMakeLists.txt index 1031da817..2118674c6 100644 --- a/host/include/uhd/transport/CMakeLists.txt +++ b/host/include/uhd/transport/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011 Ettus Research LLC +# Copyright 2010-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 @@ -21,8 +21,10 @@ UHD_INSTALL(FILES bounded_buffer.ipp buffer_pool.hpp if_addrs.hpp + udp_constants.hpp udp_simple.hpp udp_zero_copy.hpp + tcp_zero_copy.hpp usb_control.hpp usb_zero_copy.hpp usb_device_handle.hpp diff --git a/host/include/uhd/transport/nirio/nifpga_lvbitx.h b/host/include/uhd/transport/nirio/nifpga_lvbitx.h new file mode 100644 index 000000000..598f7fcbe --- /dev/null +++ b/host/include/uhd/transport/nirio/nifpga_lvbitx.h @@ -0,0 +1,59 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIFPGA_LVBITX_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIFPGA_LVBITX_H + +#include <uhd/transport/nirio/nirio_resource_manager.h> +#include <uhd/transport/nirio/niriok_proxy.h> +#include <boost/smart_ptr.hpp> + +namespace uhd { namespace niusrprio { + +class UHD_API nifpga_lvbitx { +public: + typedef boost::shared_ptr<nifpga_lvbitx> sptr; + + virtual ~nifpga_lvbitx() {}; + + virtual const char* get_bitfile_path() = 0; + virtual const char* get_signature() = 0; + virtual const char* get_bitstream_checksum() = 0; + + virtual size_t get_input_fifo_count() = 0; + virtual const char** get_input_fifo_names() = 0; + + virtual size_t get_output_fifo_count() = 0; + virtual const char** get_output_fifo_names() = 0; + + virtual size_t get_control_count() = 0; + virtual const char** get_control_names() = 0; + + virtual size_t get_indicator_count() = 0; + virtual const char** get_indicator_names() = 0; + + virtual void init_register_info(nirio_register_info_vtr& vtr) = 0; + virtual void init_fifo_info(nirio_fifo_info_vtr& vtr) = 0; + +protected: + std::string _get_bitstream_checksum(const std::string& file_path); + std::string _get_fpga_images_dir(const std::string search_paths); +}; +}} + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIFPGA_LVBITX_H */ + diff --git a/host/include/uhd/transport/nirio/nirio_driver_iface.h b/host/include/uhd/transport/nirio/nirio_driver_iface.h new file mode 100644 index 000000000..46a1146de --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_driver_iface.h @@ -0,0 +1,535 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIRIO_DRIVER_IFACE_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_DRIVER_IFACE_H + +#include <stddef.h> +#include <stdint.h> +#include <string> +#include <uhd/transport/nirio/status.h> +#include <uhd/config.hpp> +#if defined(UHD_PLATFORM_WIN32) + #include <Windows.h> + #pragma warning(disable:4201) // nonstandard extension used : nameless struct/union + #include <WinIoCtl.h> + #pragma warning(default:4201) +#elif defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + #include <IOKit/IOKitLib.h> +#endif + +// CTL_CODE macro for non-win OSes +#ifndef UHD_PLATFORM_WIN32 + #define CTL_CODE(a,controlCode,b,c) (controlCode) +#endif + +namespace nirio_driver_iface { + +const uint32_t NIRIO_IOCTL_BASE = 0x800; + +const uint32_t NIRIO_IOCTL_SYNCOP = + CTL_CODE(FILE_DEVICE_UNKNOWN, + NIRIO_IOCTL_BASE + 4, + METHOD_OUT_DIRECT, + FILE_READ_DATA | FILE_WRITE_DATA); + ///< The synchronous operation code. Note: We + /// must use METHOD_OUT_DIRECT on the syncOp() + /// IOCTL to ensure the contents of the output + /// block are available in the kernel. + +const uint32_t NIRIO_IOCTL_GET_IFACE_NUM = + CTL_CODE(FILE_DEVICE_UNKNOWN, + NIRIO_IOCTL_BASE + 6, + METHOD_BUFFERED, + FILE_READ_DATA); ///< Get the interface number for a device + +const uint32_t NIRIO_IOCTL_GET_SESSION = + CTL_CODE(FILE_DEVICE_UNKNOWN, + NIRIO_IOCTL_BASE + 8, + METHOD_BUFFERED, + FILE_READ_ACCESS); ///< Gets a previously opened session to a device + +const uint32_t NIRIO_IOCTL_POST_OPEN = + CTL_CODE(FILE_DEVICE_UNKNOWN, + NIRIO_IOCTL_BASE + 9, + METHOD_BUFFERED, + FILE_READ_ACCESS); ///< Called after opening a session + +const uint32_t NIRIO_IOCTL_PRE_CLOSE = + CTL_CODE(FILE_DEVICE_UNKNOWN, + NIRIO_IOCTL_BASE + 10, + METHOD_BUFFERED, + FILE_READ_ACCESS); ///< Called before closing a session + + +// ------------------------------- +// Function Codes: defined as integers rather than enums because they +// are going to be carried accross boundaries so size matters + +namespace NIRIO_FUNC +{ + const uint32_t GET32 = 0x00000001; + const uint32_t SET32 = 0x00000002; + const uint32_t SET_DRIVER_CONFIG = 0x00000007; + const uint32_t FIFO = 0x00000008; + const uint32_t IO = 0x0000000A; + const uint32_t FIFO_STOP_ALL = 0x0000000C; + const uint32_t ADD_RESOURCE = 0x0000000D; + const uint32_t GET_STRING = 0x0000000E; + const uint32_t SET_STRING = 0x0000000F; + const uint32_t DOWNLOAD = 0x00000013; + const uint32_t RESET = 0x00000014; +} + +namespace NIRIO_RESOURCE +{ + const uint32_t INPUT_FIFO = 0xD0000001; + const uint32_t OUTPUT_FIFO = 0xD0000002; +} + +namespace NIRIO_FIFO +{ + const uint32_t CONFIGURE = 0x80000001; + const uint32_t START = 0x80000002; + const uint32_t STOP = 0x80000003; + const uint32_t READ = 0x80000004; + const uint32_t WRITE = 0x80000005; + const uint32_t WAIT = 0x80000006; + const uint32_t GRANT = 0x80000007; +} + +namespace NIRIO_IO +{ + const uint32_t POKE64 = 0xA0000005; + const uint32_t POKE32 = 0xA0000006; + const uint32_t POKE16 = 0xA0000007; + const uint32_t POKE8 = 0xA0000008; + const uint32_t PEEK64 = 0xA0000009; + const uint32_t PEEK32 = 0xA000000A; + const uint32_t PEEK16 = 0xA000000B; + const uint32_t PEEK8 = 0xA000000C; + const uint32_t READ_BLOCK = 0xA000000D; + const uint32_t WRITE_BLOCK = 0xA000000E; + const uint32_t GET_IO_WINDOW = 0xA000000F; + const uint32_t GET_IO_WINDOW_SIZE = 0xA0000010; +} + +struct nirio_ioctl_packet_t { + nirio_ioctl_packet_t( + void* const _outBuf, + const uint32_t _outSize, + const int32_t _statusCode) + { + outBuf._64BitField = 0; + outBuf.pointer = _outBuf; + outSize = _outSize; + statusCode = _statusCode; + }; + + union { + void* pointer; + uint64_t _64BitField; + } outBuf; + + uint32_t outSize; + int32_t statusCode; +}; + +struct nirio_ioctl_block_t +{ + uint64_t inBuf; + uint64_t outBuf; + uint32_t inBufLength; + uint32_t outBufLength; + uint32_t bytesReturned; + uint32_t padding; +}; + +struct nirio_syncop_in_params_t +{ + uint32_t function; + uint32_t subfunction; + + union + { + struct + { + uint32_t attribute; + uint32_t value; + } attribute32; + + struct + { + uint32_t attribute; + uint64_t value; + } attribute64; + + struct + { + uint32_t attribute; + } attributeStr; + + struct + { + uint32_t attribute; + } download; + + union + { + struct + { + uint32_t reserved_field_0_0_0; + } reserved_field_0_0; + struct + { + uint32_t reserved_field_0_1_0; + uint32_t reserved_field_0_1_1; + } reserved_field_0_1; + struct + { + uint32_t reserved_field_0_2_0; + } reserved_field_0_2; + } reserved_field_0; + + union + { + struct + { + uint32_t channel; + uint32_t baseAddress; + uint32_t depthInSamples; + uint32_t version; + } fifo; + struct + { + uint32_t channel; + uint32_t baseAddress; + uint32_t depthInSamples; + uint32_t version; + uint32_t scalarType; + uint32_t bitWidth; + } fifoWithDataType; + struct + { + uint64_t rangeBaseAddress; + uint32_t rangeSizeInBytes; + uint32_t rangeAttribute; + } atomic; // obsolete + } add; + + struct + { + uint32_t channel; + + union + { + struct + { + uint32_t requestedDepth; + uint8_t requiresActuals; + } config; + struct + { + uint32_t timeout; + } read; + struct + { + uint32_t timeout; + uint32_t scalarType; + uint32_t bitWidth; + } readWithDataType; + struct + { + uint32_t timeout; + } write; + struct + { + uint32_t timeout; + uint32_t scalarType; + uint32_t bitWidth; + } writeWithDataType; + struct + { + uint32_t elementsRequested; + uint32_t scalarType; + uint32_t bitWidth; + uint32_t timeout; + uint8_t output; + } wait; + struct + { + uint32_t elements; + } grant; + } op; + } fifo; + + struct + { + uint64_t reserved_field_1_0; + uint32_t reserved_field_1_1; + uint32_t reserved_field_1_2; + } reserved_field_1; // Obsolete + + struct + { + uint32_t offset; + union + { + uint64_t value64; + uint32_t value32; + uint16_t value16; + uint8_t value8; + } value; + union + { + uint32_t sizeToMap; + } memoryMappedIoWindow; + } io; + + struct + { + uint32_t reserved_field_2_0; + uint32_t reserved_field_2_1; + } reserved_field_2; + + struct + { + uint32_t reserved_field_3_0; + } reserved_field_3; + + union + { + struct + { + uint32_t reserved_field_4_0; + int32_t reserved_field_4_1; + } wait; + } reserved_field_4; + + } params; + + uint32_t inbufByteLen; + + union + { + const void* pointer; + uint64_t _64BitField; + } inbuf; +}; + +static inline void init_syncop_in_params(nirio_syncop_in_params_t& param, const void* const buf, const uint32_t len) +{ + param.inbuf._64BitField = 0; + param.inbuf.pointer = buf; + param.inbufByteLen = len; +} + + +struct nirio_syncop_out_params_t +{ + union + { + struct + { + uint32_t value; + } attribute32; + + struct + { + uint64_t value; + } attribute64; + + union + { + struct + { + uint32_t reserved_field_0_0; + } enable; + } reserved_field_0; + + struct + { + union + { + struct + { + uint32_t actualDepth; + uint32_t actualSize; + } config; + struct + { + uint32_t numberRead; + uint32_t numberRemaining; + } read; + struct + { + uint32_t numberRemaining; + } write; + struct + { + union + { + void* pointer; + uint64_t _64BitField; + } elements; + } wait; + } op; + } fifo; + + struct + { + union + { + union + { + uint64_t value64; + uint32_t value32; + uint16_t value16; + uint8_t value8; + } value; + union + { + void* memoryMappedAddress; + uint64_t _64BitField; + } memoryMappedIoWindow; + union + { + uint32_t size; + } memoryMappedIoWindowSize; + }; + } io; + + uint32_t stringLength; + + struct + { + uint32_t reserved_field_1_0; + } reserved_field_1; + + } params; + + uint32_t outbufByteLen; + + union + { + void* pointer; + uint64_t _64BitField; + } outbuf; +}; + +static inline void init_syncop_out_params(nirio_syncop_out_params_t& param, void* buf, uint32_t len) +{ + param.outbuf._64BitField = 0; + param.outbuf.pointer = buf; + param.outbufByteLen = len; +} + + + +//Device handle definition +#if defined(UHD_PLATFORM_LINUX) + typedef int rio_dev_handle_t; +#elif defined(UHD_PLATFORM_WIN32) + typedef HANDLE rio_dev_handle_t; +#elif defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + typedef io_connect_t rio_dev_handle_t; +#else + #error OS not supported by nirio_driver_iface. +#endif +static const rio_dev_handle_t INVALID_RIO_HANDLE = ((rio_dev_handle_t)-1); + +//Memory mapping container definition +#if defined(UHD_PLATFORM_LINUX) + struct rio_mmap_t { + rio_mmap_t() : addr(NULL), size(0) {} + void *addr; + size_t size; + + bool is_null() { return (size == 0 || addr == NULL); } + }; +#elif defined(UHD_PLATFORM_WIN32) + enum access_mode_t { + ACCESS_MODE_READ, + ACCESS_MODE_WRITE + }; + + struct rio_mmap_params_t + { + uint64_t mapped_va_ptr; + uint64_t map_ready_event_handle; + uint32_t size; + uint16_t memoryType; + uint8_t access_mode; + }; + + struct rio_mmap_threadargs_t + { + rio_dev_handle_t device_handle; + rio_mmap_params_t params; + nirio_status status; + }; + + struct rio_mmap_t + { + rio_mmap_t() : addr(NULL) {} + void *addr; + HANDLE map_thread_handle; + rio_mmap_threadargs_t map_thread_args; + + bool is_null() { return addr == NULL; } + }; +#elif defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + struct rio_mmap_t { + rio_mmap_t() : addr(NULL) {} + void *addr; + + bool is_null() { return addr == NULL; } + }; +#else + #error OS not supported by nirio_driver_iface. +#endif + + nirio_status rio_open( + const std::string& device_path, + rio_dev_handle_t& device_handle); + + void rio_close( + rio_dev_handle_t& device_handle); + + bool rio_isopen( + rio_dev_handle_t device_handle); + + nirio_status rio_ioctl( + rio_dev_handle_t device_handle, + uint32_t ioctl_code, + const void *write_buf, + size_t write_buf_len, + void *read_buf, + size_t read_buf_len); + + nirio_status rio_mmap( + rio_dev_handle_t device_handle, + uint16_t memory_type, + size_t size, + bool writable, + rio_mmap_t &map); + + nirio_status rio_munmap( + rio_mmap_t &map); +} + +#endif diff --git a/host/include/uhd/transport/nirio/nirio_err_template.h b/host/include/uhd/transport/nirio/nirio_err_template.h new file mode 100644 index 000000000..9235c0384 --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_err_template.h @@ -0,0 +1,66 @@ +// +// 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/>. +// + +/* + * NIRIO_ERR_INFO is undefined on purpose. + * This header is designed to be included with a custom definition of NIRIO_ERR_INFO + * To "generate" code in multiple files. This prevents duplication of code. + */ + +NIRIO_ERR_INFO(NiRio_Status_Success, 0, "No errors or warnings.") +NIRIO_ERR_INFO(NiRio_Status_FifoTimeout, -50400, "The timeout expired before the FIFO operation could complete.") +NIRIO_ERR_INFO(NiRio_Status_MemoryFull, -52000, "A memory allocation failed. Try again after rebooting.") +NIRIO_ERR_INFO(NiRio_Status_SoftwareFault, -52003, "An unexpected software error occurred.") +NIRIO_ERR_INFO(NiRio_Status_InvalidParameter, -52005, "A parameter to a function was not valid. This could be a NULL pointer, a bad value, etc.") +NIRIO_ERR_INFO(NiRio_Status_ResourceNotFound, -52006, "A required resource was not found. The NiFpga.* library, the RIO resource, or some other resource may be missing.") +NIRIO_ERR_INFO(NiRio_Status_ResourceNotInitialized, -52010, "A required resource was not properly initialized. This could occur if NiFpga_Initialize was not called or a required NiFpga_IrqContext was not reserved.") +NIRIO_ERR_INFO(NiRio_Status_FpgaAlreadyRunning, -61003, "The FPGA is already running.") +NIRIO_ERR_INFO(NiRio_Status_DeviceTypeMismatch, -61024, "The bitfile was not compiled for the specified resource's device type.") +NIRIO_ERR_INFO(NiRio_Status_CommunicationTimeout, -61046, "An error was detected in the communication between the host computer and the USRP device. This could be due to a hardware failure on the bus.") +NIRIO_ERR_INFO(NiRio_Status_IrqTimeout, -61060, "The timeout expired before any of the IRQs were asserted.") +NIRIO_ERR_INFO(NiRio_Status_CorruptBitfile, -61070, "The LVBITX configuration bitstream seems to be corrupt.") +NIRIO_ERR_INFO(NiRio_Status_BadDepth, -61072, "The requested FIFO depth is invalid. It is either 0 or an amount not supported by the hardware.") +NIRIO_ERR_INFO(NiRio_Status_BadReadWriteCount, -61073, "The number of FIFO elements is invalid. Either the number is greater than the depth of the host memory DMA FIFO, or more elements were requested for release than had been acquired.") +NIRIO_ERR_INFO(NiRio_Status_ClockLostLock, -61083, "A hardware clocking error occurred.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusy, -61141, "The operation could not be performed because the FPGA is busy.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusyFpgaInterfaceCApi, -61200, "The operation could not be performed because the FPGA is busy.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusyScanInterface, -61201, "The operation could not be performed because the chassis is in Scan Interface programming mode.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusyFpgaInterface, -61202, "The operation could not be performed because the FPGA is busy operating in FPGA Interface mode. Stop all activities on the FPGA before requesting this operation.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusyInteractive, -61203, "The operation could not be performed because the FPGA is busy operating in FPGA Interactive mode. Stop all activities on the FPGA before requesting this operation.") +NIRIO_ERR_INFO(NiRio_Status_FpgaBusyEmulation, -61204, "The operation could not be performed because the FPGA is busy operating in FPGA Emulation mode. Stop all activities on the FPGA before requesting this operation.") +NIRIO_ERR_INFO(NiRio_Status_GatedClockHandshakingViolation, -61216, "A gated clock has violated the handshaking protocol.") +NIRIO_ERR_INFO(NiRio_Status_RegionsOutstandingForSession, -61217, "A session cannot be closed, reset, nor can a bitfile be downloaded while DMA FIFO region references are still outstanding for the specified session.") +NIRIO_ERR_INFO(NiRio_Status_ElementsNotPermissibleToBeAcquired, -61219, "There are currently fewer unacquired elements left in the FIFO than are being requested. Release some acquired elements before acquiring more elements.") +NIRIO_ERR_INFO(NiRio_Status_InternalError, -61499, "An unexpected internal error occurred.") +NIRIO_ERR_INFO(NiRio_Status_AccessDenied, -63033, "Access to the local or remote system was denied.") +NIRIO_ERR_INFO(NiRio_Status_RpcConnectionError, -63040, "A connection could not be established to the specified remote device manager. Ensure that the devices are on, that NI-USRPRIO software is installed, and that the USRPRIO server is running and properly configured.") +NIRIO_ERR_INFO(NiRio_Status_RpcOperationError, -63042, "A fault on the network caused the RPC operation to fail.") +NIRIO_ERR_INFO(NiRio_Status_RpcSessionError, -63043, "The RPC session to the remote device manager is invalid. Ensure that the device is connected and try restarting the server.") +NIRIO_ERR_INFO(NiRio_Status_FifoReserved, -63082, "The operation could not complete because another session is accessing the FIFO. Close the other session and retry.") +NIRIO_ERR_INFO(NiRio_Status_FifoElementsCurrentlyAcquired, -63083, "A Configure FIFO, Stop FIFO, Read FIFO, or Write FIFO function was called while the host had acquired elements of the FIFO. Release all acquired elements before configuring, stopping, reading, or writing.") +NIRIO_ERR_INFO(NiRio_Status_MisalignedAccess, -63084, "A function was called using a misaligned address. The address must be a multiple of the size of the datatype.") +NIRIO_ERR_INFO(NiRio_Status_BitfileReadError, -63101, "A valid .lvbitx bitfile is required. If you are using a valid .lvbitx bitfile, the bitfile may not be compatible with the software you are using.") +NIRIO_ERR_INFO(NiRio_Status_SignatureMismatch, -63106, "The specified signature does not match the signature of the bitfile. If the bitfile has been recompiled, regenerate the C API and rebuild the application.") +NIRIO_ERR_INFO(NiRio_Status_IncompatibleBitfile, -63107, "The bitfile you are trying to use is not compatible with the version of NI-RIO installed on the target and/or the host.") +NIRIO_ERR_INFO(NiRio_Status_InvalidResourceName, -63192, "Either the supplied resource name is invalid as a RIO resource name, or the device was not found.") +NIRIO_ERR_INFO(NiRio_Status_FeatureNotSupported, -63193, "The requested feature is not supported.") +NIRIO_ERR_INFO(NiRio_Status_VersionMismatch, -63194, "Software version mismatch.") +NIRIO_ERR_INFO(NiRio_Status_InvalidSession, -63195, "The session is invalid or has been closed.") +NIRIO_ERR_INFO(NiRio_Status_OutOfHandles, -63198, "The maximum number of open FPGA sessions has been reached. Close some open sessions.") +NIRIO_ERR_INFO(NiRio_Status_DeviceLocked, -63031, "The operation is not allowed because another session in a different process is accessing the device. Close all other sessions and retry.") + + diff --git a/host/include/uhd/transport/nirio/nirio_fifo.h b/host/include/uhd/transport/nirio/nirio_fifo.h new file mode 100644 index 000000000..f7abb396f --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_fifo.h @@ -0,0 +1,132 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIRIO_FIFO_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_FIFO_H + +#include <uhd/transport/nirio/nirio_driver_iface.h> +#include <uhd/transport/nirio/niriok_proxy.h> +#include <uhd/transport/nirio/status.h> +#include <boost/noncopyable.hpp> +#include <boost/smart_ptr.hpp> +#include <string> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread/thread.hpp> + +namespace uhd { namespace niusrprio { + +enum fifo_direction_t { + INPUT_FIFO, + OUTPUT_FIFO +}; + +enum nirio_scalar_t { + SCALAR_I8 = 1UL, + SCALAR_I16 = 2UL, + SCALAR_I32 = 3UL, + SCALAR_I64 = 4UL, + SCALAR_U8 = 5UL, + SCALAR_U16 = 6UL, + SCALAR_U32 = 7UL, + SCALAR_U64 = 8UL +}; + +struct datatype_info_t { + datatype_info_t(nirio_scalar_t t, uint32_t w):scalar_type(t),width(w) {} + nirio_scalar_t scalar_type; + uint32_t width; +}; + +template <typename data_t> +class nirio_fifo : private boost::noncopyable +{ +public: + typedef boost::shared_ptr< nirio_fifo<data_t> > sptr; + + nirio_fifo( + niriok_proxy& riok_proxy, + fifo_direction_t direction, + const std::string& name, + uint32_t fifo_instance); + virtual ~nirio_fifo(); + + nirio_status initialize( + const size_t requested_depth, + size_t& actual_depth, + size_t& actual_size); + + void finalize(); + + inline const std::string& get_name() const { return _name; } + inline uint32_t get_channel() const { return _fifo_channel; } + inline uint32_t get_direction() const { return _fifo_direction; } + inline uint32_t get_scalar_type() const { return _datatype_info.scalar_type; } + + nirio_status start(); + + nirio_status stop(); + + nirio_status acquire( + data_t*& elements, + const size_t elements_requested, + const uint32_t timeout, + size_t& elements_acquired, + size_t& elements_remaining); + + nirio_status release(const size_t elements); + + nirio_status read( + data_t* buf, + const uint32_t num_elements, + uint32_t timeout, + uint32_t& num_read, + uint32_t& num_remaining); + + nirio_status write( + const data_t* buf, + const uint32_t num_elements, + uint32_t timeout, + uint32_t& num_remaining); + +private: //Methods + bool _is_initialized(); + datatype_info_t _get_datatype_info(); + nirio_status _get_transfer_count(uint64_t& transfer_count); + nirio_status _ensure_transfer_completed(uint32_t timeout_ms); + +private: //Members + std::string _name; + fifo_direction_t _fifo_direction; + uint32_t _fifo_channel; + datatype_info_t _datatype_info; + size_t _acquired_pending; + nirio_driver_iface::rio_mmap_t _mem_map; + boost::recursive_mutex _mutex; + niriok_proxy* _riok_proxy_ptr; + + uint64_t _expected_xfer_count; + uint32_t _dma_base_addr; + + static const uint32_t FIFO_LOCK_TIMEOUT_IN_MS = 5000; +}; + +#include "nirio_fifo.ipp" + +}} + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_FIFO_H */ diff --git a/host/include/uhd/transport/nirio/nirio_fifo.ipp b/host/include/uhd/transport/nirio/nirio_fifo.ipp new file mode 100644 index 000000000..80a0c2a89 --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_fifo.ipp @@ -0,0 +1,378 @@ +// +// 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/>. +// + +#ifdef __clang__ + #pragma GCC diagnostic push ignored "-Wmissing-field-initializers" +#elif defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +template <typename data_t> +nirio_fifo<data_t>::nirio_fifo( + niriok_proxy& riok_proxy, + fifo_direction_t direction, + const std::string& name, + uint32_t fifo_instance) : + _name(name), + _fifo_direction(direction), + _fifo_channel(fifo_instance), + _datatype_info(_get_datatype_info()), + _acquired_pending(0), + _mem_map(), + _riok_proxy_ptr(&riok_proxy), + _expected_xfer_count(0), + _dma_base_addr(0) +{ + nirio_status status = 0; + nirio_status_chain(_riok_proxy_ptr->set_attribute(ADDRESS_SPACE, BUS_INTERFACE), status); + uint32_t base_addr, addr_space_word; + nirio_status_chain(_riok_proxy_ptr->peek(0x1C, base_addr), status); + nirio_status_chain(_riok_proxy_ptr->peek(0xC, addr_space_word), status); + _dma_base_addr = base_addr + (_fifo_channel * (1<<((addr_space_word>>16)&0xF))); +} + +template <typename data_t> +nirio_fifo<data_t>::~nirio_fifo() +{ + finalize(); +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::initialize( + const size_t requested_depth, + size_t& actual_depth, + size_t& actual_size) +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + //Forcefully stop the fifo if it is running + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::STOP; + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::CONFIGURE; + + in.params.fifo.channel = _fifo_channel; + in.params.fifo.op.config.requestedDepth = static_cast<uint32_t>(requested_depth); + in.params.fifo.op.config.requiresActuals = 1; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + if (nirio_status_fatal(status)) return status; + + actual_depth = out.params.fifo.op.config.actualDepth; + actual_size = out.params.fifo.op.config.actualSize; + + status = _riok_proxy_ptr->map_fifo_memory(_fifo_channel, actual_size, _mem_map); + return status; +} + +template <typename data_t> +void nirio_fifo<data_t>::finalize() +{ + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + if (!_mem_map.is_null()) { + stop(); + _riok_proxy_ptr->unmap_fifo_memory(_mem_map); + } +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::start() +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::START; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + if (nirio_status_not_fatal(status)) { + _acquired_pending = 0; + _expected_xfer_count = 0; + } + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::stop() +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + if (_acquired_pending > 0) release(_acquired_pending); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::STOP; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::acquire( + data_t*& elements, + const size_t elements_requested, + const uint32_t timeout, + size_t& elements_acquired, + size_t& elements_remaining) +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr || _mem_map.is_null()) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + uint32_t stuffed[2]; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + init_syncop_out_params(out, stuffed, sizeof(stuffed)); + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::WAIT; + + in.params.fifo.channel = _fifo_channel; + in.params.fifo.op.wait.elementsRequested = static_cast<uint32_t>(elements_requested); + in.params.fifo.op.wait.scalarType = static_cast<uint32_t>(_datatype_info.scalar_type); + in.params.fifo.op.wait.bitWidth = _datatype_info.width * 8; + in.params.fifo.op.wait.output = _fifo_direction == OUTPUT_FIFO; + in.params.fifo.op.wait.timeout = timeout; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + + if (nirio_status_not_fatal(status)) { + elements = static_cast<data_t*>(out.params.fifo.op.wait.elements.pointer); + elements_acquired = stuffed[0]; + elements_remaining = stuffed[1]; + _acquired_pending = elements_acquired; + + if (UHD_NIRIO_RX_FIFO_XFER_CHECK_EN && + _riok_proxy_ptr->get_rio_quirks().rx_fifo_xfer_check_en() && + get_direction() == INPUT_FIFO + ) { + _expected_xfer_count += static_cast<uint64_t>(elements_requested * sizeof(data_t)); + status = _ensure_transfer_completed(timeout); + } + } + + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::release(const size_t elements) +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::GRANT; + + in.params.fifo.channel = _fifo_channel; + in.params.fifo.op.grant.elements = static_cast<uint32_t>(elements); + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + _acquired_pending = 0; + + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::read( + data_t* buf, + const uint32_t num_elements, + uint32_t timeout, + uint32_t& num_read, + uint32_t& num_remaining) +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + init_syncop_out_params(out, buf, num_elements * _datatype_info.width); + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::READ; + + in.params.fifo.channel = _fifo_channel; + in.params.fifo.op.readWithDataType.timeout = timeout; + in.params.fifo.op.readWithDataType.scalarType = static_cast<uint32_t>(_datatype_info.scalar_type); + in.params.fifo.op.readWithDataType.bitWidth = _datatype_info.width * 8; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + + if (nirio_status_not_fatal(status) || status == NiRio_Status_FifoTimeout) { + num_read = out.params.fifo.op.read.numberRead; + num_remaining = out.params.fifo.op.read.numberRemaining; + } + + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::write( + const data_t* buf, + const uint32_t num_elements, + uint32_t timeout, + uint32_t& num_remaining) +{ + nirio_status status = NiRio_Status_Success; + if (!_riok_proxy_ptr) return NiRio_Status_ResourceNotInitialized; + + boost::unique_lock<boost::recursive_mutex> lock(_mutex); + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + init_syncop_in_params(in, buf, num_elements * _datatype_info.width); + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::FIFO; + in.subfunction = nirio_driver_iface::NIRIO_FIFO::WRITE; + + in.params.fifo.channel = _fifo_channel; + in.params.fifo.op.writeWithDataType.timeout = timeout; + in.params.fifo.op.readWithDataType.scalarType = static_cast<uint32_t>(_datatype_info.scalar_type); + in.params.fifo.op.readWithDataType.bitWidth = _datatype_info.width * 8; + + status = _riok_proxy_ptr->sync_operation(&in, sizeof(in), &out, sizeof(out)); + + if (nirio_status_not_fatal(status) || status == NiRio_Status_FifoTimeout) { + num_remaining = out.params.fifo.op.write.numberRemaining; + } + + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::_get_transfer_count(uint64_t& transfer_count) +{ + //_riok_proxy_ptr must be valid and _mutex must be locked + + nirio_status status = NiRio_Status_Success; + uint32_t lower_half = 0, upper_half = 0; + nirio_status_chain(_riok_proxy_ptr->peek(_dma_base_addr + 0xA8, lower_half), status); //Latches both halves + nirio_status_chain(_riok_proxy_ptr->peek(_dma_base_addr + 0xAC, upper_half), status); + + if (nirio_status_not_fatal(status)) { + transfer_count = lower_half | (((uint64_t)upper_half) << 32); + } + return status; +} + +template <typename data_t> +nirio_status nirio_fifo<data_t>::_ensure_transfer_completed(uint32_t timeout_ms) +{ + //_riok_proxy_ptr must be valid and _mutex must be locked + + static const size_t MIN_TIMEOUT_IN_US = 2000; + + nirio_status status = NiRio_Status_Success; + uint64_t actual_xfer_count = 0; + nirio_status_chain(_get_transfer_count(actual_xfer_count), status); + + //We count the elapsed time using a simple counter instead of the high + //resolution timebase for efficieny reasons. The call to fetch the time + //requires a user-kernel transition which has a large overhead compared + //to a simple mem read. As a tradeoff, we deal with a less precise timeout. + size_t approx_us_elapsed = 0; + while ( + nirio_status_not_fatal(status) && + (_expected_xfer_count > actual_xfer_count) && + approx_us_elapsed++ < std::max<size_t>(MIN_TIMEOUT_IN_US, timeout_ms * 1000) + ) { + boost::this_thread::sleep(boost::posix_time::microseconds(1)); + nirio_status_chain(_get_transfer_count(actual_xfer_count), status); + } + + if (_expected_xfer_count > actual_xfer_count) { + nirio_status_chain(NiRio_Status_CommunicationTimeout, status); + } + + return status; +} + +template <> +inline datatype_info_t nirio_fifo<int8_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_I8, 1); +} + +template <> +inline datatype_info_t nirio_fifo<int16_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_I16, 2); +} + +template <> +inline datatype_info_t nirio_fifo<int32_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_I32, 4); +} + +template <> +inline datatype_info_t nirio_fifo<int64_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_I64, 8); +} + +template <> +inline datatype_info_t nirio_fifo<uint8_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_U8, 1); +} + +template <> +inline datatype_info_t nirio_fifo<uint16_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_U16, 2); +} + +template <> +inline datatype_info_t nirio_fifo<uint32_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_U32, 4); +} + +template <> +inline datatype_info_t nirio_fifo<uint64_t>::_get_datatype_info() +{ + return datatype_info_t(SCALAR_U64, 8); +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/host/include/uhd/transport/nirio/nirio_quirks.h b/host/include/uhd/transport/nirio/nirio_quirks.h new file mode 100644 index 000000000..326eeeb8c --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_quirks.h @@ -0,0 +1,78 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIRIO_QUIRKS_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_QUIRKS_H + +#include <set> +#include <uhd/utils/log.hpp> + +//Quirk#1: We need to verify RX zero-copy data transfers from the RIO +// driver if we are in full duplex mode. +// This option allows disabling this quirk by compiling it out. +#define UHD_NIRIO_RX_FIFO_XFER_CHECK_EN 1 + +namespace uhd { namespace niusrprio { + +class nirio_quirks { +public: + nirio_quirks() : _tx_stream_count(0) { + } + + UHD_INLINE void register_tx_streams(const uint32_t tx_stream_indices[]) { + for (size_t i = 0; i < sizeof(tx_stream_indices)/sizeof(*tx_stream_indices); i++) { + _tx_stream_fifo_indices.insert(tx_stream_indices[i]); + } + } + + UHD_INLINE void add_tx_fifo(uint32_t index) { + if (_tx_stream_fifo_indices.find(index) != _tx_stream_fifo_indices.end()) { + if (_tx_stream_count == 0) { + UHD_LOG << "NI-RIO RX FIFO Transfer Check Quirk Enabled."; + } + _tx_stream_count++; + } + } + + UHD_INLINE void remove_tx_fifo(uint32_t index) { + if (_tx_stream_fifo_indices.find(index) != _tx_stream_fifo_indices.end()) { + _tx_stream_count--; + if (_tx_stream_count == 0) { + UHD_LOG << "NI-RIO RX FIFO Transfer Check Quirk Disabled."; + } + } + } + + //Quirk#1: We need to verify RX zero-copy data transfers from the RIO + // driver if we are in full duplex mode. + // This function returns true if the quirk is enabled for the + // current uhd_device configuration (dynamic) + UHD_INLINE bool rx_fifo_xfer_check_en() const { + //This function must be as fast as possible because it is in a high + //throughput data path + return _tx_stream_count > 0; + } + +private: + std::set<uint32_t> _tx_stream_fifo_indices; + size_t _tx_stream_count; +}; + +}} + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_QUIRKS_H */ diff --git a/host/include/uhd/transport/nirio/nirio_resource_manager.h b/host/include/uhd/transport/nirio/nirio_resource_manager.h new file mode 100644 index 000000000..a10b3d532 --- /dev/null +++ b/host/include/uhd/transport/nirio/nirio_resource_manager.h @@ -0,0 +1,145 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIRIO_RESOURCE_MANAGER_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_RESOURCE_MANAGER_H + +#include <uhd/transport/nirio/nirio_fifo.h> +#include <uhd/transport/nirio/niriok_proxy.h> +#include <vector> +#include <map> +#include <string> + +namespace uhd { namespace niusrprio +{ +enum register_direction_t { + CONTROL, + INDICATOR +}; + +struct nirio_register_info_t { + nirio_register_info_t( + uint32_t arg_offset, + const char* arg_name, + register_direction_t arg_direction) : + offset(arg_offset), + name(arg_name), + direction(arg_direction) + {} + + uint32_t offset; + std::string name; + register_direction_t direction; +}; + +typedef std::vector<nirio_register_info_t> nirio_register_info_vtr; + +struct nirio_fifo_info_t { + nirio_fifo_info_t( + uint32_t arg_channel, + const char* arg_name, + fifo_direction_t arg_direction, + uint32_t arg_base_addr, + uint32_t arg_depth, + nirio_scalar_t arg_scalar_type, + uint32_t arg_width, + uint32_t arg_version) : + channel(arg_channel), + name(arg_name), + direction(arg_direction), + base_addr(arg_base_addr), + depth(arg_depth), + scalar_type(arg_scalar_type), + width(arg_width), + version(arg_version) + {} + + uint32_t channel; + std::string name; + fifo_direction_t direction; + uint32_t base_addr; + uint32_t depth; + nirio_scalar_t scalar_type; + uint32_t width; + uint32_t version; +}; + +typedef std::vector<nirio_fifo_info_t> nirio_fifo_info_vtr; + + +class nirio_resource_manager +{ +public: + nirio_resource_manager(niriok_proxy& proxy); + virtual ~nirio_resource_manager(); + + nirio_status initialize(const nirio_register_info_vtr& reg_info_vtr, const nirio_fifo_info_vtr& fifo_info_vtr); + void finalize(); + + nirio_status get_register_offset(const char* register_name, uint32_t& offset); + + template<typename data_t> + nirio_status create_tx_fifo(const char* fifo_name, boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + nirio_fifo_info_t* fifo_info_ptr = _lookup_fifo_info(fifo_name); + if (fifo_info_ptr) { + fifo.reset(new nirio_fifo<data_t>(_kernel_proxy, OUTPUT_FIFO, fifo_info_ptr->name, fifo_info_ptr->channel)); + } else { + return NiRio_Status_ResourceNotFound; + } + + if (fifo->get_channel() != fifo_info_ptr->channel) return NiRio_Status_InvalidParameter; + if (fifo->get_scalar_type() != fifo_info_ptr->scalar_type) return NiRio_Status_InvalidParameter; + + return NiRio_Status_Success; + } + + template<typename data_t> + nirio_status create_rx_fifo(const char* fifo_name, boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + nirio_fifo_info_t* fifo_info_ptr = _lookup_fifo_info(fifo_name); + if (fifo_info_ptr) { + fifo.reset(new nirio_fifo<data_t>(_kernel_proxy, INPUT_FIFO, fifo_info_ptr->name, fifo_info_ptr->channel)); + } else { + return NiRio_Status_ResourceNotFound; + } + + if (fifo->get_channel() != fifo_info_ptr->channel) return NiRio_Status_InvalidParameter; + if (fifo->get_scalar_type() != fifo_info_ptr->scalar_type) return NiRio_Status_InvalidParameter; + + return NiRio_Status_Success; + } + +private: + nirio_resource_manager (const nirio_resource_manager&); + nirio_resource_manager& operator = (const nirio_resource_manager&); + + typedef std::map<const std::string, nirio_fifo_info_t> fifo_info_map_t; + typedef std::map<const std::string, nirio_register_info_t> register_info_map_t; + + nirio_status _add_fifo_resource(const nirio_fifo_info_t& fifo_info); + nirio_status _set_driver_config(); + nirio_fifo_info_t* _lookup_fifo_info(const char* fifo_name); + + niriok_proxy& _kernel_proxy; + fifo_info_map_t _fifo_info_map; + register_info_map_t _reg_info_map; +}; + +}} +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_RESOURCE_MANAGER_H */ diff --git a/host/include/uhd/transport/nirio/niriok_proxy.h b/host/include/uhd/transport/nirio/niriok_proxy.h new file mode 100644 index 000000000..a6b6183d1 --- /dev/null +++ b/host/include/uhd/transport/nirio/niriok_proxy.h @@ -0,0 +1,160 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIRIO_INTERFACE_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_INTERFACE_H + +#include <boost/smart_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <uhd/transport/nirio/nirio_driver_iface.h> +#include <uhd/transport/nirio/nirio_quirks.h> + +namespace uhd { namespace niusrprio +{ + enum nirio_version_t { CURRENT, OLDEST_COMPATIBLE }; + + enum nirio_device_attr_32_t { + INTERFACE_NUMBER = 1UL, + PRODUCT_NUMBER = 2UL, + VENDOR_NUMBER = 3UL, + SERIAL_NUMBER = 4UL, + BUS_NUMBER = 10UL, + DEVICE_NUMBER = 11UL, + FUNCTION_NUMBER = 12UL, + CURRENT_VERSION = 14UL, + OLDEST_COMPATIBLE_VERSION = 15UL, + ADDRESS_SPACE = 25UL, + IS_FPGA_PROGRAMMED = 48UL, + DEFAULT_FPGA_SIGNATURE_OFFSET = 53UL, + DEFAULT_FPGA_RESET_OFFSET = 54UL, + DEFAULT_FPGA_RESET_SIZE = 55UL, + DEFAULT_FPGA_CONTROL_OFFSET = 56UL, + DEFAULT_FPGA_INTERRUPT_OFFSET = 57UL, + }; + + enum nirio_device_attr_str_t { + PRODUCT_NAME = 0UL, + FPGA_TARGET_CLASS = 6UL, + SAVED_BITFILE = 7UL, + }; + + enum nirio_addr_space_t { + INVALID = 0, + BUS_INTERFACE = 1, + FPGA = 2, + BAR_WINDOW = 3, + }; + + + class UHD_API niriok_proxy : public boost::noncopyable { + public: + typedef boost::shared_ptr<niriok_proxy> sptr; + + niriok_proxy(); + virtual ~niriok_proxy(); + + //File operations + nirio_status open(const std::string& interface_path); + void close(void); + + nirio_status reset(); + + inline uint32_t get_interface_num() { return _interface_num; } + + nirio_status get_cached_session( + uint32_t& session); + + nirio_status get_version( + nirio_version_t type, + uint32_t& major, + uint32_t& upgrade, + uint32_t& maintenance, + char& phase, + uint32_t& build); + + nirio_status sync_operation( + const void *writeBuffer, + size_t writeBufferLength, + void *readBuffer, + size_t readBufferLength); + + nirio_status get_attribute( + const nirio_device_attr_32_t attribute, + uint32_t& attrValue); + + nirio_status get_attribute( + const nirio_device_attr_str_t attribute, + char* buf, + const uint32_t bufLen, + uint32_t& stringLen); + + nirio_status set_attribute( + const nirio_device_attr_32_t attribute, + const uint32_t value); + + nirio_status set_attribute( + const nirio_device_attr_str_t attribute, + const char* const buffer); + + nirio_status peek(uint32_t offset, uint32_t& value); + + nirio_status peek(uint32_t offset, uint64_t& value); + + nirio_status poke(uint32_t offset, const uint32_t& value); + + nirio_status poke(uint32_t offset, const uint64_t& value); + + nirio_status map_fifo_memory( + uint32_t fifo_instance, + size_t size, + nirio_driver_iface::rio_mmap_t& map); + + nirio_status unmap_fifo_memory( + nirio_driver_iface::rio_mmap_t& map); + + nirio_quirks& get_rio_quirks() { + return _rio_quirks; + } + + private: //Members + nirio_driver_iface::rio_dev_handle_t _device_handle; + uint32_t _interface_num; + nirio_quirks _rio_quirks; + }; + + class niriok_scoped_addr_space : public boost::noncopyable { + public: + explicit niriok_scoped_addr_space(niriok_proxy& proxy, nirio_addr_space_t addr_space, nirio_status& status) : + driver_proxy(proxy) + { + cache_status = driver_proxy.get_attribute(ADDRESS_SPACE, cached_addr_space); + nirio_status_chain(driver_proxy.set_attribute(ADDRESS_SPACE, addr_space), status); + } + + ~niriok_scoped_addr_space() { + if (nirio_status_not_fatal(cache_status)) + driver_proxy.set_attribute(ADDRESS_SPACE, cached_addr_space); + } + + private: + niriok_proxy& driver_proxy; + uint32_t cached_addr_space; + nirio_status cache_status; + }; +}} + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIRIO_INTERFACE_H */ diff --git a/host/include/uhd/transport/nirio/niusrprio_session.h b/host/include/uhd/transport/nirio/niusrprio_session.h new file mode 100644 index 000000000..c9a61ae76 --- /dev/null +++ b/host/include/uhd/transport/nirio/niusrprio_session.h @@ -0,0 +1,125 @@ +// +// 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_UHD_TRANSPORT_NIRIO_NIUSRPRIO_SESSION_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_NIUSRPRIO_SESSION_H + +#include <uhd/transport/nirio/rpc/usrprio_rpc_client.hpp> +#include <uhd/transport/nirio/niriok_proxy.h> +#include <uhd/transport/nirio/nirio_resource_manager.h> +#include <uhd/transport/nirio/nifpga_lvbitx.h> +#include <boost/noncopyable.hpp> +#include <boost/smart_ptr.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <string> + +namespace uhd { namespace niusrprio { + +class UHD_API niusrprio_session : private boost::noncopyable +{ +public: + typedef boost::shared_ptr<niusrprio_session> sptr; + typedef uhd::usrprio_rpc::usrprio_device_info device_info; + typedef uhd::usrprio_rpc::usrprio_device_info_vtr device_info_vtr; + + static nirio_status enumerate( + const std::string& rpc_port_name, + device_info_vtr& device_info_vtr); + + niusrprio_session( + const std::string& resource_name, + const std::string& port_name); + virtual ~niusrprio_session(); + + nirio_status open( + nifpga_lvbitx::sptr lvbitx, + bool force_download = false); + + void close(bool skip_reset = false); + + nirio_status reset(); + + template<typename data_t> + nirio_status create_tx_fifo( + const char* fifo_name, + boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + if (!_session_open) return NiRio_Status_ResourceNotInitialized; + return _resource_manager.create_tx_fifo(fifo_name, fifo); + } + + template<typename data_t> + nirio_status create_tx_fifo( + uint32_t fifo_instance, + boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + if ((size_t)fifo_instance >= _lvbitx->get_output_fifo_count()) return NiRio_Status_InvalidParameter; + return create_tx_fifo(_lvbitx->get_output_fifo_names()[fifo_instance], fifo); + } + + template<typename data_t> + nirio_status create_rx_fifo( + const char* fifo_name, + boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + if (!_session_open) return NiRio_Status_ResourceNotInitialized; + return _resource_manager.create_rx_fifo(fifo_name, fifo); + } + + template<typename data_t> + nirio_status create_rx_fifo( + uint32_t fifo_instance, + boost::shared_ptr< nirio_fifo<data_t> >& fifo) + { + if ((size_t)fifo_instance >= _lvbitx->get_input_fifo_count()) return NiRio_Status_InvalidParameter; + return create_rx_fifo(_lvbitx->get_input_fifo_names()[fifo_instance], fifo); + } + + niriok_proxy& get_kernel_proxy() { + return _riok_proxy; + } + + nirio_status download_bitstream_to_flash(const std::string& bitstream_path); + + //Static + static niriok_proxy::sptr create_kernel_proxy( + const std::string& resource_name, + const std::string& rpc_port_name); + +private: + nirio_status _verify_signature(); + std::string _read_bitstream_checksum(); + nirio_status _write_bitstream_checksum(const std::string& checksum); + nirio_status _wait_for_device_available(); + + std::string _resource_name; + nifpga_lvbitx::sptr _lvbitx; + std::string _interface_path; + bool _session_open; + niriok_proxy _riok_proxy; + nirio_resource_manager _resource_manager; + usrprio_rpc::usrprio_rpc_client _rpc_client; + boost::recursive_mutex _session_mutex; + + static const uint32_t SESSION_LOCK_TIMEOUT_IN_MS = 3000; + static const uint32_t SESSION_LOCK_RETRY_INT_IN_MS = 500; +}; + +}} + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_NIUSRPRIO_SESSION_H */ diff --git a/host/include/uhd/transport/nirio/rpc/rpc_client.hpp b/host/include/uhd/transport/nirio/rpc/rpc_client.hpp new file mode 100644 index 000000000..f90034e37 --- /dev/null +++ b/host/include/uhd/transport/nirio/rpc/rpc_client.hpp @@ -0,0 +1,94 @@ +// +// 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_RPC_CLIENT_HPP +#define INCLUDED_RPC_CLIENT_HPP + +#include <boost/asio.hpp> +#include <boost/smart_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <boost/thread/condition_variable.hpp> +#include "rpc_common.hpp" +#include <uhd/utils/log.hpp> + +namespace uhd { namespace usrprio_rpc { + +class rpc_client : private boost::noncopyable +{ +public: + static const boost::uint32_t CURRENT_VERSION = 1; + static const boost::uint32_t OLDEST_COMPATIBLE_VERSION = 1; + + rpc_client( + const std::string& server, + const std::string& port, + boost::uint32_t process_id, + boost::uint32_t host_id); + ~rpc_client(); + + const boost::system::error_code& call( + func_id_t func_id, + const func_args_writer_t& in_args, + func_args_reader_t &out_args, + boost::posix_time::milliseconds timeout); + + inline const boost::system::error_code& status() const { + return _exec_err; + } + + /* [Possible boost::system::error_code values] + * boost::asio::error::connection_aborted: Network connection failure + * boost::asio::error::eof: Network connection closed cleanly + * boost::asio::error::connection_refused: Software handshake failure (version mismatch, etc) + * boost::asio::error::timed_out: RPC call timed out + * boost::asio::error::operation_aborted: RPC call aborted but connection alive + */ + +private: + void _handle_response_hdr(const boost::system::error_code& err, size_t transferred, size_t expected); + void _handle_response_data(const boost::system::error_code& err, size_t transferred, size_t expected); + void _wait_for_next_response_header(); + + inline void _stop_io_service() { + if (_io_service_thread.get()) { + UHD_LOG << "rpc_client stopping..." << std::endl; + _io_service.stop(); + _io_service_thread->join(); + _io_service_thread.reset(); + UHD_LOG << "rpc_client stopped." << std::endl; + } + } + + //Services + boost::asio::io_service _io_service; + boost::scoped_ptr<boost::thread> _io_service_thread; + boost::asio::ip::tcp::socket _socket; + //Handshake info + hshake_args_t _hshake_args_client; + hshake_args_t _hshake_args_server; + //In-out function args + func_xport_buf_t _request; + func_xport_buf_t _response; + //Synchronization + boost::mutex _mutex; + boost::condition_variable _exec_gate; + boost::system::error_code _exec_err; +}; + +}} + +#endif /* INCLUDED_RPC_CLIENT_HPP */ diff --git a/host/include/uhd/transport/nirio/rpc/rpc_common.hpp b/host/include/uhd/transport/nirio/rpc/rpc_common.hpp new file mode 100644 index 000000000..ea5b3337d --- /dev/null +++ b/host/include/uhd/transport/nirio/rpc/rpc_common.hpp @@ -0,0 +1,158 @@ +// +// 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_RPC_COMMON_HPP +#define INCLUDED_RPC_COMMON_HPP + +#define USE_BINARY_ARCHIVE 0 + +#include <string> +#include <iostream> +#include <sstream> +#include <vector> +#if (USE_BINARY_ARCHIVE) + #include <boost/archive/binary_oarchive.hpp> + #include <boost/archive/binary_iarchive.hpp> +#else + #include <boost/archive/text_oarchive.hpp> + #include <boost/archive/text_iarchive.hpp> +#endif + +namespace uhd { namespace usrprio_rpc { + +//[Over-the-wire] IDs +typedef boost::int32_t func_id_t; +typedef boost::uint64_t client_id_t; + +#define build_client_id(host_id, process_id) \ + ((static_cast<boost::uint64_t>(host_id) << 32) | static_cast<boost::uint64_t>(process_id)) +#define get_process_id_from_client_id(client_id) \ + (static_cast<boost::int32_t>(client_id)) +#define get_host_id_from_client_id(client_id) \ + (static_cast<boost::uint32_t>(client_id >> 32)) + +//[Over-the-wire] Handshake format +struct hshake_args_t { + boost::uint32_t version; + boost::uint32_t oldest_comp_version; + boost::int32_t boost_archive_version; + client_id_t client_id; +}; + +//[Over-the-wire] Header for RPC request and response +class func_args_header_t { +public: + func_id_t func_id; + client_id_t client_id; + boost::uint32_t func_args_size; + + static bool match_function(const func_args_header_t& a, const func_args_header_t& b) { + return ((a.func_id == b.func_id) && (a.client_id == b.client_id)); + } +}; + +//[Internal] Storage for RPC header and arguments +class func_xport_buf_t { +public: + func_args_header_t header; + std::vector<char> data; +}; + +//[Internal] Serializer for RPC input parameters +class func_args_writer_t { +public: + func_args_writer_t() : _stream(), _archive(_stream, boost::archive::no_header) {} + + template<typename data_t> + void push(const data_t& d) { + _archive << d; + } + + template<typename data_t> + func_args_writer_t& operator<< (const data_t& data) { + push(data); + return *this; + } + + void store(std::vector<char>& data) const { + const std::string& str = _stream.str(); + data.resize(str.length()); + data.assign((char*)str.c_str(), ((char*)str.c_str()) + str.length()); + } + +private: + std::ostringstream _stream; +#if (USE_BINARY_ARCHIVE) + boost::archive::binary_oarchive _archive; +#else + boost::archive::text_oarchive _archive; +#endif +}; + +//[Internal] Deserializer for RPC output parameters +class func_args_reader_t { +public: + func_args_reader_t() : _stream(), _archive() {} + + template<typename data_t> + void pull(data_t& d) const { + if (_archive) (*_archive) >> d; + } + + template<typename data_t> + const func_args_reader_t& operator>> (data_t& data) const { + pull(data); + return *this; + } + + void load(const std::vector<char>& data) { + _stream.str(std::string(data.begin(), data.end())); +#if (USE_BINARY_ARCHIVE) + _archive.reset(new boost::archive::binary_iarchive(_stream, boost::archive::no_header)); +#else + _archive.reset(new boost::archive::text_iarchive(_stream, boost::archive::no_header)); +#endif + } + +private: + std::istringstream _stream; +#if (USE_BINARY_ARCHIVE) + boost::scoped_ptr<boost::archive::binary_iarchive> _archive; +#else + boost::scoped_ptr<boost::archive::text_iarchive> _archive; +#endif +}; + +class boost_serialization_archive_utils { +public: + static boost::int32_t get_version() { + #if (USE_BINARY_ARCHIVE) + typedef boost::archive::binary_oarchive archive_t; + #else + typedef boost::archive::text_oarchive archive_t; + #endif + std::ostringstream stream; + archive_t dummy_archive(stream, boost::archive::no_header); + return static_cast<boost::int32_t>(dummy_archive.get_library_version()); + } +}; + +}} + +#undef USE_BINARY_ARCHIVE + +#endif /* INCLUDED_RPC_COMMON_HPP */ diff --git a/host/include/uhd/transport/nirio/rpc/usrprio_rpc_client.hpp b/host/include/uhd/transport/nirio/rpc/usrprio_rpc_client.hpp new file mode 100644 index 000000000..4b205cceb --- /dev/null +++ b/host/include/uhd/transport/nirio/rpc/usrprio_rpc_client.hpp @@ -0,0 +1,70 @@ +// +// 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_USRPRIO_RPC_CLIENT_HPP +#define INCLUDED_USRPRIO_RPC_CLIENT_HPP + +#include <uhd/transport/nirio/rpc/rpc_common.hpp> +#include <uhd/transport/nirio/rpc/rpc_client.hpp> +#include <uhd/transport/nirio/rpc/usrprio_rpc_common.hpp> +#include <uhd/transport/nirio/status.h> + +namespace uhd { namespace usrprio_rpc { + +class UHD_API usrprio_rpc_client { +public: + usrprio_rpc_client( + std::string server, + std::string port); + ~usrprio_rpc_client(); + + inline void set_rpc_timeout(boost::posix_time::milliseconds timeout_in_ms) { + _timeout = timeout_in_ms; + } + + inline nirio_status get_ctor_status() { + return _ctor_status; + } + + nirio_status niusrprio_enumerate( + NIUSRPRIO_ENUMERATE_ARGS); + nirio_status niusrprio_open_session( + NIUSRPRIO_OPEN_SESSION_ARGS); + nirio_status niusrprio_close_session( + NIUSRPRIO_CLOSE_SESSION_ARGS); + nirio_status niusrprio_reset_device( + NIUSRPRIO_RESET_SESSION_ARGS); + nirio_status niusrprio_download_bitstream_to_fpga( + NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA_ARGS); + nirio_status niusrprio_get_interface_path( + NIUSRPRIO_GET_INTERFACE_PATH_ARGS); + nirio_status niusrprio_download_fpga_to_flash( + NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH_ARGS); + + static const boost::int64_t DEFAULT_TIMEOUT_IN_MS = 5000; + +private: + static nirio_status _boost_error_to_nirio_status(const boost::system::error_code& err); + + rpc_client _rpc_client; + boost::posix_time::milliseconds _timeout; + nirio_status _ctor_status; +}; + +}} + +#endif /* INCLUDED_USRPRIO_RPC_CLIENT_HPP */ diff --git a/host/include/uhd/transport/nirio/rpc/usrprio_rpc_common.hpp b/host/include/uhd/transport/nirio/rpc/usrprio_rpc_common.hpp new file mode 100644 index 000000000..362d9a174 --- /dev/null +++ b/host/include/uhd/transport/nirio/rpc/usrprio_rpc_common.hpp @@ -0,0 +1,85 @@ +// +// 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_USRPRIO_RPC_COMMON_HPP +#define INCLUDED_USRPRIO_RPC_COMMON_HPP + +#include <uhd/transport/nirio/rpc/rpc_common.hpp> + +namespace uhd { namespace usrprio_rpc { + +//Function IDs + +static const func_id_t NIUSRPRIO_FUNC_BASE = 0x100; + +static const func_id_t NIUSRPRIO_ENUMERATE = NIUSRPRIO_FUNC_BASE + 0; +static const func_id_t NIUSRPRIO_OPEN_SESSION = NIUSRPRIO_FUNC_BASE + 1; +static const func_id_t NIUSRPRIO_CLOSE_SESSION = NIUSRPRIO_FUNC_BASE + 2; +static const func_id_t NIUSRPRIO_RESET_SESSION = NIUSRPRIO_FUNC_BASE + 3; +static const func_id_t NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA = NIUSRPRIO_FUNC_BASE + 4; +static const func_id_t NIUSRPRIO_GET_INTERFACE_PATH = NIUSRPRIO_FUNC_BASE + 5; +static const func_id_t NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH = NIUSRPRIO_FUNC_BASE + 6; + +//Function Args + +struct usrprio_device_info { + boost::uint32_t interface_num; + std::string resource_name; + std::string pcie_serial_num; + std::string interface_path; + + template <typename Archive> + void serialize(Archive& ar, const unsigned int version) + { + if (version || !version) { //Suppress unused warning + ar & interface_num; + ar & resource_name; + ar & pcie_serial_num; + ar & interface_path; + } + } +}; +typedef std::vector<usrprio_device_info> usrprio_device_info_vtr; + +#define NIUSRPRIO_ENUMERATE_ARGS \ + usrprio_device_info_vtr& device_info_vtr + +#define NIUSRPRIO_OPEN_SESSION_ARGS \ + const std::string& resource, \ + const std::string& path, \ + const std::string& signature, \ + const boost::uint16_t& download_fpga + +#define NIUSRPRIO_CLOSE_SESSION_ARGS \ + const std::string& resource + +#define NIUSRPRIO_RESET_SESSION_ARGS \ + const std::string& resource + +#define NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA_ARGS \ + const std::string& resource + +#define NIUSRPRIO_GET_INTERFACE_PATH_ARGS \ + const std::string& resource, \ + std::string& interface_path + +#define NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH_ARGS \ + const std::string& resource, \ + const std::string& bitstream_path +}} + +#endif /* INCLUDED_USRPRIO_RPC_COMMON_HPP */ diff --git a/host/include/uhd/transport/nirio/status.h b/host/include/uhd/transport/nirio/status.h new file mode 100644 index 000000000..e4117ac73 --- /dev/null +++ b/host/include/uhd/transport/nirio/status.h @@ -0,0 +1,65 @@ +// +// 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_UHD_TRANSPORT_NIRIO_STATUS_H +#define INCLUDED_UHD_TRANSPORT_NIRIO_STATUS_H + +#include <stdio.h> +#include <stddef.h> +#include <stdint.h> +#include <string> +#include <uhd/exception.hpp> + +#define ENABLE_EXTENDED_ERROR_INFO false + +typedef int32_t nirio_status; + +namespace uhd { namespace niusrprio { +struct nirio_err_info { + nirio_err_info(nirio_status arg_code, const char* arg_msg): code(arg_code), msg(arg_msg) {} + + nirio_status code; + const char* msg; + + static const nirio_err_info NIRIO_ERROR_TABLE[]; + static const size_t NIRIO_ERROR_TABLE_SIZE; +}; + +const std::string lookup_err_msg(nirio_status code); + +void nirio_status_to_exception(const nirio_status& status, const std::string& message); +}} + +#define nirio_status_fatal(status) ((status) < 0) +#define nirio_status_not_fatal(status) ((status) >= 0) + +#define nirio_status_chain(func, status) \ + if (nirio_status_not_fatal(status)) { \ + status = (func); \ + if (ENABLE_EXTENDED_ERROR_INFO && nirio_status_fatal(status)) { \ + fprintf(stderr,"ERROR: The following function call returned status code %d\n%s\n%s:%d\n",status,#func,__FILE__,__LINE__); \ + } \ + } \ + + +#define NIRIO_ERR_INFO(CONST_NAME, ERR_CODE, ERR_MSG) \ + static const nirio_status CONST_NAME = ERR_CODE; +#include "nirio_err_template.h" +#undef NIRIO_ERR_INFO + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_STATUS_H */ diff --git a/host/include/uhd/transport/nirio_zero_copy.hpp b/host/include/uhd/transport/nirio_zero_copy.hpp new file mode 100644 index 000000000..5b16b1975 --- /dev/null +++ b/host/include/uhd/transport/nirio_zero_copy.hpp @@ -0,0 +1,43 @@ +// +// 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_UHD_TRANSPORT_NIRIO_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_NIRIO_ZERO_COPY_HPP + +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + +class UHD_API nirio_zero_copy : public virtual zero_copy_if{ +public: + typedef boost::shared_ptr<nirio_zero_copy> sptr; + + static sptr make( + uhd::niusrprio::niusrprio_session::sptr fpga_session, + const uint32_t instance, + const zero_copy_xport_params &default_buff_args, + const device_addr_t &hints = device_addr_t() + ); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_NIRIO_ZERO_COPY_HPP */ diff --git a/host/include/uhd/transport/tcp_zero_copy.hpp b/host/include/uhd/transport/tcp_zero_copy.hpp new file mode 100644 index 000000000..a9878a396 --- /dev/null +++ b/host/include/uhd/transport/tcp_zero_copy.hpp @@ -0,0 +1,57 @@ +// +// Copyright 2010-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_UHD_TRANSPORT_TCP_ZERO_COPY_HPP +#define INCLUDED_UHD_TRANSPORT_TCP_ZERO_COPY_HPP + +#include <uhd/config.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <boost/shared_ptr.hpp> + +namespace uhd{ namespace transport{ + +/*! + * The zero copy TCP transport. + * This transport provides the uhd zero copy interface + * on top of a standard tcp socket from boost asio. + */ +struct UHD_API tcp_zero_copy : public virtual zero_copy_if +{ + /*! + * Make a new zero copy TCP transport: + * This transport is for sending and receiving + * between this host and a single endpoint. + * The primary usage for this transport will be data transactions. + * + * The address will be resolved, it can be a host name or ipv4. + * The port will be resolved, it can be a port type or number. + * + * \param addr a string representing the destination address + * \param port a string representing the destination port + * \param hints optional parameters to pass to the underlying transport + */ + static zero_copy_if::sptr make( + const std::string &addr, + const std::string &port, + const device_addr_t &hints = device_addr_t() + ); +}; + +}} //namespace + +#endif /* INCLUDED_UHD_TRANSPORT_TCP_ZERO_COPY_HPP */ diff --git a/host/include/uhd/transport/udp_constants.hpp b/host/include/uhd/transport/udp_constants.hpp new file mode 100644 index 000000000..74ce3a2f8 --- /dev/null +++ b/host/include/uhd/transport/udp_constants.hpp @@ -0,0 +1,26 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_TRANSPORT_UDP_CONSTANTS_HPP +#define INCLUDED_UHD_TRANSPORT_UDP_CONSTANTS_HPP + +// Constants related to UDP (over Ethernet) + +static const size_t IP_PROTOCOL_MIN_MTU_SIZE = 576; //bytes +static const size_t IP_PROTOCOL_UDP_PLUS_IP_HEADER = 28; //bytes. Note that this is the minimum value! + +#endif /* INCLUDED_UHD_TRANSPORT_UDP_CONSTANTS_HPP */ diff --git a/host/include/uhd/transport/udp_zero_copy.hpp b/host/include/uhd/transport/udp_zero_copy.hpp index bbba97b21..de4721be0 100644 --- a/host/include/uhd/transport/udp_zero_copy.hpp +++ b/host/include/uhd/transport/udp_zero_copy.hpp @@ -37,6 +37,11 @@ namespace uhd{ namespace transport{ */ class UHD_API udp_zero_copy : public virtual zero_copy_if{ public: + struct buff_params { + size_t recv_buff_size; + size_t send_buff_size; + }; + typedef boost::shared_ptr<udp_zero_copy> sptr; /*! @@ -51,11 +56,15 @@ public: * * \param addr a string representing the destination address * \param port a string representing the destination port + * \param default_buff_args Default values for frame sizes and num frames + * \param[out] buff_params_out Returns the actual buffer sizes * \param hints optional parameters to pass to the underlying transport */ static sptr make( const std::string &addr, const std::string &port, + const zero_copy_xport_params &default_buff_args, + udp_zero_copy::buff_params& buff_params_out, const device_addr_t &hints = device_addr_t() ); }; diff --git a/host/include/uhd/transport/zero_copy.hpp b/host/include/uhd/transport/zero_copy.hpp index 40d7b8c59..9ff3042aa 100644 --- a/host/include/uhd/transport/zero_copy.hpp +++ b/host/include/uhd/transport/zero_copy.hpp @@ -29,7 +29,14 @@ namespace uhd{ namespace transport{ //! Simple managed buffer with release interface class UHD_API managed_buffer{ public: - managed_buffer(void):_ref_count(0),_buffer(NULL),_length(0){} + managed_buffer(void):_ref_count(0),_buffer(NULL),_length(0) + { +#ifdef UHD_TXRX_DEBUG_PRINTS + _mb_num = s_buffer_count; + // From Boost website: atomic_count seems only to have precrement operator. + ++s_buffer_count; +#endif + } virtual ~managed_buffer(void) {} @@ -76,9 +83,27 @@ namespace uhd{ namespace transport{ boost::detail::atomic_count _ref_count; typedef boost::intrusive_ptr<managed_buffer> sptr; + int ref_count(){ + return (int) _ref_count; + } + +#ifdef UHD_TXRX_DEBUG_PRINTS + int num() const{ + return _mb_num; + } +#endif + protected: void *_buffer; size_t _length; +#ifdef UHD_TXRX_DEBUG_PRINTS + int _mb_num; +#endif + + private: +#ifdef UHD_TXRX_DEBUG_PRINTS + static boost::detail::atomic_count s_buffer_count; +#endif }; UHD_INLINE void intrusive_ptr_add_ref(managed_buffer *p){ @@ -110,6 +135,16 @@ namespace uhd{ namespace transport{ }; /*! + * Transport parameters + */ + struct zero_copy_xport_params { + size_t recv_frame_size; + size_t send_frame_size; + size_t num_recv_frames; + size_t num_send_frames; + }; + + /*! * A zero-copy interface for transport objects. * Provides a way to get send and receive buffers * with memory managed by the transport object. @@ -119,6 +154,11 @@ namespace uhd{ namespace transport{ typedef boost::shared_ptr<zero_copy_if> sptr; /*! + * Clean up tasks before releasing the transport object. + */ + virtual ~zero_copy_if() {}; + + /*! * Get a new receive buffer from this transport object. * \param timeout the timeout to get the buffer in seconds * \return a managed buffer, or null sptr on timeout/error diff --git a/host/include/uhd/types/metadata.hpp b/host/include/uhd/types/metadata.hpp index 788999900..6a79720d0 100644 --- a/host/include/uhd/types/metadata.hpp +++ b/host/include/uhd/types/metadata.hpp @@ -30,6 +30,26 @@ namespace uhd{ * The receive routines will convert IF data headers into metadata. */ struct UHD_API rx_metadata_t{ + + //! Default constructor. + rx_metadata_t() + { + reset(); + } + + //! Reset values. + void reset() + { + has_time_spec = false; + time_spec = time_spec_t(0.0); + more_fragments = false; + fragment_offset = 0; + start_of_burst = false; + end_of_burst = false; + error_code = ERROR_CODE_NONE; + out_of_sequence = false; + } + //! Has time specification? bool has_time_spec; @@ -80,13 +100,23 @@ namespace uhd{ ERROR_CODE_LATE_COMMAND = 0x2, //! Expected another stream command. ERROR_CODE_BROKEN_CHAIN = 0x4, - //! An internal receive buffer has filled. + /*! + * An internal receive buffer has filled or a sequence error has been detected. + * So, why is this overloaded? Simple: legacy support. It would have been much cleaner + * to create a separate error code for a sequence error, but that would have broken + * legacy applications. So, the out_of_sequence flag was added to differentiate between + * the two error cases. In either case, data is missing between this time_spec and the + * and the time_spec of the next successful receive. + */ ERROR_CODE_OVERFLOW = 0x8, //! Multi-channel alignment failed. ERROR_CODE_ALIGNMENT = 0xc, //! The packet could not be parsed. ERROR_CODE_BAD_PACKET = 0xf } error_code; + + //! Out of sequence. The transport has either dropped a packet or received data out of order. + bool out_of_sequence; }; /*! diff --git a/host/include/uhd/types/tune_request.hpp b/host/include/uhd/types/tune_request.hpp index dd6123a9f..d1f3b4463 100644 --- a/host/include/uhd/types/tune_request.hpp +++ b/host/include/uhd/types/tune_request.hpp @@ -96,7 +96,7 @@ namespace uhd{ * - mode_n: Allows the user to tell the daughterboard tune code * to choose between an integer N diviver or fractional N divider. * Default is fractional N on boards that support fractional N tuning. - * Fractional N provides greater tuning acuracy at the expense of spurs. + * Fractional N provides greater tuning accuracy at the expense of spurs. * Possible options for this key: "integer" or "fractional". */ device_addr_t args; diff --git a/host/include/uhd/usrp/gps_ctrl.hpp b/host/include/uhd/usrp/gps_ctrl.hpp index abe5c37ba..bbccac8bb 100644 --- a/host/include/uhd/usrp/gps_ctrl.hpp +++ b/host/include/uhd/usrp/gps_ctrl.hpp @@ -32,7 +32,7 @@ public: typedef boost::shared_ptr<gps_ctrl> sptr; /*! - * Make a GPS config for NMEA GPS devices + * Make a GPS config for internal GPSDOs or generic NMEA GPS devices */ static sptr make(uart_iface::sptr uart); diff --git a/host/include/uhd/usrp/multi_usrp.hpp b/host/include/uhd/usrp/multi_usrp.hpp index d60f0e60c..5b4991202 100644 --- a/host/include/uhd/usrp/multi_usrp.hpp +++ b/host/include/uhd/usrp/multi_usrp.hpp @@ -339,6 +339,26 @@ public: virtual std::vector<std::string> get_clock_sources(const size_t mboard) = 0; /*! + * Send the clock source to an output connector. + * This call is only applicable on devices with reference outputs. + * By default, the reference output will be enabled for ease of use. + * This call may be used to enable or disable the output. + * \param enb true to output the clock source. + * \param mboard which motherboard to set + */ + virtual void set_clock_source_out(const bool enb, const size_t mboard = ALL_MBOARDS) = 0; + + /*! + * Send the time source to an output connector. + * This call is only applicable on devices with PPS outputs. + * By default, the PPS output will be enabled for ease of use. + * This call may be used to enable or disable the output. + * \param enb true to output the time source. + * \param mboard which motherboard to set + */ + virtual void set_time_source_out(const bool enb, const size_t mboard = ALL_MBOARDS) = 0; + + /*! * Get the number of USRP motherboards in this configuration. */ virtual size_t get_num_mboards(void) = 0; @@ -856,6 +876,53 @@ public: */ virtual void set_tx_iq_balance(const std::complex<double> &correction, size_t chan = ALL_CHANS) = 0; + /******************************************************************* + * GPIO methods + ******************************************************************/ + + /*! + * Enumerate gpio banks on the specified device. + * \param mboard the motherboard index 0 to M-1 + * \return a list of string for each bank name + */ + virtual std::vector<std::string> get_gpio_banks(const size_t mboard) = 0; + + /*! + * Set a GPIO attribute on a particular GPIO bank. + * Possible attribute names: + * - CTRL - 1 for ATR mode 0 for GPIO mode + * - DDR - 1 for output 0 for input + * - OUT - GPIO output level (not ATR mode) + * - ATR_0X - ATR idle state + * - ATR_RX - ATR receive only state + * - ATR_TX - ATR transmit only state + * - ATR_XX - ATR full duplex state + * \param bank the name of a GPIO bank + * \param attr the name of a GPIO attribute + * \param value the new value for this GPIO bank + * \param mask the bit mask to effect which pins are changed + * \param mboard the motherboard index 0 to M-1 + */ + virtual void set_gpio_attr(const std::string &bank, const std::string &attr, const boost::uint32_t value, const boost::uint32_t mask = 0xffffffff, const size_t mboard = 0) = 0; + + /*! + * Get a GPIO attribute on a particular GPIO bank. + * Possible attribute names: + * - CTRL - 1 for ATR mode 0 for GPIO mode + * - DDR - 1 for output 0 for input + * - OUT - GPIO output level (not ATR mode) + * - ATR_0X - ATR idle state + * - ATR_RX - ATR receive only state + * - ATR_TX - ATR transmit only state + * - ATR_XX - ATR full duplex state + * - READBACK - readback input GPIOs + * \param bank the name of a GPIO bank + * \param attr the name of a GPIO attribute + * \param mboard the motherboard index 0 to M-1 + * \return the value set for this attribute + */ + virtual boost::uint32_t get_gpio_attr(const std::string &bank, const std::string &attr, const size_t mboard = 0) = 0; + }; }} diff --git a/host/include/uhd/utils/CMakeLists.txt b/host/include/uhd/utils/CMakeLists.txt index e86826435..c0991b3ce 100644 --- a/host/include/uhd/utils/CMakeLists.txt +++ b/host/include/uhd/utils/CMakeLists.txt @@ -30,6 +30,7 @@ UHD_INSTALL(FILES msg_task.hpp paths.hpp pimpl.hpp + platform.hpp safe_call.hpp safe_main.hpp static.hpp diff --git a/host/include/uhd/utils/platform.hpp b/host/include/uhd/utils/platform.hpp new file mode 100644 index 000000000..de778f61c --- /dev/null +++ b/host/include/uhd/utils/platform.hpp @@ -0,0 +1,36 @@ +// +// Copyright 2010,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_UHD_UTILS_PLATFORM_HPP +#define INCLUDED_UHD_UTILS_PLATFORM_HPP + +#include <boost/cstdint.hpp> + +namespace uhd { + + /* Returns the process ID of the current process */ + boost::int32_t get_process_id(); + + /* Returns a unique identifier for the current machine */ + boost::uint32_t get_host_id(); + + /* Get a unique identifier for the current machine and process */ + boost::uint32_t get_process_hash(); + +} //namespace uhd + +#endif /* INCLUDED_UHD_UTILS_PLATFORM_HPP */ diff --git a/host/include/uhd/version.hpp b/host/include/uhd/version.hpp index a62ee8285..1bb89dd84 100644 --- a/host/include/uhd/version.hpp +++ b/host/include/uhd/version.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// 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 @@ -27,7 +27,7 @@ * The format is oldest API compatible release - ABI compat number. * The compatibility number allows pre-release ABI to be versioned. */ -#define UHD_VERSION_ABI_STRING "3.6.0-1" +#define UHD_VERSION_ABI_STRING "3.7.0-0" namespace uhd{ diff --git a/host/lib/convert/convert_pack_sc12.cpp b/host/lib/convert/convert_pack_sc12.cpp index 92cd5d152..081f60a89 100644 --- a/host/lib/convert/convert_pack_sc12.cpp +++ b/host/lib/convert/convert_pack_sc12.cpp @@ -80,13 +80,26 @@ struct convert_star_1_to_sc12_item32_1 : public converter void operator()(const input_type &inputs, const output_type &outputs, const size_t nsamps) { const std::complex<type> *input = reinterpret_cast<const std::complex<type> *>(inputs[0]); - item32_sc12_3x *output = reinterpret_cast<item32_sc12_3x *>(size_t(outputs[0]) & ~0x3); + + /* + * Effectively outputs will point to a managed_buffer instance. These buffers are 32 bit aligned. + * For a detailed description see comments in 'convert_unpack_sc12.cpp'. + */ + const size_t head_samps = size_t(inputs[0]) & 0x3; + size_t rewind = 0; + switch(head_samps) + { + case 0: break; + case 1: rewind = 9; break; + case 2: rewind = 6; break; + case 3: rewind = 3; break; + } + item32_sc12_3x *output = reinterpret_cast<item32_sc12_3x *>(size_t(outputs[0]) - rewind); //helper variables size_t i = 0, o = 0; //handle the head case - const size_t head_samps = size_t(outputs[0]) & 0x3; switch (head_samps) { case 0: break; //no head diff --git a/host/lib/device.cpp b/host/lib/device.cpp index c5bc047da..ff5163f2d 100644 --- a/host/lib/device.cpp +++ b/host/lib/device.cpp @@ -153,3 +153,9 @@ device::sptr device::make(const device_addr_t &hint, size_t which){ return dev; } } + +uhd::property_tree::sptr +device::get_tree(void) const +{ + return _tree; +} diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt index b6e121db3..889c725db 100644 --- a/host/lib/ic_reg_maps/CMakeLists.txt +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2011 Ettus Research LLC +# Copyright 2010-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 @@ -98,6 +98,11 @@ LIBUHD_PYTHON_GEN_SOURCE( ) LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_ads62p48_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/ads62p48_regs.hpp +) + +LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_SOURCE_DIR}/gen_tuner_4937di5_regs.py ${CMAKE_CURRENT_BINARY_DIR}/tuner_4937di5_regs.hpp ) @@ -107,4 +112,9 @@ LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_BINARY_DIR}/tda18272hnm_regs.hpp ) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_lmk04816_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/lmk04816_regs.hpp +) + UNSET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/gen_ads62p48_regs.py b/host/lib/ic_reg_maps/gen_ads62p48_regs.py new file mode 100644 index 000000000..c38ce8ff1 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_ads62p48_regs.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# +# 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +reset 0[7] 0 +serial_readout 0[0] 0 +enable_low_speed_mode 0x20[2] 0 +ref 0x3f[6:5] 0 internal=0, external=3 +standby 0x3f[1] 0 normal, standby +power_down 0x40[3:0] 0 pins=0, normal=8, chb=9, cha=10, chab=11, global=12, chb_standby=13, cha_standby=14, mux=15 +lvds_cmos 0x41[7] 0 parallel_cmos, ddr_lvds +clk_out_pos_edge 0x44[7:5] 0 normal=0, plus4_26=5, minus4_26=7, minus7_26=6 +clk_out_neg_edge 0x44[4:2] 0 normal=0, plus4_26=5, minus4_26=7, minus7_26=6 +channel_control 0x50[6] 0 common, independent +data_format 0x50[2:1] 2 2s_compliment=2, offset_binary=3 +custom_pattern_low 0x51[7:0] 0 +custom_pattern_high 0x52[5:0] 0 +enable_offset_corr_chA 0x53[6] 0 +gain_chA 0x55[7:4] 0 +offset_corr_time_const_chA 0x55[3:0] 0 +fine_gain_adjust_chA 0x57[6:0] 0 +test_patterns_chA 0x62[2:0] 0 normal, zeros, ones, toggle, ramp, custom +offset_pedestal_chA 0x63[5:0] 0 +enable_offset_corr_chB 0x66[6] 0 +gain_chB 0x68[7:4] 0 +offset_corr_time_const_chB 0x68[3:0] 0 +fine_gain_adjust_chB 0x6A[6:0] 0 +test_patterns_chB 0x75[2:0] 0 normal, zeros, ones, toggle, ramp, custom +offset_pedestal_chB 0x76[5:0] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ + boost::uint8_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + case $addr: + #for $reg in filter(lambda r: r.get_addr() == addr, $regs) + reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); + #end for + break; + #end for + } + return reg; +} + +boost::uint16_t get_write_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | get_reg(addr); +} + +boost::uint16_t get_read_reg(boost::uint8_t addr){ + return (boost::uint16_t(addr) << 8) | (1 << 7); +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='ads62p48_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/ic_reg_maps/gen_lmk04816_regs.py b/host/lib/ic_reg_maps/gen_lmk04816_regs.py new file mode 100644 index 000000000..e89a82671 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_lmk04816_regs.py @@ -0,0 +1,407 @@ +#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 +######################################################################## +address0 0[0:4] 0 +CLKout0_1_DIV 0[5:15] 25 +CLKout0_1_HS 0[16] 0 +RESET 0[17] 0 no_reset, reset +CLKout0_1_DDLY 0[18:27] 0 five +CLKout0_ADLY_SEL 0[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout1_ADLY_SEL 0[29] 0 d_pd, d_ev_x, d_odd_y, d_both +Required_0 0[30] 0 +CLKout0_1_PD 0[31] 1 power_up, power_down +######################################################################## +## address 1 +######################################################################## +address1 1[0:4] 1 +CLKout2_3_DIV 1[5:15] 25 +CLKout2_3_HS 1[16] 0 +Powerdown 1[17] 0 normal, disabled +CLKout2_3_DDLY 1[18:27] 0 +CLKout2_ADLY_SEL 1[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout3_ADLY_SEL 1[29] 0 d_pd, d_ev_x, d_odd_y, d_both +Required_1 1[30] 0 +CLKout2_3_PD 1[31] 1 power_up, power_down +######################################################################## +## address 2 +######################################################################## +address2 2[0:4] 2 +CLKout4_5_DIV 2[5:15] 25 +CLKout4_5_HS 2[16] 0 +Required_2_17 2[17] 0 +CLKout4_5_DDLY 2[18:27] 0 +CLKout4_ADLY_SEL 2[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout5_ADLY_SEL 2[29] 0 d_pd, d_ev_x, d_odd_y, d_both +Required_2_30 2[30] 0 +CLKout4_5_PD 2[31] 1 power_up, power_down +######################################################################## +## address 3 +######################################################################## +address3 3[0:4] 3 +CLKout6_7_DIV 3[5:15] 1 +CLKout6_7_HS 3[16] 0 +Required_3_17 3[17] 0 +CLKout6_7_DDLY 3[18:27] 0 +CLKout6_ADLY_SEL 3[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout7_ADLY_SEL 3[29] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout6_7_OSCin_Sel 3[30] 1 VCO,OSCin +CLKout6_7_PD 3[31] 0 power_up, power_down +######################################################################## +## address 4 +######################################################################## +address4 4[0:4] 4 +CLKout8_9_DIV 4[5:15] 25 +CLKout8_9_HS 4[16] 0 +Required_4_17 4[17] 0 +CLKout8_9_DDLY 4[18:27] 0 +CLKout8_ADLY_SEL 4[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout9_ADLY_SEL 4[29] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout8_9_OSCin_Sel 4[30] 0 VCO, OSCin +CLKout8_9_PD 4[31] 0 power_up, power_down +######################################################################## +## address 5 +######################################################################## +address5 5[0:4] 5 +CLKout10_11_DIV 5[5:15] 25 +CLKout10_11_HS 5[16] 0 +Required_5_17 5[17] 0 +CLKout10_11_DDLY 5[18:27] 0 +CLKout10_ADLY_SEL 5[28] 0 d_pd, d_ev_x, d_odd_y, d_both +CLKout11_ADLY_SEL 5[29] 0 d_pd, d_ev_x, d_odd_y, d_both +Required_5_30 5[30] 0 +CLKout10_11_PD 5[31] 1 normal, power_down +######################################################################## +## address 6 +######################################################################## +#set $CLKoutX_TYPE_ENUMS = "p_down=0, LVDS=1, LVPECL_700mVpp=2, LVPECL_1200mVpp=3, LVPECL_1600mVpp=4, LVPECL_200mVpp=5, LVCMOS=6, LVCMOS_IN=7, LVCMOS_NN=8, LVCMOS_II=9, LVCMOS_LN=10, LVCMOS_LI=11, LVCMOS_NL=12, LVCMOS_IL=13, LVCMOS_LL=1" +address6 6[0:4] 6 +CLKout0_1_ADLY 6[5:9] 0 +Required_6_10 6[10] 0 +CLKout2_3_ADLY 6[11:15] 0 +CLKout0_TYPE 6[16:19] 0 $(CLKoutX_TYPE_ENUMS) +CLKout1_TYPE 6[20:23] 0 $(CLKoutX_TYPE_ENUMS) +CLKout2_TYPE 6[24:27] 0 $(CLKoutX_TYPE_ENUMS) +CLKout3_TYPE 6[28:31] 0 $(CLKoutX_TYPE_ENUMS) +######################################################################## +## address 7 +######################################################################## +address7 7[0:4] 7 +CLKout4_5_ADLY 7[5:9] 0 +Required_7_10 7[10] 0 +CLKout6_7_ADLY 7[11:15] 0 +CLKout4_TYPE 7[16:19] 0 $(CLKoutX_TYPE_ENUMS) +CLKout5_TYPE 7[20:23] 0 $(CLKoutX_TYPE_ENUMS) +CLKout6_TYPE 7[24:27] 0 $(CLKoutX_TYPE_ENUMS) +CLKout7_TYPE 7[28:31] 0 $(CLKoutX_TYPE_ENUMS) +######################################################################## +## address 8 +######################################################################## +address8 8[0:4] 8 +CLKout8_9_ADLY 8[5:9] 0 +Required_8_10 8[10] 0 +CLKout10_11_ADLY 8[11:15] 0 $(CLKoutX_TYPE_ENUMS) +CLKout8_TYPE 8[16:19] 0 $(CLKoutX_TYPE_ENUMS) +CLKout9_TYPE 8[20:23] 0 $(CLKoutX_TYPE_ENUMS) +CLKout10_TYPE 8[24:27] 0 $(CLKoutX_TYPE_ENUMS) +CLKout11_TYPE 8[28:31] 0 $(CLKoutX_TYPE_ENUMS) +######################################################################## +## address 9 +######################################################################## +address9 9[0:4] 9 +Required_9_5 9[5] 0 +Required_9_6 9[6] 1 +Required_9_7 9[7] 0 +Required_9_8 9[8] 1 +Required_9_9 9[9] 0 +Required_9_10 9[10] 1 +Required_9_11 9[11] 0 +Required_9_12 9[12] 1 +Required_9_13 9[13] 0 +Required_9_14 9[14] 1 +Required_9_15 9[15] 0 +Required_9_16 9[16] 1 +Required_9_17 9[17] 0 +Required_9_18 9[18] 1 +Required_9_19 9[19] 0 +Required_9_20 9[20] 1 +Required_9_21 9[21] 0 +Required_9_22 9[22] 1 +Required_9_23 9[23] 0 +Required_9_24 9[24] 1 +Required_9_25 9[25] 0 +Required_9_26 9[26] 1 +Required_9_27 9[27] 0 +Required_9_28 9[28] 1 +Required_9_29 9[29] 0 +Required_9_30 9[30] 1 +Required_9_31 9[31] 0 +######################################################################## +## address 10 +######################################################################## +address10 10[0:4] 10 +FEEDBACK_MUX 10[5:7] 0 +VCO_DIV 10[8:10] 2 +EN_FEEDBACK_MUX 10[11] 0 powered_down, enabled +VCO_MUX 10[12] 0 just_vco, vco_divider +Required_10_13 10[13] 0 +Required_10_14 10[14] 1 +Required_10_15 10[15] 0 +OSCout_DIV 10[16:18] 0 +PD_OSCin 10[19] 0 normal, power_down +OSCout10_MUX 10[20] 0 bypass_div, divided +Required_10_21 10[21] 0 +EN_OSCout0 10[22] 1 disabled, enabled +Required_10_23 10[23] 0 +OSCout0_TYPE 10[24:27] 0 +Required_10_28 10[28] 1 +Required_10_range 10[29:31] 0 +######################################################################## +## address 11 +######################################################################## +address11 11[0:4] 11 +EN_PLL2_XTAL 11[5] 0 osc_disabled=0,osc_enabled=1 +Required_11 11[6:11] 0 +SYNC_TYPE 11[12:14] 1 input=0,in_pull_up=1,in_pull_down=2,out_push_pull=3,out_inverted=4,out_open_sources=5,out_open_drains=6 +SYNC_EN_AUTO 11[15] 0 man_sync=0,sync_int_gen=1 +SYNC_POL_INV 11[16] 1 sync_high=0,sync_low=1 +SYNC_QUAL 11[17] 0 not_qual=0,fb_mux=1 +SYNC_CLKin2_MUX 11[18:19] 0 log_low=0,CLKin2_LOS=1,CLKin2_Selected=2,uWire_RB=3 +NO_SYNC_CLKout0_1 11[20] 0 clock_xy_sync=0,clock_xy_nosync=1 +NO_SYNC_CLKout2_3 11[21] 0 clock_xy_sync=0,clock_xy_nosync=1 +NO_SYNC_CLKout4_5 11[22] 1 clock_xy_sync=0,clock_xy_nosync=1 +NO_SYNC_CLKout6_7 11[23] 1 clock_xy_sync=0,clock_xy_nosync=1 +NO_SYNC_CLKout8_9 11[24] 0 clock_xy_sync=0,clock_xy_nosync=1 +NO_SYNC_CLKout10_11 11[25] 0 clock_xy_sync=0,clock_xy_nosync=1 +EN_SYNC 11[26] 1 disable=0,enable=1 +MODE 11[27:31] 0 dual_int=0, dual_int_zer_delay=2,dual_ext=3,dual_ext_zer_delay=5,pll_two_int=6,pll_two_int_zer_delay=8,pll_two_ext=11,clock_dist=16 +######################################################################## +## address 12 +######################################################################## +address12 12[0:4] 12 +HOLDOVER_MODE 12[6:7] 2 disabled=1,enabled=2 +EN_TRACK 12[8] 1 disabled=0,enabled=1 +Required_12_range917 12[9:17] 0 +Required_12_range1819 12[18:19] 1 +Required_12_20 12[20] 0 +Required_LE_12 12[21] 0 +SYNC_PLL1_DLD 12[22] 0 sync_mode_noforce=0,sync_mode_force=1 +SYNC_PLL2_DLD 12[23] 0 sync_mode_noforce=0,sync_mode_force=1 +LD_TYPE 12[24:26] 3 out_push_pull=3,out_inverted=4,out_open_sources=5,out_open_drains=6 +LD_MUX 12[27:31] 3 log_low=0,pll1_dld=1,pll2_dld=2,both=3,holdover=4,locked_dac=5,uWire_RB=7,rail=8,low=9,high=10,pll1_N=11,pll1_Nhalf=12,pll2_N=13,pll2_Nhalf=14,pll1_R=15,pll1_Rhalf=16,pll2_R=17,pll2_Rhalf=18 +######################################################################## +## address 13 +######################################################################## +address13 13[0:4] 13 +EN_CLKin0 13[5] 1 no_valid_use=0,valid_use=1 +EN_CLKin1 13[6] 1 no_valid_use=0,valid_use=1 +EN_CLKin2 13[7] 1 no_valid_use=0,valid_use=1 +CLKin_Sel_INV 13[8] 0 active_high=0,active_low=1 +CLKin_Select_MODE 13[9:11] 3 CLKin0_man=0,CLKin1_man=1,CLKin2_man=2,pin_sel_mode=3,auto_mode=4,auto_mode_w_next_block_sel=6 +Status_CLKin0_MUX 13[14:12] 0 log_low=0,CLKin0_LOS=1,CLKin0_Selected=2,dac_lock=3,dac_low=4,dac_high=5,uWire_RB=6 +DISABLE_DLD1_DET 13[15] 0 pll1_dld_cause_event=0,pll1_dld_not_cause=1 +Status_CLKin0_TYPE 13[16:18] 2 input=0,in_pull_up=1,in_pull_down=2,out_push_pull=3,out_inverted=4,out_open_sources=5,out_open_drains=6 +Required_13_19 13[19] 0 +Status_CLKin1_MUX 13[20:22] 0 log_low=0,CLKin1_LOS=1,CLKin1_Selected=2,DAC_lock=3,DAC_low=4,DAC_high=5,uWire_RB=6 +Required_19_23 13[23] 0 +HOLDOVER_TYPE 13[24:26] 3 out_push_pull=3,out_inverted=4,out_open_sources=5,out_open_drains=6 +HOLDOVER_MUX 13[27:31] 7 log_low=0,pll1_dld=1,pll2_dld=2,both=3,holdover=4,locked_dac=5,uWire_RB=7,rail=8,low=9,high=10,pll1_N=11,pll1_Nhalf=12,pll2_N=13,pll2_Nhalf=14,pll1_R=15,pll1_Rhalf=16,pll2_R=17,pll2_Rhalf=18 +######################################################################## +## address 14 +######################################################################## +address14 14[0:4] 14 +EN_VTUNE_RAIL_DET 14[5] 0 disabled=0,enabled=1 +DAC_LOW_TRIP 14[6:11] 0 +Required_14_1213 14[12:13] 0 +DAC_HIGH_TRIP 14[14:19] 0 +CLKin0_BUF_TYPE 14[20] 0 bipolar=0,cmos=1 +CLKin1_BUF_TYPE 14[21] 0 bipolar=0,cmos=1 +CLKin2_BUF_TYPE 14[22] 0 bipolar=0,cmos=1 +Required_14_23 14[23] 0 +Status_CLKin1_TYPE 14[24:26] 2 input=0,in_pull_up=1,in_pull_down=2,out_push_pull=3,out_inverted=4,out_open_sources=5,out_open_drains=6 +Required_14_27 14[27] 0 +EN_LOS 14[28] 1 disable_timeout=0,enable_timeout=1 +Required_14_29 14[29] 0 +LOS_TIMEOUT 14[30:31] 0 1200ns_at_4p2KHz=0,206ns_at_2p5MHz=1,52p9at10MHz=2,23p7_at_22MHz=3 +######################################################################## +## address 15 +######################################################################## +address15 15[0:4] 15 +FORCE_HOLDOVER 15[5] 0 disabled=0,enabled=1 +HOLDOVER_DLD_CNT 15[6:19] 512 +EN_MAN_DAC 15[20] 0 disabled=0,enabled=1 +Required_15 15[21] 0 +MAN_DAC 15[22:31] 512 +######################################################################## +## address 16 +######################################################################## +address16 16[0:4] 16 +Required_16_5 16[5] 0 +Required_16_6 16[6] 0 +Required_16_7 16[7] 0 +Required_16_8 16[8] 0 +Required_16_9 16[9] 0 +Required_16_10 16[10] 1 +Required_16_11 16[11] 0 +Required_16_12 16[12] 0 +Required_16_13 16[13] 0 +Required_16_14 16[14] 0 +Required_16_15 16[15] 0 +Required_16_16 16[16] 1 +Required_16_17 16[17] 0 +Required_16_18 16[18] 1 +Required_16_19 16[19] 0 +Required_16_20 16[20] 1 +Required_16_21 16[21] 0 +Required_16_22 16[22] 1 +Required_16_23 16[23] 0 +Required_16_24 16[24] 1 +Required_16_25 16[25] 0 +Required_16_26 16[26] 0 +Required_16_27 16[27] 0 +Required_16_28 16[28] 0 +Required_16_29 16[29] 0 +XTAL_LVL 16[30:31] 0 1p65VPP=0, 1p75VPP=1, 1p90VPP=2, 2p05VPP=3 +######################################################################## +## address 24 +######################################################################## +address24 24[0:4] 24 +PLL1_WND_SIZE 24[6:7] 3 5p5ns=0, 10ns=1, 18p6=2, 40ns=3 +PLL1_R_DLY 24[8:10] 0 0ps=0, 205ps=1, 410ps=2, 615ps=3, 820ps=4, 1025ps=5, 1230ps=6, 1345ps=7 +Required_24_11 24[11] 0 +PLL2_N_DLY 24[12:14] 0 0ps=0, 205ps=1, 410ps=2, 615ps=3, 820ps=4, 1025ps=5, 1230ps=6, 1345ps=7 +Required_24_15 24[15] 0 +PLL2_R3_LF 24[16:18] 0 200ohm=0, 1kilo_ohm=1, 2kilo_ohm=2, 4kilo_ohm=3, 16kilo_ohm=4 +Required_24_19 24[19] 0 +PLL2_R4_LF 24[20:22] 0 200ohm=0, 1kilo_ohm=1, 2kilo_ohm=2, 4kilo_ohm=3, 16kilo_ohm=4 +Required_24_23 24[23] 0 +PLL2_C3_LF 24[24:27] 0 10pF=0, 11pF=1, 15pF=2, 16pF=3, 19pF=4, 20pF=5, 24pF=6, 25pF=7, 29pF=8, 30pF=9, 33pF=10, 34pF=11, 38pF=12, 39pF=13 +PLL2_C4_LF 24[28:31] 0 10pF=0, 15pF=1, 29pF=2, 34pF=3, 47pF=4, 52pF=5, 66pF=6, 71pF=7, 103pF=8, 108pF=9, 122pF=10, 126pF=11, 141pF=12, 146pF=13 +######################################################################### +## address 25 +######################################################################### +addres25 25[0:4] 25 +PLL2_CP_TRI_25 25[5] 0 +PLL1_DLD_CNT_25 25[6:19] 1024 +Required_25 25[20:21] 0 +DAC_CLK_DIV 25[22:31] 4 +######################################################################### +## address 26 +######################################################################### +address26 26[0:4] 26 +PLL2_CP_TRI_26 26[5] 0 PLL2_cpout2_active=0,PLL2cpout2T=1 +PLL2_DLD_CNT_26 26[6:19] 81 +Required_26_20 26[20] 0 +Required_26_21 26[21] 1 +Required_26_22 26[22] 0 +Required_26_23 26[23] 1 +Required_26_24 26[24] 1 +Required_26_25 26[25] 1 +PLL2_CP_GAIN_26 26[26:27] 3 100uA=0, 400uA=1, 1600uA=2, 3200uA=3 +PLL2_CP_POL_26 26[28] 0 neg_slope=0, pos_slope=1 +EN_PLL2_REF_2X 26[29] 0 normal_freq_ref=0, doubled_freq_ref=1 +PLL2_WND_SIZE 26[30:31] 2 3p7ns=2 +######################################################################### +## address 27 +######################################################################### +address27 27[0:4] 27 +PLL1_CP_TRI_27 27[5] 0 PLL2_cpout2_active=0,PLL2_cpout2TRI=1 +PLL1_R_27 27[6:19] 96 +CLKin0_PreR_DIV 27[20:21] 0 div1=0,div2=1,div4=2,div8=3 +CLKin1_PreR_DIV 27[22:23] 0 div1=0,div2=1,div4=2,div8=3 +CLKin2_PreR_DIV 27[24:25] 0 div1=0,div2=1,div4=2,div8=3 +PLL1_CP_GAIN_27 27[26:27] 0 100uA=0, 200uA=1, 400uA=2, 1600uA=3 +PLL1_CP_POL_27 27[28] 1 neg_slove=0, pos_slope=1 +Reqiured_27 27[29:31] 0 +######################################################################### +## address 28 +######################################################################### +address28 28[0:4] 28 +Required_28 28[5] 0 +PLL1_N_28 28[6:19] 192 +PLL2_R_28 28[20:31] 4 +######################################################################### +## address 29 +######################################################################### +address29 29[0:4] 29 +PLL2_N_CAL_29 29[5:22] 48 +PLL2_FAST_PDF_29 29[23] 1 100MHz_less=0, 100MHz_more=1 +OSCin_FREQ_29 29[24:26] 1 zero_to_63MHz=0, 63_to_127MHz=1, 127_to_255MHz=2, 255_to_400MHz=4 +Required29 29[27:31] 0 +######################################################################### +## address 30 +######################################################################### +address30 30[0:4] 30 +PLL2_N_30 30[5:22] 48 +Required_30_522 30[23] 0 +PLL2_P_30 30[24:26] 2 div_8=0, div_2=1, div_2a=2, div_3=3, div_4=4, div_5=5, div_6=6, div_7=7 +Required_2731 30[27:31] 0 +######################################################################### +## address 31 +######################################################################### +address31 31[0:4] 31 +uWire_LOCK 31[5] 0 reg_locked=0, reg_unlocked=1 +Required_615 31[6:15] 0 +READBACK_ADDR 31[16:20] 31 R0=0, R1=1, R2=2, R3=3, R4=4, R5=5, R6=6, R7=7, R8=8, R10=10, R11=11, R12=12, R13=13, R14=14, R15=15, R24=24, R25=25, R26=26, R27=27, R28=28, R29=29, R30=30, R31=31 +READBACK_LE 31[21] 0 LE_low=0, LE_high=1 +Required_2231 31[22:31] 0 +""" +######################################################################## +# Template for methods in the body of the struct +######################################################################## + +BODY_TMPL = """\ + + + + +boost::uint32_t get_reg(int addr){ + boost::uint32_t reg = 0; + switch(addr){ + #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) + 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(); + #end for + break; + #end for + } + return reg; +} +""" + + + + + +if __name__ == '__main__': + import common; common.generate( + name='lmk04816_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) + + + + diff --git a/host/lib/transport/CMakeLists.txt b/host/lib/transport/CMakeLists.txt index 963edcf85..5920f3d78 100644 --- a/host/lib/transport/CMakeLists.txt +++ b/host/lib/transport/CMakeLists.txt @@ -20,6 +20,11 @@ ######################################################################## ######################################################################## +# Include subdirectories (different than add) +######################################################################## +INCLUDE_SUBDIRECTORY(nirio) + +######################################################################## # Setup libusb ######################################################################## MESSAGE(STATUS "") @@ -119,7 +124,18 @@ LIBUHD_PYTHON_GEN_SOURCE( ) LIBUHD_APPEND_SOURCES( + ${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 ) + +# 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" "" ) +IF(UHD_TXRX_DEBUG_PRINTS) + MESSAGE(STATUS "Using verbose debug output for send/recv") + ADD_DEFINITIONS(-DUHD_TXRX_DEBUG_PRINTS) +ENDIF() + diff --git a/host/lib/transport/buffer_pool.cpp b/host/lib/transport/buffer_pool.cpp index 971bbb75a..0fcebecb1 100644 --- a/host/lib/transport/buffer_pool.cpp +++ b/host/lib/transport/buffer_pool.cpp @@ -16,11 +16,21 @@ // #include <uhd/transport/buffer_pool.hpp> +#include <uhd/transport/zero_copy.hpp> #include <boost/shared_array.hpp> #include <vector> using namespace uhd::transport; +#ifdef UHD_TXRX_DEBUG_PRINTS +/* + * This is the implementation for the static variable 's_buffer_count' + * located in uhd/transport/zero_copy.hpp. + * It is used in the managed_buffer class. + */ +boost::detail::atomic_count managed_buffer::s_buffer_count(0); +#endif // UHD_TXRX_DEBUG_PRINTS + //! pad the byte count to a multiple of alignment static size_t pad_to_boundary(const size_t bytes, const size_t alignment){ return bytes + (alignment - bytes)%alignment; diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp index 8bd0f4354..23117f128 100644 --- a/host/lib/transport/libusb1_base.cpp +++ b/host/lib/transport/libusb1_base.cpp @@ -21,6 +21,7 @@ #include <uhd/utils/log.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/types/dict.hpp> +#include <uhd/types/serial.hpp> #include <boost/weak_ptr.hpp> #include <boost/thread/mutex.hpp> #include <boost/foreach.hpp> @@ -184,7 +185,14 @@ public: ); if (ret < 0) return ""; //on error, just return empty string - return std::string((char *)buff, ret); + std::string string_descriptor((char *)buff, ret); + byte_vector_t string_vec(string_descriptor.begin(), string_descriptor.end()); + std::string out; + BOOST_FOREACH(boost::uint8_t byte, string_vec){ + if (byte < 32 or byte > 127) return out; + out += byte; + } + return out; } private: diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp index 2d18e1623..4ea2032e3 100644 --- a/host/lib/transport/libusb1_zero_copy.cpp +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -31,6 +31,12 @@ #include <boost/thread/condition_variable.hpp> #include <list> +#ifdef UHD_TXRX_DEBUG_PRINTS +#include <vector> +#include <fstream> +#include <boost/format.hpp> +#endif + using namespace uhd; using namespace uhd::transport; @@ -69,12 +75,23 @@ struct lut_result_t completed = 0; status = LIBUSB_TRANSFER_COMPLETED; actual_length = 0; +#ifdef UHD_TXRX_DEBUG_PRINTS + start_time = 0; + buff_num = -1; +#endif } int completed; libusb_transfer_status status; int actual_length; boost::mutex mut; boost::condition_variable usb_transfer_complete; + +#ifdef UHD_TXRX_DEBUG_PRINTS + // These are fore debugging + long start_time; + int buff_num; + bool is_recv; +#endif }; // Created to be used as an argument to boost::condition_variable::timed_wait() function @@ -84,6 +101,14 @@ struct lut_result_completed { bool operator()() const {return (_result.completed ? true : false);} }; +#ifdef UHD_TXRX_DEBUG_PRINTS +static std::string dbg_prefix("libusb1_zero_copy,"); +static void libusb1_zerocopy_dbg_print_err(std::string msg){ + msg = dbg_prefix + msg; + fprintf(stderr, "%s\n", msg.c_str()); +} +#endif + /*! * All libusb callback functions should be marked with the LIBUSB_CALL macro * to ensure that they are compiled with the same calling convention as libusb. @@ -98,6 +123,10 @@ static void LIBUSB_CALL libusb_async_cb(libusb_transfer *lut) r->actual_length = lut->actual_length; r->completed = 1; r->usb_transfer_complete.notify_one(); // wake up thread waiting in wait_for_completion() member function below +#ifdef UHD_TXRX_DEBUG_PRINTS + long end_time = boost::get_system_time().time_of_day().total_microseconds(); + libusb1_zerocopy_dbg_print_err( (boost::format("libusb_async_cb,%s,%i,%i,%i,%ld,%ld") % (r->is_recv ? "rx":"tx") % r->buff_num % r->actual_length % r->status % end_time % r->start_time).str() ); +#endif } /*********************************************************************** @@ -113,11 +142,18 @@ public: _ctx(libusb::session::get_global_session()->get_context()), _lut(lut), _frame_size(frame_size) { /* NOP */ } - void release(void){_release_cb(this);} + void release(void){ + _release_cb(this); + } UHD_INLINE void submit(void) { - _lut->length = (_is_recv)? _frame_size : size(); //always set length + _lut->length = (_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); if (ret != 0) throw uhd::runtime_error(str(boost::format( "usb %s submit failed: %s") % _name % libusb_error_name(ret))); @@ -160,7 +196,6 @@ public: } private: - boost::function<void(libusb_zero_copy_mb *)> _release_cb; const bool _is_recv; const std::string _name; @@ -214,7 +249,7 @@ public: UHD_ASSERT_THROW(lut != NULL); _mb_pool.push_back(boost::make_shared<libusb_zero_copy_mb>( - lut, this->get_frame_size(), boost::bind(&libusb_zero_copy_single::enqueue_damn_buffer, this, _1), is_recv, name + lut, this->get_frame_size(), boost::bind(&libusb_zero_copy_single::enqueue_buffer, this, _1), is_recv, name )); libusb_fill_bulk_transfer( @@ -304,7 +339,7 @@ private: //! why 2 queues? there is room in the future to have > N buffers but only N in flight boost::circular_buffer<libusb_zero_copy_mb *> _enqueued, _released; - void enqueue_damn_buffer(libusb_zero_copy_mb *mb) + void enqueue_buffer(libusb_zero_copy_mb *mb) { boost::mutex::scoped_lock l(_mutex); _released.push_back(mb); diff --git a/host/lib/transport/nirio/CMakeLists.txt b/host/lib/transport/nirio/CMakeLists.txt new file mode 100644 index 000000000..6a33da6c5 --- /dev/null +++ b/host/lib/transport/nirio/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# 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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Include subdirectories (different than add) +######################################################################## +INCLUDE_SUBDIRECTORY(lvbitx) +INCLUDE_SUBDIRECTORY(rpc) + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/nifpga_lvbitx.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/niusrprio_session.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/niriok_proxy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nirio_resource_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/status.cpp +) + +IF(WIN32) + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/nirio_driver_iface_win.cpp) +ELSE(WIN32) + IF(APPLE) + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/nirio_driver_iface_macos.cpp) + ELSE(APPLE) + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_SOURCE_DIR}/nirio_driver_iface_linux.cpp) + ENDIF(APPLE) +ENDIF(WIN32) diff --git a/host/lib/transport/nirio/lvbitx/CMakeLists.txt b/host/lib/transport/nirio/lvbitx/CMakeLists.txt new file mode 100644 index 000000000..35cfaa456 --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/CMakeLists.txt @@ -0,0 +1,65 @@ +# +# 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/>. +# + +MACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM lvbitx binfile) + GET_FILENAME_COMPONENT(lvbitxprefix ${lvbitx} NAME_WE) + + IF( ${binfile} STREQUAL "OFF" ) + SET( GEN_OPTIONS ) + MESSAGE( STATUS " Using ${lvbitx} for codegen" ) + ELSE( ${binfile} STREQUAL "OFF" ) + SET( GEN_OPTIONS --merge-bin=${CMAKE_SOURCE_DIR}/../binaries/${binfile} --output-lvbitx-path=${CMAKE_SOURCE_DIR}/../binaries ) + MESSAGE( STATUS " Merging ${lvbitx} with ${binfile} for codegen" ) + ENDIF( ${binfile} STREQUAL "OFF" ) + + SET(OUTPUT_PATH_OPT --output-src-path=${CMAKE_CURRENT_BINARY_DIR}) + SET(IMAGES_PATH_OPT --uhd-images-path=${FPGA_IMAGES_DIR}) + + ADD_CUSTOM_COMMAND( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.hpp + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/process-lvbitx.py + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.hpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/template_lvbitx.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${lvbitx} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/process-lvbitx.py ${OUTPUT_PATH_OPT} ${IMAGES_PATH_OPT} ${GEN_OPTIONS} ${CMAKE_CURRENT_SOURCE_DIR}/${lvbitx} + COMMENT "Generating ${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp" + ) + + #make libuhd depend on the output file + LIBUHD_APPEND_SOURCES(${CMAKE_CURRENT_BINARY_DIR}/${lvbitxprefix}_lvbitx.cpp) +ENDMACRO(LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + +######################################################################## +# Generation code +######################################################################## + +MESSAGE(STATUS "") +MESSAGE(STATUS "Processing NI-RIO FPGA LVBITX Bitstreams...") + +FILE(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/share/uhd/images default_images_dir) +SET( FPGA_IMAGES_DIR ${default_images_dir} CACHE STRING "Path to installed FPGA image files." ) +OPTION( FPGA_IMAGES_DIR "Path to installed FPGA image files." "" ) +MESSAGE( STATUS " LVBITX install directory: ${FPGA_IMAGES_DIR}" ) + +# X300 Stuff +LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM(x300.lvbitx_base OFF) + +# X310 Stuff +LIBUHD_LVBITX_GEN_SOURCE_AND_BITSTREAM(x310.lvbitx_base OFF) diff --git a/host/lib/transport/nirio/lvbitx/process-lvbitx.py b/host/lib/transport/nirio/lvbitx/process-lvbitx.py new file mode 100755 index 000000000..a3d88d0bb --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/process-lvbitx.py @@ -0,0 +1,185 @@ +#!/usr/bin/python +# +# 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/>. +# + +from xml.etree import ElementTree +from collections import namedtuple +import optparse +import base64 +import hashlib +import os +import sys + +# Parse options +parser = optparse.OptionParser() +parser.add_option("--uhd-images-path", type="string", dest="uhd_images_path", help="Install location for UHD images", default='') +parser.add_option("--merge-bin", type="string", dest="merge_bin", help="Path to bin file that needs to be merged with the LVBITX before exporting", default=None) +parser.add_option("--output-bin", action="store_true", dest="output_bin", help="Generate a binary FPGA programming bitstream file", default=False) +parser.add_option("--output-lvbitx-path", type="string", dest="output_lvbitx_path", help="Output path for autogenerated LVBITX file", default=None) +parser.add_option("--output-src-path", type="string", dest="output_src_path", help="Output path for autogenerated src file", default=None) +(options, args) = parser.parse_args() + +# Args +if (len(args) < 1): + print 'ERROR: Please specify the input LVBITX file name' + sys.exit(1) + +lvbitx_filename = args[0] +input_filename = os.path.abspath(lvbitx_filename) +autogen_src_path = os.path.abspath(options.output_src_path) if (options.output_src_path is not None) else os.path.dirname(input_filename) +class_name = os.path.splitext(os.path.basename(input_filename))[0] + +if (not os.path.isfile(input_filename)): + print 'ERROR: FPGA File ' + input_filename + ' could not be accessed or is not a file.' + sys.exit(1) +if (options.merge_bin is not None and not os.path.isfile(os.path.abspath(options.merge_bin))): + print 'ERROR: FPGA Bin File ' + options.merge_bin + ' could not be accessed or is not a file.' + sys.exit(1) +if (not os.path.exists(autogen_src_path)): + print 'ERROR: Output path ' + autogen_src_path + ' could not be accessed.' + sys.exit(1) +if (options.output_lvbitx_path is not None and input_filename == os.path.join(autogen_src_path, class_name + '.lvbitx')): + print 'ERROR: Input and output LVBITX files were the same. Choose a difference input file or output path.' + sys.exit(1) + +# Get XML Tree Node +tree = ElementTree.parse(input_filename) +root = tree.getroot() +codegen_transform = {} + +# General info +codegen_transform['autogen_msg'] = '// Auto-generated file: DO NOT EDIT!\n// Generated from a LabVIEW FPGA LVBITX image using "process-lvbitx.py"' +codegen_transform['lvbitx_search_paths'] = options.uhd_images_path.replace('\\', '\\\\') +codegen_transform['lvbitx_classname'] = class_name +codegen_transform['lvbitx_classname_u'] = class_name.upper() +bitstream_version = root.find('BitstreamVersion').text + +# Enumerate registers (controls and indicators) +register_list = root.find('VI').find('RegisterList') + +reg_init_seq = '' +control_list = '' +indicator_list = '' +control_idx = 0 +indicator_idx = 0 +for register in register_list.findall('Register'): + reg_type = 'INDICATOR' if (register.find('Indicator').text.lower() == 'true') else 'CONTROL' + reg_name = '\"' + register.find('Name').text + '\"' + + if (reg_type == 'INDICATOR'): + indicator_list += '\n ' + reg_name + ',' + idx = indicator_idx + indicator_idx += 1 + else: + control_list += '\n ' + reg_name + ',' + idx = control_idx + control_idx += 1 + + reg_init_seq += '\n vtr.push_back(nirio_register_info_t(' + reg_init_seq += hex(int(register.find('Offset').text)) + ', ' + reg_init_seq += reg_type + 'S[' + str(idx) + '], ' + reg_init_seq += reg_type + reg_init_seq += ')); //' + reg_name + + +codegen_transform['register_init'] = reg_init_seq +codegen_transform['control_list'] = control_list +codegen_transform['indicator_list'] = indicator_list + +# Enumerate FIFOs +nifpga_metadata = root.find('Project').find('CompilationResultsTree').find('CompilationResults').find('NiFpga') +dma_channel_list = nifpga_metadata.find('DmaChannelAllocationList') +reg_block_list = nifpga_metadata.find('RegisterBlockList') + +fifo_init_seq = '' +out_fifo_list = '' +in_fifo_list = '' +out_fifo_idx = 0 +in_fifo_idx = 0 +for dma_channel in dma_channel_list: + fifo_name = '\"' + dma_channel.attrib['name'] + '\"' + direction = 'OUTPUT_FIFO' if (dma_channel.find('Direction').text == 'HostToTarget') else 'INPUT_FIFO' + for reg_block in reg_block_list.findall('RegisterBlock'): + if (reg_block.attrib['name'] == dma_channel.find('BaseAddressTag').text): + base_addr = reg_block.find('Offset').text + break + + if (direction == 'OUTPUT_FIFO'): + out_fifo_list += '\n ' + fifo_name + ',' + idx = out_fifo_idx + out_fifo_idx += 1 + else: + in_fifo_list += '\n ' + fifo_name + ',' + idx = in_fifo_idx + in_fifo_idx += 1 + + fifo_init_seq += '\n vtr.push_back(nirio_fifo_info_t(' + fifo_init_seq += dma_channel.find('Number').text + ', ' + fifo_init_seq += direction + 'S[' + str(idx) + '], ' + fifo_init_seq += direction + ', ' + fifo_init_seq += str.lower(base_addr) + ', ' + fifo_init_seq += dma_channel.find('NumberOfElements').text + ', ' + fifo_init_seq += 'SCALAR_' + dma_channel.find('DataType').find('SubType').text + ', ' + fifo_init_seq += dma_channel.find('DataType').find('WordLength').text + ', ' + fifo_init_seq += bitstream_version + fifo_init_seq += ')); //' + fifo_name + + +codegen_transform['fifo_init'] = fifo_init_seq +codegen_transform['out_fifo_list'] = out_fifo_list +codegen_transform['in_fifo_list'] = in_fifo_list + +# Merge bitstream into LVBITX +if (options.merge_bin is not None): + with open(os.path.abspath(options.merge_bin), 'rb') as bin_file: + bitstream = bin_file.read() + bitstream_md5 = hashlib.md5(bitstream).hexdigest() + bitstream_b64 = base64.b64encode(bitstream) + bitstream_b64_lb = '' + for i in range(0, len(bitstream_b64), 76): + bitstream_b64_lb += bitstream_b64[i:i+76] + '\n' + + root.find('Bitstream').text = bitstream_b64_lb + root.find('BitstreamMD5').text = bitstream_md5 + +codegen_transform['lvbitx_signature'] = str.upper(root.find('SignatureRegister').text) + +# Write BIN file +bitstream = base64.b64decode(root.find('Bitstream').text) +if (options.output_lvbitx_path is not None and hashlib.md5(bitstream).hexdigest() != root.find('BitstreamMD5').text): + print 'ERROR: The MD5 sum for the output LVBITX was incorrect. Make sure that the bitstream in the input LVBITX or BIN file is valid.' + sys.exit(1) +if (options.output_bin): + fpga_bin_file = open(os.path.join(options.output_lvbitx_path, class_name + '.bin'), 'w') + fpga_bin_file.write(bitstream) + fpga_bin_file.close() + +# Save LVBITX +if (options.output_lvbitx_path is not None): + tree.write(os.path.join(options.output_lvbitx_path, class_name + '_fpga.lvbitx'), encoding="utf-8", xml_declaration=True, default_namespace=None, method="xml") + +# Save HPP and CPP +with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'template_lvbitx.hpp'), 'r') as template_file: + template_string = template_file.read() +with open(os.path.join(autogen_src_path, class_name + '_lvbitx.hpp'), 'w') as source_file: + source_file.write(template_string.format(**codegen_transform)) + +with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'template_lvbitx.cpp'), 'r') as template_file: + template_string = template_file.read() +with open(os.path.join(autogen_src_path, class_name + '_lvbitx.cpp'), 'w') as source_file: + source_file.write(template_string.format(**codegen_transform)) + diff --git a/host/lib/transport/nirio/lvbitx/template_lvbitx.cpp b/host/lib/transport/nirio/lvbitx/template_lvbitx.cpp new file mode 100644 index 000000000..a1899c771 --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/template_lvbitx.cpp @@ -0,0 +1,86 @@ +{autogen_msg} + +#include "{lvbitx_classname}_lvbitx.hpp" +#include <string> +#include <iostream> +#include <fstream> +#include <streambuf> +#include <boost/filesystem/path.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> + +namespace uhd {{ namespace niusrprio {{ + +#define SEARCH_PATHS "{lvbitx_search_paths}" + +const char* {lvbitx_classname}_lvbitx::CONTROLS[] = {{{control_list} +}}; + +const char* {lvbitx_classname}_lvbitx::INDICATORS[] = {{{indicator_list} +}}; + +const char* {lvbitx_classname}_lvbitx::OUTPUT_FIFOS[] = {{{out_fifo_list} +}}; + +const char* {lvbitx_classname}_lvbitx::INPUT_FIFOS[] = {{{in_fifo_list} +}}; + +{lvbitx_classname}_lvbitx::{lvbitx_classname}_lvbitx(const std::string& option) +{{ + boost::filesystem::path fpga_path(_get_fpga_images_dir(SEARCH_PATHS)); + fpga_path /= "usrp_{lvbitx_classname}_fpga_" + option + ".lvbitx"; + _fpga_file_name = fpga_path.string(); + _bitstream_checksum = _get_bitstream_checksum(_fpga_file_name); +}} + +const char* {lvbitx_classname}_lvbitx::get_bitfile_path() {{ + return _fpga_file_name.c_str(); +}} + +const char* {lvbitx_classname}_lvbitx::get_signature() {{ + return "{lvbitx_signature}"; +}} + +const char* {lvbitx_classname}_lvbitx::get_bitstream_checksum() {{ + return _bitstream_checksum.c_str(); +}} + +size_t {lvbitx_classname}_lvbitx::get_input_fifo_count() {{ + return sizeof(INPUT_FIFOS)/sizeof(*INPUT_FIFOS); +}} + +const char** {lvbitx_classname}_lvbitx::get_input_fifo_names() {{ + return INPUT_FIFOS; +}} + +size_t {lvbitx_classname}_lvbitx::get_output_fifo_count() {{ + return sizeof(OUTPUT_FIFOS)/sizeof(*OUTPUT_FIFOS); +}} + +const char** {lvbitx_classname}_lvbitx::get_output_fifo_names() {{ + return OUTPUT_FIFOS; +}} + +size_t {lvbitx_classname}_lvbitx::get_control_count() {{ + return sizeof(CONTROLS)/sizeof(*CONTROLS); +}} + +const char** {lvbitx_classname}_lvbitx::get_control_names() {{ + return CONTROLS; +}} + +size_t {lvbitx_classname}_lvbitx::get_indicator_count() {{ + return sizeof(INDICATORS)/sizeof(*INDICATORS); +}} + +const char** {lvbitx_classname}_lvbitx::get_indicator_names() {{ + return INDICATORS; +}} + +void {lvbitx_classname}_lvbitx::init_register_info(nirio_register_info_vtr& vtr) {{ {register_init} +}} + +void {lvbitx_classname}_lvbitx::init_fifo_info(nirio_fifo_info_vtr& vtr) {{ {fifo_init} +}} + +}}}} diff --git a/host/lib/transport/nirio/lvbitx/template_lvbitx.hpp b/host/lib/transport/nirio/lvbitx/template_lvbitx.hpp new file mode 100644 index 000000000..d8872e15c --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/template_lvbitx.hpp @@ -0,0 +1,48 @@ +{autogen_msg} + +#ifndef INCLUDED_{lvbitx_classname_u}_LVBITX_HPP +#define INCLUDED_{lvbitx_classname_u}_LVBITX_HPP + +#include <uhd/transport/nirio/nifpga_lvbitx.h> + +namespace uhd {{ namespace niusrprio {{ + +class {lvbitx_classname}_lvbitx : public nifpga_lvbitx {{ +public: + {lvbitx_classname}_lvbitx(const std::string& option); + + virtual ~{lvbitx_classname}_lvbitx() {{}}; + + virtual const char* get_bitfile_path(); + virtual const char* get_signature(); + virtual const char* get_bitstream_checksum(); + + virtual size_t get_input_fifo_count(); + virtual const char** get_input_fifo_names(); + + virtual size_t get_output_fifo_count(); + virtual const char** get_output_fifo_names(); + + virtual size_t get_control_count(); + virtual const char** get_control_names(); + + virtual size_t get_indicator_count(); + virtual const char** get_indicator_names(); + + virtual void init_register_info(nirio_register_info_vtr& vtr); + virtual void init_fifo_info(nirio_fifo_info_vtr& vtr); + + static const char* CONTROLS[]; + static const char* INDICATORS[]; + static const char* OUTPUT_FIFOS[]; + static const char* INPUT_FIFOS[]; + +private: + std::string _fpga_file_name; + std::string _bitstream_checksum; +}}; + +}}}} + +#endif /* INCLUDED_{lvbitx_classname_u}_LVBITX_HPP */ + diff --git a/host/lib/transport/nirio/lvbitx/x300.lvbitx_base b/host/lib/transport/nirio/lvbitx/x300.lvbitx_base new file mode 100755 index 000000000..c264e7157 --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/x300.lvbitx_base @@ -0,0 +1,469 @@ +<?xml version="1.0" encoding="utf-8"?> +<Bitfile> + <BitfileVersion>4.0</BitfileVersion> + <Documentation> + <BuildSpecVersion/> + <BuildSpecDescription/> + </Documentation> + <SignatureRegister>97C6D9F4F4829001B83378F93CAB0C94</SignatureRegister> + <SignatureGuids>7BAD6AEB9741248079F13147B3F8AD94</SignatureGuids> + <SignatureNames>AE54C47F787D92DB46F7DC973338D786</SignatureNames> + <TimeStamp/> + <CompilationStatus/> + <BitstreamVersion>2</BitstreamVersion> + <VI> + <Name>USRP_X3x0_Top.vi</Name> + <RegisterList> + <Register> + <Name>ViSignature</Name> + <Hidden>true</Hidden> + <Indicator>true</Indicator> + <Datatype> + <Array> + <Name/> + <Size>4</Size> + <Type> + <U32> + <Name/> + </U32> + </Type> + </Array> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262132</Offset> + <SizeInBits>128</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>false</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>DiagramReset</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262140</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>ViControl</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262136</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptEnable</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262116</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptMask</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262124</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptStatus</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262128</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + </RegisterList> + <Icon> + <ImageType>0</ImageType> + <ImageDepth>8</ImageDepth> + <Image>////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA/////////////////////////wAAAAAAAAAAAP//AAD/+fn5+fn5+fn5+fn59/ks+SzgAAAAAAAAAAAA//8AAP/5///////////////3+Sz5LP8AAAAAAAAAAAD//wAA//n/6OTo////6OTo//f8K/ws/wAAAAAAAAAAAP//AAD/+f/k/+T////k/+T/9ywsLCzgAAAAAAAAAAAA//8AAP/56OT/5Oj/6OT/5Oj3K/wrLP8AAAAAAAAAAAD//wAA//nk6P/o5P/k6P/o5Pf8CPws/wAAAAAAAAAAAP//AAD/+eT////k/+T////k9/wI/Cz/AAAAAAAAAAAA//8AAP/5/////+jk6P/////3K/wrLP8AAAAAAAAAAAD//wAA//n///////////////csLCws/wAAAAAAAAAAAP//AAD/9/f39/f39/f39/f39ywsg4P/AAAAAAAAAAAA//8AAP8sLCwsLCwsLCwsLCwsLCyDBYODAAAAAAAAAAD//wAA/yz8LCwsLCz8LCwsLCMjI4MFBQWDgwAAAAAAAP//AAD//PD8LCws/CP8LCMjLCwsgwUF/wUFg4MAAAAA//8AAP8s7ywsLCwjLCwjLCwsLCyDBf///wUFBYMjIwD//wAA///w////I///I////////4MFBf8FBYODAAAAAP//AAAAAO8AAAAjAAAjAADw7+/wgwUFBYODAAAAAAAA//8AAAAAAPAAAAAjIwAA7wAAAACDBYODAAAAAAAAAAD//wAAAAAAAO/vAAAAAPAAAAAAAIODAAAAAAAAAAAAAP//AAAAAAAAAADw7/DvAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////w==</Image> + <Mask>//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=</Mask> + <Colors>AP///wD//8wA//+ZAP//ZgD//zMA//8AAP/M/wD/zMwA/8yZAP/MZgD/zDMA/8wAAP+Z/wD/mcwA/5mZAP+ZZgD/mTMA/5kAAP9m/wD/ZswA/2aZAP9mZgD/ZjMA/2YAAP8z/wD/M8wA/zOZAP8zZgD/MzMA/zMAAP8A/wD/AMwA/wCZAP8AZgD/ADMA/wAAAMz//wDM/8wAzP+ZAMz/ZgDM/zMAzP8AAMzM/wDMzMwAzMyZAMzMZgDMzDMAzMwAAMyZ/wDMmcwAzJmZAMyZZgDMmTMAzJkAAMxm/wDMZswAzGaZAMxmZgDMZjMAzGYAAMwz/wDMM8wAzDOZAMwzZgDMMzMAzDMAAMwA/wDMAMwAzACZAMwAZgDMADMAzAAAAJn//wCZ/8wAmf+ZAJn/ZgCZ/zMAmf8AAJnM/wCZzMwAmcyZAJnMZgCZzDMAmcwAAJmZ/wCZmcwAmZmZAJmZZgCZmTMAmZkAAJlm/wCZZswAmWaZAJlmZgCZZjMAmWYAAJkz/wCZM8wAmTOZAJkzZgCZMzMAmTMAAJkA/wCZAMwAmQCZAJkAZgCZADMAmQAAAGb//wBm/8wAZv+ZAGb/ZgBm/zMAZv8AAGbM/wBmzMwAZsyZAGbMZgBmzDMAZswAAGaZ/wBmmcwAZpmZAGaZZgBmmTMAZpkAAGZm/wBmZswAZmaZAGZmZgBmZjMAZmYAAGYz/wBmM8wAZjOZAGYzZgBmMzMAZjMAAGYA/wBmAMwAZgCZAGYAZgBmADMAZgAAADP//wAz/8wAM/+ZADP/ZgAz/zMAM/8AADPM/wAzzMwAM8yZADPMZgAzzDMAM8wAADOZ/wAzmcwAM5mZADOZZgAzmTMAM5kAADNm/wAzZswAM2aZADNmZgAzZjMAM2YAADMz/wAzM8wAMzOZADMzZgAzMzMAMzMAADMA/wAzAMwAMwCZADMAZgAzADMAMwAAAAD//wAA/8wAAP+ZAAD/ZgAA/zMAAP8AAADM/wAAzMwAAMyZAADMZgAAzDMAAMwAAACZ/wAAmcwAAJmZAACZZgAAmTMAAJkAAABm/wAAZswAAGaZAABmZgAAZjMAAGYAAAAz/wAAM8wAADOZAAAzZgAAMzMAADMAAAAA/wAAAMwAAACZAAAAZgAAADMA7gAAAN0AAAC7AAAAqgAAAIgAAAB3AAAAVQAAAEQAAAAiAAAAEQAAAADuAAAA3QAAALsAAACqAAAAiAAAAHcAAABVAAAARAAAACIAAAARAAAAAO4AAADdAAAAuwAAAKoAAACIAAAAdwAAAFUAAABEAAAAIgAAABEA7u7uAN3d3QC7u7sAqqqqAIiIiAB3d3cAVVVVAERERAAiIiIAERERAAAAAA==</Colors> + <Rectangle> + <Left>0</Left> + <Top>0</Top> + <Right>32</Right> + <Bottom>32</Bottom> + </Rectangle> + </Icon> + </VI> + <Project> + <TargetClass>294XR; 295XR</TargetClass> + <AutoRunWhenDownloaded>false</AutoRunWhenDownloaded> + <CompilationResultsTree> + <CompilationResults> + <NiFpga> + <BaseAddressOnDevice>0</BaseAddressOnDevice> + <DmaChannelAllocationList> + <Channel name="RX FIFO 0"> + <BaseAddressTag>NiLvFpgaFIFO 0</BaseAddressTag> + <ControlSet>0</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>0</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 1"> + <BaseAddressTag>NiLvFpgaFIFO 1</BaseAddressTag> + <ControlSet>1</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>1</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 2"> + <BaseAddressTag>NiLvFpgaFIFO 2</BaseAddressTag> + <ControlSet>2</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>2</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 3"> + <BaseAddressTag>NiLvFpgaFIFO 3</BaseAddressTag> + <ControlSet>3</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>3</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 4"> + <BaseAddressTag>NiLvFpgaFIFO 4</BaseAddressTag> + <ControlSet>4</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>4</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 5"> + <BaseAddressTag>NiLvFpgaFIFO 5</BaseAddressTag> + <ControlSet>5</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>5</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 0"> + <BaseAddressTag>NiLvFpgaFIFO 6</BaseAddressTag> + <ControlSet>6</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>6</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 1"> + <BaseAddressTag>NiLvFpgaFIFO 7</BaseAddressTag> + <ControlSet>7</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>7</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 2"> + <BaseAddressTag>NiLvFpgaFIFO 8</BaseAddressTag> + <ControlSet>8</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>8</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 3"> + <BaseAddressTag>NiLvFpgaFIFO 9</BaseAddressTag> + <ControlSet>9</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>9</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 4"> + <BaseAddressTag>NiLvFpgaFIFO 10</BaseAddressTag> + <ControlSet>10</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>10</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 5"> + <BaseAddressTag>NiLvFpgaFIFO 11</BaseAddressTag> + <ControlSet>11</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>11</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + </DmaChannelAllocationList> + <RegisterBlockList> + <RegisterBlock name="NiLvFpgaFIFO 0"> + <Offset>0xFF80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 1"> + <Offset>0xFF40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 2"> + <Offset>0xFF00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 3"> + <Offset>0xFEC0</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 4"> + <Offset>0xFE80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 5"> + <Offset>0xFE40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 6"> + <Offset>0xFE00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 7"> + <Offset>0xFDC0</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 8"> + <Offset>0xFD80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 9"> + <Offset>0xFD40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 10"> + <Offset>0xFD00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 11"> + <Offset>0xFCC0</Offset> + </RegisterBlock> + </RegisterBlockList> + <UsedBaseClockList> + <BaseClock name="ReliableClkIn"> + </BaseClock> + <BaseClock name="ChinchClk"> + </BaseClock> + <BaseClock name="40 MHz Onboard Clock"> + </BaseClock> + </UsedBaseClockList> + <version>1</version> + </NiFpga> + </CompilationResults> + </CompilationResultsTree> + <MultipleUserClocks>false</MultipleUserClocks> + <AllowImplicitEnableRemoval>false</AllowImplicitEnableRemoval> + </Project> + <ClientData/> + <BitstreamMD5>a72ba1716893a0bd02f88dac3ab28e1b</BitstreamMD5> + <Bitstream>a72ba1716893a0bd02f88dac3ab28e1b</Bitstream> +</Bitfile> diff --git a/host/lib/transport/nirio/lvbitx/x310.lvbitx_base b/host/lib/transport/nirio/lvbitx/x310.lvbitx_base new file mode 100755 index 000000000..c264e7157 --- /dev/null +++ b/host/lib/transport/nirio/lvbitx/x310.lvbitx_base @@ -0,0 +1,469 @@ +<?xml version="1.0" encoding="utf-8"?> +<Bitfile> + <BitfileVersion>4.0</BitfileVersion> + <Documentation> + <BuildSpecVersion/> + <BuildSpecDescription/> + </Documentation> + <SignatureRegister>97C6D9F4F4829001B83378F93CAB0C94</SignatureRegister> + <SignatureGuids>7BAD6AEB9741248079F13147B3F8AD94</SignatureGuids> + <SignatureNames>AE54C47F787D92DB46F7DC973338D786</SignatureNames> + <TimeStamp/> + <CompilationStatus/> + <BitstreamVersion>2</BitstreamVersion> + <VI> + <Name>USRP_X3x0_Top.vi</Name> + <RegisterList> + <Register> + <Name>ViSignature</Name> + <Hidden>true</Hidden> + <Indicator>true</Indicator> + <Datatype> + <Array> + <Name/> + <Size>4</Size> + <Type> + <U32> + <Name/> + </U32> + </Type> + </Array> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262132</Offset> + <SizeInBits>128</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>false</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>DiagramReset</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262140</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>ViControl</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262136</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptEnable</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262116</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptMask</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262124</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + <Register> + <Name>InterruptStatus</Name> + <Hidden>false</Hidden> + <Indicator>false</Indicator> + <Datatype> + <U32> + <Name/> + </U32> + </Datatype> + <FlattenedType/> + <Grouping/> + <Offset>262128</Offset> + <SizeInBits>32</SizeInBits> + <Class>0</Class> + <Internal>true</Internal> + <TypedefPath/> + <TypedefRelativePath/> + <ID>0</ID> + <Bidirectional>true</Bidirectional> + <Synchronous>false</Synchronous> + <MechanicalAction>Switch When Pressed</MechanicalAction> + <AccessMayTimeout>false</AccessMayTimeout> + <RegisterNode>false</RegisterNode> + <SubControlList/> + </Register> + </RegisterList> + <Icon> + <ImageType>0</ImageType> + <ImageDepth>8</ImageDepth> + <Image>////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA/////////////////////////wAAAAAAAAAAAP//AAD/+fn5+fn5+fn5+fn59/ks+SzgAAAAAAAAAAAA//8AAP/5///////////////3+Sz5LP8AAAAAAAAAAAD//wAA//n/6OTo////6OTo//f8K/ws/wAAAAAAAAAAAP//AAD/+f/k/+T////k/+T/9ywsLCzgAAAAAAAAAAAA//8AAP/56OT/5Oj/6OT/5Oj3K/wrLP8AAAAAAAAAAAD//wAA//nk6P/o5P/k6P/o5Pf8CPws/wAAAAAAAAAAAP//AAD/+eT////k/+T////k9/wI/Cz/AAAAAAAAAAAA//8AAP/5/////+jk6P/////3K/wrLP8AAAAAAAAAAAD//wAA//n///////////////csLCws/wAAAAAAAAAAAP//AAD/9/f39/f39/f39/f39ywsg4P/AAAAAAAAAAAA//8AAP8sLCwsLCwsLCwsLCwsLCyDBYODAAAAAAAAAAD//wAA/yz8LCwsLCz8LCwsLCMjI4MFBQWDgwAAAAAAAP//AAD//PD8LCws/CP8LCMjLCwsgwUF/wUFg4MAAAAA//8AAP8s7ywsLCwjLCwjLCwsLCyDBf///wUFBYMjIwD//wAA///w////I///I////////4MFBf8FBYODAAAAAP//AAAAAO8AAAAjAAAjAADw7+/wgwUFBYODAAAAAAAA//8AAAAAAPAAAAAjIwAA7wAAAACDBYODAAAAAAAAAAD//wAAAAAAAO/vAAAAAPAAAAAAAIODAAAAAAAAAAAAAP//AAAAAAAAAADw7/DvAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////w==</Image> + <Mask>//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=</Mask> + <Colors>AP///wD//8wA//+ZAP//ZgD//zMA//8AAP/M/wD/zMwA/8yZAP/MZgD/zDMA/8wAAP+Z/wD/mcwA/5mZAP+ZZgD/mTMA/5kAAP9m/wD/ZswA/2aZAP9mZgD/ZjMA/2YAAP8z/wD/M8wA/zOZAP8zZgD/MzMA/zMAAP8A/wD/AMwA/wCZAP8AZgD/ADMA/wAAAMz//wDM/8wAzP+ZAMz/ZgDM/zMAzP8AAMzM/wDMzMwAzMyZAMzMZgDMzDMAzMwAAMyZ/wDMmcwAzJmZAMyZZgDMmTMAzJkAAMxm/wDMZswAzGaZAMxmZgDMZjMAzGYAAMwz/wDMM8wAzDOZAMwzZgDMMzMAzDMAAMwA/wDMAMwAzACZAMwAZgDMADMAzAAAAJn//wCZ/8wAmf+ZAJn/ZgCZ/zMAmf8AAJnM/wCZzMwAmcyZAJnMZgCZzDMAmcwAAJmZ/wCZmcwAmZmZAJmZZgCZmTMAmZkAAJlm/wCZZswAmWaZAJlmZgCZZjMAmWYAAJkz/wCZM8wAmTOZAJkzZgCZMzMAmTMAAJkA/wCZAMwAmQCZAJkAZgCZADMAmQAAAGb//wBm/8wAZv+ZAGb/ZgBm/zMAZv8AAGbM/wBmzMwAZsyZAGbMZgBmzDMAZswAAGaZ/wBmmcwAZpmZAGaZZgBmmTMAZpkAAGZm/wBmZswAZmaZAGZmZgBmZjMAZmYAAGYz/wBmM8wAZjOZAGYzZgBmMzMAZjMAAGYA/wBmAMwAZgCZAGYAZgBmADMAZgAAADP//wAz/8wAM/+ZADP/ZgAz/zMAM/8AADPM/wAzzMwAM8yZADPMZgAzzDMAM8wAADOZ/wAzmcwAM5mZADOZZgAzmTMAM5kAADNm/wAzZswAM2aZADNmZgAzZjMAM2YAADMz/wAzM8wAMzOZADMzZgAzMzMAMzMAADMA/wAzAMwAMwCZADMAZgAzADMAMwAAAAD//wAA/8wAAP+ZAAD/ZgAA/zMAAP8AAADM/wAAzMwAAMyZAADMZgAAzDMAAMwAAACZ/wAAmcwAAJmZAACZZgAAmTMAAJkAAABm/wAAZswAAGaZAABmZgAAZjMAAGYAAAAz/wAAM8wAADOZAAAzZgAAMzMAADMAAAAA/wAAAMwAAACZAAAAZgAAADMA7gAAAN0AAAC7AAAAqgAAAIgAAAB3AAAAVQAAAEQAAAAiAAAAEQAAAADuAAAA3QAAALsAAACqAAAAiAAAAHcAAABVAAAARAAAACIAAAARAAAAAO4AAADdAAAAuwAAAKoAAACIAAAAdwAAAFUAAABEAAAAIgAAABEA7u7uAN3d3QC7u7sAqqqqAIiIiAB3d3cAVVVVAERERAAiIiIAERERAAAAAA==</Colors> + <Rectangle> + <Left>0</Left> + <Top>0</Top> + <Right>32</Right> + <Bottom>32</Bottom> + </Rectangle> + </Icon> + </VI> + <Project> + <TargetClass>294XR; 295XR</TargetClass> + <AutoRunWhenDownloaded>false</AutoRunWhenDownloaded> + <CompilationResultsTree> + <CompilationResults> + <NiFpga> + <BaseAddressOnDevice>0</BaseAddressOnDevice> + <DmaChannelAllocationList> + <Channel name="RX FIFO 0"> + <BaseAddressTag>NiLvFpgaFIFO 0</BaseAddressTag> + <ControlSet>0</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>0</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 1"> + <BaseAddressTag>NiLvFpgaFIFO 1</BaseAddressTag> + <ControlSet>1</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>1</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 2"> + <BaseAddressTag>NiLvFpgaFIFO 2</BaseAddressTag> + <ControlSet>2</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>2</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 3"> + <BaseAddressTag>NiLvFpgaFIFO 3</BaseAddressTag> + <ControlSet>3</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>3</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 4"> + <BaseAddressTag>NiLvFpgaFIFO 4</BaseAddressTag> + <ControlSet>4</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>4</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="RX FIFO 5"> + <BaseAddressTag>NiLvFpgaFIFO 5</BaseAddressTag> + <ControlSet>5</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>TargetToHost</Direction> + <Implementation>niFpgaTargetToHost</Implementation> + <Number>5</Number> + <NumberOfElements>1023</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 0"> + <BaseAddressTag>NiLvFpgaFIFO 6</BaseAddressTag> + <ControlSet>6</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>6</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 1"> + <BaseAddressTag>NiLvFpgaFIFO 7</BaseAddressTag> + <ControlSet>7</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>7</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 2"> + <BaseAddressTag>NiLvFpgaFIFO 8</BaseAddressTag> + <ControlSet>8</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>8</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 3"> + <BaseAddressTag>NiLvFpgaFIFO 9</BaseAddressTag> + <ControlSet>9</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>9</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 4"> + <BaseAddressTag>NiLvFpgaFIFO 10</BaseAddressTag> + <ControlSet>10</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>10</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + <Channel name="TX FIFO 5"> + <BaseAddressTag>NiLvFpgaFIFO 11</BaseAddressTag> + <ControlSet>11</ControlSet> + <DataType> + <Delta>1.000000000000000000000000000000000000000000000000000000</Delta> + <IntegerWordLength>64</IntegerWordLength> + <Maximum>18446744073709551600.00000000000000000000000000000000000</Maximum> + <Minimum>0.000000000000000000000000000000000000000000000000000000</Minimum> + <Signed>false</Signed> + <SubType>U64</SubType> + <WordLength>64</WordLength> + </DataType> + <Direction>HostToTarget</Direction> + <Implementation>niFpgaHostToTarget</Implementation> + <Number>11</Number> + <NumberOfElements>1029</NumberOfElements> + <UserVisible>true</UserVisible> + </Channel> + </DmaChannelAllocationList> + <RegisterBlockList> + <RegisterBlock name="NiLvFpgaFIFO 0"> + <Offset>0xFF80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 1"> + <Offset>0xFF40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 2"> + <Offset>0xFF00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 3"> + <Offset>0xFEC0</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 4"> + <Offset>0xFE80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 5"> + <Offset>0xFE40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 6"> + <Offset>0xFE00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 7"> + <Offset>0xFDC0</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 8"> + <Offset>0xFD80</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 9"> + <Offset>0xFD40</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 10"> + <Offset>0xFD00</Offset> + </RegisterBlock> + <RegisterBlock name="NiLvFpgaFIFO 11"> + <Offset>0xFCC0</Offset> + </RegisterBlock> + </RegisterBlockList> + <UsedBaseClockList> + <BaseClock name="ReliableClkIn"> + </BaseClock> + <BaseClock name="ChinchClk"> + </BaseClock> + <BaseClock name="40 MHz Onboard Clock"> + </BaseClock> + </UsedBaseClockList> + <version>1</version> + </NiFpga> + </CompilationResults> + </CompilationResultsTree> + <MultipleUserClocks>false</MultipleUserClocks> + <AllowImplicitEnableRemoval>false</AllowImplicitEnableRemoval> + </Project> + <ClientData/> + <BitstreamMD5>a72ba1716893a0bd02f88dac3ab28e1b</BitstreamMD5> + <Bitstream>a72ba1716893a0bd02f88dac3ab28e1b</Bitstream> +</Bitfile> diff --git a/host/lib/transport/nirio/nifpga_lvbitx.cpp b/host/lib/transport/nirio/nifpga_lvbitx.cpp new file mode 100644 index 000000000..289a44d4a --- /dev/null +++ b/host/lib/transport/nirio/nifpga_lvbitx.cpp @@ -0,0 +1,138 @@ +// +// 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 <uhd/transport/nirio/nifpga_lvbitx.h> +#include <string> +#include <iostream> +#include <fstream> +#include <streambuf> +#include <boost/foreach.hpp> +#include <boost/format.hpp> +#include <boost/filesystem.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> + +namespace uhd { namespace niusrprio { + +std::string nifpga_lvbitx::_get_bitstream_checksum(const std::string& file_path) +{ + std::string checksum; + std::ifstream lvbitx_stream(file_path.c_str()); + if (lvbitx_stream.is_open()) { + std::string lvbitx_contents; + lvbitx_stream.seekg(0, std::ios::end); + lvbitx_contents.reserve(static_cast<size_t>(lvbitx_stream.tellg())); + lvbitx_stream.seekg(0, std::ios::beg); + lvbitx_contents.assign((std::istreambuf_iterator<char>(lvbitx_stream)), std::istreambuf_iterator<char>()); + try { + boost::smatch md5_match; + if (boost::regex_search(lvbitx_contents, md5_match, boost::regex("<BitstreamMD5>([a-zA-Z0-9]{32})<\\/BitstreamMD5>", boost::regex::icase))) { + checksum = std::string(md5_match[1].first, md5_match[1].second); + } + } catch (boost::exception&) { + checksum = ""; + } + } + boost::to_upper(checksum); + return checksum; +} + +#ifdef UHD_PLATFORM_WIN32 +#include <windows.h> + +std::string _get_path_from_registry(const std::string& registry_key_path) +{ + boost::smatch reg_key_match; + //If a substring in the search path is enclosed in [] (square brackets) then it is interpreted as a registry path + if (not boost::regex_search(registry_key_path, reg_key_match, boost::regex("\\[(.+)\\](.*)", boost::regex::icase))) + return std::string(); + std::string reg_key_path = std::string(reg_key_match[1].first, reg_key_match[1].second); + std::string path_suffix = std::string(reg_key_match[2].first, reg_key_match[2].second); + + //Split the registry path into parent, key-path and value. + boost::smatch reg_parent_match; + if (not boost::regex_search(reg_key_path, reg_parent_match, boost::regex("^(.+?)\\\\(.+)\\\\(.+)$", boost::regex::icase))) + return std::string(); + std::string reg_parent = std::string(reg_parent_match[1].first, reg_parent_match[1].second); + std::string reg_path = std::string(reg_parent_match[2].first, reg_parent_match[2].second); + std::string reg_val_name = std::string(reg_parent_match[3].first, reg_parent_match[3].second); + + HKEY hkey_parent = HKEY_LOCAL_MACHINE; + if (reg_parent == "HKEY_LOCAL_MACHINE") hkey_parent = HKEY_LOCAL_MACHINE; + else if (reg_parent == "HKEY_CURRENT_USER") hkey_parent = HKEY_CURRENT_USER; + else if (reg_parent == "HKEY_CLASSES_ROOT") hkey_parent = HKEY_CLASSES_ROOT; + else if (reg_parent == "HKEY_CURRENT_CONFIG") hkey_parent = HKEY_CURRENT_CONFIG; + else if (reg_parent == "HKEY_USERS") hkey_parent = HKEY_CURRENT_USER; + + TCHAR value_buff[1024]; + DWORD value_buff_size = 1024*sizeof(TCHAR); + + //Get a handle to the key location + HKEY hkey_location; + if (RegOpenKeyExA(hkey_parent, reg_path.c_str(), NULL, KEY_QUERY_VALUE, &hkey_location) != ERROR_SUCCESS) + return std::string(); + + //Query key value + DWORD dw_type = REG_SZ; + if(RegQueryValueExA(hkey_location, reg_val_name.c_str(), NULL, &dw_type, (LPBYTE)value_buff, &value_buff_size) == ERROR_SUCCESS) { + RegCloseKey(hkey_location); + if (value_buff_size >= 1024*sizeof(TCHAR)) { + return std::string(); + } else { + std::string return_value(value_buff, value_buff_size-1); //value_buff_size includes the null terminator + return_value += path_suffix; + return return_value; + } + } else { + return std::string(); + } +} + +#endif /*UHD_PLATFORM_WIN32*/ + +std::string nifpga_lvbitx::_get_fpga_images_dir(const std::string search_paths) +{ + std::vector<std::string> search_path_vtr; + boost::split(search_path_vtr, search_paths, boost::is_any_of(",")); + + std::string lvbitx_dir; + //Traverse through the list of search paths. Priority: lexical + BOOST_FOREACH(std::string& search_path, search_path_vtr) { + boost::algorithm::trim(search_path); + if (search_path.empty()) continue; + +#ifdef UHD_PLATFORM_WIN32 + lvbitx_dir = _get_path_from_registry(search_path); + if (lvbitx_dir.empty()) { + //Could not read from the registry due to missing key, invalid values, etc + //Just use the search path. The is_directory check will fail if this is a + //registry path and we will move on to the next item in the list. + lvbitx_dir = search_path; + } +#else + lvbitx_dir = search_path; +#endif + + //If the current directory exists then stop traversing the search path list. + if (boost::filesystem::is_directory(lvbitx_dir)) break; + } + + return lvbitx_dir; +} + + +}} diff --git a/host/lib/transport/nirio/nirio_driver_iface_linux.cpp b/host/lib/transport/nirio/nirio_driver_iface_linux.cpp new file mode 100644 index 000000000..b1f4bb49f --- /dev/null +++ b/host/lib/transport/nirio/nirio_driver_iface_linux.cpp @@ -0,0 +1,111 @@ +// +// 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 <uhd/transport/nirio/nirio_driver_iface.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +namespace nirio_driver_iface { + +nirio_status rio_open( + const std::string& device_path, + rio_dev_handle_t& device_handle) +{ + device_handle = ::open(device_path.c_str(), O_RDWR | O_CLOEXEC); + return (device_handle < 0) ? NiRio_Status_InvalidParameter : NiRio_Status_Success; +} + +void rio_close(rio_dev_handle_t& device_handle) +{ + ::close(device_handle); + device_handle = -1; +} + +bool rio_isopen(rio_dev_handle_t device_handle) +{ + return (device_handle >= 0); +} + +nirio_status rio_ioctl( + rio_dev_handle_t device_handle, + uint32_t ioctl_code, + const void *write_buf, + size_t write_buf_len, + void *read_buf, + size_t read_buf_len) +{ + nirio_ioctl_block_t ioctl_block = {0,0,0,0,0,0}; + + // two-casts necessary to prevent pointer sign-extension + ioctl_block.inBuf = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(write_buf)); + ioctl_block.inBufLength = write_buf_len; + ioctl_block.outBuf = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(read_buf)); + ioctl_block.outBufLength = read_buf_len; + + int status = ::ioctl(device_handle, ioctl_code, &ioctl_block); + if (status == -1) { + switch (errno) { + case EINVAL: return NiRio_Status_InvalidParameter; + case EFAULT: return NiRio_Status_MemoryFull; + default: return NiRio_Status_SoftwareFault; + } + } else { + return NiRio_Status_Success; + } +} + +nirio_status rio_mmap( + rio_dev_handle_t device_handle, + uint16_t memory_type, + size_t size, + bool writable, + rio_mmap_t &map) +{ + int access_mode = PROT_READ; //Write-only mode not supported + if (writable) access_mode |= PROT_WRITE; + map.addr = ::mmap(NULL, size, access_mode, MAP_SHARED, device_handle, (off_t) memory_type * sysconf(_SC_PAGESIZE)); + map.size = size; + + if (map.addr == MAP_FAILED) { + map.addr = NULL; + map.size = 0; + switch (errno) { + case EINVAL: return NiRio_Status_InvalidParameter; + case EFAULT: return NiRio_Status_MemoryFull; + default: return NiRio_Status_SoftwareFault; + } + } + return NiRio_Status_Success; +} + +nirio_status rio_munmap(rio_mmap_t &map) +{ + nirio_status status = 0; + if (map.addr != NULL) { + status = ::munmap(map.addr, map.size); + + map.addr = NULL; + map.size = 0; + } + return status; +} + +} diff --git a/host/lib/transport/nirio/nirio_driver_iface_macos.cpp b/host/lib/transport/nirio/nirio_driver_iface_macos.cpp new file mode 100644 index 000000000..1a1142525 --- /dev/null +++ b/host/lib/transport/nirio/nirio_driver_iface_macos.cpp @@ -0,0 +1,63 @@ +// +// 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 <uhd/transport/nirio/nirio_driver_iface.h> + +namespace nirio_driver_iface { + +nirio_status rio_open( + const std::string& device_path, + rio_dev_handle_t& device_handle) +{ + return NiRio_Status_FeatureNotSupported; +} + +void rio_close(rio_dev_handle_t& device_handle) +{ +} + +bool rio_isopen(rio_dev_handle_t device_handle) +{ + return false; +} + +nirio_status rio_ioctl( + rio_dev_handle_t device_handle, + uint32_t ioctl_code, + const void *write_buf, + size_t write_buf_len, + void *read_buf, + size_t read_buf_len) +{ + return NiRio_Status_FeatureNotSupported; +} + +nirio_status rio_mmap( + rio_dev_handle_t device_handle, + uint16_t memory_type, + size_t size, + bool writable, + rio_mmap_t &map) +{ + return NiRio_Status_FeatureNotSupported; +} + +nirio_status rio_munmap(rio_mmap_t &map) +{ + return NiRio_Status_FeatureNotSupported; +} + +} diff --git a/host/lib/transport/nirio/nirio_driver_iface_win.cpp b/host/lib/transport/nirio/nirio_driver_iface_win.cpp new file mode 100644 index 000000000..b47c6ce85 --- /dev/null +++ b/host/lib/transport/nirio/nirio_driver_iface_win.cpp @@ -0,0 +1,148 @@ +// +// 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 <uhd/transport/nirio/nirio_driver_iface.h> +#include <process.h> + +#define NIRIO_IOCTL_MAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0xF00, METHOD_BUFFERED, (FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define NIRIO_IOCTL_UNMAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0xF01, METHOD_BUFFERED, (FILE_READ_ACCESS | FILE_WRITE_ACCESS)) + +namespace nirio_driver_iface { + +nirio_status rio_open( + const std::string& device_path, + rio_dev_handle_t& device_handle) +{ + device_handle = CreateFileA(device_path.c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, /* default security */ + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL /* template file */); + + return (device_handle == INVALID_HANDLE_VALUE) ? NiRio_Status_InvalidParameter : NiRio_Status_Success; +} + +void rio_close(rio_dev_handle_t& device_handle) +{ + ::CloseHandle(device_handle); + device_handle = INVALID_HANDLE_VALUE; +} + +bool rio_isopen(rio_dev_handle_t device_handle) +{ + return (device_handle != INVALID_HANDLE_VALUE); +} + +nirio_status rio_ioctl( + rio_dev_handle_t device_handle, + uint32_t ioctl_code, + const void *write_buf, + size_t write_buf_len, + void *read_buf, + size_t read_buf_len) +{ + if (!rio_isopen(device_handle)) return NiRio_Status_ResourceNotInitialized; + + /* Note, if the file handle was opened with the OVERLAPPED flag, you must + * supply an OVERLAPPED structure to ReadFile, WriteFile, and + * DeviceIoControl, even when doing synchronous IO. */ + OVERLAPPED zeroedOverlapped = {0}; + DWORD outLen = 0; + int_fast32_t lastError = 0; + + if (!(DeviceIoControl(device_handle, ioctl_code, + const_cast<void*>(write_buf), static_cast<DWORD>(write_buf_len), + read_buf, static_cast<DWORD>(read_buf_len), + &outLen, &zeroedOverlapped ))) + { + lastError = GetLastError(); + return NiRio_Status_SoftwareFault; + } + + return NiRio_Status_Success; +} + +unsigned int __stdcall memory_map_thread_routine(void *context) +{ + rio_mmap_threadargs_t *args = (rio_mmap_threadargs_t*)context; + args->status = rio_ioctl(args->device_handle, NIRIO_IOCTL_MAP_MEMORY, &(args->params), sizeof(args->params), NULL, 0); + if (nirio_status_fatal(args->status)) + { + SetEvent(reinterpret_cast<HANDLE>(args->params.map_ready_event_handle)); + } + return 0; +} + +nirio_status rio_mmap( + rio_dev_handle_t device_handle, + uint16_t memory_type, + size_t size, + bool writable, + rio_mmap_t &map) +{ + if (!rio_isopen(device_handle)) return NiRio_Status_ResourceNotInitialized; + + access_mode_t access_mode = writable ? ACCESS_MODE_WRITE : ACCESS_MODE_READ; + + uint64_t mapped_addr = 0; + map.map_thread_args.device_handle = device_handle; + map.map_thread_args.status = NiRio_Status_Success; + map.map_thread_args.params.memoryType = memory_type; + map.map_thread_args.params.size = (uint32_t)size; + map.map_thread_args.params.mapped_va_ptr = reinterpret_cast<uintptr_t>(&mapped_addr); + map.map_thread_args.params.access_mode = (uint8_t)access_mode; + HANDLE map_ready_event_handle = CreateEventA(NULL, TRUE, FALSE, NULL); + if (map_ready_event_handle == NULL) { + map.addr = NULL; + return NiRio_Status_SoftwareFault; + } + map.map_thread_args.params.map_ready_event_handle = reinterpret_cast<uint64_t>(map_ready_event_handle); + map.map_thread_handle = (HANDLE) _beginthreadex(NULL, 0, memory_map_thread_routine, &(map.map_thread_args), 0, NULL); + + nirio_status status = NiRio_Status_Success; + if (map.map_thread_handle == NULL) { + map.addr = NULL; + return NiRio_Status_SoftwareFault; + } else { + WaitForSingleObject(map_ready_event_handle, INFINITE); + map.addr = reinterpret_cast<void*>(mapped_addr); + if (map.addr == NULL) { + WaitForSingleObject(map.map_thread_handle, INFINITE); + CloseHandle(map.map_thread_handle); + nirio_status_chain(map.map_thread_args.status, status); + } + } + CloseHandle(map_ready_event_handle); + return status; +} + +nirio_status rio_munmap(rio_mmap_t &map) +{ + if (!rio_isopen(map.map_thread_args.device_handle)) return NiRio_Status_ResourceNotInitialized; + + nirio_status status = NiRio_Status_Success; + if (map.addr != NULL) { + uint64_t mapped_addr = reinterpret_cast<uintptr_t>(map.addr); + status = rio_ioctl(map.map_thread_args.device_handle, NIRIO_IOCTL_UNMAP_MEMORY, &mapped_addr, sizeof(mapped_addr), NULL, 0); + if (nirio_status_not_fatal(status)) { + WaitForSingleObject(map.map_thread_handle, INFINITE); + } + CloseHandle(map.map_thread_handle); + map.addr = NULL; + } + return status; +} + +} diff --git a/host/lib/transport/nirio/nirio_resource_manager.cpp b/host/lib/transport/nirio/nirio_resource_manager.cpp new file mode 100644 index 000000000..85e789087 --- /dev/null +++ b/host/lib/transport/nirio/nirio_resource_manager.cpp @@ -0,0 +1,120 @@ +// +// 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 <uhd/transport/nirio/nirio_resource_manager.h> + +#ifdef __clang__ + #pragma GCC diagnostic push ignored "-Wmissing-field-initializers" +#elif defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +namespace uhd { namespace niusrprio +{ + +nirio_resource_manager::nirio_resource_manager( + niriok_proxy& proxy) : _kernel_proxy(proxy), _fifo_info_map(), _reg_info_map() +{ +} + +nirio_resource_manager::~nirio_resource_manager() +{ + finalize(); +} + +nirio_status nirio_resource_manager::initialize( + const nirio_register_info_vtr& reg_info_vtr, + const nirio_fifo_info_vtr& fifo_info_vtr) +{ + nirio_status status = 0; + for (nirio_fifo_info_vtr::const_iterator it = fifo_info_vtr.begin(); it != fifo_info_vtr.end(); it++) { + const nirio_fifo_info_t& fifo_info = *it; + status = _add_fifo_resource(fifo_info); + if (nirio_status_fatal(status)) return status; + + _fifo_info_map.insert(fifo_info_map_t::value_type(fifo_info.name, fifo_info)); + } + for (nirio_register_info_vtr::const_iterator it = reg_info_vtr.begin(); it != reg_info_vtr.end(); it++) { + const nirio_register_info_t& reg_info = *it; + + _reg_info_map.insert(register_info_map_t::value_type(reg_info.name, reg_info)); + } + return _set_driver_config(); +} + +void nirio_resource_manager::finalize() +{ + _fifo_info_map.clear(); +} + +nirio_status nirio_resource_manager::get_register_offset( + const char* register_name, + uint32_t& offset) +{ + register_info_map_t::const_iterator it = _reg_info_map.find(fifo_info_map_t::key_type(register_name)); + if (it == _reg_info_map.end()) return NiRio_Status_InvalidParameter; + + offset = (*it).second.offset; + + return NiRio_Status_Success; +} + + +nirio_status nirio_resource_manager::_add_fifo_resource( + const nirio_fifo_info_t& fifo_info) +{ + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::ADD_RESOURCE; + in.subfunction = (fifo_info.direction == OUTPUT_FIFO) ? + nirio_driver_iface::NIRIO_RESOURCE::OUTPUT_FIFO : + nirio_driver_iface::NIRIO_RESOURCE::INPUT_FIFO; + + in.params.add.fifoWithDataType.channel = fifo_info.channel; + in.params.add.fifoWithDataType.baseAddress = fifo_info.base_addr; + in.params.add.fifoWithDataType.depthInSamples = fifo_info.depth; + in.params.add.fifoWithDataType.scalarType = fifo_info.scalar_type; + in.params.add.fifoWithDataType.bitWidth = fifo_info.width; + in.params.add.fifoWithDataType.version = fifo_info.version; + + return _kernel_proxy.sync_operation(&in, sizeof(in), &out, sizeof(out)); +} + +nirio_status nirio_resource_manager::_set_driver_config() +{ + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + in.function = nirio_driver_iface::NIRIO_FUNC::SET_DRIVER_CONFIG; + in.subfunction = 0; + + return _kernel_proxy.sync_operation(&in, sizeof(in), &out, sizeof(out)); +} + +nirio_fifo_info_t* nirio_resource_manager::_lookup_fifo_info(const char* fifo_name) { + fifo_info_map_t::iterator it = _fifo_info_map.find(fifo_info_map_t::key_type(fifo_name)); + if (it == _fifo_info_map.end()) return NULL; + + return &((*it).second); +} + +}} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/host/lib/transport/nirio/niriok_proxy.cpp b/host/lib/transport/nirio/niriok_proxy.cpp new file mode 100644 index 000000000..031623c9a --- /dev/null +++ b/host/lib/transport/nirio/niriok_proxy.cpp @@ -0,0 +1,300 @@ +// +// 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 <uhd/transport/nirio/niriok_proxy.h> +#include <cstring> + +#define NI_VENDOR_NUM 0x1093 + +#define VERSION_BUILD_SHIFT 0 +#define VERSION_PHASE_SHIFT 14 +#define VERSION_MAINT_SHIFT 16 +#define VERSION_UPGRD_SHIFT 20 +#define VERSION_MAJOR_SHIFT 24 +#define VERSION_BUILD_MASK 0x00003FFF +#define VERSION_PHASE_MASK 0x0000C000 +#define VERSION_MAINT_MASK 0x000F0000 +#define VERSION_UPGRD_MASK 0x00F00000 +#define VERSION_MAJOR_MASK 0xFF000000 + +#define GET_FIFO_MEMORY_TYPE(fifo_inst) (static_cast<uint16_t>(0x0100 | static_cast<uint16_t>(fifo_inst))) + +#ifdef __clang__ + #pragma GCC diagnostic push ignored "-Wmissing-field-initializers" +#elif defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + +namespace uhd { namespace niusrprio +{ + //------------------------------------------------------- + // niriok_proxy + //------------------------------------------------------- + niriok_proxy::niriok_proxy(): _device_handle(nirio_driver_iface::INVALID_RIO_HANDLE) + { + } + + niriok_proxy::~niriok_proxy() + { + close(); + } + + nirio_status niriok_proxy::open(const std::string& interface_path) + { + if (interface_path.empty()) return NiRio_Status_ResourceNotFound; + + //close if already open. + close(); + + nirio_status status = NiRio_Status_Success; + nirio_status_chain(nirio_driver_iface::rio_open( + interface_path, _device_handle), status); + if (nirio_status_not_fatal(status)) { + nirio_status_chain(nirio_driver_iface::rio_ioctl(_device_handle, + nirio_driver_iface::NIRIO_IOCTL_POST_OPEN, + NULL, 0, NULL, 0), status); + nirio_driver_iface::nirio_ioctl_packet_t out(&_interface_num, sizeof(_interface_num), 0); + nirio_status_chain(nirio_driver_iface::rio_ioctl(_device_handle, + nirio_driver_iface::NIRIO_IOCTL_GET_IFACE_NUM, + NULL, 0, + &out, sizeof(out)), status); + + if (nirio_status_fatal(status)) close(); + } + return status; + } + + void niriok_proxy::close(void) + { + if(nirio_driver_iface::rio_isopen(_device_handle)) + { + nirio_driver_iface::rio_ioctl( + _device_handle, nirio_driver_iface::NIRIO_IOCTL_PRE_CLOSE, NULL, 0, NULL, 0); + nirio_driver_iface::rio_close(_device_handle); + } + } + + nirio_status niriok_proxy::reset() + { + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::RESET; + + return sync_operation(&in, sizeof(in), &out, sizeof(out)); + } + + nirio_status niriok_proxy::get_cached_session( + uint32_t& session) + { + nirio_driver_iface::nirio_ioctl_packet_t out(&session, sizeof(session), 0); + return nirio_driver_iface::rio_ioctl(_device_handle, + nirio_driver_iface::NIRIO_IOCTL_GET_SESSION, + NULL, 0, + &out, sizeof(out)); + } + + nirio_status niriok_proxy::get_version( + nirio_version_t type, + uint32_t& major, + uint32_t& upgrade, + uint32_t& maintenance, + char& phase, + uint32_t& build) + { + nirio_device_attr_32_t version_attr = (type==CURRENT)?CURRENT_VERSION:OLDEST_COMPATIBLE_VERSION; + uint32_t raw_version = 0; + nirio_status status = get_attribute(version_attr, raw_version); + + major = (raw_version & VERSION_MAJOR_MASK) >> VERSION_MAJOR_SHIFT; + upgrade = (raw_version & VERSION_UPGRD_MASK) >> VERSION_UPGRD_SHIFT; + maintenance = (raw_version & VERSION_MAINT_MASK) >> VERSION_MAINT_SHIFT; + build = (raw_version & VERSION_BUILD_MASK) >> VERSION_BUILD_SHIFT; + + uint32_t phase_num = (raw_version & VERSION_PHASE_MASK) >> VERSION_PHASE_SHIFT; + switch (phase_num) { + case 0: phase = 'd'; break; + case 1: phase = 'a'; break; + case 2: phase = 'b'; break; + case 3: phase = 'f'; break; + } + + return status; + } + + nirio_status niriok_proxy::sync_operation( + const void *writeBuffer, + size_t writeBufferLength, + void *readBuffer, + size_t readBufferLength) + { + nirio_driver_iface::nirio_ioctl_packet_t out(readBuffer, readBufferLength, 0); + nirio_status ioctl_status = nirio_driver_iface::rio_ioctl(_device_handle, + nirio_driver_iface::NIRIO_IOCTL_SYNCOP, + writeBuffer, writeBufferLength, + &out, sizeof(out)); + if (nirio_status_fatal(ioctl_status)) return ioctl_status; + + return out.statusCode; + } + + nirio_status niriok_proxy::get_attribute( + const nirio_device_attr_32_t attribute, + uint32_t& attrValue) + { + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::GET32; + in.params.attribute32.attribute = attribute; + + nirio_status status = sync_operation(&in, sizeof(in), &out, sizeof(out)); + + attrValue = out.params.attribute32.value; + return status; + } + + nirio_status niriok_proxy::get_attribute( + const nirio_device_attr_str_t attribute, + char *buf, + const uint32_t bufLen, + uint32_t& stringLen) + { + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + nirio_driver_iface::init_syncop_out_params(out, buf, bufLen); + + in.function = nirio_driver_iface::NIRIO_FUNC::GET_STRING; + in.params.attributeStr.attribute = attribute; + + nirio_status status = sync_operation(&in, sizeof(in), &out, sizeof(out)); + + stringLen = out.params.stringLength; + return status; + } + + nirio_status niriok_proxy::set_attribute( + const nirio_device_attr_32_t attribute, + const uint32_t value) + { + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::SET32; + in.params.attribute32.attribute = attribute; + in.params.attribute32.value = value; + + return sync_operation(&in, sizeof(in), &out, sizeof(out)); + } + + nirio_status niriok_proxy::set_attribute( + const nirio_device_attr_str_t attribute, + const char* const buffer) + { + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::init_syncop_in_params(in, buffer, strlen(buffer) + 1); + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::SET_STRING; + in.params.attributeStr.attribute = attribute; + + return sync_operation(&in, sizeof(in), &out, sizeof(out)); + } + + nirio_status niriok_proxy::peek(uint32_t offset, uint32_t& value) + { + if (offset % 4 != 0) return NiRio_Status_MisalignedAccess; + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::IO; + in.subfunction = nirio_driver_iface::NIRIO_IO::PEEK32; + in.params.io.offset = offset; + + nirio_status status = sync_operation(&in, sizeof(in), &out, sizeof(out)); + value = out.params.io.value.value32; + return status; + } + + nirio_status niriok_proxy::peek(uint32_t offset, uint64_t& value) + { + if (offset % 8 != 0) return NiRio_Status_MisalignedAccess; + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::IO; + in.subfunction = nirio_driver_iface::NIRIO_IO::PEEK64; + in.params.io.offset = offset; + + nirio_status status = sync_operation(&in, sizeof(in), &out, sizeof(out)); + value = out.params.io.value.value64; + return status; + } + + nirio_status niriok_proxy::poke(uint32_t offset, const uint32_t& value) + { + if (offset % 4 != 0) return NiRio_Status_MisalignedAccess; + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::IO; + in.subfunction = nirio_driver_iface::NIRIO_IO::POKE32; + in.params.io.offset = offset; + in.params.io.value.value32 = value; + + return sync_operation(&in, sizeof(in), &out, sizeof(out)); + } + + nirio_status niriok_proxy::poke(uint32_t offset, const uint64_t& value) + { + if (offset % 8 != 0) return NiRio_Status_MisalignedAccess; + + nirio_driver_iface::nirio_syncop_in_params_t in = {}; + nirio_driver_iface::nirio_syncop_out_params_t out = {}; + + in.function = nirio_driver_iface::NIRIO_FUNC::IO; + in.subfunction = nirio_driver_iface::NIRIO_IO::POKE64; + in.params.io.offset = offset; + in.params.io.value.value64 = value; + + return sync_operation(&in, sizeof(in), &out, sizeof(out)); + } + + nirio_status niriok_proxy::map_fifo_memory( + uint32_t fifo_instance, + size_t size, + nirio_driver_iface::rio_mmap_t& map) + { + return nirio_driver_iface::rio_mmap(_device_handle, + GET_FIFO_MEMORY_TYPE(fifo_instance), + size, true, map); + } + + nirio_status niriok_proxy::unmap_fifo_memory( + nirio_driver_iface::rio_mmap_t& map) + { + return nirio_driver_iface::rio_munmap(map); + } +}} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif diff --git a/host/lib/transport/nirio/niusrprio_session.cpp b/host/lib/transport/nirio/niusrprio_session.cpp new file mode 100644 index 000000000..a07bc4fdf --- /dev/null +++ b/host/lib/transport/nirio/niusrprio_session.cpp @@ -0,0 +1,199 @@ +// +// 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 <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/transport/nirio/nirio_fifo.h> +#include <uhd/transport/nirio/status.h> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/lexical_cast.hpp> +#include <stdio.h> +#include <fstream> +//@TODO: Move the register defs required by the class to a common location +#include "../../usrp/x300/x300_regs.hpp" + +namespace uhd { namespace niusrprio { + +niusrprio_session::niusrprio_session(const std::string& resource_name, const std::string& rpc_port_name) : + _resource_name(resource_name), + _session_open(false), + _resource_manager(_riok_proxy), + _rpc_client("localhost", rpc_port_name) +{ +} + +niusrprio_session::~niusrprio_session() +{ + close(); +} + +nirio_status niusrprio_session::enumerate(const std::string& rpc_port_name, device_info_vtr& device_info_vtr) +{ + usrprio_rpc::usrprio_rpc_client temp_rpc_client("localhost", rpc_port_name); + nirio_status status = temp_rpc_client.get_ctor_status(); + nirio_status_chain(temp_rpc_client.niusrprio_enumerate(device_info_vtr), status); + return status; +} + +nirio_status niusrprio_session::open( + nifpga_lvbitx::sptr lvbitx, + bool force_download) +{ + boost::unique_lock<boost::recursive_mutex> lock(_session_mutex); + + _lvbitx = lvbitx; + + nirio_status status = NiRio_Status_Success; + std::string bitfile_path(_lvbitx->get_bitfile_path()); + std::string signature(_lvbitx->get_signature()); + + //Make sure that the RPC client connected to the server properly + nirio_status_chain(_rpc_client.get_ctor_status(), status); + //Get a handle to the kernel driver + nirio_status_chain(_rpc_client.niusrprio_get_interface_path(_resource_name, _interface_path), status); + nirio_status_chain(_riok_proxy.open(_interface_path), status); + + if (nirio_status_not_fatal(status)) { + //Bitfile build for a particular LVFPGA interface will have the same signature + //because the API of the bitfile does not change. Two files with the same signature + //can however have different bitstreams because of non-LVFPGA code differences. + //That is why we need another identifier to qualify the signature. The BIN + //checksum is a good candidate. + std::string lvbitx_checksum(_lvbitx->get_bitstream_checksum()); + boost::uint16_t download_fpga = (force_download || (_read_bitstream_checksum() != lvbitx_checksum)) ? 1 : 0; + + nirio_status_chain(_rpc_client.niusrprio_open_session( + _resource_name, bitfile_path, signature, download_fpga), status); + _session_open = nirio_status_not_fatal(status); + + if (nirio_status_not_fatal(status)) { + nirio_register_info_vtr reg_vtr; + nirio_fifo_info_vtr fifo_vtr; + _lvbitx->init_register_info(reg_vtr); + _lvbitx->init_fifo_info(fifo_vtr); + _resource_manager.initialize(reg_vtr, fifo_vtr); + + nirio_status_chain(_verify_signature(), status); + nirio_status_chain(_write_bitstream_checksum(lvbitx_checksum), status); + } + } + + return status; +} + +void niusrprio_session::close(bool skip_reset) +{ + boost::unique_lock<boost::recursive_mutex> lock(_session_mutex); + + if (_session_open) { + nirio_status status = NiRio_Status_Success; + if (!skip_reset) reset(); + nirio_status_chain(_rpc_client.niusrprio_close_session(_resource_name), status); + _session_open = false; + } +} + +nirio_status niusrprio_session::reset() +{ + boost::unique_lock<boost::recursive_mutex> lock(_session_mutex); + return _rpc_client.niusrprio_reset_device(_resource_name); +} + +nirio_status niusrprio_session::download_bitstream_to_flash(const std::string& bitstream_path) +{ + boost::unique_lock<boost::recursive_mutex> lock(_session_mutex); + return _rpc_client.niusrprio_download_fpga_to_flash(_resource_name, bitstream_path); +} + +niriok_proxy::sptr niusrprio_session::create_kernel_proxy( + const std::string& resource_name, + const std::string& rpc_port_name) +{ + usrprio_rpc::usrprio_rpc_client temp_rpc_client("localhost", rpc_port_name); + nirio_status status = temp_rpc_client.get_ctor_status(); + + std::string interface_path; + nirio_status_chain(temp_rpc_client.niusrprio_get_interface_path(resource_name, interface_path), status); + + niriok_proxy::sptr proxy; + if (nirio_status_not_fatal(status)) { + proxy.reset(new niriok_proxy()); + if (proxy) nirio_status_chain(proxy->open(interface_path), status); + } + + return proxy; +} + +nirio_status niusrprio_session::_verify_signature() +{ + //Validate the signature using the kernel proxy + nirio_status status = NiRio_Status_Success; + boost::uint32_t sig_offset = 0; + nirio_status_chain(_riok_proxy.get_attribute(DEFAULT_FPGA_SIGNATURE_OFFSET, sig_offset), status); + niriok_scoped_addr_space(_riok_proxy, FPGA, status); + std::string signature; + for (boost::uint32_t i = 0; i < 8; i++) { + boost::uint32_t quarter_sig; + nirio_status_chain(_riok_proxy.peek(sig_offset, quarter_sig), status); + signature += boost::str(boost::format("%08x") % quarter_sig); + } + + std::string expected_signature(_lvbitx->get_signature()); + boost::to_upper(signature); + boost::to_upper(expected_signature); + if (signature.find(expected_signature) == std::string::npos) { + nirio_status_chain(NiRio_Status_SignatureMismatch, status); + } + + return status; +} + +std::string niusrprio_session::_read_bitstream_checksum() +{ + nirio_status status = NiRio_Status_Success; + niriok_scoped_addr_space(_riok_proxy, BUS_INTERFACE, status); + std::string usr_signature; + for (boost::uint32_t i = 0; i < FPGA_USR_SIG_REG_SIZE; i+=4) { + boost::uint32_t quarter_sig; + nirio_status_chain(_riok_proxy.peek(FPGA_USR_SIG_REG_BASE + i, quarter_sig), status); + usr_signature += boost::str(boost::format("%08x") % quarter_sig); + } + boost::to_upper(usr_signature); + + return usr_signature; +} + +nirio_status niusrprio_session::_write_bitstream_checksum(const std::string& checksum) +{ + nirio_status status = NiRio_Status_Success; + niriok_scoped_addr_space(_riok_proxy, BUS_INTERFACE, status); + for (boost::uint32_t i = 0; i < FPGA_USR_SIG_REG_SIZE; i+=4) { + boost::uint32_t quarter_sig; + try { + std::stringstream ss; + ss << std::hex << checksum.substr(i*2,8); + ss >> quarter_sig; + } catch (std::exception&) { + quarter_sig = 0; + } + nirio_status_chain(_riok_proxy.poke(FPGA_USR_SIG_REG_BASE + i, quarter_sig), status); + } + return status; +} + +}} diff --git a/host/lib/transport/nirio/rpc/CMakeLists.txt b/host/lib/transport/nirio/rpc/CMakeLists.txt new file mode 100644 index 000000000..02c16d2ff --- /dev/null +++ b/host/lib/transport/nirio/rpc/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# 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 +######################################################################## + +######################################################################## +# Append to the list of sources for lib uhd +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/rpc_client.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrprio_rpc_client.cpp +) diff --git a/host/lib/transport/nirio/rpc/rpc_client.cpp b/host/lib/transport/nirio/rpc/rpc_client.cpp new file mode 100644 index 000000000..f8dc26b50 --- /dev/null +++ b/host/lib/transport/nirio/rpc/rpc_client.cpp @@ -0,0 +1,227 @@ +/// +// 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 <uhd/transport/nirio/rpc/rpc_client.hpp> +#include <boost/bind.hpp> +#include <boost/version.hpp> +#include <boost/format.hpp> +#include <boost/asio/error.hpp> + +#define CHAIN_BLOCKING_XFER(func, exp, status) \ + if (status) { \ + status = (static_cast<size_t>((func)) == exp); \ + } else { \ + UHD_LOG << "rpc_client operation skipped: " #func "\n"; \ + } \ + +namespace uhd { namespace usrprio_rpc { + +using boost::asio::ip::tcp; + +rpc_client::rpc_client ( + const std::string& server, + const std::string& port, + boost::uint32_t process_id, + boost::uint32_t host_id +) : _socket(_io_service) +{ + //Fill in handshake info + _hshake_args_client.version = CURRENT_VERSION; + _hshake_args_client.oldest_comp_version = OLDEST_COMPATIBLE_VERSION; + _hshake_args_client.client_id = build_client_id(host_id, process_id); + _hshake_args_client.boost_archive_version = boost_serialization_archive_utils::get_version(); + + try { + //Synchronous resolve + connect + tcp::resolver resolver(_io_service); + tcp::resolver::query query(tcp::v4(), server, port); + 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 + + UHD_LOG << "rpc_client connected to server." << std::endl; + + try { + //Perform handshake + bool status = true; + CHAIN_BLOCKING_XFER( + boost::asio::write(_socket, boost::asio::buffer(&_hshake_args_client, sizeof(_hshake_args_client))), + sizeof(_hshake_args_client), status); + CHAIN_BLOCKING_XFER( + boost::asio::read(_socket, boost::asio::buffer(&_hshake_args_server, sizeof(_hshake_args_server))), + sizeof(_hshake_args_server), status); + + _request.header.client_id = _hshake_args_server.client_id; + + if (_hshake_args_server.version >= _hshake_args_client.oldest_comp_version && + _hshake_args_client.version >= _hshake_args_server.oldest_comp_version && + status) + { + UHD_LOG << "rpc_client bound to server." << std::endl; + _wait_for_next_response_header(); + + //Spawn a thread for the io_service callback handler. This thread will run until rpc_client is destroyed. + _io_service_thread.reset(new boost::thread(boost::bind(&boost::asio::io_service::run, &_io_service))); + } else { + UHD_LOG << "rpc_client handshake failed." << std::endl; + _exec_err.assign(boost::asio::error::connection_refused, boost::asio::error::get_system_category()); + } + UHD_LOG << boost::format("rpc_client archive = %d, rpc_server archive = %d\n.") % + _hshake_args_client.boost_archive_version % + _hshake_args_server.boost_archive_version; + } catch (boost::exception&) { + UHD_LOG << "rpc_client handshake aborted." << std::endl; + _exec_err.assign(boost::asio::error::connection_refused, boost::asio::error::get_system_category()); + } + } 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()); + } +} + +rpc_client::~rpc_client () { + _stop_io_service(); +} + +const boost::system::error_code& rpc_client::call( + func_id_t func_id, + const func_args_writer_t& in_args, + func_args_reader_t &out_args, + boost::posix_time::milliseconds timeout +) +{ + boost::mutex::scoped_lock lock(_mutex); + + if (_io_service_thread.get()) { + _request.header.func_id = func_id; + in_args.store(_request.data); + _request.header.func_args_size = _request.data.size(); + + _exec_err.clear(); + + //Send function call header and args + bool status = true; + try { + CHAIN_BLOCKING_XFER( + boost::asio::write(_socket, boost::asio::buffer(&_request.header, sizeof(_request.header))), + sizeof(_request.header), status); + if (not _request.data.empty()) + { + CHAIN_BLOCKING_XFER( + boost::asio::write(_socket, boost::asio::buffer(&(*_request.data.begin()), _request.data.size())), + _request.data.size(), status); + } + } catch (boost::exception&) { + status = false; + } + + //Wait for response using condition variable + if (status) { + if (!_exec_gate.timed_wait(lock, timeout)) { + UHD_LOG << "rpc_client function timed out." << std::endl; + _exec_err.assign(boost::asio::error::timed_out, boost::asio::error::get_system_category()); + } + } else { + UHD_LOG << "rpc_client connection dropped." << std::endl; + _exec_err.assign(boost::asio::error::connection_aborted, boost::asio::error::get_system_category()); + _stop_io_service(); + } + + //Verify that we are talking to the correct endpoint + if ((_request.header.client_id != _response.header.client_id) && !_exec_err) { + UHD_LOG << "rpc_client confused about who its talking to." << std::endl; + _exec_err.assign(boost::asio::error::operation_aborted, boost::asio::error::get_system_category()); + } + + if (!_exec_err) out_args.load(_response.data); + } + + return _exec_err; +} + +void rpc_client::_handle_response_hdr(const boost::system::error_code& err, size_t transferred, size_t expected) +{ + boost::mutex::scoped_lock lock(_mutex); + _exec_err = err; + if (!_exec_err && (transferred == expected)) { + //Response header received. Verify that it is expected + if (func_args_header_t::match_function(_request.header, _response.header)) { + if (_response.header.func_args_size) + { + _response.data.resize(_response.header.func_args_size); + + //Wait for response data + boost::asio::async_read(_socket, + boost::asio::buffer(&(*_response.data.begin()), _response.data.size()), + boost::bind(&rpc_client::_handle_response_data, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred, + _response.data.size())); + } else { + _handle_response_data(err, 0, 0); + } + } else { + //Unexpected response. Ignore it. + UHD_LOG << "rpc_client received garbage responses." << std::endl; + _exec_err.assign(boost::asio::error::operation_aborted, boost::asio::error::get_system_category()); + + _wait_for_next_response_header(); + } + } + + if (_exec_err) _exec_gate.notify_all(); +} + +void rpc_client::_handle_response_data(const boost::system::error_code& err, size_t transferred, size_t expected) +{ + boost::mutex::scoped_lock lock(_mutex); + _exec_err = err; + if (transferred != expected) { + _exec_err.assign(boost::asio::error::operation_aborted, boost::asio::error::get_system_category()); + } + + _exec_gate.notify_all(); + + _wait_for_next_response_header(); +} + +void rpc_client::_wait_for_next_response_header() { + //_mutex must be locked when this call is made + boost::asio::async_read( + _socket, + boost::asio::buffer(&_response.header, sizeof(_response.header)), + boost::bind(&rpc_client::_handle_response_hdr, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred, + sizeof(_response.header))); +} + +}} diff --git a/host/lib/transport/nirio/rpc/usrprio_rpc_client.cpp b/host/lib/transport/nirio/rpc/usrprio_rpc_client.cpp new file mode 100644 index 000000000..1a1f1cd21 --- /dev/null +++ b/host/lib/transport/nirio/rpc/usrprio_rpc_client.cpp @@ -0,0 +1,229 @@ +// +// 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 <uhd/transport/nirio/rpc/usrprio_rpc_client.hpp> +#include <uhd/utils/platform.hpp> + +namespace uhd { namespace usrprio_rpc { + +usrprio_rpc_client::usrprio_rpc_client( + std::string server, + std::string port +) : _rpc_client(server, port, uhd::get_process_id(), uhd::get_host_id()), + _timeout(boost::posix_time::milliseconds(DEFAULT_TIMEOUT_IN_MS)) +{ + _ctor_status = _rpc_client.status() ? NiRio_Status_RpcConnectionError : NiRio_Status_Success; +} + +usrprio_rpc_client::~usrprio_rpc_client() +{ +} + +nirio_status usrprio_rpc_client::niusrprio_enumerate(NIUSRPRIO_ENUMERATE_ARGS) +/* +#define NIUSRPRIO_ENUMERATE_ARGS \ + usrprio_device_info_vtr& device_info_vtr +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + boost::uint32_t vtr_size = 0; + + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_ENUMERATE, in_args, out_args, _timeout)); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + out_args >> vtr_size; + } + if (nirio_status_not_fatal(status) && vtr_size > 0) { + device_info_vtr.resize(vtr_size); + for (size_t i = 0; i < (size_t)vtr_size; i++) { + usrprio_device_info info; + out_args >> info; + device_info_vtr[i] = info; + } + } + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_open_session(NIUSRPRIO_OPEN_SESSION_ARGS) +/* +#define NIUSRPRIO_OPEN_SESSION_ARGS \ + const std::string& resource, \ + const std::string& path, \ + const std::string& signature, \ + const boost::uint16_t& download_fpga +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + in_args << path; + in_args << signature; + in_args << download_fpga; + + //Open needs a longer timeout because the FPGA download can take upto 6 secs and the NiFpga libload can take 4. + static const boost::uint32_t OPEN_TIMEOUT = 15000; + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_OPEN_SESSION, in_args, out_args, boost::posix_time::milliseconds(OPEN_TIMEOUT))); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + } + + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_close_session(NIUSRPRIO_CLOSE_SESSION_ARGS) +/* +#define NIUSRPRIO_CLOSE_SESSION_ARGS \ + const std::string& resource +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_CLOSE_SESSION, in_args, out_args, _timeout)); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + } + + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_reset_device(NIUSRPRIO_RESET_SESSION_ARGS) +/* +#define NIUSRPRIO_RESET_SESSION_ARGS \ + const std::string& resource +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_RESET_SESSION, in_args, out_args, _timeout)); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + } + + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_get_interface_path(NIUSRPRIO_GET_INTERFACE_PATH_ARGS) +/* +#define NIUSRPRIO_GET_INTERFACE_PATH_ARGS \ + const std::string& resource, \ + std::string& interface_path +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_GET_INTERFACE_PATH, in_args, out_args, _timeout)); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + out_args >> interface_path; + } + + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_download_fpga_to_flash(NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH_ARGS) +/* +#define NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH_ARGS \ + const boost::uint32_t& interface_num, \ + const std::string& bitstream_path +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + in_args << bitstream_path; + + static const boost::uint32_t DOWNLOAD_FPGA_TIMEOUT = 1200000; + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_DOWNLOAD_FPGA_TO_FLASH, in_args, out_args, + boost::posix_time::milliseconds(DOWNLOAD_FPGA_TIMEOUT))); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + } + + return status; +} + +nirio_status usrprio_rpc_client::niusrprio_download_bitstream_to_fpga(NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA_ARGS) +/* +#define NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA_ARGS \ + const std::string& resource +*/ +{ + usrprio_rpc::func_args_writer_t in_args; + usrprio_rpc::func_args_reader_t out_args; + nirio_status status = NiRio_Status_Success; + + in_args << resource; + + status = _boost_error_to_nirio_status( + _rpc_client.call(NIUSRPRIO_DOWNLOAD_BITSTREAM_TO_FPGA, in_args, out_args, _timeout)); + + if (nirio_status_not_fatal(status)) { + out_args >> status; + } + + return status; +} + +nirio_status usrprio_rpc_client::_boost_error_to_nirio_status(const boost::system::error_code& err) { + if (err) { + switch (err.value()) { + case boost::asio::error::connection_aborted: + case boost::asio::error::connection_refused: + case boost::asio::error::eof: + return NiRio_Status_RpcSessionError; + case boost::asio::error::timed_out: + case boost::asio::error::operation_aborted: + return NiRio_Status_RpcOperationError; + default: + return NiRio_Status_SoftwareFault; + } + } else { + return NiRio_Status_Success; + } +} + +}} diff --git a/host/lib/transport/nirio/status.cpp b/host/lib/transport/nirio/status.cpp new file mode 100644 index 000000000..f3f8d4cd1 --- /dev/null +++ b/host/lib/transport/nirio/status.cpp @@ -0,0 +1,55 @@ +// +// 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 <uhd/transport/nirio/status.h> +#include <boost/format.hpp> + +namespace uhd { namespace niusrprio { + +#define NIRIO_ERR_INFO(CONST_NAME, ERR_CODE, ERR_MSG) \ + nirio_err_info(ERR_CODE, ERR_MSG), + +const nirio_err_info nirio_err_info::NIRIO_ERROR_TABLE[] = { + #include "../../../include/uhd/transport/nirio/nirio_err_template.h" +}; + +#undef NIRIO_ERR_INFO + +const size_t nirio_err_info::NIRIO_ERROR_TABLE_SIZE = sizeof(NIRIO_ERROR_TABLE)/sizeof(*NIRIO_ERROR_TABLE); + +const std::string lookup_err_msg(nirio_status code) { + std::string error_msg = (boost::format("Unknown error. (Error code %d)") % code).str(); + for (size_t i = 0; i < nirio_err_info::NIRIO_ERROR_TABLE_SIZE; i++) { + if (nirio_err_info::NIRIO_ERROR_TABLE[i].code == code) { + error_msg = (boost::format("%s (Error code %d)") % nirio_err_info::NIRIO_ERROR_TABLE[i].msg % code).str(); + break; + } + } + return error_msg; +} + +void nirio_status_to_exception(const nirio_status& status, const std::string& message) { + if (nirio_status_fatal(status)) { + throw uhd::runtime_error((boost::format("%s %s") % message % lookup_err_msg(status)).str()); + } +} + +}} + + + diff --git a/host/lib/transport/nirio_zero_copy.cpp b/host/lib/transport/nirio_zero_copy.cpp new file mode 100644 index 000000000..7b1e32fe0 --- /dev/null +++ b/host/lib/transport/nirio_zero_copy.cpp @@ -0,0 +1,352 @@ +// +// 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 <uhd/transport/nirio_zero_copy.hpp> +#include <stdio.h> +#include <uhd/transport/nirio/nirio_fifo.h> +#include <uhd/transport/nirio/nirio_fifo.h> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/atomic.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <vector> +#include <algorithm> // std::max +//@TODO: Move the register defs required by the class to a common location +#include "../usrp/x300/x300_regs.hpp" + +using namespace uhd; +using namespace uhd::transport; +using namespace uhd::niusrprio; + +typedef uint64_t fifo_data_t; + +class nirio_zero_copy_mrb : public managed_recv_buffer +{ +public: + nirio_zero_copy_mrb(nirio_fifo<fifo_data_t>& fifo, const size_t frame_size): + _fifo(fifo), _frame_size(frame_size) { } + + void release(void) + { + _fifo.release(_frame_size / sizeof(fifo_data_t)); + } + + UHD_INLINE sptr get_new(const double timeout, size_t &index) + { + nirio_status status = 0; + size_t elems_acquired, elems_remaining; + nirio_status_chain(_fifo.acquire( + _typed_buffer, _frame_size / sizeof(fifo_data_t), + static_cast<uint32_t>(timeout*1000), + elems_acquired, elems_remaining), status); + _length = elems_acquired * sizeof(fifo_data_t); + _buffer = static_cast<void*>(_typed_buffer); + + if (nirio_status_not_fatal(status)) { + index++; //Advances the caller's buffer + return make(this, _buffer, _length); + } else if (status == NiRio_Status_CommunicationTimeout) { + nirio_status_to_exception(status, "NI-RIO PCIe data transfer failed."); + return sptr(); + } else { + return sptr(); //NULL for timeout or error. + } + } + +private: + nirio_fifo<fifo_data_t>& _fifo; + fifo_data_t* _typed_buffer; + const size_t _frame_size; + size_t _num_frames; +}; + +class nirio_zero_copy_msb : public managed_send_buffer +{ +public: + nirio_zero_copy_msb(nirio_fifo<fifo_data_t>& fifo, const size_t frame_size): + _fifo(fifo), _frame_size(frame_size) { } + + void release(void) + { + _fifo.release(_frame_size / sizeof(fifo_data_t)); + } + + UHD_INLINE sptr get_new(const double timeout, size_t &index) + { + nirio_status status = 0; + size_t elems_acquired, elems_remaining; + nirio_status_chain(_fifo.acquire( + _typed_buffer, _frame_size / sizeof(fifo_data_t), + static_cast<uint32_t>(timeout*1000), + elems_acquired, elems_remaining), status); + _length = elems_acquired * sizeof(fifo_data_t); + _buffer = static_cast<void*>(_typed_buffer); + + if (nirio_status_not_fatal(status)) { + index++; //Advances the caller's buffer + return make(this, _buffer, _length); + } else if (status == NiRio_Status_CommunicationTimeout) { + nirio_status_to_exception(status, "NI-RIO PCIe data transfer failed."); + return sptr(); + } else { + return sptr(); //NULL for timeout or error. + } + } + +private: + nirio_fifo<fifo_data_t>& _fifo; + fifo_data_t* _typed_buffer; + const size_t _frame_size; + size_t _num_frames; +}; + +class nirio_zero_copy_impl : public nirio_zero_copy { +public: + typedef boost::shared_ptr<nirio_zero_copy_impl> sptr; + + nirio_zero_copy_impl( + uhd::niusrprio::niusrprio_session::sptr fpga_session, + uint32_t instance, + const zero_copy_xport_params& xport_params + ): + _fpga_session(fpga_session), + _fifo_instance(instance), + _xport_params(xport_params), + _next_recv_buff_index(0), _next_send_buff_index(0) + { + UHD_LOG << boost::format("Creating PCIe transport for channel %d") % instance << std::endl; + UHD_LOG << boost::format("nirio zero-copy RX transport configured with frame size = %u, #frames = %u, buffer size = %u\n") + % _xport_params.recv_frame_size % _xport_params.num_recv_frames % + (_xport_params.recv_frame_size * _xport_params.num_recv_frames); + UHD_LOG << boost::format("nirio zero-copy TX transport configured with frame size = %u, #frames = %u, buffer size = %u\n") + % _xport_params.send_frame_size % _xport_params.num_send_frames % (_xport_params.send_frame_size * _xport_params.num_send_frames); + + _recv_buffer_pool = buffer_pool::make(_xport_params.num_recv_frames, _xport_params.recv_frame_size); + _send_buffer_pool = buffer_pool::make(_xport_params.num_send_frames, _xport_params.send_frame_size); + + nirio_status status = 0; + size_t actual_depth = 0, actual_size = 0; + + //Configure frame width + nirio_status_chain( + _proxy().poke(PCIE_TX_DMA_REG(DMA_FRAME_SIZE_REG, _fifo_instance), + static_cast<uint32_t>(_xport_params.send_frame_size/sizeof(fifo_data_t))), + status); + nirio_status_chain( + _proxy().poke(PCIE_RX_DMA_REG(DMA_FRAME_SIZE_REG, _fifo_instance), + static_cast<uint32_t>(_xport_params.recv_frame_size/sizeof(fifo_data_t))), + status); + //Config 32-bit word flipping and Reset DMA streams + nirio_status_chain( + _proxy().poke(PCIE_TX_DMA_REG(DMA_CTRL_STATUS_REG, _fifo_instance), + DMA_CTRL_SW_BUF_U32 | DMA_CTRL_RESET), + status); + nirio_status_chain( + _proxy().poke(PCIE_RX_DMA_REG(DMA_CTRL_STATUS_REG, _fifo_instance), + DMA_CTRL_SW_BUF_U32 | DMA_CTRL_RESET), + status); + + //Create FIFOs + nirio_status_chain( + _fpga_session->create_rx_fifo(_fifo_instance, _recv_fifo), + status); + nirio_status_chain( + _fpga_session->create_tx_fifo(_fifo_instance, _send_fifo), + status); + + if ((_recv_fifo.get() != NULL) && (_send_fifo.get() != NULL)) { + //Initialize FIFOs + nirio_status_chain( + _recv_fifo->initialize( + (_xport_params.recv_frame_size*_xport_params.num_recv_frames)/sizeof(fifo_data_t), + actual_depth, actual_size), + status); + nirio_status_chain( + _send_fifo->initialize( + (_xport_params.send_frame_size*_xport_params.num_send_frames)/sizeof(fifo_data_t), + actual_depth, actual_size), + status); + + _proxy().get_rio_quirks().add_tx_fifo(_fifo_instance); + + nirio_status_chain(_recv_fifo->start(), status); + nirio_status_chain(_send_fifo->start(), status); + + if (nirio_status_not_fatal(status)) { + //Flush RX kernel buffers in case some cruft was + //left behind from the last run + _flush_rx_buff(); + + //allocate re-usable managed receive buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + _mrb_pool.push_back(boost::shared_ptr<nirio_zero_copy_mrb>(new nirio_zero_copy_mrb( + *_recv_fifo, get_recv_frame_size()))); + } + + //allocate re-usable managed send buffers + for (size_t i = 0; i < get_num_send_frames(); i++){ + _msb_pool.push_back(boost::shared_ptr<nirio_zero_copy_msb>(new nirio_zero_copy_msb( + *_send_fifo, get_send_frame_size()))); + } + } + } else { + nirio_status_chain(NiRio_Status_ResourceNotInitialized, status); + } + + nirio_status_to_exception(status, "Could not create nirio_zero_copy transport."); + } + + virtual ~nirio_zero_copy_impl() + { + _proxy().get_rio_quirks().remove_tx_fifo(_fifo_instance); + + //Reset DMA streams (Teardown, so don't status chain) + _proxy().poke(PCIE_TX_DMA_REG(DMA_CTRL_STATUS_REG, _fifo_instance), DMA_CTRL_RESET); + _proxy().poke(PCIE_RX_DMA_REG(DMA_CTRL_STATUS_REG, _fifo_instance), DMA_CTRL_RESET); + + _flush_rx_buff(); + + //Stop DMA channels. Stop is called in the fifo dtor but + //it doesn't hurt to do it here. + _send_fifo->stop(); + _recv_fifo->stop(); + } + + /******************************************************************* + * Receive implementation: + * Block on the managed buffer's get call and advance the index. + ******************************************************************/ + managed_recv_buffer::sptr get_recv_buff(double timeout) + { + if (_next_recv_buff_index == _xport_params.num_recv_frames) _next_recv_buff_index = 0; + return _mrb_pool[_next_recv_buff_index]->get_new(timeout, _next_recv_buff_index); + } + + size_t get_num_recv_frames(void) const {return _xport_params.num_recv_frames;} + size_t get_recv_frame_size(void) const {return _xport_params.recv_frame_size;} + + /******************************************************************* + * Send implementation: + * Block on the managed buffer's get call and advance the index. + ******************************************************************/ + managed_send_buffer::sptr get_send_buff(double timeout) + { + if (_next_send_buff_index == _xport_params.num_send_frames) _next_send_buff_index = 0; + return _msb_pool[_next_send_buff_index]->get_new(timeout, _next_send_buff_index); + } + + size_t get_num_send_frames(void) const {return _xport_params.num_send_frames;} + size_t get_send_frame_size(void) const {return _xport_params.send_frame_size;} + +private: + + UHD_INLINE niriok_proxy& _proxy() { return _fpga_session->get_kernel_proxy(); } + + UHD_INLINE void _flush_rx_buff() + { + nirio_status flush_status = 0; + while (nirio_status_not_fatal(flush_status)) { + static const size_t NUM_ELEMS_TO_FLUSH = 1; + static const uint32_t FLUSH_TIMEOUT_IN_MS = 0; + + fifo_data_t* flush_data_ptr = NULL; + size_t flush_elems_acquired = 0, flush_elems_remaining = 0; + flush_status = _recv_fifo->acquire( + flush_data_ptr, NUM_ELEMS_TO_FLUSH, FLUSH_TIMEOUT_IN_MS, + flush_elems_acquired, flush_elems_remaining); + if (nirio_status_not_fatal(flush_status)) { + _recv_fifo->release(flush_elems_acquired); + } + } + } + + //memory management -> buffers and fifos + niusrprio::niusrprio_session::sptr _fpga_session; + uint32_t _fifo_instance; + nirio_fifo<fifo_data_t>::sptr _recv_fifo, _send_fifo; + const zero_copy_xport_params _xport_params; + buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; + std::vector<boost::shared_ptr<nirio_zero_copy_msb> > _msb_pool; + std::vector<boost::shared_ptr<nirio_zero_copy_mrb> > _mrb_pool; + size_t _next_recv_buff_index, _next_send_buff_index; +}; + + +nirio_zero_copy::sptr nirio_zero_copy::make( + uhd::niusrprio::niusrprio_session::sptr fpga_session, + const uint32_t instance, + const zero_copy_xport_params& default_buff_args, + const device_addr_t &hints +){ + //Initialize xport_params + zero_copy_xport_params xport_params = default_buff_args; + + //The kernel buffer for this transport must be (num_frames * frame_size) big. Unlike ethernet, + //where the kernel buffer size is independent of the circular buffer size for the transport, + //it is possible for users to over constrain the system when they set the num_frames and the buff_size + //So we give buff_size priority over num_frames and throw an error if they conflict. + + //RX + xport_params.recv_frame_size = size_t(hints.cast<double>("recv_frame_size", default_buff_args.recv_frame_size)); + + size_t usr_num_recv_frames = static_cast<size_t>( + hints.cast<double>("num_recv_frames", default_buff_args.num_recv_frames)); + size_t usr_recv_buff_size = static_cast<size_t>( + hints.cast<double>("recv_buff_size", default_buff_args.num_recv_frames)); + + if (hints.has_key("num_recv_frames") and hints.has_key("recv_buff_size")) { + if (usr_recv_buff_size < xport_params.recv_frame_size) + throw uhd::value_error("recv_buff_size must be equal to or greater than (num_recv_frames * recv_frame_size)"); + + if ((usr_recv_buff_size/xport_params.recv_frame_size) != usr_num_recv_frames) + throw uhd::value_error("Conflicting values for recv_buff_size and num_recv_frames"); + } + + if (hints.has_key("recv_buff_size")) { + xport_params.num_recv_frames = std::max<size_t>(1, usr_recv_buff_size/xport_params.recv_frame_size); //Round down + } else if (hints.has_key("num_recv_frames")) { + xport_params.num_recv_frames = usr_num_recv_frames; + } + + //TX + xport_params.send_frame_size = size_t(hints.cast<double>("send_frame_size", default_buff_args.send_frame_size)); + + size_t usr_num_send_frames = static_cast<size_t>( + hints.cast<double>("num_send_frames", default_buff_args.num_send_frames)); + size_t usr_send_buff_size = static_cast<size_t>( + hints.cast<double>("send_buff_size", default_buff_args.num_send_frames)); + + if (hints.has_key("num_send_frames") and hints.has_key("send_buff_size")) { + if (usr_send_buff_size < xport_params.send_frame_size) + throw uhd::value_error("send_buff_size must be equal to or greater than (num_send_frames * send_frame_size)"); + + if ((usr_send_buff_size/xport_params.send_frame_size) != usr_num_send_frames) + throw uhd::value_error("Conflicting values for send_buff_size and num_send_frames"); + } + + if (hints.has_key("send_buff_size")) { + xport_params.num_send_frames = std::max<size_t>(1, usr_send_buff_size/xport_params.send_frame_size); //Round down + } else if (hints.has_key("num_send_frames")) { + xport_params.num_send_frames = usr_num_send_frames; + } + + return nirio_zero_copy::sptr(new nirio_zero_copy_impl(fpga_session, instance, xport_params)); +} + diff --git a/host/lib/transport/super_recv_packet_handler.hpp b/host/lib/transport/super_recv_packet_handler.hpp index 5080182d6..5c84327a4 100644 --- a/host/lib/transport/super_recv_packet_handler.hpp +++ b/host/lib/transport/super_recv_packet_handler.hpp @@ -39,6 +39,13 @@ #include <iostream> #include <vector> +// Included for debugging +#ifdef UHD_TXRX_DEBUG_PRINTS +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include "boost/date_time/posix_time/posix_time.hpp" +#endif + namespace uhd{ namespace transport{ namespace sph{ UHD_INLINE boost::uint32_t get_context_code( @@ -76,6 +83,10 @@ public: _queue_error_for_next_call(false), _buffers_infos_index(0) { + #ifdef ERROR_INJECT_DROPPED_PACKETS + recvd_packets = 0; + #endif + this->resize(size); set_alignment_failure_threshold(1000); } @@ -142,6 +153,16 @@ public: } /*! + * Flush all transports in the streamer: + * The packet payload is discarded. + */ + void flush_all(const double timeout = 0.0) + { + _flush_all(timeout); + return; + } + + /*! * Set the function to handle flow control * \param xport_chan which transport channel * \param handle_flowctrl the callback function @@ -213,7 +234,12 @@ public: buffs, nsamps_per_buff, metadata, timeout ); - if (one_packet) return accum_num_samps; + if (one_packet){ +#ifdef UHD_TXRX_DEBUG_PRINTS + dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet); +#endif + return accum_num_samps; + } //first recv had an error code set, return immediately if (metadata.error_code != rx_metadata_t::ERROR_CODE_NONE) return accum_num_samps; @@ -232,11 +258,13 @@ public: } accum_num_samps += num_samps; } +#ifdef UHD_TXRX_DEBUG_PRINTS + dbg_gather_data(nsamps_per_buff, accum_num_samps, metadata, timeout, one_packet); +#endif return accum_num_samps; } private: - vrt_unpacker_type _vrt_unpacker; size_t _header_offset_words32; double _tick_rate, _samp_rate; @@ -264,6 +292,13 @@ private: //! information stored for a received buffer struct per_buffer_info_type{ + void reset() + { + buff.reset(); + vrt_hdr = NULL; + time = time_spec_t(0.0); + copy_buff = NULL; + } managed_recv_buffer::sptr buff; const boost::uint32_t *vrt_hdr; vrt::if_packet_info_t ifpi; @@ -280,6 +315,17 @@ private: data_bytes_to_copy(0), fragment_offset_in_samps(0) {/* NOP */} + void reset() + { + indexes_todo.set(); + alignment_time = time_spec_t(0.0); + alignment_time_valid = false; + data_bytes_to_copy = 0; + fragment_offset_in_samps = 0; + metadata.reset(); + for (size_t i = 0; i < size(); i++) + at(i).reset(); + } boost::dynamic_bitset<> indexes_todo; //used in alignment logic time_spec_t alignment_time; //used in alignment logic bool alignment_time_valid; //used in alignment logic @@ -305,6 +351,10 @@ private: PACKET_SEQUENCE_ERROR }; + #ifdef ERROR_INJECT_DROPPED_PACKETS + int recvd_packets; + #endif + /******************************************************************* * Get and process a single packet from the transport: * Receive a single packet at the given index. @@ -313,15 +363,25 @@ private: ******************************************************************/ UHD_INLINE packet_type get_and_process_single_packet( const size_t index, - buffers_info_type &prev_buffer_info, - buffers_info_type &curr_buffer_info, + per_buffer_info_type &prev_buffer_info, + per_buffer_info_type &curr_buffer_info, double timeout ){ //get a single packet from the transport layer - managed_recv_buffer::sptr &buff = curr_buffer_info[index].buff; + managed_recv_buffer::sptr &buff = curr_buffer_info.buff; buff = _props[index].get_buff(timeout); if (buff.get() == NULL) return PACKET_TIMEOUT_ERROR; + #ifdef ERROR_INJECT_DROPPED_PACKETS + if (++recvd_packets > 1000) + { + recvd_packets = 0; + buff.reset(); + buff = _props[index].get_buff(timeout); + if (buff.get() == NULL) return PACKET_TIMEOUT_ERROR; + } + #endif + //bounds check before extract size_t num_packet_words32 = buff->size()/sizeof(boost::uint32_t); if (num_packet_words32 <= _header_offset_words32){ @@ -329,7 +389,7 @@ private: } //extract packet info - per_buffer_info_type &info = curr_buffer_info[index]; + per_buffer_info_type &info = curr_buffer_info; info.ifpi.num_packet_words32 = num_packet_words32 - _header_offset_words32; info.vrt_hdr = buff->cast<const boost::uint32_t *>() + _header_offset_words32; _vrt_unpacker(info.vrt_hdr, info.ifpi); @@ -339,7 +399,7 @@ private: //handle flow control if (_props[index].handle_flowctrl) { - if ((info.ifpi.packet_count % _props[index].fc_update_window/2) == 0) + if ((info.ifpi.packet_count % _props[index].fc_update_window) == 0) { _props[index].handle_flowctrl(info.ifpi.packet_count); } @@ -366,7 +426,7 @@ private: #endif //3) check for out of order timestamps - if (info.ifpi.has_tsf and prev_buffer_info[index].time > info.time){ + if (info.ifpi.has_tsf and prev_buffer_info.time > info.time){ return PACKET_TIMESTAMP_ERROR; } @@ -374,6 +434,33 @@ private: return PACKET_IF_DATA; } + void _flush_all(double timeout) + { + for (size_t i = 0; i < _props.size(); i++) + { + per_buffer_info_type prev_buffer_info, curr_buffer_info; + while (true) + { + //receive a single packet from the transport + try + { + // call into get_and_process_single_packet() + // to make sure flow control is handled + if (get_and_process_single_packet( + i, + prev_buffer_info, + curr_buffer_info, + timeout) == PACKET_TIMEOUT_ERROR) break; + } catch(...){} + prev_buffer_info = curr_buffer_info; + curr_buffer_info.reset(); + } + } + get_prev_buffer_info().reset(); + get_curr_buffer_info().reset(); + get_next_buffer_info().reset(); + } + /******************************************************************* * Alignment check: * Check the received packet for alignment and mark accordingly. @@ -411,7 +498,10 @@ private: ******************************************************************/ UHD_INLINE void get_aligned_buffs(double timeout){ + get_prev_buffer_info().reset(); // no longer need the previous info - reset it for future use + increment_buffer_info(); //increment to next buffer + buffers_info_type &prev_info = get_prev_buffer_info(); buffers_info_type &curr_info = get_curr_buffer_info(); buffers_info_type &next_info = get_next_buffer_info(); @@ -430,7 +520,7 @@ private: //receive a single packet from the transport try{ packet = get_and_process_single_packet( - index, prev_info, curr_info, timeout + index, prev_info[index], curr_info[index], timeout ); } @@ -440,12 +530,6 @@ private: "The receive packet handler caught an exception.\n%s" ) % e.what() << std::endl; std::swap(curr_info, next_info); //save progress from curr -> next - curr_info.metadata.has_time_spec = false; - curr_info.metadata.time_spec = time_spec_t(0.0); - curr_info.metadata.more_fragments = false; - curr_info.metadata.fragment_offset = 0; - curr_info.metadata.start_of_burst = false; - curr_info.metadata.end_of_burst = false; curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_BAD_PACKET; return; } @@ -470,25 +554,17 @@ private: std::swap(curr_info, next_info); //save progress from curr -> next curr_info.metadata.has_time_spec = next_info[index].ifpi.has_tsf; curr_info.metadata.time_spec = next_info[index].time; - curr_info.metadata.more_fragments = false; - curr_info.metadata.fragment_offset = 0; - curr_info.metadata.start_of_burst = false; - curr_info.metadata.end_of_burst = false; 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){ + rx_metadata_t metadata = curr_info.metadata; _props[index].handle_overflow(); + curr_info.metadata = metadata; UHD_MSG(fastpath) << "O"; } return; case PACKET_TIMEOUT_ERROR: std::swap(curr_info, next_info); //save progress from curr -> next - curr_info.metadata.has_time_spec = false; - curr_info.metadata.time_spec = time_spec_t(0.0); - curr_info.metadata.more_fragments = false; - curr_info.metadata.fragment_offset = 0; - curr_info.metadata.start_of_burst = false; - curr_info.metadata.end_of_burst = false; curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_TIMEOUT; return; @@ -498,10 +574,7 @@ private: curr_info.metadata.has_time_spec = prev_info.metadata.has_time_spec; curr_info.metadata.time_spec = prev_info.metadata.time_spec + time_spec_t::from_ticks( prev_info[index].ifpi.num_payload_words32*sizeof(boost::uint32_t)/_bytes_per_otw_item, _samp_rate); - curr_info.metadata.more_fragments = false; - curr_info.metadata.fragment_offset = 0; - curr_info.metadata.start_of_burst = false; - curr_info.metadata.end_of_burst = false; + curr_info.metadata.out_of_sequence = true; curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_OVERFLOW; UHD_MSG(fastpath) << "D"; return; @@ -516,12 +589,6 @@ private: "However, a timestamp match could not be determined.\n" ) % iterations << std::endl; std::swap(curr_info, next_info); //save progress from curr -> next - curr_info.metadata.has_time_spec = false; - curr_info.metadata.time_spec = time_spec_t(0.0); - curr_info.metadata.more_fragments = false; - curr_info.metadata.fragment_offset = 0; - curr_info.metadata.start_of_burst = false; - curr_info.metadata.end_of_burst = false; curr_info.metadata.error_code = rx_metadata_t::ERROR_CODE_ALIGNMENT; _props[index].handle_overflow(); return; @@ -554,13 +621,8 @@ private: const size_t buffer_offset_bytes = 0 ){ //get the next buffer if the current one has expired - if (get_curr_buffer_info().data_bytes_to_copy == 0){ - - //reset current buffer info members for reuse - get_curr_buffer_info().fragment_offset_in_samps = 0; - get_curr_buffer_info().alignment_time_valid = false; - get_curr_buffer_info().indexes_todo.set(); - + if (get_curr_buffer_info().data_bytes_to_copy == 0) + { //perform receive with alignment logic get_aligned_buffs(timeout); } @@ -641,6 +703,65 @@ private: size_t _convert_buffer_offset_bytes; size_t _convert_bytes_to_copy; + /* + * This last section is only for debugging purposes. + * It causes a lot of prints to stderr which can be piped to a file. + * Gathered data can be used to post process it with external tools. + */ +#ifdef UHD_TXRX_DEBUG_PRINTS + struct dbg_recv_stat_t { + dbg_recv_stat_t(long wc, size_t nspb, size_t nsr, uhd::rx_metadata_t md, double to, bool op, double rate): + wallclock(wc), nsamps_per_buff(nspb), nsamps_recv(nsr), metadata(md), timeout(to), one_packet(op), samp_rate(rate) + {} + long wallclock; + size_t nsamps_per_buff; + size_t nsamps_recv; + uhd::rx_metadata_t metadata; + double timeout; + bool one_packet; + double samp_rate; + // Create a formatted print line for all the info gathered in this struct. + std::string print_line() { + boost::format fmt("recv,%ld,%f,%i,%i,%s,%i,%s,%s,%s,%i,%s,%ld"); + fmt % wallclock; + fmt % timeout % (int)nsamps_per_buff % (int) nsamps_recv; + fmt % (one_packet ? "true":"false"); + fmt % metadata.error_code; + fmt % (metadata.start_of_burst ? "true":"false") % (metadata.end_of_burst ? "true":"false"); + fmt % (metadata.more_fragments ? "true":"false") % (int)metadata.fragment_offset; + fmt % (metadata.has_time_spec ? "true":"false") % metadata.time_spec.to_ticks(samp_rate); + return fmt.str(); + } + }; + + void dbg_gather_data(const size_t nsamps_per_buff, const size_t nsamps_recv, + uhd::rx_metadata_t &metadata, const double timeout, + const bool one_packet, + bool dbg_print_directly = true + ) + { + // Initialize a struct with all available data. It can return a formatted string with all infos if wanted. + dbg_recv_stat_t data(boost::get_system_time().time_of_day().total_microseconds(), + nsamps_per_buff, + nsamps_recv, + metadata, + timeout, + one_packet, + _samp_rate + ); + if(dbg_print_directly) { + dbg_print_err(data.print_line()); + } + } + + + + void dbg_print_err(std::string msg) { + std::string dbg_prefix("super_recv_packet_handler,"); + msg = dbg_prefix + msg; + fprintf(stderr, "%s\n", msg.c_str()); + } +#endif }; class recv_packet_streamer : public recv_packet_handler, public rx_streamer{ diff --git a/host/lib/transport/super_send_packet_handler.hpp b/host/lib/transport/super_send_packet_handler.hpp index ae483d1f3..c2810842e 100644 --- a/host/lib/transport/super_send_packet_handler.hpp +++ b/host/lib/transport/super_send_packet_handler.hpp @@ -35,7 +35,18 @@ #include <iostream> #include <vector> -namespace uhd{ namespace transport{ namespace sph{ +#ifdef UHD_TXRX_DEBUG_PRINTS +// Included for debugging +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include "boost/date_time/posix_time/posix_time.hpp" +#include <map> +#include <fstream> +#endif + +namespace uhd { +namespace transport { +namespace sph { /*********************************************************************** * Super send packet handler @@ -56,7 +67,7 @@ public: * \param size the number of transport channels */ send_packet_handler(const size_t size = 1): - _next_packet_seq(0) + _next_packet_seq(0), _cached_metadata(false) { this->set_enable_trailer(true); this->resize(size); @@ -183,6 +194,23 @@ public: if_packet_info.sob = metadata.start_of_burst; if_packet_info.eob = metadata.end_of_burst; + /* + * Metadata is cached when we get a send requesting a start of burst with no samples. + * It is applied here on the next call to send() that actually has samples to send. + */ + if (_cached_metadata && nsamps_per_buff != 0) + { + // If the new metada has a time_spec, do not use the cached time_spec. + if (!metadata.has_time_spec) + { + if_packet_info.has_tsf = _metadata_cache.has_time_spec; + if_packet_info.tsf = _metadata_cache.time_spec.to_ticks(_tick_rate); + } + if_packet_info.sob = _metadata_cache.start_of_burst; + if_packet_info.eob = _metadata_cache.end_of_burst; + _cached_metadata = false; + } + if (nsamps_per_buff <= _max_samples_per_packet){ //TODO remove this code when sample counts of zero are supported by hardware @@ -190,13 +218,27 @@ public: static const boost::uint64_t zero = 0; _zero_buffs.resize(buffs.size(), &zero); - if (nsamps_per_buff == 0) return send_one_packet( - _zero_buffs, 1, if_packet_info, timeout - ) & 0x0; + if (nsamps_per_buff == 0) + { + // if this is a start of a burst and there are no samples + if (metadata.start_of_burst) + { + // cache metadata and apply on the next send() + _metadata_cache = metadata; + _cached_metadata = true; + return 0; + } else { + // send requests with no samples are handled here (such as end of burst) + return send_one_packet(_zero_buffs, 1, if_packet_info, timeout) & 0x0; + } + } #endif - return send_one_packet(buffs, nsamps_per_buff, if_packet_info, timeout); - } + size_t nsamps_sent = send_one_packet(buffs, nsamps_per_buff, if_packet_info, timeout); +#ifdef UHD_TXRX_DEBUG_PRINTS + dbg_print_send(nsamps_per_buff, nsamps_sent, metadata, timeout); +#endif + return nsamps_sent; } size_t total_num_samps_sent = 0; //false until final fragment @@ -226,11 +268,14 @@ public: //send the final fragment with the helper function if_packet_info.eob = metadata.end_of_burst; - return total_num_samps_sent + send_one_packet( - buffs, final_length, - if_packet_info, timeout, - total_num_samps_sent*_bytes_per_cpu_item - ); + size_t nsamps_sent = total_num_samps_sent + + send_one_packet(buffs, final_length, if_packet_info, timeout, + total_num_samps_sent * _bytes_per_cpu_item); +#ifdef UHD_TXRX_DEBUG_PRINTS + dbg_print_send(nsamps_per_buff, nsamps_sent, metadata, timeout); + +#endif + return nsamps_sent; } private: @@ -255,6 +300,53 @@ private: size_t _next_packet_seq; bool _has_tlr; async_receiver_type _async_receiver; + bool _cached_metadata; + uhd::tx_metadata_t _metadata_cache; + +#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): + wallclock(wc), nsamps_per_buff(nspb), nsamps_sent(nss), metadata(md), timeout(to), samp_rate(rate) + {} + long wallclock; + size_t nsamps_per_buff; + size_t nsamps_sent; + uhd::tx_metadata_t metadata; + double timeout; + double samp_rate; + // Create a formatted print line for all the info gathered in this struct. + std::string print_line() { + boost::format fmt("send,%ld,%f,%i,%i,%s,%s,%s,%ld"); + fmt % wallclock; + fmt % timeout % (int)nsamps_per_buff % (int) nsamps_sent; + fmt % (metadata.start_of_burst ? "true":"false") % (metadata.end_of_burst ? "true":"false"); + fmt % (metadata.has_time_spec ? "true":"false") % metadata.time_spec.to_ticks(samp_rate); + return fmt.str(); + } + }; + + void dbg_print_send(size_t nsamps_per_buff, size_t nsamps_sent, + const uhd::tx_metadata_t &metadata, const double timeout, + bool dbg_print_directly = true) + { + dbg_send_stat_t data(boost::get_system_time().time_of_day().total_microseconds(), + nsamps_per_buff, + nsamps_sent, + metadata, + timeout, + _samp_rate + ); + if(dbg_print_directly){ + dbg_print_err(data.print_line()); + } + } + void dbg_print_err(std::string msg) { + msg = "super_send_packet_handler," + msg; + fprintf(stderr, "%s\n", msg.c_str()); + } + + +#endif /******************************************************************* * Send a single packet: @@ -266,6 +358,7 @@ private: const double timeout, const size_t buffer_offset_bytes = 0 ){ + //load the rest of the if_packet_info in here if_packet_info.num_payload_bytes = nsamps_per_buff*_num_inputs*_bytes_per_otw_item; if_packet_info.num_payload_words32 = (if_packet_info.num_payload_bytes + 3/*round up*/)/sizeof(boost::uint32_t); @@ -374,6 +467,8 @@ private: size_t _max_num_samps; }; -}}} //namespace +} // namespace sph +} // namespace transport +} // namespace uhd #endif /* INCLUDED_LIBUHD_TRANSPORT_SUPER_SEND_PACKET_HANDLER_HPP */ diff --git a/host/lib/transport/tcp_zero_copy.cpp b/host/lib/transport/tcp_zero_copy.cpp new file mode 100644 index 000000000..402bda1e8 --- /dev/null +++ b/host/lib/transport/tcp_zero_copy.cpp @@ -0,0 +1,225 @@ +// +// Copyright 2010-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 "udp_common.hpp" +#include <uhd/transport/tcp_zero_copy.hpp> +#include <uhd/transport/buffer_pool.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/atomic.hpp> +#include <boost/format.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <vector> + +using namespace uhd; +using namespace uhd::transport; +namespace asio = boost::asio; + +static const size_t DEFAULT_NUM_FRAMES = 32; +static const size_t DEFAULT_FRAME_SIZE = 2048; + +/*********************************************************************** + * Reusable managed receiver buffer: + * - get_new performs the recv operation + **********************************************************************/ +class tcp_zero_copy_asio_mrb : public managed_recv_buffer{ +public: + tcp_zero_copy_asio_mrb(void *mem, int sock_fd, const size_t frame_size): + _mem(mem), _sock_fd(sock_fd), _frame_size(frame_size) { /*NOP*/ } + + void release(void){ + _claimer.release(); + } + + UHD_INLINE sptr get_new(const double timeout, size_t &index){ + if (not _claimer.claim_with_wait(timeout)) return sptr(); + + #ifdef MSG_DONTWAIT //try a non-blocking recv() if supported + _len = ::recv(_sock_fd, (char *)_mem, _frame_size, MSG_DONTWAIT); + if (_len > 0){ + index++; //advances the caller's buffer + return make(this, _mem, size_t(_len)); + } + #endif + + if (wait_for_recv_ready(_sock_fd, timeout)){ + _len = ::recv(_sock_fd, (char *)_mem, _frame_size, 0); + index++; //advances the caller's buffer + return make(this, _mem, size_t(_len)); + } + + _claimer.release(); //undo claim + return sptr(); //null for timeout + } + +private: + void *_mem; + int _sock_fd; + size_t _frame_size; + ssize_t _len; + simple_claimer _claimer; +}; + +/*********************************************************************** + * Reusable managed send buffer: + * - commit performs the send operation + **********************************************************************/ +class tcp_zero_copy_asio_msb : public managed_send_buffer{ +public: + tcp_zero_copy_asio_msb(void *mem, int sock_fd, const size_t frame_size): + _mem(mem), _sock_fd(sock_fd), _frame_size(frame_size) { /*NOP*/ } + + void release(void){ + //Retry logic because send may fail with ENOBUFS. + //This is known to occur at least on some OSX systems. + //But it should be safe to always check for the error. + while (true) + { + this->commit(_frame_size); //always full size frames to avoid pkt coalescing + const ssize_t ret = ::send(_sock_fd, (const char *)_mem, size(), 0); + if (ret == ssize_t(size())) break; + if (ret == -1 and errno == ENOBUFS) + { + boost::this_thread::sleep(boost::posix_time::microseconds(1)); + continue; //try to send again + } + UHD_ASSERT_THROW(ret == ssize_t(size())); + } + _claimer.release(); + } + + UHD_INLINE sptr get_new(const double timeout, size_t &index){ + if (not _claimer.claim_with_wait(timeout)) return sptr(); + index++; //advances the caller's buffer + return make(this, _mem, _frame_size); + } + +private: + void *_mem; + int _sock_fd; + size_t _frame_size; + simple_claimer _claimer; +}; + +/*********************************************************************** + * Zero Copy TCP implementation with ASIO: + * This is the portable zero copy implementation for systems + * where a faster, platform specific solution is not available. + * However, it is not a true zero copy implementation as each + * send and recv requires a copy operation to/from userspace. + **********************************************************************/ +class tcp_zero_copy_asio_impl : public tcp_zero_copy{ +public: + typedef boost::shared_ptr<tcp_zero_copy_asio_impl> sptr; + + tcp_zero_copy_asio_impl( + const std::string &addr, + const std::string &port, + const device_addr_t &hints + ): + _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", DEFAULT_FRAME_SIZE))), + _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), + _send_frame_size(size_t(hints.cast<double>("send_frame_size", DEFAULT_FRAME_SIZE))), + _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), + _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), + _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), + _next_recv_buff_index(0), _next_send_buff_index(0) + { + UHD_LOG << boost::format("Creating tcp transport for %s %s") % addr % port << std::endl; + + //resolve the address + asio::ip::tcp::resolver resolver(_io_service); + asio::ip::tcp::resolver::query query(asio::ip::tcp::v4(), addr, port); + asio::ip::tcp::endpoint receiver_endpoint = *resolver.resolve(query); + + //create, open, and connect the socket + _socket.reset(new asio::ip::tcp::socket(_io_service)); + _socket->connect(receiver_endpoint); + _sock_fd = _socket->native(); + + //packets go out ASAP + asio::ip::tcp::no_delay option(true); + _socket->set_option(option); + + //allocate re-usable managed receive buffers + for (size_t i = 0; i < get_num_recv_frames(); i++){ + _mrb_pool.push_back(boost::make_shared<tcp_zero_copy_asio_mrb>( + _recv_buffer_pool->at(i), _sock_fd, get_recv_frame_size() + )); + } + + //allocate re-usable managed send buffers + for (size_t i = 0; i < get_num_send_frames(); i++){ + _msb_pool.push_back(boost::make_shared<tcp_zero_copy_asio_msb>( + _send_buffer_pool->at(i), _sock_fd, get_send_frame_size() + )); + } + } + + /******************************************************************* + * Receive implementation: + * Block on the managed buffer's get call and advance the index. + ******************************************************************/ + managed_recv_buffer::sptr get_recv_buff(double timeout){ + if (_next_recv_buff_index == _num_recv_frames) _next_recv_buff_index = 0; + return _mrb_pool[_next_recv_buff_index]->get_new(timeout, _next_recv_buff_index); + } + + size_t get_num_recv_frames(void) const {return _num_recv_frames;} + size_t get_recv_frame_size(void) const {return _recv_frame_size;} + + /******************************************************************* + * Send implementation: + * Block on the managed buffer's get call and advance the index. + ******************************************************************/ + managed_send_buffer::sptr get_send_buff(double timeout){ + if (_next_send_buff_index == _num_send_frames) _next_send_buff_index = 0; + return _msb_pool[_next_send_buff_index]->get_new(timeout, _next_send_buff_index); + } + + size_t get_num_send_frames(void) const {return _num_send_frames;} + size_t get_send_frame_size(void) const {return _send_frame_size;} + +private: + //memory management -> buffers and fifos + const size_t _recv_frame_size, _num_recv_frames; + const size_t _send_frame_size, _num_send_frames; + buffer_pool::sptr _recv_buffer_pool, _send_buffer_pool; + std::vector<boost::shared_ptr<tcp_zero_copy_asio_msb> > _msb_pool; + std::vector<boost::shared_ptr<tcp_zero_copy_asio_mrb> > _mrb_pool; + size_t _next_recv_buff_index, _next_send_buff_index; + + //asio guts -> socket and service + asio::io_service _io_service; + boost::shared_ptr<asio::ip::tcp::socket> _socket; + int _sock_fd; +}; + +/*********************************************************************** + * TCP zero copy make function + **********************************************************************/ +zero_copy_if::sptr tcp_zero_copy::make( + const std::string &addr, + const std::string &port, + const device_addr_t &hints +){ + zero_copy_if::sptr xport; + xport.reset(new tcp_zero_copy_asio_impl(addr, port, hints)); + while (xport->get_recv_buff(0.0)){} //flush + return xport; +} diff --git a/host/lib/transport/udp_wsa_zero_copy.cpp b/host/lib/transport/udp_wsa_zero_copy.cpp index 6fe4e3cad..52382f84d 100644 --- a/host/lib/transport/udp_wsa_zero_copy.cpp +++ b/host/lib/transport/udp_wsa_zero_copy.cpp @@ -182,14 +182,15 @@ public: udp_zero_copy_wsa_impl( const std::string &addr, const std::string &port, + zero_copy_xport_params& xport_params, const device_addr_t &hints ): - _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", udp_simple::mtu))), - _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), - _send_frame_size(size_t(hints.cast<double>("send_frame_size", udp_simple::mtu))), - _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), - _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), - _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), + _recv_frame_size(xport_params.recv_frame_size), + _num_recv_frames(xport_params.num_recv_frames), + _send_frame_size(xport_params.send_frame_size), + _num_send_frames(xport_params.num_send_frames), + _recv_buffer_pool(buffer_pool::make(xport_params.num_recv_frames, xport_params.recv_frame_size)), + _send_buffer_pool(buffer_pool::make(xport_params.num_send_frames, xport_params.send_frame_size)), _next_recv_buff_index(0), _next_send_buff_index(0) { #ifdef CHECK_REG_SEND_THRESH @@ -275,6 +276,36 @@ public: size_t get_num_send_frames(void) const {return _num_send_frames;} size_t get_send_frame_size(void) const {return _send_frame_size;} + //! Read back the socket's buffer space reserved for receives + size_t get_recv_buff_size(void) { + int recv_buff_size = 0; + int opt_len = sizeof(recv_buff_size); + getsockopt( + _sock_fd, + SOL_SOCKET, + SO_RCVBUF, + (char *)&recv_buff_size, + (int *)&opt_len + ); + + return (size_t) recv_buff_size; + } + + //! Read back the socket's buffer space reserved for sends + size_t get_send_buff_size(void) { + int send_buff_size = 0; + int opt_len = sizeof(send_buff_size); + getsockopt( + _sock_fd, + SOL_SOCKET, + SO_SNDBUF, + (char *)&send_buff_size, + (int *)&opt_len + ); + + return (size_t) send_buff_size; + } + private: //memory management -> buffers and fifos const size_t _recv_frame_size, _num_recv_frames; @@ -291,10 +322,67 @@ private: /*********************************************************************** * UDP zero copy make function **********************************************************************/ +void check_usr_buff_size( + size_t actual_buff_size, + size_t user_buff_size, // Set this to zero for no user-defined preference + const std::string tx_rx +){ + UHD_LOG << boost::format( + "Target %s sock buff size: %d bytes\n" + "Actual %s sock buff size: %d bytes" + ) % tx_rx % user_buff_size % tx_rx % actual_buff_size << std::endl; + if ((user_buff_size != 0.0) and (actual_buff_size < user_buff_size)) UHD_MSG(warning) << boost::format( + "The %s buffer could not be resized sufficiently.\n" + "Target sock buff size: %d bytes.\n" + "Actual sock buff size: %d bytes.\n" + "See the transport application notes on buffer resizing.\n" + ) % tx_rx % user_buff_size % actual_buff_size; +} + + + udp_zero_copy::sptr udp_zero_copy::make( const std::string &addr, const std::string &port, + const zero_copy_xport_params &default_buff_args, + udp_zero_copy::buff_params& buff_params_out, const device_addr_t &hints ){ - return sptr(new udp_zero_copy_wsa_impl(addr, port, hints)); + //Initialize xport_params + zero_copy_xport_params xport_params = default_buff_args; + + xport_params.recv_frame_size = size_t(hints.cast<double>("recv_frame_size", default_buff_args.recv_frame_size)); + xport_params.num_recv_frames = size_t(hints.cast<double>("num_recv_frames", default_buff_args.num_recv_frames)); + xport_params.send_frame_size = size_t(hints.cast<double>("send_frame_size", default_buff_args.send_frame_size)); + xport_params.num_send_frames = size_t(hints.cast<double>("num_send_frames", default_buff_args.num_send_frames)); + + //extract buffer size hints from the device addr and check if they match up + size_t usr_recv_buff_size = size_t(hints.cast<double>("recv_buff_size", 0.0)); + size_t usr_send_buff_size = size_t(hints.cast<double>("send_buff_size", 0.0)); + if (hints.has_key("recv_buff_size")) { + if (usr_recv_buff_size < xport_params.recv_frame_size * xport_params.num_recv_frames) { + throw uhd::value_error((boost::format( + "recv_buff_size must be equal to or greater than (num_recv_frames * recv_frame_size) where num_recv_frames=%d, recv_frame_size=%d") + % xport_params.num_recv_frames % xport_params.recv_frame_size).str()); + } + } + if (hints.has_key("send_buff_size")) { + if (usr_send_buff_size < xport_params.send_frame_size * xport_params.num_send_frames) { + throw uhd::value_error((boost::format( + "send_buff_size must be equal to or greater than (num_send_frames * send_frame_size) where num_send_frames=%d, send_frame_size=%d") + % xport_params.num_send_frames % xport_params.send_frame_size).str()); + } + } + + udp_zero_copy_wsa_impl::sptr udp_trans( + new udp_zero_copy_wsa_impl(addr, port, xport_params, hints) + ); + + // Read back the actual socket buffer sizes + buff_params_out.recv_buff_size = udp_trans->get_recv_buff_size(); + buff_params_out.send_buff_size = udp_trans->get_send_buff_size(); + check_usr_buff_size(buff_params_out.recv_buff_size, usr_recv_buff_size, "recv"); + check_usr_buff_size(buff_params_out.send_buff_size, usr_send_buff_size, "send"); + + return udp_trans; } diff --git a/host/lib/transport/udp_zero_copy.cpp b/host/lib/transport/udp_zero_copy.cpp index 7b6a476f5..adc7d5585 100644 --- a/host/lib/transport/udp_zero_copy.cpp +++ b/host/lib/transport/udp_zero_copy.cpp @@ -158,14 +158,14 @@ public: udp_zero_copy_asio_impl( const std::string &addr, const std::string &port, - const device_addr_t &hints + const zero_copy_xport_params& xport_params ): - _recv_frame_size(size_t(hints.cast<double>("recv_frame_size", udp_simple::mtu))), - _num_recv_frames(size_t(hints.cast<double>("num_recv_frames", DEFAULT_NUM_FRAMES))), - _send_frame_size(size_t(hints.cast<double>("send_frame_size", udp_simple::mtu))), - _num_send_frames(size_t(hints.cast<double>("num_send_frames", DEFAULT_NUM_FRAMES))), - _recv_buffer_pool(buffer_pool::make(_num_recv_frames, _recv_frame_size)), - _send_buffer_pool(buffer_pool::make(_num_send_frames, _send_frame_size)), + _recv_frame_size(xport_params.recv_frame_size), + _num_recv_frames(xport_params.num_recv_frames), + _send_frame_size(xport_params.send_frame_size), + _num_send_frames(xport_params.num_send_frames), + _recv_buffer_pool(buffer_pool::make(xport_params.num_recv_frames, xport_params.recv_frame_size)), + _send_buffer_pool(buffer_pool::make(xport_params.num_send_frames, xport_params.send_frame_size)), _next_recv_buff_index(0), _next_send_buff_index(0) { UHD_LOG << boost::format("Creating udp transport for %s %s") % addr % port << std::endl; @@ -256,11 +256,12 @@ private: /*********************************************************************** * UDP zero copy make function **********************************************************************/ -template<typename Opt> static void resize_buff_helper( +template<typename Opt> static size_t resize_buff_helper( udp_zero_copy_asio_impl::sptr udp_trans, const size_t target_size, const std::string &name ){ + size_t actual_size = 0; std::string help_message; #if defined(UHD_PLATFORM_LINUX) help_message = str(boost::format( @@ -270,7 +271,7 @@ template<typename Opt> static void resize_buff_helper( //resize the buffer if size was provided if (target_size > 0){ - size_t actual_size = udp_trans->resize_buff<Opt>(target_size); + actual_size = udp_trans->resize_buff<Opt>(target_size); UHD_LOG << boost::format( "Target %s sock buff size: %d bytes\n" "Actual %s sock buff size: %d bytes" @@ -282,24 +283,54 @@ template<typename Opt> static void resize_buff_helper( "See the transport application notes on buffer resizing.\n%s" ) % name % target_size % actual_size % help_message; } + + return actual_size; } udp_zero_copy::sptr udp_zero_copy::make( const std::string &addr, const std::string &port, + const zero_copy_xport_params &default_buff_args, + udp_zero_copy::buff_params& buff_params_out, const device_addr_t &hints ){ - udp_zero_copy_asio_impl::sptr udp_trans( - new udp_zero_copy_asio_impl(addr, port, hints) - ); + //Initialize xport_params + zero_copy_xport_params xport_params = default_buff_args; + + xport_params.recv_frame_size = size_t(hints.cast<double>("recv_frame_size", default_buff_args.recv_frame_size)); + xport_params.num_recv_frames = size_t(hints.cast<double>("num_recv_frames", default_buff_args.num_recv_frames)); + xport_params.send_frame_size = size_t(hints.cast<double>("send_frame_size", default_buff_args.send_frame_size)); + xport_params.num_send_frames = size_t(hints.cast<double>("num_send_frames", default_buff_args.num_send_frames)); //extract buffer size hints from the device addr - size_t recv_buff_size = size_t(hints.cast<double>("recv_buff_size", 0.0)); - size_t send_buff_size = size_t(hints.cast<double>("send_buff_size", 0.0)); + size_t usr_recv_buff_size = size_t(hints.cast<double>("recv_buff_size", 0.0)); + size_t usr_send_buff_size = size_t(hints.cast<double>("send_buff_size", 0.0)); + + if (hints.has_key("recv_buff_size")) { + if (usr_recv_buff_size < xport_params.recv_frame_size * xport_params.num_recv_frames) { + throw uhd::value_error((boost::format( + "recv_buff_size must be equal to or greater than (num_recv_frames * recv_frame_size) where num_recv_frames=%d, recv_frame_size=%d") + % xport_params.num_recv_frames % xport_params.recv_frame_size).str()); + } + } + + if (hints.has_key("send_buff_size")) { + if (usr_send_buff_size < xport_params.send_frame_size * xport_params.num_send_frames) { + throw uhd::value_error((boost::format( + "send_buff_size must be equal to or greater than (num_send_frames * send_frame_size) where num_send_frames=%d, send_frame_size=%d") + % xport_params.num_send_frames % xport_params.send_frame_size).str()); + } + } + + udp_zero_copy_asio_impl::sptr udp_trans( + new udp_zero_copy_asio_impl(addr, port, xport_params) + ); //call the helper to resize send and recv buffers - resize_buff_helper<asio::socket_base::receive_buffer_size>(udp_trans, recv_buff_size, "recv"); - resize_buff_helper<asio::socket_base::send_buffer_size> (udp_trans, send_buff_size, "send"); + buff_params_out.recv_buff_size = + resize_buff_helper<asio::socket_base::receive_buffer_size>(udp_trans, usr_recv_buff_size, "recv"); + buff_params_out.send_buff_size = + resize_buff_helper<asio::socket_base::send_buffer_size> (udp_trans, usr_send_buff_size, "send"); return udp_trans; } diff --git a/host/lib/transport/xport_benchmarker.cpp b/host/lib/transport/xport_benchmarker.cpp new file mode 100644 index 000000000..d58dbea47 --- /dev/null +++ b/host/lib/transport/xport_benchmarker.cpp @@ -0,0 +1,155 @@ +// +// Copyright 2010-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 "xport_benchmarker.hpp" + +namespace uhd { namespace transport { + +const device_addr_t& xport_benchmarker::benchmark_throughput_chdr +( + zero_copy_if::sptr tx_transport, + zero_copy_if::sptr rx_transport, + boost::uint32_t sid, + bool big_endian, + boost::uint32_t duration_ms) +{ + vrt::if_packet_info_t pkt_info; + _initialize_chdr(tx_transport, rx_transport, sid, pkt_info); + _reset_counters(); + boost::posix_time::ptime start_time(boost::posix_time::microsec_clock::local_time()); + + _tx_thread.reset(new boost::thread(boost::bind(&xport_benchmarker::_stream_tx, this, tx_transport.get(), &pkt_info, big_endian))); + _rx_thread.reset(new boost::thread(boost::bind(&xport_benchmarker::_stream_rx, this, rx_transport.get(), &pkt_info, big_endian))); + + boost::this_thread::sleep(boost::posix_time::milliseconds(duration_ms)); + + _tx_thread->interrupt(); + _rx_thread->interrupt(); + _tx_thread->join(); + _rx_thread->join(); + + boost::posix_time::ptime stop_time(boost::posix_time::microsec_clock::local_time()); + double duration_s = ((double)(stop_time-start_time).total_microseconds())/1e6; + + boost::uint64_t tx_bytes = pkt_info.num_payload_words32*sizeof(uint32_t)*_num_tx_packets; + boost::uint64_t rx_bytes = pkt_info.num_payload_words32*sizeof(uint32_t)*_num_rx_packets; + double tx_rate = (((double)tx_bytes)/duration_s); + double rx_rate = (((double)rx_bytes)/duration_s); + + _results["TX-Bytes"] = (boost::format("%.2fMB") % (tx_bytes/(1024*1024))).str(); + _results["RX-Bytes"] = (boost::format("%.2fMB") % (rx_bytes/(1024*1024))).str(); + _results["TX-Throughput"] = (boost::format("%.2fMB/s") % (tx_rate/(1024*1024))).str(); + _results["RX-Throughput"] = (boost::format("%.2fMB/s") % (rx_rate/(1024*1024))).str(); + _results["TX-Timeouts"] = boost::lexical_cast<std::string>(_num_tx_timeouts); + _results["RX-Timeouts"] = boost::lexical_cast<std::string>(_num_rx_timeouts); + _results["Data-Errors"] = boost::lexical_cast<std::string>(_num_data_errors); + + return _results; +} + +void xport_benchmarker::_stream_tx(zero_copy_if* transport, vrt::if_packet_info_t* pkt_info, bool big_endian) +{ + while (not boost::this_thread::interruption_requested()) { + managed_send_buffer::sptr buff = transport->get_send_buff(_tx_timeout); + if (buff) { + boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>(); + //Populate packet + if (big_endian) { + vrt::if_hdr_pack_be(packet_buff, *pkt_info); + } else { + vrt::if_hdr_pack_le(packet_buff, *pkt_info); + } + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(pkt_info->num_packet_words32)); + _num_tx_packets++; + } else { + _num_tx_timeouts++; + } + } +} + +void xport_benchmarker::_stream_rx(zero_copy_if* transport, const vrt::if_packet_info_t* exp_pkt_info, bool big_endian) +{ + while (not boost::this_thread::interruption_requested()) { + managed_recv_buffer::sptr buff = transport->get_recv_buff(_rx_timeout); + if (buff) { + //Extract packet info + vrt::if_packet_info_t pkt_info; + pkt_info.link_type = exp_pkt_info->link_type; + pkt_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + _num_rx_packets++; + + //unpacking can fail + try { + if (big_endian) { + vrt::if_hdr_unpack_be(packet_buff, pkt_info); + } else { + vrt::if_hdr_unpack_le(packet_buff, pkt_info); + } + + if (exp_pkt_info->packet_type != pkt_info.packet_type || + exp_pkt_info->num_payload_bytes != pkt_info.num_payload_bytes) { + _num_data_errors++; + } + } catch(const std::exception &ex) { + _num_data_errors++; + } + } else { + _num_rx_timeouts++; + } + } +} + +void xport_benchmarker::_reset_counters(void) +{ + _num_tx_packets = 0; + _num_rx_packets = 0; + _num_tx_timeouts = 0; + _num_rx_timeouts = 0; + _num_data_errors = 0; +} + +void xport_benchmarker::_initialize_chdr( + zero_copy_if::sptr tx_transport, + zero_copy_if::sptr rx_transport, + boost::uint32_t sid, + vrt::if_packet_info_t& pkt_info) +{ + _tx_timeout = 0.5; + _rx_timeout = 0.5; + + size_t frame_size = std::min(tx_transport->get_send_frame_size(), rx_transport->get_recv_frame_size()); + + pkt_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + pkt_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_DATA; + pkt_info.num_packet_words32 = (frame_size/sizeof(boost::uint32_t)); + pkt_info.num_payload_words32 = pkt_info.num_packet_words32 - 2; + pkt_info.num_payload_bytes = pkt_info.num_payload_words32*sizeof(boost::uint32_t); + pkt_info.packet_count = 0; + pkt_info.sob = false; + pkt_info.eob = false; + pkt_info.sid = sid; + pkt_info.has_sid = true; + pkt_info.has_cid = false; + pkt_info.has_tsi = false; + pkt_info.has_tsf = false; + pkt_info.has_tlr = false; +} + +}} diff --git a/host/lib/transport/xport_benchmarker.hpp b/host/lib/transport/xport_benchmarker.hpp new file mode 100644 index 000000000..9fca8d1fb --- /dev/null +++ b/host/lib/transport/xport_benchmarker.hpp @@ -0,0 +1,77 @@ +// +// Copyright 2010-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_LIBUHD_XPORT_BENCHMARKER_HPP +#define INCLUDED_LIBUHD_XPORT_BENCHMARKER_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/thread/thread.hpp> +#include <uhd/transport/vrt_if_packet.hpp> + +namespace uhd { namespace transport { + +//Test class to benchmark a low-level transport object with a VITA/C-VITA data stream +class xport_benchmarker : boost::noncopyable { +public: + const device_addr_t& benchmark_throughput_chdr( + zero_copy_if::sptr tx_transport, + zero_copy_if::sptr rx_transport, + boost::uint32_t sid, + bool big_endian, + boost::uint32_t duration_ms); + +private: + void _stream_tx( + zero_copy_if* transport, + vrt::if_packet_info_t* pkt_info, + bool big_endian); + + void _stream_rx( + zero_copy_if* transport, + const vrt::if_packet_info_t* exp_pkt_info, + bool big_endian); + + void _initialize_chdr( + zero_copy_if::sptr tx_transport, + zero_copy_if::sptr rx_transport, + boost::uint32_t sid, + vrt::if_packet_info_t& pkt_info); + + void _reset_counters(void); + + boost::shared_ptr<boost::thread> _tx_thread; + boost::shared_ptr<boost::thread> _rx_thread; + + boost::uint64_t _num_tx_packets; + boost::uint64_t _num_rx_packets; + boost::uint64_t _num_tx_timeouts; + boost::uint64_t _num_rx_timeouts; + boost::uint64_t _num_data_errors; + + double _tx_timeout; + double _rx_timeout; + + device_addr_t _results; +}; + + +}} //namespace + +#endif /* INCLUDED_LIBUHD_XPORT_BENCHMARKER_HPP */ diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index f8c817df5..c8c2e6a8d 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -37,4 +37,5 @@ INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) INCLUDE_SUBDIRECTORY(e100) +INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index 305ba42a7..a47856b07 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -52,9 +52,9 @@ static device_addrs_t b100_find(const device_addr_t &hint) //return an empty list of addresses when type is set to non-b100 if (hint.has_key("type") and hint["type"] != "b100") return b100_addrs; - //Return an empty list of addresses when an address is specified, - //since an address is intended for a different, non-USB, device. - if (hint.has_key("addr")) return b100_addrs; + //Return an empty list of addresses when an address or resource is specified, + //since an address and resource is intended for a different, non-USB, device. + if (hint.has_key("addr") || hint.has_key("resource")) return b100_addrs; unsigned int vid, pid; diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp index ab83c80a3..7d71d5ec3 100644 --- a/host/lib/usrp/b100/b100_impl.hpp +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -105,8 +105,6 @@ public: bool recv_async_msg(uhd::async_metadata_t &, double); private: - uhd::property_tree::sptr _tree; - //controllers fifo_ctrl_excelsior::sptr _fifo_ctrl; i2c_core_200::sptr _fpga_i2c_ctrl; @@ -129,11 +127,6 @@ private: uhd::usrp::dboard_manager::sptr _dboard_manager; uhd::usrp::dboard_iface::sptr _dboard_iface; - //device properties interface - uhd::property_tree::sptr get_tree(void) const{ - return _tree; - } - std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; std::vector<boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index ab437af6b..a7f9b11bd 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2013 Ettus Research LLC +// Copyright 2012-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 @@ -32,6 +32,7 @@ #include <boost/functional/hash.hpp> #include <cstdio> #include <ctime> +#include <cmath> using namespace uhd; using namespace uhd::usrp; @@ -53,9 +54,9 @@ static device_addrs_t b200_find(const device_addr_t &hint) //return an empty list of addresses when type is set to non-b200 if (hint.has_key("type") and hint["type"] != "b200") return b200_addrs; - //Return an empty list of addresses when an address is specified, - //since an address is intended for a different, non-USB, device. - if (hint.has_key("addr")) return b200_addrs; + //Return an empty list of addresses when an address or resource is specified, + //since an address and resource is intended for a different, non-USB, device. + if (hint.has_key("addr") || hint.has_key("resource")) return b200_addrs; unsigned int vid, pid; @@ -332,7 +333,7 @@ b200_impl::b200_impl(const device_addr_t &device_addr) data_xport_args // param hints ); while (_data_transport->get_recv_buff(0.0)){} //flush ctrl xport - _demux.reset(new recv_packet_demuxer_3000(_data_transport)); + _demux = recv_packet_demuxer_3000::make(_data_transport); //////////////////////////////////////////////////////////////////// // Init codec - turns on clocks @@ -373,12 +374,15 @@ b200_impl::b200_impl(const device_addr_t &device_addr) //////////////////////////////////////////////////////////////////// // create frontend mapping //////////////////////////////////////////////////////////////////// + std::vector<size_t> default_map(2, 0); default_map[1] = 1; // Set this to A->0 B->1 even if there's only A + _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") .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_rx_subdev_spec, this, _1)); + .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_tx_subdev_spec, this, _1)); + .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // setup radio control @@ -510,7 +514,7 @@ void b200_impl::setup_radio(const size_t dspno) // 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)); + perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP), true /*is_b200?*/); perif.ddc->set_link_rate(10e9/8); //whatever _tree->access<double>(mb_path / "tick_rate") .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp index bee42679b..a370e54f9 100644 --- a/host/lib/usrp/b200/b200_impl.hpp +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -80,8 +80,9 @@ static const boost::uint32_t B200_LOCAL_RESP_SID = FLIP_SID(B200_LOCAL_CTRL_SID) **********************************************************************/ //! Implementation guts -struct b200_impl : public uhd::device +class b200_impl : public uhd::device { +public: //structors b200_impl(const uhd::device_addr_t &); ~b200_impl(void); @@ -91,8 +92,7 @@ struct b200_impl : public uhd::device uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); bool recv_async_msg(uhd::async_metadata_t &, double); - uhd::property_tree::sptr _tree; - +private: //controllers b200_iface::sptr _iface; radio_ctrl_core_3000::sptr _local_ctrl; @@ -104,13 +104,7 @@ struct b200_impl : public uhd::device //transports uhd::transport::zero_copy_if::sptr _data_transport; uhd::transport::zero_copy_if::sptr _ctrl_transport; - boost::shared_ptr<uhd::usrp::recv_packet_demuxer_3000> _demux; - - //device properties interface - uhd::property_tree::sptr get_tree(void) const - { - return _tree; - } + uhd::usrp::recv_packet_demuxer_3000::sptr _demux; boost::weak_ptr<uhd::rx_streamer> _rx_streamer; boost::weak_ptr<uhd::tx_streamer> _tx_streamer; @@ -133,8 +127,7 @@ struct b200_impl : public uhd::device void set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &); void check_fw_compat(void); void check_fpga_compat(void); - void update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); - void update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void update_subdev_spec(const std::string &tx_rx, const uhd::usrp::subdev_spec_t &); void update_time_source(const std::string &); void update_clock_source(const std::string &); void update_bandsel(const std::string& which, double freq); @@ -156,8 +149,12 @@ struct b200_impl : public uhd::device bool ant_rx2; }; std::vector<radio_perifs_t> _radio_perifs; - void setup_radio(const size_t which_radio); - void handle_overflow(const size_t index); + + /*! \brief Setup the DSP chain for one radio front-end. + * + */ + void setup_radio(const size_t radio_index); + void handle_overflow(const size_t radio_index); struct gpio_state { boost::uint32_t tx_bandsel_a, tx_bandsel_b, rx_bandsel_a, rx_bandsel_b, rx_bandsel_c, codec_arst, mimo, ref_sel; diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index 4768aa37b..4f072c4d4 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -72,44 +72,31 @@ void b200_impl::update_tx_samp_rate(const size_t dspno, const double rate) /*********************************************************************** * frontend selection **********************************************************************/ -void b200_impl::update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +void b200_impl::update_subdev_spec(const std::string &tx_rx, const uhd::usrp::subdev_spec_t &spec) { //sanity checking - if (spec.size()) validate_subdev_spec(_tree, spec, "rx"); + if (spec.size()) validate_subdev_spec(_tree, spec, tx_rx); UHD_ASSERT_THROW(spec.size() <= _radio_perifs.size()); - if (spec.size() > 0) + if (spec.size() >= 1) { UHD_ASSERT_THROW(spec[0].db_name == "A"); - UHD_ASSERT_THROW(spec[0].sd_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A" or spec[0].sd_name == "B"); } - if (spec.size() > 1) + if (spec.size() == 2) { - //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"); + UHD_ASSERT_THROW( + (spec[0].sd_name == "A" and spec[1].sd_name == "B") or + (spec[0].sd_name == "B" and spec[1].sd_name == "A") + ); } - this->update_enables(); -} - -void b200_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() <= _radio_perifs.size()); - - 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"); + std::vector<size_t> chan_to_dsp_map(spec.size(), 0); + for (size_t i = 0; i < spec.size(); i++) { + chan_to_dsp_map[i] = (spec[i].sd_name == "A") ? 0 : 1; } + _tree->access<std::vector<size_t> >("/mboards/0" / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); this->update_enables(); } @@ -238,13 +225,14 @@ rx_streamer::sptr b200_impl::get_rx_stream(const uhd::stream_args_t &args_) 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_perifs_t &perif = _radio_perifs[chan]; + const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/0/rx_chan_dsp_mapping") + .get().at(args.channels[stream_i]); + radio_perifs_t &perif = _radio_perifs[radio_index]; if (args.otw_format == "sc16") perif.ctrl->poke32(TOREG(SR_RX_FMT), 0); if (args.otw_format == "sc12") perif.ctrl->poke32(TOREG(SR_RX_FMT), 1); if (args.otw_format == "fc32") perif.ctrl->poke32(TOREG(SR_RX_FMT), 2); if (args.otw_format == "sc8") perif.ctrl->poke32(TOREG(SR_RX_FMT), 3); - const boost::uint32_t sid = chan?B200_RX_DATA1_SID:B200_RX_DATA0_SID; + const boost::uint32_t sid = radio_index ? B200_RX_DATA1_SID : B200_RX_DATA0_SID; //calculate packet size static const size_t hdr_size = 0 @@ -283,7 +271,7 @@ rx_streamer::sptr b200_impl::get_rx_stream(const uhd::stream_args_t &args_) &recv_packet_demuxer_3000::get_recv_buff, _demux, sid, _1 ), true /*flush*/); my_streamer->set_overflow_handler(stream_i, boost::bind( - &b200_impl::handle_overflow, this, chan + &b200_impl::handle_overflow, this, radio_index )); my_streamer->set_issue_stream_cmd(stream_i, boost::bind( &rx_vita_core_3000::issue_stream_command, perif.framer, _1 @@ -292,21 +280,21 @@ rx_streamer::sptr b200_impl::get_rx_stream(const uhd::stream_args_t &args_) //sets all tick and samp rates on this streamer this->update_tick_rate(this->get_tick_rate()); - _tree->access<double>(str(boost::format("/mboards/0/rx_dsps/%u/rate/value") % chan)).update(); + _tree->access<double>(str(boost::format("/mboards/0/rx_dsps/%u/rate/value") % radio_index)).update(); } this->update_enables(); return my_streamer; } -void b200_impl::handle_overflow(const size_t i) +void b200_impl::handle_overflow(const size_t radio_index) { boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[i].rx_streamer.lock()); + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[radio_index].rx_streamer.lock()); if (my_streamer->get_num_channels() == 2) //MIMO time { //find out if we were in continuous mode before stopping - const bool in_continuous_streaming_mode = _radio_perifs[i].framer->in_continuous_streaming_mode(); + const bool in_continuous_streaming_mode = _radio_perifs[radio_index].framer->in_continuous_streaming_mode(); //stop streaming my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); //flush demux @@ -319,11 +307,11 @@ void b200_impl::handle_overflow(const size_t i) { stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); stream_cmd.stream_now = false; - stream_cmd.time_spec = _radio_perifs[i].time64->get_time_now() + time_spec_t(0.01); + stream_cmd.time_spec = _radio_perifs[radio_index].time64->get_time_now() + time_spec_t(0.01); my_streamer->issue_stream_cmd(stream_cmd); } } - else _radio_perifs[i].framer->handle_overflow(); + else _radio_perifs[radio_index].framer->handle_overflow(); } /*********************************************************************** @@ -340,8 +328,9 @@ tx_streamer::sptr b200_impl::get_tx_stream(const uhd::stream_args_t &args_) 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_perifs_t &perif = _radio_perifs[chan]; + const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/0/tx_chan_dsp_mapping") + .get().at(args.channels[stream_i]); + radio_perifs_t &perif = _radio_perifs[radio_index]; if (args.otw_format == "sc16") perif.ctrl->poke32(TOREG(SR_TX_FMT), 0); if (args.otw_format == "sc12") perif.ctrl->poke32(TOREG(SR_TX_FMT), 1); if (args.otw_format == "fc32") perif.ctrl->poke32(TOREG(SR_TX_FMT), 2); @@ -382,13 +371,13 @@ tx_streamer::sptr b200_impl::get_tx_stream(const uhd::stream_args_t &args_) my_streamer->set_async_receiver(boost::bind( &async_md_type::pop_with_timed_wait, _async_task_data->async_md, _1, _2 )); - my_streamer->set_xport_chan_sid(stream_i, true, chan?B200_TX_DATA1_SID:B200_TX_DATA0_SID); + my_streamer->set_xport_chan_sid(stream_i, true, radio_index ? B200_TX_DATA1_SID : B200_TX_DATA0_SID); my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet perif.tx_streamer = my_streamer; //store weak pointer //sets all tick and samp rates on this streamer this->update_tick_rate(this->get_tick_rate()); - _tree->access<double>(str(boost::format("/mboards/0/tx_dsps/%u/rate/value") % chan)).update(); + _tree->access<double>(str(boost::format("/mboards/0/tx_dsps/%u/rate/value") % radio_index)).update(); } this->update_enables(); diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 1728b63f9..b99464873 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -30,6 +30,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/apply_corrections.cpp ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp new file mode 100644 index 000000000..f0df6a334 --- /dev/null +++ b/host/lib/usrp/common/adf435x_common.cpp @@ -0,0 +1,157 @@ +// +// 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 <uhd/types/tune_request.hpp> +#include <uhd/utils/log.hpp> + +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>((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, 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 = 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 new file mode 100644 index 000000000..617b9d97f --- /dev/null +++ b/host/lib/usrp/common/adf435x_common.hpp @@ -0,0 +1,63 @@ +// +// 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/recv_packet_demuxer_3000.hpp b/host/lib/usrp/common/recv_packet_demuxer_3000.hpp index 4fb6c4604..ec930f3ad 100644 --- a/host/lib/usrp/common/recv_packet_demuxer_3000.hpp +++ b/host/lib/usrp/common/recv_packet_demuxer_3000.hpp @@ -28,11 +28,18 @@ #include <uhd/utils/byteswap.hpp> #include <queue> #include <map> +#include <boost/enable_shared_from_this.hpp> namespace uhd{ namespace usrp{ - struct recv_packet_demuxer_3000 + struct recv_packet_demuxer_3000 : boost::enable_shared_from_this<recv_packet_demuxer_3000> { + typedef boost::shared_ptr<recv_packet_demuxer_3000> sptr; + static sptr make(transport::zero_copy_if::sptr xport) + { + return sptr(new recv_packet_demuxer_3000(xport)); + } + recv_packet_demuxer_3000(transport::zero_copy_if::sptr xport): _xport(xport) {/*NOP*/} @@ -70,7 +77,10 @@ namespace uhd{ namespace usrp{ return buff; } } - + // Following is disabled by default as super_recv_packet_handler (caller) is not thread safe + // Only underlying transport (libusb1_zero_copy) is thread safe + // The onus is on the caller to super_recv_packet_handler (and therefore this) to serialise access +#ifdef RECV_PACKET_DEMUXER_3000_THREAD_SAFE //---------------------------------------------------------- //-- Try to claim the transport or wait patiently //---------------------------------------------------------- @@ -84,6 +94,7 @@ namespace uhd{ namespace usrp{ //-- Wait on the transport for input buffers //---------------------------------------------------------- else +#endif // RECV_PACKET_DEMUXER_3000_THREAD_SAFE { buff = _xport->get_recv_buff(timeout); if (buff) @@ -99,8 +110,10 @@ namespace uhd{ namespace usrp{ buff.reset(); } } +#ifdef RECV_PACKET_DEMUXER_3000_THREAD_SAFE _claimed.write(0); cond.notify_all(); +#endif // RECV_PACKET_DEMUXER_3000_THREAD_SAFE } return buff; } @@ -114,14 +127,54 @@ namespace uhd{ namespace usrp{ } } + transport::zero_copy_if::sptr make_proxy(const boost::uint32_t sid); + typedef std::queue<transport::managed_recv_buffer::sptr> queue_type_t; std::map<boost::uint32_t, queue_type_t> _queues; transport::zero_copy_if::sptr _xport; +#ifdef RECV_PACKET_DEMUXER_3000_THREAD_SAFE uhd::atomic_uint32_t _claimed; boost::condition_variable cond; +#endif // RECV_PACKET_DEMUXER_3000_THREAD_SAFE boost::mutex mutex; }; + struct recv_packet_demuxer_proxy_3000 : transport::zero_copy_if + { + recv_packet_demuxer_proxy_3000(recv_packet_demuxer_3000::sptr demux, transport::zero_copy_if::sptr xport, const boost::uint32_t sid): + _demux(demux), _xport(xport), _sid(sid) + { + _demux->realloc_sid(_sid); //causes clear + } + + ~recv_packet_demuxer_proxy_3000(void) + { + _demux->realloc_sid(_sid); //causes clear + } + + size_t get_num_recv_frames(void) const {return _xport->get_num_recv_frames();} + size_t get_recv_frame_size(void) const {return _xport->get_recv_frame_size();} + transport::managed_recv_buffer::sptr get_recv_buff(double timeout) + { + return _demux->get_recv_buff(_sid, timeout); + } + size_t get_num_send_frames(void) const {return _xport->get_num_send_frames();} + size_t get_send_frame_size(void) const {return _xport->get_send_frame_size();} + transport::managed_send_buffer::sptr get_send_buff(double timeout) + { + return _xport->get_send_buff(timeout); + } + + recv_packet_demuxer_3000::sptr _demux; + transport::zero_copy_if::sptr _xport; + const boost::uint32_t _sid; + }; + + inline transport::zero_copy_if::sptr recv_packet_demuxer_3000::make_proxy(const boost::uint32_t sid) + { + return transport::zero_copy_if::sptr(new recv_packet_demuxer_proxy_3000(this->shared_from_this(), _xport, sid)); + } + }} //namespace uhd::usrp #endif /* INCLUDED_LIBUHD_USRP_COMMON_RECV_PACKET_DEMUXER_3000_HPP */ diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp index 51c23aa4b..e55f1f51e 100644 --- a/host/lib/usrp/cores/gpio_core_200.cpp +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -45,8 +45,8 @@ public: void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ _gpio_ddr[unit] = value; //shadow _iface->poke32(REG_GPIO_DDR, //update the 32 bit register - (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | - (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)) + (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)) ); } @@ -56,7 +56,7 @@ public: } boost::uint16_t read_gpio(const unit_t unit){ - return boost::uint16_t(_iface->peek32(_rb_addr) >> unit2shit(unit)); + return boost::uint16_t(_iface->peek32(_rb_addr) >> shift_by_unit(unit)); } private: @@ -68,7 +68,7 @@ private: 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; - unsigned unit2shit(const unit_t unit){ + unsigned shift_by_unit(const unit_t unit){ return (unit == dboard_iface::UNIT_RX)? 0 : 16; } @@ -81,16 +81,16 @@ private: void update(const atr_reg_t atr, const size_t addr){ const boost::uint32_t atr_val = - (boost::uint32_t(_atr_regs[dboard_iface::UNIT_RX][atr]) << unit2shit(dboard_iface::UNIT_RX)) | - (boost::uint32_t(_atr_regs[dboard_iface::UNIT_TX][atr]) << unit2shit(dboard_iface::UNIT_TX)); + (boost::uint32_t(_atr_regs[dboard_iface::UNIT_RX][atr]) << shift_by_unit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_atr_regs[dboard_iface::UNIT_TX][atr]) << shift_by_unit(dboard_iface::UNIT_TX)); const boost::uint32_t gpio_val = - (boost::uint32_t(_gpio_out[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | - (boost::uint32_t(_gpio_out[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); + (boost::uint32_t(_gpio_out[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_gpio_out[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX)); const boost::uint32_t ctrl = - (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_RX]) << unit2shit(dboard_iface::UNIT_RX)) | - (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_TX]) << unit2shit(dboard_iface::UNIT_TX)); + (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) | + (boost::uint32_t(_pin_ctrl[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX)); const boost::uint32_t val = (ctrl & atr_val) | ((~ctrl) & gpio_val); if (not _update_cache.has_key(addr) or _update_cache[addr] != val) { diff --git a/host/lib/usrp/cores/radio_ctrl_core_3000.cpp b/host/lib/usrp/cores/radio_ctrl_core_3000.cpp index 27fac3471..2005bcf33 100644 --- a/host/lib/usrp/cores/radio_ctrl_core_3000.cpp +++ b/host/lib/usrp/cores/radio_ctrl_core_3000.cpp @@ -88,9 +88,6 @@ public: boost::mutex::scoped_lock lock(_mutex); UHD_LOGV(always) << _name << std::hex << " addr 0x" << addr << std::dec << std::endl; this->send_pkt(SR_READBACK, addr/8); - this->wait_for_ack(false); - - this->send_pkt(0); 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); @@ -103,9 +100,6 @@ public: UHD_LOGV(always) << _name << std::hex << " addr 0x" << addr << std::dec << std::endl; this->send_pkt(SR_READBACK, addr/8); - this->wait_for_ack(false); - - this->send_pkt(0); return this->wait_for_ack(true); } diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.cpp b/host/lib/usrp/cores/rx_dsp_core_3000.cpp index 525916032..86846667f 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2013 Ettus Research LLC +// 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 @@ -32,6 +32,8 @@ #define REG_DSP_RX_SCALE_IQ _dsp_base + 4 #define REG_DSP_RX_DECIM _dsp_base + 8 #define REG_DSP_RX_MUX _dsp_base + 12 +#define REG_DSP_RX_COEFFS _dsp_base + 16 +//FIXME: Add code to support REG_DSP_RX_COEFFS #define FLAG_DSP_RX_MUX_SWAP_IQ (1 << 0) #define FLAG_DSP_RX_MUX_REAL_MODE (1 << 1) @@ -46,9 +48,10 @@ class rx_dsp_core_3000_impl : public rx_dsp_core_3000{ public: rx_dsp_core_3000_impl( wb_iface::sptr iface, - const size_t dsp_base + const size_t dsp_base, + const bool is_b200 ): - _iface(iface), _dsp_base(dsp_base) + _iface(iface), _dsp_base(dsp_base), _is_b200(is_b200) { // previously uninitialized - assuming zero for all _link_rate = _host_extra_scaling = _fxpt_scalar_correction = 0.0; @@ -88,6 +91,9 @@ public: uhd::meta_range_t get_host_rates(void){ meta_range_t range; + for (int rate = 1024; rate > 512; rate -= 8){ + range.push_back(range_t(_tick_rate/rate)); + } for (int rate = 512; rate > 256; rate -= 4){ range.push_back(range_t(_tick_rate/rate)); } @@ -105,7 +111,7 @@ public: size_t decim = decim_rate; //determine which half-band filters are activated - int hb0 = 0, hb1 = 0; + int hb0 = 0, hb1 = 0, hb2 = 0, hb_enable=0; if (decim % 2 == 0){ hb0 = 1; decim /= 2; @@ -114,16 +120,43 @@ public: hb1 = 1; decim /= 2; } + //the third half-band is not supported by the B200 + if (decim % 2 == 0 && !_is_b200){ + hb2 = 1; + decim /= 2; + } - _iface->poke32(REG_DSP_RX_DECIM, (hb1 << 9) | (hb0 << 8) | (decim & 0xff)); - - if (decim > 1 and hb0 == 0 and hb1 == 0) - { - UHD_MSG(warning) << boost::format( - "The requested decimation is odd; the user should expect CIC rolloff.\n" - "Select an even decimation to ensure that a halfband filter is enabled.\n" - "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" - ) % decim_rate % (_tick_rate/1e6) % (rate/1e6); + if (_is_b200) { + _iface->poke32(REG_DSP_RX_DECIM, (hb1 << 9) | (hb0 << 8) | (decim & 0xff)); + + if (decim > 1 and hb0 == 0 and hb1 == 0) { + UHD_MSG(warning) << boost::format( + "The requested decimation is odd; the user should expect CIC rolloff.\n" + "Select an even decimation to ensure that a halfband filter is enabled.\n" + "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" + ) % decim_rate % (_tick_rate/1e6) % (rate/1e6); + } + } else { + // Encode Halfband config for setting register programming. + if (hb2) { // Implies HB1 and HB0 also asserted + hb_enable=3; + } else if (hb1) { // Implies HB0 is also asserted + hb_enable=2; + } else if (hb0) { + hb_enable=1; + } else { + hb_enable=0; + } + _iface->poke32(REG_DSP_RX_DECIM, (hb_enable << 8) | (decim & 0xff)); + + if (decim > 1 and hb0 == 0 and hb1 == 0 and hb2 == 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 % (_tick_rate/1e6) % (rate/1e6); + } } // Calculate CIC decimation (i.e., without halfband decimators) @@ -137,7 +170,7 @@ public: void update_scalar(void){ const double factor = 1.0 + std::max(ceil_log2(_scaling_adjustment), 0.0); - const double target_scalar = (1 << 17)*_scaling_adjustment/_dsp_extra_scaling/factor; + const double target_scalar = (1 << 15)*_scaling_adjustment/_dsp_extra_scaling/factor; const boost::int32_t actual_scalar = boost::math::iround(target_scalar); _fxpt_scalar_correction = target_scalar/actual_scalar*factor; //should be small _iface->poke32(REG_DSP_RX_SCALE_IQ, actual_scalar); @@ -202,11 +235,12 @@ public: private: wb_iface::sptr _iface; const size_t _dsp_base; + 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; }; -rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base) +rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */) { - return sptr(new rx_dsp_core_3000_impl(iface, dsp_base)); + return sptr(new rx_dsp_core_3000_impl(iface, dsp_base, is_b200)); } diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.hpp b/host/lib/usrp/cores/rx_dsp_core_3000.hpp index 02e5587a2..f35e1e3d3 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.hpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2013 Ettus Research LLC +// 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 @@ -33,7 +33,8 @@ public: static sptr make( uhd::wb_iface::sptr iface, - const size_t dsp_base + const size_t dsp_base, + 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) = 0; diff --git a/host/lib/usrp/dboard/db_cbx.cpp b/host/lib/usrp/dboard/db_cbx.cpp index 04399e64e..78ecd9794 100644 --- a/host/lib/usrp/dboard/db_cbx.cpp +++ b/host/lib/usrp/dboard/db_cbx.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -18,7 +18,7 @@ #include "max2870_regs.hpp" #include "db_sbx_common.hpp" - +#include <boost/algorithm/string.hpp> using namespace uhd; using namespace uhd::usrp; @@ -46,6 +46,16 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) "CBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); + //clip the input target_freq = cbx_freq_range.clip(target_freq); @@ -64,14 +74,13 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) (64, max2870_regs_t::RF_DIVIDER_SELECT_DIV64) (128, max2870_regs_t::RF_DIVIDER_SELECT_DIV128) ; - + double actual_freq, pfd_freq; double ref_freq = self_base->get_iface()->get_clock_rate(unit); - max2870_regs_t::int_n_mode_t int_n_mode; int R=0, BS=0, N=0, FRAC=0, MOD=4095; int RFdiv = 1; max2870_regs_t::reference_divide_by_2_t T = max2870_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - max2870_regs_t::reference_doubler_t D = max2870_regs_t::REFERENCE_DOUBLER_DISABLED; + max2870_regs_t::reference_doubler_t D = max2870_regs_t::REFERENCE_DOUBLER_DISABLED; //Reference doubler for 50% duty cycle // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 @@ -115,11 +124,15 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) //Fractional-N calculation FRAC = int((vco_freq/pfd_freq - N)*MOD); - //are we in int-N or frac-N mode? - int_n_mode = (FRAC == 0) ? max2870_regs_t::INT_N_MODE_INT_N : max2870_regs_t::INT_N_MODE_FRAC_N; + if(is_int_n) { + if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target + N++; + } + FRAC = 0; + } //keep N within int divider requirements - if(int_n_mode == max2870_regs_t::INT_N_MODE_INT_N) { + if(is_int_n) { if(N < int_n_mode_div_range.start()) continue; if(N > int_n_mode_div_range.stop()) continue; } else { @@ -144,17 +157,20 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) //actual frequency calculation actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); + boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); + std::string board_name = (rx_id == 0x0085) ? "CBX-120" : "CBX"; UHD_LOGV(often) - << boost::format("CBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - << boost::format("CBX tune: 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 - << boost::format("CBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + << boost::format("%s Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f" + ) % board_name.c_str() % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + << boost::format("%s tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s" + ) % board_name.c_str() % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl + << boost::format("%s Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % board_name.c_str() % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; //load the register values max2870_regs_t regs; - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) regs.output_power = max2870_regs_t::OUTPUT_POWER_2DBM; else regs.output_power = max2870_regs_t::OUTPUT_POWER_5DBM; @@ -163,7 +179,7 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) max2870_regs_t::cpl_t cpl; max2870_regs_t::ldf_t ldf; max2870_regs_t::cpoc_t cpoc; - if(int_n_mode == max2870_regs_t::INT_N_MODE_INT_N) { + if(is_int_n) { cpl = max2870_regs_t::CPL_DISABLED; cpoc = max2870_regs_t::CPOC_ENABLED; ldf = max2870_regs_t::LDF_INT_N; @@ -184,10 +200,10 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) regs.band_select_clock_div = BS; UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - regs.int_n_mode = int_n_mode; + regs.int_n_mode = (is_int_n) ? max2870_regs_t::INT_N_MODE_INT_N : max2870_regs_t::INT_N_MODE_FRAC_N; regs.cpl = cpl; regs.ldf = ldf; - regs.cpoc = cpoc; + regs.cpoc = cpoc; //write the registers //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) @@ -195,8 +211,8 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) for(addr=5; addr>=0; addr--){ UHD_LOGV(often) << boost::format( - "CBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; + "%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 @@ -205,8 +221,8 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) //return the actual frequency UHD_LOGV(often) << boost::format( - "CBX tune: actual frequency %f Mhz" - ) % (actual_freq/1e6) << std::endl; + "%s tune: actual frequency %f Mhz" + ) % board_name.c_str() % (actual_freq/1e6) << std::endl; return actual_freq; } diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp index 5b713c6d7..b67910e9a 100644 --- a/host/lib/usrp/dboard/db_sbx_common.cpp +++ b/host/lib/usrp/dboard/db_sbx_common.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -21,137 +21,6 @@ using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; -/*********************************************************************** - * ADF 4350/4351 Tuning Utility - **********************************************************************/ -sbx_xcvr::sbx_versionx::adf435x_tuning_settings sbx_xcvr::sbx_versionx::_tune_adf435x_synth( - double target_freq, - 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_rf = f_vco/RFdiv) - * f_actual = f_rf/2 - */ - 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; - - //ignore fractional part of tuning - //N is computed from target_freq and not vco_freq because the feedback - //mode is set to FEEDBACK_SELECT_DIVIDED - N = boost::uint16_t(std::floor(target_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 - //N is computed from target_freq and not vco_freq because the feedback - //mode is set to FEEDBACK_SELECT_DIVIDED - FRAC = static_cast<boost::uint16_t>((target_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; - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(D?2:1)/(R*(T?2:1))); - - //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, 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 = BS; - settings.rf_divider = RFdiv; - settings.feedback_after_divider = true; - - 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): VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" - ) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << 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; -} - /*********************************************************************** * Register the SBX dboard (min freq, max freq, rx div2, tx div2) @@ -164,6 +33,9 @@ UHD_STATIC_BLOCK(reg_sbx_dboards){ dboard_manager::register_dboard(0x0054, 0x0055, &make_sbx, "SBX"); dboard_manager::register_dboard(0x0065, 0x0064, &make_sbx, "SBX v4"); dboard_manager::register_dboard(0x0067, 0x0066, &make_sbx, "CBX"); + dboard_manager::register_dboard(0x0069, 0x0068, &make_sbx, "SBX v5"); + dboard_manager::register_dboard(0x0083, 0x0082, &make_sbx, "SBX-120"); + dboard_manager::register_dboard(0x0085, 0x0084, &make_sbx, "CBX-120"); } @@ -244,15 +116,27 @@ double sbx_xcvr::set_rx_gain(double gain, const std::string &name){ **********************************************************************/ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ switch(get_rx_id().to_uint16()) { - case 0x054: + case 0x0054: db_actual = sbx_versionx_sptr(new sbx_version3(this)); freq_range = sbx_freq_range; break; - case 0x065: + case 0x0065: + db_actual = sbx_versionx_sptr(new sbx_version4(this)); + freq_range = sbx_freq_range; + break; + case 0x0067: + db_actual = sbx_versionx_sptr(new cbx(this)); + freq_range = cbx_freq_range; + break; + case 0x0069: db_actual = sbx_versionx_sptr(new sbx_version4(this)); freq_range = sbx_freq_range; break; - case 0x067: + case 0x0083: + db_actual = sbx_versionx_sptr(new sbx_version4(this)); + freq_range = sbx_freq_range; + break; + case 0x0085: db_actual = sbx_versionx_sptr(new cbx(this)); freq_range = cbx_freq_range; break; @@ -264,9 +148,14 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// - if(get_rx_id() == 0x054) this->get_rx_subtree()->create<std::string>("name").set("SBXv3 RX"); - else if(get_rx_id() == 0x065) this->get_rx_subtree()->create<std::string>("name").set("SBXv4 RX"); - else if(get_rx_id() == 0x067) this->get_rx_subtree()->create<std::string>("name").set("CBX RX"); + this->get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); + + boost::uint16_t rx_id = get_rx_id().to_uint16(); + if(rx_id == 0x0054) this->get_rx_subtree()->create<std::string>("name").set("SBXv3 RX"); + else if(rx_id == 0x0065) this->get_rx_subtree()->create<std::string>("name").set("SBXv4 RX"); + else if(rx_id == 0x0067) this->get_rx_subtree()->create<std::string>("name").set("CBX RX"); + else if(rx_id == 0x0083) this->get_rx_subtree()->create<std::string>("name").set("SBX-120 RX"); + else if(rx_id == 0x0085) this->get_rx_subtree()->create<std::string>("name").set("CBX-120 RX"); else this->get_rx_subtree()->create<std::string>("name").set("SBX/CBX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") @@ -290,16 +179,24 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<std::string>("connection").set("IQ"); this->get_rx_subtree()->create<bool>("enabled").set(true); //always enabled this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); - this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + + //Value of bw low-pass dependent on board, we want complex double-sided + double rx_bw = ((rx_id != 0x0083) && (rx_id != 0x0085)) ? 20.0e6 : 60.0e6; + this->get_rx_subtree()->create<double>("bandwidth/value").set(2*rx_bw); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); + .set(freq_range_t(2*rx_bw, 2*rx_bw)); //////////////////////////////////////////////////////////////////// // Register TX properties //////////////////////////////////////////////////////////////////// - if(get_tx_id() == 0x055) this->get_tx_subtree()->create<std::string>("name").set("SBXv3 TX"); - else if(get_tx_id() == 0x064) this->get_tx_subtree()->create<std::string>("name").set("SBXv4 TX"); - else if(get_tx_id() == 0x066) this->get_tx_subtree()->create<std::string>("name").set("CBX TX"); + this->get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); + + boost::uint16_t tx_id = get_tx_id().to_uint16(); + if(tx_id == 0x0055) this->get_tx_subtree()->create<std::string>("name").set("SBXv3 TX"); + else if(tx_id == 0x0064) this->get_tx_subtree()->create<std::string>("name").set("SBXv4 TX"); + else if(tx_id == 0x0066) this->get_tx_subtree()->create<std::string>("name").set("CBX TX"); + else if(tx_id == 0x0082) this->get_tx_subtree()->create<std::string>("name").set("SBX-120 TX"); + else if(tx_id == 0x0084) this->get_tx_subtree()->create<std::string>("name").set("CBX-120 TX"); else this->get_tx_subtree()->create<std::string>("name").set("SBX/CBX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") @@ -323,9 +220,12 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<std::string>("connection").set("QI"); this->get_tx_subtree()->create<bool>("enabled").set(true); //always enabled this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); - this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + + //Value of bw low-pass dependent on board, we want complex double-sided + double tx_bw = ((tx_id != 0x0082) && (tx_id != 0x0084)) ? 20.0e6 : 60.0e6; + this->get_tx_subtree()->create<double>("bandwidth/value").set(2*tx_bw); this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); + .set(freq_range_t(2*tx_bw, 2*tx_bw)); //enable the clocks that we need this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); @@ -493,3 +393,4 @@ void sbx_xcvr::flash_leds(void) { 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 e9bb2434c..a4bbfde38 100644 --- a/host/lib/usrp/dboard/db_sbx_common.hpp +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// Copyright 2011-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 @@ -15,14 +15,12 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <uhd/types/device_addr.hpp> +#include "../common/adf435x_common.hpp" // Common IO Pins #define LO_LPF_EN (1 << 15) -#define SYNTH_CE (1 << 3) -#define SYNTH_PDBRF (1 << 2) -#define SYNTH_MUXOUT (1 << 1) // INPUT!!! -#define LOCKDET_MASK (1 << 0) // INPUT!!! // TX IO Pins #define TRSW (1 << 14) // 0 = TX, 1 = RX @@ -38,33 +36,29 @@ #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 -// RX Attenuator Pins -#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 #define TX_ATTN_MASK (63 << TX_ATTN_SHIFT) // valid bits of TX Attenuator Control // Mixer functions -#define TX_MIXER_ENB (SYNTH_PDBRF|TX_ENABLE) +#define TX_MIXER_ENB (ADF435X_PDBRF|TX_ENABLE) #define TX_MIXER_DIS 0 -#define RX_MIXER_ENB (SYNTH_PDBRF) +#define RX_MIXER_ENB (ADF435X_PDBRF) #define RX_MIXER_DIS 0 // Pin functions #define TX_LED_IO (TX_LED_TXRX|TX_LED_LD) // LED gpio lines, pull down for LED -#define TXIO_MASK (LO_LPF_EN|TRSW|SYNTH_CE|SYNTH_PDBRF|TX_ATTN_MASK|DIS_POWER_TX|TX_ENABLE) +#define TXIO_MASK (LO_LPF_EN|TRSW|ADF435X_CE|ADF435X_PDBRF|TX_ATTN_MASK|DIS_POWER_TX|TX_ENABLE) #define RX_LED_IO (RX_LED_RX1RX2|RX_LED_LD) // LED gpio lines, pull down for LED -#define RXIO_MASK (LO_LPF_EN|LNASW|SYNTH_CE|SYNTH_PDBRF|RX_ATTN_MASK|DIS_POWER_RX|RX_DISABLE) +#define RXIO_MASK (LO_LPF_EN|LNASW|ADF435X_CE|ADF435X_PDBRF|RX_ATTN_MASK|DIS_POWER_RX|RX_DISABLE) // Power functions -#define TX_POWER_UP (SYNTH_CE) +#define TX_POWER_UP (ADF435X_CE) #define TX_POWER_DOWN (DIS_POWER_TX) -#define RX_POWER_UP (SYNTH_CE) +#define RX_POWER_UP (ADF435X_CE) #define RX_POWER_DOWN (DIS_POWER_RX) // Antenna constants @@ -181,34 +175,6 @@ protected: ~sbx_versionx(void) {} virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0; - protected: - struct adf435x_tuning_constraints { - bool force_frac0; - 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; - bool feedback_after_divider; - }; - - adf435x_tuning_settings _tune_adf435x_synth( - double target_freq, - double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq); }; /*! diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp index b0c9cd18f..463de5e15 100644 --- a/host/lib/usrp/dboard/db_sbx_version3.cpp +++ b/host/lib/usrp/dboard/db_sbx_version3.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -18,7 +18,9 @@ #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> using namespace uhd; using namespace uhd::usrp; @@ -45,6 +47,16 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar "SBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); + //clip the input target_freq = sbx_freq_range.clip(target_freq); @@ -67,15 +79,16 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar 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 = false; + 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; double actual_freq; - adf435x_tuning_settings tuning_settings = _tune_adf435x_synth( + adf435x_tuning_settings tuning_settings = tune_adf435x_synth( target_freq, self_base->get_iface()->get_clock_rate(unit), tuning_constraints, actual_freq); @@ -91,7 +104,7 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar 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_settings.feedback_after_divider ? + 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; @@ -106,6 +119,9 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar 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; diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp index 8d95b0655..ff4e19163 100644 --- a/host/lib/usrp/dboard/db_sbx_version4.cpp +++ b/host/lib/usrp/dboard/db_sbx_version4.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -18,7 +18,9 @@ #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> using namespace uhd; using namespace uhd::usrp; @@ -46,6 +48,16 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar "SBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); + //clip the input target_freq = sbx_freq_range.clip(target_freq); @@ -70,15 +82,16 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar 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 = false; + 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( + adf435x_tuning_settings tuning_settings = tune_adf435x_synth( target_freq, self_base->get_iface()->get_clock_rate(unit), tuning_constraints, actual_freq); @@ -94,7 +107,7 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar 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_settings.feedback_after_divider ? + 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; @@ -109,6 +122,9 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar 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; @@ -119,10 +135,12 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar //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( - "SBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; + "%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 @@ -131,8 +149,8 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar //return the actual frequency UHD_LOGV(often) << boost::format( - "SBX tune: actual frequency %f Mhz" - ) % (actual_freq/1e6) << std::endl; + "%s tune: actual frequency %f Mhz" + ) % board_name.c_str() % (actual_freq/1e6) << std::endl; return actual_freq; } diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp index 0bfa5229a..c593c5437 100644 --- a/host/lib/usrp/dboard/db_tvrx2.cpp +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010,2012 Ettus Research LLC +// Copyright 2010,2012-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 @@ -1005,6 +1005,17 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ _freq_scalar = (4*16.0e6)/(this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)); } else if (ref_clock == 100e6) { + + this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV8); + + UHD_LOGV(often) << boost::format( + "TVRX2 (%s): Dividing Refclock by 6" + ) % (get_subdev_name()) << std::endl; + + _freq_scalar = (6*16.0e6)/this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); + } else if (ref_clock == 200e6) { + UHD_MSG(warning) << boost::format("ref_clock was 200e6, setting ref_clock divider for 100e6.") % ref_clock << std::endl; + this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, 100e6); this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); UHD_LOGV(often) << boost::format( @@ -1014,7 +1025,7 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ _freq_scalar = (6*16.0e6)/this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); } else { this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, REFCLOCK_DIV6); - UHD_MSG(warning) << boost::format("Unsupported ref_clock %0.2f, valid options 64e6 and 100e6") % ref_clock << std::endl; + UHD_MSG(warning) << boost::format("Unsupported ref_clock %0.2f, valid options 64e6, 100e6, 200e6") % ref_clock << std::endl; _freq_scalar = 1.0; } diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index 503e5aabf..97357bc90 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -63,8 +63,11 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); //////////////////////////////////////////////////////////////////// - // Register RX properties + // Register RX and TX properties //////////////////////////////////////////////////////////////////// + boost::uint16_t rx_id = this->get_rx_id().to_uint16(); + + 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)); BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ @@ -79,30 +82,34 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1)) .set(true); //start enabled this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); - this->get_rx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided + + //Value of bw low-pass dependent on board, we want complex double-sided + double bw = (rx_id != 0x0081) ? 20.0e6 : 60.0e6; + this->get_rx_subtree()->create<double>("bandwidth/value").set(2*bw); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); + .set(freq_range_t(2*bw, 2*bw)); + this->get_tx_subtree()->create<double>("bandwidth/value").set(2*bw); + this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(2*bw, 2*bw)); - //////////////////////////////////////////////////////////////////// - // Register TX properties - //////////////////////////////////////////////////////////////////// + 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)); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); - this->get_tx_subtree()->create<double>("bandwidth/value").set(2*20.0e6); //20MHz low-pass, we want complex double-sided - this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); // instantiate subclass foo - switch(get_rx_id().to_uint16()) { - case 0x053: + switch(rx_id) { + case 0x0053: db_actual = wbx_versionx_sptr(new wbx_version2(this)); return; - case 0x057: + case 0x0057: db_actual = wbx_versionx_sptr(new wbx_version3(this)); return; - case 0x063: + case 0x0063: + db_actual = wbx_versionx_sptr(new wbx_version4(this)); + return; + case 0x0081: db_actual = wbx_versionx_sptr(new wbx_version4(this)); return; default: diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index d1beb160e..7609beb19 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011,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 @@ -18,15 +18,9 @@ #ifndef INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP #define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP -// Common IO Pins -#define ADF4350_CE (1 << 3) -#define ADF4350_PDBRF (1 << 2) -#define ADF4350_MUXOUT (1 << 1) // INPUT!!! -#define ADF4351_CE (1 << 3) -#define ADF4351_PDBRF (1 << 2) -#define ADF4351_MUXOUT (1 << 1) // INPUT!!! +#include <uhd/types/device_addr.hpp> -#define LOCKDET_MASK (1 << 0) // INPUT!!! +#include "../common/adf435x_common.hpp" // TX IO Pins #define TX_PUP_5V (1 << 7) // enables 5.0V power supply @@ -38,10 +32,6 @@ #define RX_PUP_3V (1 << 6) // enables 3.3V supply #define RXBB_PDB (1 << 4) // on UNIT_RX, 1 powers up RX baseband -// RX Attenuator Pins -#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 (v3 only) #define TX_ATTN_16 (1 << 14) #define TX_ATTN_8 (1 << 5) @@ -51,17 +41,17 @@ #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 // Mixer functions -#define TX_MIXER_ENB (TXMOD_EN|ADF4350_PDBRF) // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate +#define TX_MIXER_ENB (TXMOD_EN|ADF435X_PDBRF) // for v3, TXMOD_EN tied to ADF435X_PDBRF rather than separate #define TX_MIXER_DIS 0 -#define RX_MIXER_ENB (RXBB_PDB|ADF4350_PDBRF) +#define RX_MIXER_ENB (RXBB_PDB|ADF435X_PDBRF) #define RX_MIXER_DIS 0 // Power functions #define TX_POWER_UP (TX_PUP_5V|TX_PUP_3V) // high enables power supply #define TX_POWER_DOWN 0 -#define RX_POWER_UP (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply +#define RX_POWER_UP (RX_PUP_5V|RX_PUP_3V|ADF435X_CE) // high enables power supply #define RX_POWER_DOWN 0 @@ -86,6 +76,23 @@ namespace uhd{ namespace usrp{ static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = boost::assign::map_list_of ("PGA0", gain_range_t(0, 31.5, 0.5)); +static const freq_range_t wbx_tx_lo_5dbm = boost::assign::list_of + (range_t(0.05e9, 1.7e9)) + (range_t(1.9e9, 2.2e9)) +; + +static const freq_range_t wbx_tx_lo_m1dbm = boost::assign::list_of + (range_t(1.7e9, 1.9e9)) +; + +static const freq_range_t wbx_rx_lo_5dbm = boost::assign::list_of + (range_t(0.05e9, 1.4e9)) +; + +static const freq_range_t wbx_rx_lo_2dbm = boost::assign::list_of + (range_t(1.4e9, 2.2e9)) +; + /*********************************************************************** * The WBX dboard base class diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index 4ba30255d..c8f2be155 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -71,6 +71,8 @@ UHD_STATIC_BLOCK(reg_wbx_simple_dboards){ dboard_manager::register_dboard(0x0057, 0x004f, &make_wbx_simple, "WBX v3 + Simple GDB"); dboard_manager::register_dboard(0x0063, 0x0062, &make_wbx_simple, "WBX v4"); dboard_manager::register_dboard(0x0063, 0x004f, &make_wbx_simple, "WBX v4 + Simple GDB"); + dboard_manager::register_dboard(0x0081, 0x0080, &make_wbx_simple, "WBX-120"); + dboard_manager::register_dboard(0x0081, 0x004f, &make_wbx_simple, "WBX-120 + Simple GDB"); } /*********************************************************************** diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp index 5f6118a91..c5945483d 100644 --- a/host/lib/usrp/dboard/db_wbx_version2.cpp +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -17,6 +17,8 @@ #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> #include <uhd/types/ranges.hpp> @@ -28,6 +30,7 @@ #include <boost/assign/list_of.hpp> #include <boost/format.hpp> #include <boost/math/special_functions/round.hpp> +#include <boost/algorithm/string.hpp> using namespace uhd; using namespace uhd::usrp; @@ -104,14 +107,14 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { .set(true); //start enabled //set attenuator control bits - int v2_iobits = ADF4350_CE; - int v2_tx_mod = TXMOD_EN|ADF4350_PDBRF; + int v2_iobits = ADF435X_CE; + int v2_tx_mod = TXMOD_EN|ADF435X_PDBRF; //set the gpio directions and atr controls self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v2_tx_mod); - self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); + self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF435X_PDBRF); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v2_tx_mod|v2_iobits); - self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); + 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); @@ -134,7 +137,7 @@ wbx_base::wbx_version2::~wbx_version2(void){ **********************************************************************/ void wbx_base::wbx_version2::set_tx_enabled(bool enb){ self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, - (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | ADF4350_CE); + (enb)? TX_POWER_UP | ADF435X_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | ADF435X_CE); } @@ -166,8 +169,15 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar "WBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; - //start with target_freq*2 because mixer has divide by 2 - target_freq *= 2; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); //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 @@ -184,137 +194,69 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) ; - double actual_freq, pfd_freq; - double ref_freq = self_base->get_iface()->get_clock_rate(unit); - int R=0, BS=0, N=0, FRAC=0, MOD=0; - int RFdiv = 1; - adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - - //Reference doubler for 50% duty cycle - // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 - if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; - - //increase RF divider until acceptable VCO frequency - const bool do_sync = (target_freq/2 > ref_freq); - double vco_freq = target_freq; - while (vco_freq < 2.2e9) { - vco_freq *= 2; - RFdiv *= 2; - } - if (do_sync) vco_freq = target_freq; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - /* - * 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_rf = f_vco/RFdiv) - * f_actual = f_rf/2 - */ - 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*(1+D)/(R*(1+T)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > 25e6) continue; - - //ignore fractional part of tuning - N = int(std::floor(vco_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < prescaler_to_min_int_div[prescaler]) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below 100KHz - //constraint on band select clock - if (pfd_freq/BS > 100e3) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = int((vco_freq/pfd_freq - N)*MOD); - - //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 = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; - R /= 2; - } - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/2/(vco_freq/target_freq)); - - UHD_LOGV(often) - << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - - << boost::format("WBX tune: 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 - << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + 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 + double synth_target_freq = target_freq * 2; + //TODO: Document why the following has to be true + bool div_resync_enabled = (target_freq > reference_freq); + + adf4350_regs_t::prescaler_t prescaler = + synth_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); + tuning_constraints.pfd_freq_max = 25e6; + tuning_constraints.rf_divider_range = uhd::range_t(1, 16); + //When divider resync is enabled, a 180 deg phase error is introduced when syncing + //multiple WBX boards. Switching to fundamental mode works arounds this issue. + tuning_constraints.feedback_after_divider = div_resync_enabled; + + double synth_actual_freq = 0; + adf435x_tuning_settings tuning_settings = tune_adf435x_synth( + synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + + //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; - regs.frac_12_bit = FRAC; - regs.int_16_bit = N; - regs.mod_12_bit = MOD; - if (do_sync) - { - regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - regs.feedback_select = adf4350_regs_t::FEEDBACK_SELECT_DIVIDED; - regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - } - regs.prescaler = prescaler; - regs.r_counter_10_bit = R; - regs.reference_divide_by_2 = T; - regs.reference_doubler = D; - regs.band_select_clock_div = BS; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - - if (unit == dboard_iface::UNIT_RX) { - freq_range_t rx_lo_5dbm = list_of - (range_t(0.05e9, 1.4e9)) - ; - - freq_range_t rx_lo_2dbm = list_of - (range_t(1.4e9, 2.2e9)) - ; - - if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - - } else if (unit == dboard_iface::UNIT_TX) { - freq_range_t tx_lo_5dbm = list_of - (range_t(0.05e9, 1.7e9)) - (range_t(1.9e9, 2.2e9)) - ; - - freq_range_t tx_lo_m1dbm = list_of - (range_t(1.7e9, 1.9e9)) - ; - - if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; - - } + 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 = div_resync_enabled ? + 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; diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp index 3e8fc8095..80ecb426b 100644 --- a/host/lib/usrp/dboard/db_wbx_version3.cpp +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -17,6 +17,7 @@ #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> @@ -28,6 +29,7 @@ #include <boost/assign/list_of.hpp> #include <boost/format.hpp> #include <boost/math/special_functions/round.hpp> +#include <boost/algorithm/string.hpp> using namespace uhd; using namespace uhd::usrp; @@ -111,17 +113,17 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //set attenuator control bits int v3_iobits = TX_ATTN_MASK; - int v3_tx_mod = ADF4350_PDBRF; + int v3_tx_mod = ADF435X_PDBRF; //set the gpio directions and atr controls self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, \ v3_tx_mod|v3_iobits); self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, \ - RXBB_PDB|ADF4350_PDBRF); + RXBB_PDB|ADF435X_PDBRF); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, \ TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, \ - RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); + 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). set TX gain iobits to min gain (max attenuation) @@ -163,7 +165,7 @@ wbx_base::wbx_version3::~wbx_version3(void){ **********************************************************************/ void wbx_base::wbx_version3::set_tx_enabled(bool enb){ self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, - (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); + (enb)? TX_POWER_UP | ADF435X_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); } @@ -198,8 +200,15 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar "WBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; - //start with target_freq*2 because mixer has divide by 2 - target_freq *= 2; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); //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 @@ -216,137 +225,69 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) ; - double actual_freq, pfd_freq; - double ref_freq = self_base->get_iface()->get_clock_rate(unit); - int R=0, BS=0, N=0, FRAC=0, MOD=0; - int RFdiv = 1; - adf4350_regs_t::reference_divide_by_2_t T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - adf4350_regs_t::reference_doubler_t D = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - - //Reference doubler for 50% duty cycle - // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 - if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; - - //increase RF divider until acceptable VCO frequency - const bool do_sync = (target_freq/2 > ref_freq); - double vco_freq = target_freq; - while (vco_freq < 2.2e9) { - vco_freq *= 2; - RFdiv *= 2; - } - if (do_sync) vco_freq = target_freq; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - /* - * 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_rf = f_vco/RFdiv) - * f_actual = f_rf/2 - */ - 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*(1+D)/(R*(1+T)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > 25e6) continue; - - //ignore fractional part of tuning - N = int(std::floor(vco_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < prescaler_to_min_int_div[prescaler]) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below 100KHz - //constraint on band select clock - if (pfd_freq/BS > 100e3) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = int((vco_freq/pfd_freq - N)*MOD); - - //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 = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; - R /= 2; - } - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/2/(vco_freq/target_freq)); - - UHD_LOGV(often) - << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - - << boost::format("WBX tune: 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 - << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + 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 + double synth_target_freq = target_freq * 2; + //TODO: Document why the following has to be true + bool div_resync_enabled = (target_freq > reference_freq); + + adf4350_regs_t::prescaler_t prescaler = + synth_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); + tuning_constraints.pfd_freq_max = 25e6; + tuning_constraints.rf_divider_range = uhd::range_t(1, 16); + //When divider resync is enabled, a 180 deg phase error is introduced when syncing + //multiple WBX boards. Switching to fundamental mode works arounds this issue. + tuning_constraints.feedback_after_divider = div_resync_enabled; + + double synth_actual_freq = 0; + adf435x_tuning_settings tuning_settings = tune_adf435x_synth( + synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + + //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; - regs.frac_12_bit = FRAC; - regs.int_16_bit = N; - regs.mod_12_bit = MOD; - if (do_sync) - { - regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - regs.feedback_select = adf4350_regs_t::FEEDBACK_SELECT_DIVIDED; - regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - } - regs.prescaler = prescaler; - regs.r_counter_10_bit = R; - regs.reference_divide_by_2 = T; - regs.reference_doubler = D; - regs.band_select_clock_div = BS; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - - if (unit == dboard_iface::UNIT_RX) { - freq_range_t rx_lo_5dbm = list_of - (range_t(0.05e9, 1.4e9)) - ; - - freq_range_t rx_lo_2dbm = list_of - (range_t(1.4e9, 2.2e9)) - ; - - if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - - } else if (unit == dboard_iface::UNIT_TX) { - freq_range_t tx_lo_5dbm = list_of - (range_t(0.05e9, 1.7e9)) - (range_t(1.9e9, 2.2e9)) - ; - - freq_range_t tx_lo_m1dbm = list_of - (range_t(1.7e9, 1.9e9)) - ; - - if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; - - } + 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 = div_resync_enabled ? + 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; diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp index 1feea2c0b..80ff3f998 100644 --- a/host/lib/usrp/dboard/db_wbx_version4.cpp +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011-2012 Ettus Research LLC +// 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 @@ -17,6 +17,7 @@ #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> @@ -28,6 +29,7 @@ #include <boost/assign/list_of.hpp> #include <boost/format.hpp> #include <boost/math/special_functions/round.hpp> +#include <boost/algorithm/string.hpp> using namespace uhd; using namespace uhd::usrp; @@ -35,7 +37,7 @@ using namespace boost::assign; /*********************************************************************** - * WBX Version 3 Constants + * WBX Version 4 Constants **********************************************************************/ static const uhd::dict<std::string, gain_range_t> wbx_v4_tx_gain_ranges = map_list_of ("PGA0", gain_range_t(0, 31, 1.0)) @@ -85,7 +87,10 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// - this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX"); + boost::uint16_t rx_id = _self_wbx_base->get_rx_id().to_uint16(); + + 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((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); @@ -94,7 +99,10 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //////////////////////////////////////////////////////////////////// // Register TX properties //////////////////////////////////////////////////////////////////// - this->get_tx_subtree()->create<std::string>("name").set("WBXv4 TX"); + + //get_tx_id() will always return GDB ID, so use RX ID to determine WBXv4 vs. WBX-120 + if(rx_id == 0x0063) this->get_tx_subtree()->create<std::string>("name").set("WBXv4 TX"); + 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)) @@ -112,17 +120,17 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //set attenuator control bits int v4_iobits = TX_ATTN_MASK; - int v4_tx_mod = ADF4351_PDBRF; + int v4_tx_mod = ADF435X_PDBRF; //set the gpio directions and atr controls self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, \ v4_tx_mod|v4_iobits); self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, \ - RXBB_PDB|ADF4351_PDBRF); + RXBB_PDB|ADF435X_PDBRF); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, \ TX_PUP_5V|TX_PUP_3V|v4_tx_mod|v4_iobits); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, \ - RX_PUP_5V|RX_PUP_3V|ADF4351_CE|RXBB_PDB|ADF4351_PDBRF|RX_ATTN_MASK); + 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) set TX gain iobits to min gain (max attenuation) when @@ -164,7 +172,7 @@ wbx_base::wbx_version4::~wbx_version4(void){ **********************************************************************/ void wbx_base::wbx_version4::set_tx_enabled(bool enb) { self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, - (enb)? TX_POWER_UP | ADF4351_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); + (enb)? TX_POWER_UP | ADF435X_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); } @@ -200,8 +208,15 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar "WBX tune: target frequency %f Mhz" ) % (target_freq/1e6) << std::endl; - //start with target_freq*2 because mixer has divide by 2 - target_freq *= 2; + /* + * If the user sets 'mode_n=integer' in the tuning args, the user wishes to + * tune in Integer-N mode, which can result in better spur + * performance on some mixers. The default is fractional tuning. + */ + property_tree::sptr subtree = (unit == dboard_iface::UNIT_RX) ? self_base->get_rx_subtree() + : 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"); //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 @@ -220,137 +235,69 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) ; - double actual_freq, pfd_freq; - double ref_freq = self_base->get_iface()->get_clock_rate(unit); - int R=0, BS=0, N=0, FRAC=0, MOD=0; - int RFdiv = 1; - adf4351_regs_t::reference_divide_by_2_t T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - adf4351_regs_t::reference_doubler_t D = adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - - //Reference doubler for 50% duty cycle - // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 - if(ref_freq <= 12.5e6) D = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED; - - //increase RF divider until acceptable VCO frequency - const bool do_sync = (target_freq/2 > ref_freq); - double vco_freq = target_freq; - while (vco_freq < 2.2e9) { - vco_freq *= 2; - RFdiv *= 2; - } - if (do_sync) vco_freq = target_freq; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4351_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; - - /* - * 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_rf = f_vco/RFdiv) - * f_actual = f_rf/2 - */ - 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*(1+D)/(R*(1+T)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > 25e6) continue; - - //ignore fractional part of tuning - N = int(std::floor(vco_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < prescaler_to_min_int_div[prescaler]) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below 100KHz - //constraint on band select clock - if (pfd_freq/BS > 100e3) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = int((vco_freq/pfd_freq - N)*MOD); - - //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 = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; - R /= 2; - } - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/2/(vco_freq/target_freq)); - - UHD_LOGV(often) - << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - - << boost::format("WBX tune: 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 - << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + 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 + double synth_target_freq = target_freq * 2; + //TODO: Document why the following has to be true + bool div_resync_enabled = (target_freq > reference_freq); + + adf4351_regs_t::prescaler_t prescaler = + synth_target_freq > 3e9 ? 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); + tuning_constraints.pfd_freq_max = 25e6; + tuning_constraints.rf_divider_range = uhd::range_t(1, 64); + //When divider resync is enabled, a 180 deg phase error is introduced when syncing + //multiple WBX boards. Switching to fundamental mode works arounds this issue. + tuning_constraints.feedback_after_divider = div_resync_enabled; + + double synth_actual_freq = 0; + adf435x_tuning_settings tuning_settings = tune_adf435x_synth( + synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + + //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; - regs.frac_12_bit = FRAC; - regs.int_16_bit = N; - regs.mod_12_bit = MOD; - if (do_sync) - { - regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - regs.feedback_select = adf4351_regs_t::FEEDBACK_SELECT_DIVIDED; - regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - } - regs.prescaler = prescaler; - regs.r_counter_10_bit = R; - regs.reference_divide_by_2 = T; - regs.reference_doubler = D; - regs.band_select_clock_div = BS; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - - if (unit == dboard_iface::UNIT_RX) { - freq_range_t rx_lo_5dbm = list_of - (range_t(0.05e9, 1.4e9)) - ; - - freq_range_t rx_lo_2dbm = list_of - (range_t(1.4e9, 2.2e9)) - ; - - if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; - - } else if (unit == dboard_iface::UNIT_TX) { - freq_range_t tx_lo_5dbm = list_of - (range_t(0.05e9, 1.7e9)) - (range_t(1.9e9, 2.2e9)) - ; - - freq_range_t tx_lo_m1dbm = list_of - (range_t(1.7e9, 1.9e9)) - ; - - if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; - - if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_M1DBM; - - } + 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 = div_resync_enabled ? + 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; @@ -361,10 +308,12 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar //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( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; + "%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 @@ -373,7 +322,8 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar //return the actual frequency UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f Mhz" - ) % (actual_freq/1e6) << std::endl; + "%s tune: actual frequency %f Mhz" + ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + return actual_freq; } diff --git a/host/lib/usrp/dboard_id.cpp b/host/lib/usrp/dboard_id.cpp index 3028d2a3b..557e13914 100644 --- a/host/lib/usrp/dboard_id.cpp +++ b/host/lib/usrp/dboard_id.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010 Ettus Research LLC +// 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 @@ -51,7 +51,10 @@ template <class T> struct to_hex{ dboard_id_t dboard_id_t::from_string(const std::string &string){ if (string.substr(0, 2) == "0x"){ - return dboard_id_t::from_uint16(boost::lexical_cast<to_hex<boost::uint16_t> >(string)); + std::stringstream interpreter(string); + to_hex<boost::uint16_t> hh; + interpreter >> hh; + return dboard_id_t::from_uint16(hh); } return dboard_id_t::from_uint16(boost::lexical_cast<boost::uint16_t>(string)); } diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index 619ea8f8e..b49ba64a2 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -51,6 +51,10 @@ static device_addrs_t e100_find(const device_addr_t &hint){ //return an empty list of addresses when type is set to non-usrp-e if (hint.has_key("type") and hint["type"] != "e100") return e100_addrs; + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-USB, device. + if (hint.has_key("resource")) return e100_addrs; + //device node not provided, assume its 0 if (not hint.has_key("node")){ device_addr_t new_addr = hint; diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp index 813f9cc8f..d499c4c03 100644 --- a/host/lib/usrp/e100/e100_impl.hpp +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -85,8 +85,6 @@ public: bool recv_async_msg(uhd::async_metadata_t &, double); private: - uhd::property_tree::sptr _tree; - //controllers fifo_ctrl_excelsior::sptr _fifo_ctrl; i2c_core_200::sptr _fpga_i2c_ctrl; @@ -111,11 +109,6 @@ private: uhd::usrp::dboard_manager::sptr _dboard_manager; uhd::usrp::dboard_iface::sptr _dboard_iface; - //device properties interface - uhd::property_tree::sptr get_tree(void) const{ - return _tree; - } - std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; std::vector<boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; diff --git a/host/lib/usrp/gps_ctrl.cpp b/host/lib/usrp/gps_ctrl.cpp index 98bc90a3f..6f5c75dec 100644 --- a/host/lib/usrp/gps_ctrl.cpp +++ b/host/lib/usrp/gps_ctrl.cpp @@ -38,7 +38,7 @@ using namespace boost::algorithm; using namespace boost::this_thread; /*! - * A GPS control for NMEA compatible GPSes + * A control for GPSDO devices */ class gps_ctrl_impl : public gps_ctrl{ @@ -63,39 +63,41 @@ private: } std::string update_cached_sensors(const std::string sensor) { - if(not gps_detected() || (gps_type != GPS_TYPE_ER_GPSDO)) { + if(not gps_detected() || (gps_type != GPS_TYPE_INTERNAL_GPSDO)) { UHD_MSG(error) << "get_stat(): unsupported GPS or no GPS detected"; return std::string(); } - std::string msg = _recv(); + const std::list<std::string> list = boost::assign::list_of("GPGGA")("GPRMC")("SERVO"); static const boost::regex status_regex("\\d\\d-\\d\\d-\\d\\d"); + std::map<std::string,std::string> msgs; + + // Get all GPSDO messages available + // Creating a map here because we only want the latest of each message type + for (std::string msg = _recv(); msg.length() > 6; msg = _recv()) + { + // Look for SERVO message + if (boost::regex_search(msg, status_regex, boost::regex_constants::match_continuous)) + msgs["SERVO"] = msg; + else + msgs[msg.substr(1,5)] = msg; + } + boost::system_time time = boost::get_system_time(); - if(msg.size() < 6) - return std::string(); - std::string nmea = msg.substr(1,5); - const std::list<std::string> list = boost::assign::list_of("GPGGA")("GPRMC"); + // Update sensors with newly read data BOOST_FOREACH(std::string key, list) { - // beginning matches one of the NMEA keys - if(!nmea.compare(key)) { - sensors[key] = boost::make_tuple(msg, time, !sensor.compare(key)); - // if this was what we're looking for return it - return (!sensor.compare(key))? msg : std::string(); - } + if (msgs[key].length()) + sensors[key] = boost::make_tuple(msgs[key], time, !sensor.compare(key)); } - //We're still here so it's not one of the NMEA strings from above - if(boost::regex_search(msg, status_regex, boost::regex_constants::match_continuous)) { - trim(msg); - sensors["SERVO"] = boost::make_tuple(msg, time, false); - if(!sensor.compare("SERVO")) - return msg; - else - return std::string(); - } + // Return requested sensor if it was updated + if (msgs[sensor].length()) + return msgs[sensor]; + return std::string(); } + public: gps_ctrl_impl(uart_iface::sptr uart){ _uart = uart; @@ -105,18 +107,19 @@ public: bool i_heard_some_nmea = false, i_heard_something_weird = false; gps_type = GPS_TYPE_NONE; + //first we look for an internal GPSDO _flush(); //get whatever junk is in the rx buffer right now, and throw it away _send("HAAAY GUYYYYS\n"); //to elicit a response from the GPSDO //wait for _send(...) to return - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); //then we loop until we either timeout, or until we get a response that indicates we're a JL device const boost::system_time comm_timeout = boost::get_system_time() + milliseconds(GPS_COMM_TIMEOUT_MS); while(boost::get_system_time() < comm_timeout) { reply = _recv(); if(reply.find("Command Error") != std::string::npos) { - gps_type = GPS_TYPE_ER_GPSDO; + gps_type = GPS_TYPE_INTERNAL_GPSDO; break; } else if(reply.substr(0, 3) == "$GP") i_heard_some_nmea = true; //but keep looking for that "Command Error" response @@ -124,24 +127,25 @@ public: sleep(milliseconds(GPS_TIMEOUT_DELAY_MS)); } - if((i_heard_some_nmea) && (gps_type != GPS_TYPE_ER_GPSDO)) gps_type = GPS_TYPE_GENERIC_NMEA; + if((i_heard_some_nmea) && (gps_type != GPS_TYPE_INTERNAL_GPSDO)) gps_type = GPS_TYPE_GENERIC_NMEA; if((gps_type == GPS_TYPE_NONE) && i_heard_something_weird) { UHD_MSG(error) << "GPS invalid reply \"" << reply << "\", assuming none available" << std::endl; } switch(gps_type) { - case GPS_TYPE_ER_GPSDO: - UHD_MSG(status) << "Found an Ettus Research NMEA-capable GPSDO" << std::endl; + case GPS_TYPE_INTERNAL_GPSDO: + UHD_MSG(status) << "Found an internal GPSDO" << std::endl; init_gpsdo(); break; case GPS_TYPE_GENERIC_NMEA: - if(gps_type == GPS_TYPE_GENERIC_NMEA) UHD_MSG(status) << "Found a generic NMEA GPS device" << std::endl; + UHD_MSG(status) << "Found a generic NMEA GPS device" << std::endl; break; case GPS_TYPE_NONE: default: + UHD_MSG(status) << "No GPSDO found" << std::endl; break; } @@ -189,19 +193,19 @@ private: //issue some setup stuff so it spits out the appropriate data //none of these should issue replies so we don't bother looking for them //we have to sleep between commands because the JL device, despite not acking, takes considerable time to process each command. - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("SYST:COMM:SER:ECHO OFF\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("SYST:COMM:SER:PRO OFF\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("GPS:GPGGA 1\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("GPS:GGAST 0\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("GPS:GPRMC 1\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); _send("SERV:TRAC 0\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); } //retrieve a raw NMEA sentence @@ -304,7 +308,7 @@ private: //enable servo reporting _send("SERV:TRAC 1\n"); - sleep(milliseconds(200)); + sleep(milliseconds(GPSDO_STUPID_DELAY_MS)); std::string reply; @@ -330,8 +334,8 @@ private: } } - std::string _recv(void){ - return _uart->read_uart(GPS_TIMEOUT_DELAY_MS/1000.); + std::string _recv(double timeout = GPS_TIMEOUT_DELAY_MS/1000.){ + return _uart->read_uart(timeout); } void _send(const std::string &buf){ @@ -339,7 +343,7 @@ private: } enum { - GPS_TYPE_ER_GPSDO, + GPS_TYPE_INTERNAL_GPSDO, GPS_TYPE_GENERIC_NMEA, GPS_TYPE_NONE } gps_type; @@ -351,6 +355,7 @@ private: static const int GPS_SERVO_FRESHNESS = 2500; static const int GPS_LOCK_FRESHNESS = 2500; static const int GPS_TIMEOUT_DELAY_MS = 200; + static const int GPSDO_STUPID_DELAY_MS = 200; }; /*********************************************************************** diff --git a/host/lib/usrp/mboard_eeprom.cpp b/host/lib/usrp/mboard_eeprom.cpp index dc25379f9..68c084589 100644 --- a/host/lib/usrp/mboard_eeprom.cpp +++ b/host/lib/usrp/mboard_eeprom.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-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 @@ -229,6 +229,137 @@ static void store_n100(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface){ } /*********************************************************************** + * Implementation of X300 load/store + **********************************************************************/ +static const boost::uint8_t X300_EEPROM_ADDR = 0x50; + +struct x300_eeprom_map +{ + //indentifying numbers + unsigned char revision[2]; + unsigned char product[2]; + boost::uint8_t _pad0[4]; + + //all the mac addrs + boost::uint8_t mac_addr0[6]; + boost::uint8_t _pad1[2]; + boost::uint8_t mac_addr1[6]; + boost::uint8_t _pad2[2]; + + //all the IP addrs + boost::uint32_t gateway; + boost::uint32_t subnet[4]; + boost::uint32_t ip_addr[4]; + boost::uint8_t _pad3[16]; + + //names and serials + unsigned char name[NAME_MAX_LEN]; + unsigned char serial[SERIAL_LEN]; +}; + +static void load_x300(mboard_eeprom_t &mb_eeprom, i2c_iface &iface) +{ + //extract the revision number + mb_eeprom["revision"] = uint16_bytes_to_string( + iface.read_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, revision), 2) + ); + + //extract the product code + mb_eeprom["product"] = uint16_bytes_to_string( + iface.read_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, product), 2) + ); + + //extract the mac addresses + mb_eeprom["mac-addr0"] = mac_addr_t::from_bytes(iface.read_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, mac_addr0), 6 + )).to_string(); + mb_eeprom["mac-addr1"] = mac_addr_t::from_bytes(iface.read_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, mac_addr1), 6 + )).to_string(); + + //extract the ip addresses + boost::asio::ip::address_v4::bytes_type ip_addr_bytes; + byte_copy(iface.read_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, gateway), 4), ip_addr_bytes); + mb_eeprom["gateway"] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + for (size_t i = 0; i < 4; i++) + { + const std::string n(1, i+'0'); + byte_copy(iface.read_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, ip_addr)+(i*4), 4), ip_addr_bytes); + mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + + byte_copy(iface.read_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, subnet)+(i*4), 4), ip_addr_bytes); + mb_eeprom["subnet"+n] = boost::asio::ip::address_v4(ip_addr_bytes).to_string(); + } + + //extract the serial + mb_eeprom["serial"] = bytes_to_string(iface.read_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, serial), SERIAL_LEN + )); + + //extract the name + mb_eeprom["name"] = bytes_to_string(iface.read_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, name), NAME_MAX_LEN + )); +} + +static void store_x300(const mboard_eeprom_t &mb_eeprom, i2c_iface &iface) +{ + //parse the revision number + if (mb_eeprom.has_key("revision")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, revision), + string_to_uint16_bytes(mb_eeprom["revision"]) + ); + + //parse the product code + if (mb_eeprom.has_key("product")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, product), + string_to_uint16_bytes(mb_eeprom["product"]) + ); + + //store the mac addresses + if (mb_eeprom.has_key("mac-addr0")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, mac_addr0), + mac_addr_t::from_string(mb_eeprom["mac-addr0"]).to_bytes() + ); + if (mb_eeprom.has_key("mac-addr1")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, mac_addr1), + mac_addr_t::from_string(mb_eeprom["mac-addr1"]).to_bytes() + ); + + //store the ip addresses + byte_vector_t ip_addr_bytes(4); + if (mb_eeprom.has_key("gateway")){ + byte_copy(boost::asio::ip::address_v4::from_string(mb_eeprom["gateway"]).to_bytes(), ip_addr_bytes); + iface.write_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, gateway), ip_addr_bytes); + } + for (size_t i = 0; i < 4; i++) + { + const std::string n(1, i+'0'); + if (mb_eeprom.has_key("ip-addr"+n)){ + byte_copy(boost::asio::ip::address_v4::from_string(mb_eeprom["ip-addr"+n]).to_bytes(), ip_addr_bytes); + iface.write_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, ip_addr)+(i*4), ip_addr_bytes); + } + + if (mb_eeprom.has_key("subnet"+n)){ + byte_copy(boost::asio::ip::address_v4::from_string(mb_eeprom["subnet"+n]).to_bytes(), ip_addr_bytes); + iface.write_eeprom(X300_EEPROM_ADDR, offsetof(x300_eeprom_map, subnet)+(i*4), ip_addr_bytes); + } + } + + //store the serial + if (mb_eeprom.has_key("serial")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, serial), + string_to_bytes(mb_eeprom["serial"], SERIAL_LEN) + ); + + //store the name + if (mb_eeprom.has_key("name")) iface.write_eeprom( + X300_EEPROM_ADDR, offsetof(x300_eeprom_map, name), + string_to_bytes(mb_eeprom["name"], NAME_MAX_LEN) + ); +} + +/*********************************************************************** * Implementation of B000 load/store **********************************************************************/ static const boost::uint8_t B000_EEPROM_ADDR = 0x50; @@ -512,6 +643,7 @@ mboard_eeprom_t::mboard_eeprom_t(void){ mboard_eeprom_t::mboard_eeprom_t(i2c_iface &iface, const std::string &which){ if (which == "N100") load_n100(*this, iface); + if (which == "X300") load_x300(*this, iface); if (which == "B000") load_b000(*this, iface); if (which == "B100") load_b100(*this, iface); if (which == "B200") load_b200(*this, iface); @@ -520,6 +652,7 @@ mboard_eeprom_t::mboard_eeprom_t(i2c_iface &iface, const std::string &which){ void mboard_eeprom_t::commit(i2c_iface &iface, const std::string &which) const{ if (which == "N100") store_n100(*this, iface); + if (which == "X300") store_x300(*this, iface); if (which == "B000") store_b000(*this, iface); if (which == "B100") store_b100(*this, iface); if (which == "B200") store_b200(*this, iface); diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 26ce1ccdd..f08709669 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -35,6 +35,20 @@ using namespace uhd::usrp; const std::string multi_usrp::ALL_GAINS = ""; +UHD_INLINE std::string string_vector_to_string(std::vector<std::string> values, std::string delimiter = std::string(" ")) +{ + std::string out = ""; + for (std::vector<std::string>::iterator iter = values.begin(); iter != values.end(); iter++) + { + out += (iter != values.begin() ? delimiter : "") + *iter; + } + return out; +} + +#define THROW_GAIN_NAME_ERROR(name,chan,dir) throw uhd::exception::runtime_error( \ + (boost::format("%s: gain \"%s\" not found for channel %d.\nAvailable gains: %s\n") % \ + __FUNCTION__ % name % chan % string_vector_to_string(get_##dir##_gain_names(chan))).str()); + /*********************************************************************** * Helper methods **********************************************************************/ @@ -509,6 +523,46 @@ public: return _tree->access<std::vector<std::string> >(mb_root(mboard) / "clock_source" / "options").get(); } + void set_clock_source_out(const bool enb, const size_t mboard) + { + if (mboard != ALL_MBOARDS) + { + if (_tree->exists(mb_root(mboard) / "clock_source" / "output")) + { + _tree->access<bool>(mb_root(mboard) / "clock_source" / "output").set(enb); + } + else + { + throw uhd::runtime_error("multi_usrp::set_clock_source_out - not supported on this device"); + } + return; + } + for (size_t m = 0; m < get_num_mboards(); m++) + { + this->set_clock_source_out(enb, m); + } + } + + void set_time_source_out(const bool enb, const size_t mboard) + { + if (mboard != ALL_MBOARDS) + { + if (_tree->exists(mb_root(mboard) / "time_source" / "output")) + { + _tree->access<bool>(mb_root(mboard) / "time_source" / "output").set(enb); + } + else + { + throw uhd::runtime_error("multi_usrp::set_time_source_out - not supported on this device"); + } + return; + } + for (size_t m = 0; m < get_num_mboards(); m++) + { + this->set_time_source_out(enb, m); + } + } + size_t get_num_mboards(void){ return _tree->list("/mboards").size(); } @@ -620,15 +674,27 @@ public: } void set_rx_gain(double gain, const std::string &name, size_t chan){ - return rx_gain_group(chan)->set_value(gain, name); + try { + return rx_gain_group(chan)->set_value(gain, name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,rx); + } } double get_rx_gain(const std::string &name, size_t chan){ - return rx_gain_group(chan)->get_value(name); + try { + return rx_gain_group(chan)->get_value(name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,rx); + } } gain_range_t get_rx_gain_range(const std::string &name, size_t chan){ - return rx_gain_group(chan)->get_range(name); + try { + return rx_gain_group(chan)->get_range(name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,rx); + } } std::vector<std::string> get_rx_gain_names(size_t chan){ @@ -789,15 +855,27 @@ public: } void set_tx_gain(double gain, const std::string &name, size_t chan){ - return tx_gain_group(chan)->set_value(gain, name); + try { + return tx_gain_group(chan)->set_value(gain, name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,tx); + } } double get_tx_gain(const std::string &name, size_t chan){ - return tx_gain_group(chan)->get_value(name); + try { + return tx_gain_group(chan)->get_value(name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,tx); + } } gain_range_t get_tx_gain_range(const std::string &name, size_t chan){ - return tx_gain_group(chan)->get_range(name); + try { + return tx_gain_group(chan)->get_range(name); + } catch (uhd::key_error &e) { + THROW_GAIN_NAME_ERROR(name,chan,tx); + } } std::vector<std::string> get_tx_gain_names(size_t chan){ @@ -860,6 +938,74 @@ public: } } + /******************************************************************* + * GPIO methods + ******************************************************************/ + std::vector<std::string> get_gpio_banks(const size_t mboard) + { + std::vector<std::string> banks; + if (_tree->exists(mb_root(mboard) / "gpio")) + { + BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mboard) / "gpio")) + { + banks.push_back(name); + } + } + BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mboard) / "dboards")) + { + banks.push_back("RX"+name); + banks.push_back("TX"+name); + } + return banks; + } + + void set_gpio_attr(const std::string &bank, const std::string &attr, const boost::uint32_t value, const boost::uint32_t mask, const size_t mboard) + { + if (_tree->exists(mb_root(mboard) / "gpio" / bank)) + { + const boost::uint32_t current = _tree->access<boost::uint32_t>(mb_root(mboard) / "gpio" / bank / attr).get(); + const boost::uint32_t new_value = (current & ~mask) | (value & mask); + _tree->access<boost::uint32_t>(mb_root(mboard) / "gpio" / bank / attr).set(new_value); + return; + } + if (bank.size() > 2 and bank[1] == 'X') + { + const std::string name = bank.substr(2); + const dboard_iface::unit_t unit = (bank[0] == 'R')? dboard_iface::UNIT_RX : dboard_iface::UNIT_TX; + dboard_iface::sptr iface = _tree->access<dboard_iface::sptr>(mb_root(mboard) / "dboards" / name / "iface").get(); + 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)); + } + } + + boost::uint32_t get_gpio_attr(const std::string &bank, const std::string &attr, const size_t mboard) + { + if (_tree->exists(mb_root(mboard) / "gpio" / bank)) + { + return _tree->access<boost::uint64_t>(mb_root(mboard) / "gpio" / bank / attr).get(); + } + if (bank.size() > 2 and bank[1] == 'X') + { + const std::string name = bank.substr(2); + const dboard_iface::unit_t unit = (bank[0] == 'R')? dboard_iface::UNIT_RX : dboard_iface::UNIT_TX; + dboard_iface::sptr iface = _tree->access<dboard_iface::sptr>(mb_root(mboard) / "dboards" / name / "iface").get(); + 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 == "READBACK") return iface->read_gpio(unit); + } + return 0; + } + private: device::sptr _dev; property_tree::sptr _tree; @@ -915,6 +1061,12 @@ private: fs_path rx_dsp_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_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); + mcp.chan = map[mcp.chan]; + } + try { const std::string name = _tree->list(mb_root(mcp.mboard) / "rx_dsps").at(mcp.chan); @@ -929,6 +1081,11 @@ private: fs_path tx_dsp_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_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); + mcp.chan = map[mcp.chan]; + } try { const std::string name = _tree->list(mb_root(mcp.mboard) / "tx_dsps").at(mcp.chan); diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index 625926f36..3b902b343 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -55,9 +55,9 @@ static device_addrs_t usrp1_find(const device_addr_t &hint) //return an empty list of addresses when type is set to non-usrp1 if (hint.has_key("type") and hint["type"] != "usrp1") return usrp1_addrs; - //Return an empty list of addresses when an address is specified, - //since an address is intended for a different, non-USB, device. - if (hint.has_key("addr")) return usrp1_addrs; + //Return an empty list of addresses when an address or resource is specified, + //since an address and resource is intended for a different, non-USB, device. + if (hint.has_key("addr") || hint.has_key("resource")) return usrp1_addrs; unsigned int vid, pid; diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp index 0be8ebca9..da9fe8b16 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.hpp +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -84,13 +84,6 @@ public: bool recv_async_msg(uhd::async_metadata_t &, double); private: - uhd::property_tree::sptr _tree; - - //device properties interface - uhd::property_tree::sptr get_tree(void) const{ - return _tree; - } - //controllers uhd::usrp::fx2_ctrl::sptr _fx2_ctrl; usrp1_iface::sptr _iface; diff --git a/host/lib/usrp/usrp2/usrp2_iface.cpp b/host/lib/usrp/usrp2/usrp2_iface.cpp index 5f97045e1..7297a30d1 100644 --- a/host/lib/usrp/usrp2/usrp2_iface.cpp +++ b/host/lib/usrp/usrp2/usrp2_iface.cpp @@ -36,6 +36,7 @@ #include <boost/filesystem.hpp> #include <algorithm> #include <iostream> +#include <uhd/utils/platform.hpp> using namespace uhd; using namespace uhd::usrp; @@ -62,33 +63,6 @@ static const boost::uint32_t MIN_PROTO_COMPAT_I2C = 7; static const boost::uint32_t MIN_PROTO_COMPAT_REG = 10; static const boost::uint32_t MIN_PROTO_COMPAT_UART = 7; -//Define get_gpid() to get a globally unique identifier for this process. -//The gpid is implemented as a hash of the pid and a unique machine identifier. -#ifdef UHD_PLATFORM_WIN32 -#include <Windows.h> -static inline size_t get_gpid(void){ - //extract volume serial number - char szVolName[MAX_PATH+1], szFileSysName[MAX_PATH+1]; - DWORD dwSerialNumber, dwMaxComponentLen, dwFileSysFlags; - GetVolumeInformation("C:\\", szVolName, MAX_PATH, - &dwSerialNumber, &dwMaxComponentLen, - &dwFileSysFlags, szFileSysName, sizeof(szFileSysName)); - - size_t hash = 0; - boost::hash_combine(hash, GetCurrentProcessId()); - boost::hash_combine(hash, dwSerialNumber); - return hash; -} -#else -#include <unistd.h> -static inline size_t get_gpid(void){ - size_t hash = 0; - boost::hash_combine(hash, getpid()); - boost::hash_combine(hash, gethostid()); - return hash; -} -#endif - class usrp2_iface_impl : public usrp2_iface{ public: /*********************************************************************** @@ -122,7 +96,7 @@ public: void lock_device(bool lock){ if (lock){ - this->pokefw(U2_FW_REG_LOCK_GPID, boost::uint32_t(get_gpid())); + this->pokefw(U2_FW_REG_LOCK_GPID, get_process_hash()); _lock_task = task::make(boost::bind(&usrp2_iface_impl::lock_task, this)); } else{ @@ -147,7 +121,7 @@ public: if (time_diff >= lock_timeout_time) return false; //otherwise only lock if the device hash is different that ours - return lock_gpid != boost::uint32_t(get_gpid()); + return lock_gpid != get_process_hash(); } void lock_task(void){ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 3afb3aac7..16d9b9a54 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -41,6 +41,9 @@ using namespace uhd::usrp; using namespace uhd::transport; namespace asio = boost::asio; +//A reasonable number of frames for send/recv and async/sync +static const size_t DEFAULT_NUM_FRAMES = 32; + /*********************************************************************** * Discovery over the udp transport **********************************************************************/ @@ -49,13 +52,16 @@ static device_addrs_t usrp2_find(const device_addr_t &hint_){ device_addrs_t hints = separate_device_addr(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 = usrp2_find(hint_i); - if (found_devices_i.size() != 1) throw uhd::value_error(str(boost::format( + 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())); - found_devices.push_back(found_devices_i[0]); + ) % 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)); } @@ -68,6 +74,10 @@ static device_addrs_t usrp2_find(const device_addr_t &hint_){ //return an empty list of addresses when type is set to non-usrp2 if (hint.has_key("type") and hint["type"] != "usrp2") return usrp2_addrs; + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-USB, device. + if (hint.has_key("resource")) return usrp2_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()){ @@ -278,8 +288,15 @@ static zero_copy_if::sptr make_xport( filtered_hints[key] = hints[key]; } + zero_copy_xport_params default_buff_args; + default_buff_args.send_frame_size = transport::udp_simple::mtu; + default_buff_args.recv_frame_size = transport::udp_simple::mtu; + default_buff_args.num_send_frames = DEFAULT_NUM_FRAMES; + default_buff_args.num_recv_frames = DEFAULT_NUM_FRAMES; + //make the transport object with the filtered hints - zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, filtered_hints); + udp_zero_copy::buff_params ignored_params; + zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, default_buff_args, ignored_params, filtered_hints); //Send a small data packet so the usrp2 knows the udp source port. //This setup must happen before further initialization occurs diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index f9988287f..d7b53e56b 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -79,7 +79,6 @@ public: bool recv_async_msg(uhd::async_metadata_t &, double); private: - uhd::property_tree::sptr _tree; struct mb_container_type{ usrp2_iface::sptr iface; usrp2_fifo_ctrl::sptr fifo_ctrl; @@ -115,11 +114,6 @@ private: void set_rx_fe_corrections(const std::string &mb, const double); void set_tx_fe_corrections(const std::string &mb, const double); - //device properties interface - uhd::property_tree::sptr get_tree(void) const{ - return _tree; - } - //io impl methods and members UHD_PIMPL_DECL(io_impl) _io_impl; void io_init(void); diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt new file mode 100644 index 000000000..a588f901b --- /dev/null +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -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/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the X300 support +######################################################################## +LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF) + +IF(ENABLE_X300) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_dac_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_io_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp + ) +ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_adc_ctrl.cpp b/host/lib/usrp/x300/x300_adc_ctrl.cpp new file mode 100644 index 000000000..75bfb048c --- /dev/null +++ b/host/lib/usrp/x300/x300_adc_ctrl.cpp @@ -0,0 +1,133 @@ +// +// Copyright 2010-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 "x300_adc_ctrl.hpp" +#include "ads62p48_regs.hpp" +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> + +using namespace uhd; + +/*! + * A X300 codec control specific to the ads62p48 ic. + */ +class x300_adc_ctrl_impl : public x300_adc_ctrl +{ +public: + x300_adc_ctrl_impl(uhd::spi_iface::sptr iface, const size_t slaveno): + _iface(iface), _slaveno(slaveno) + { + //power-up adc + _ads62p48_regs.reset = 1; + this->send_ads62p48_reg(0x00); //issue a reset to the ADC + _ads62p48_regs.reset = 0; + + _ads62p48_regs.enable_low_speed_mode = 0; + _ads62p48_regs.ref = ads62p48_regs_t::REF_INTERNAL; + _ads62p48_regs.standby = ads62p48_regs_t::STANDBY_NORMAL; + _ads62p48_regs.power_down = ads62p48_regs_t::POWER_DOWN_NORMAL; + _ads62p48_regs.lvds_cmos = ads62p48_regs_t::LVDS_CMOS_DDR_LVDS; + _ads62p48_regs.channel_control = ads62p48_regs_t::CHANNEL_CONTROL_INDEPENDENT; + _ads62p48_regs.data_format = ads62p48_regs_t::DATA_FORMAT_2S_COMPLIMENT; + _ads62p48_regs.clk_out_pos_edge = ads62p48_regs_t::CLK_OUT_POS_EDGE_MINUS7_26; + _ads62p48_regs.clk_out_neg_edge = ads62p48_regs_t::CLK_OUT_NEG_EDGE_MINUS7_26; + + + this->send_ads62p48_reg(0); + this->send_ads62p48_reg(0x20); + this->send_ads62p48_reg(0x3f); + this->send_ads62p48_reg(0x40); + this->send_ads62p48_reg(0x41); + this->send_ads62p48_reg(0x44); + this->send_ads62p48_reg(0x50); + this->send_ads62p48_reg(0x51); + this->send_ads62p48_reg(0x52); + this->send_ads62p48_reg(0x53); + this->send_ads62p48_reg(0x55); + this->send_ads62p48_reg(0x57); + this->send_ads62p48_reg(0x62); + this->send_ads62p48_reg(0x63); + this->send_ads62p48_reg(0x66); + this->send_ads62p48_reg(0x68); + this->send_ads62p48_reg(0x6a); + this->send_ads62p48_reg(0x75); + this->send_ads62p48_reg(0x76); + + } + + double set_gain(const double &gain) + { + const meta_range_t gain_range = meta_range_t(0, 6.0, 0.5); + const int gain_bits = int((gain_range.clip(gain)*2.0) + 0.5); + _ads62p48_regs.gain_chA = gain_bits; + _ads62p48_regs.gain_chB = gain_bits; + this->send_ads62p48_reg(0x55); + this->send_ads62p48_reg(0x68); + return gain_bits/2; + } + + void set_test_word(const std::string &patterna, const std::string &patternb, const boost::uint32_t num) + { + _ads62p48_regs.custom_pattern_low = num & 0xff; + _ads62p48_regs.custom_pattern_high = num >> 8; + if (patterna == "ones") _ads62p48_regs.test_patterns_chA = ads62p48_regs_t::TEST_PATTERNS_CHA_ONES; + if (patterna == "zeros") _ads62p48_regs.test_patterns_chA = ads62p48_regs_t::TEST_PATTERNS_CHA_ZEROS; + if (patterna == "custom") _ads62p48_regs.test_patterns_chA = ads62p48_regs_t::TEST_PATTERNS_CHA_CUSTOM; + if (patterna == "ramp") _ads62p48_regs.test_patterns_chA = ads62p48_regs_t::TEST_PATTERNS_CHA_RAMP; + if (patterna == "normal") _ads62p48_regs.test_patterns_chA = ads62p48_regs_t::TEST_PATTERNS_CHA_NORMAL; + if (patternb == "ones") _ads62p48_regs.test_patterns_chB = ads62p48_regs_t::TEST_PATTERNS_CHB_ONES; + if (patternb == "zeros") _ads62p48_regs.test_patterns_chB = ads62p48_regs_t::TEST_PATTERNS_CHB_ZEROS; + if (patternb == "custom") _ads62p48_regs.test_patterns_chB = ads62p48_regs_t::TEST_PATTERNS_CHB_CUSTOM; + if (patterna == "ramp") _ads62p48_regs.test_patterns_chB = ads62p48_regs_t::TEST_PATTERNS_CHB_RAMP; + if (patterna == "normal") _ads62p48_regs.test_patterns_chB = ads62p48_regs_t::TEST_PATTERNS_CHB_NORMAL; + this->send_ads62p48_reg(0x51); + this->send_ads62p48_reg(0x52); + this->send_ads62p48_reg(0x62); + this->send_ads62p48_reg(0x75); + } + + ~x300_adc_ctrl_impl(void) + { + _ads62p48_regs.power_down = ads62p48_regs_t::POWER_DOWN_GLOBAL; + UHD_SAFE_CALL + ( + this->send_ads62p48_reg(0x40); + ) + } + +private: + ads62p48_regs_t _ads62p48_regs; + uhd::spi_iface::sptr _iface; + const size_t _slaveno; + + void send_ads62p48_reg(boost::uint8_t addr) + { + boost::uint16_t reg = _ads62p48_regs.get_write_reg(addr); + _iface->write_spi(_slaveno, spi_config_t::EDGE_FALL, reg, 16); + } +}; + +/*********************************************************************** + * Public make function for the ADC control + **********************************************************************/ +x300_adc_ctrl::sptr x300_adc_ctrl::make(uhd::spi_iface::sptr iface, const size_t slaveno) +{ + return sptr(new x300_adc_ctrl_impl(iface, slaveno)); +} diff --git a/host/lib/usrp/x300/x300_adc_ctrl.hpp b/host/lib/usrp/x300/x300_adc_ctrl.hpp new file mode 100644 index 000000000..fce40a434 --- /dev/null +++ b/host/lib/usrp/x300/x300_adc_ctrl.hpp @@ -0,0 +1,44 @@ +// +// Copyright 2010-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_X300_ADC_CTRL_HPP +#define INCLUDED_X300_ADC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class x300_adc_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<x300_adc_ctrl> sptr; + + /*! + * Make a codec control for the ADC. + * \param iface a pointer to the interface object + * \param spiface the interface to spi + * \return a new codec control object + */ + static sptr make(uhd::spi_iface::sptr iface, const size_t slaveno); + + virtual double set_gain(const double &) = 0; + + virtual void set_test_word(const std::string &patterna, const std::string &patternb, const boost::uint32_t = 0) = 0; + +}; + +#endif /* INCLUDED_X300_ADC_CTRL_HPP */ diff --git a/host/lib/usrp/x300/x300_clock_ctrl.cpp b/host/lib/usrp/x300/x300_clock_ctrl.cpp new file mode 100644 index 000000000..a8b30a0ab --- /dev/null +++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp @@ -0,0 +1,402 @@ +// +// 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 "lmk04816_regs.hpp" +#include "x300_clock_ctrl.hpp" +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <stdexcept> +#include <cmath> +#include <cstdlib> + +static const double X300_REF_CLK_OUT_RATE = 10e6; + +using namespace uhd; + +class x300_clock_ctrl_impl : public x300_clock_ctrl { + +public: + +~x300_clock_ctrl_impl(void) {} + +x300_clock_ctrl_impl(uhd::spi_iface::sptr spiface, + const size_t slaveno, + const size_t hw_rev, + const double master_clock_rate, + const double system_ref_rate): + _spiface(spiface), + _slaveno(slaveno), + _hw_rev(hw_rev), + _master_clock_rate(master_clock_rate), + _system_ref_rate(system_ref_rate) +{ + set_master_clock_rate(master_clock_rate); +} + +void reset_clocks() { + set_master_clock_rate(_master_clock_rate); +} + +void sync_clocks(void) { + //soft sync: + //put the sync IO into output mode - FPGA must be input + //write low, then write high - this triggers a soft sync + _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_LOW; + this->write_regs(11); + _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_HIGH; + this->write_regs(11); +} + +double get_master_clock_rate(void) { + return _master_clock_rate; +} + +double get_sysref_clock_rate(void) { + return _system_ref_rate; +} + +double get_refout_clock_rate(void) { + //We support only one reference output rate + return X300_REF_CLK_OUT_RATE; +} + +void set_dboard_rate(const x300_clock_which_t, double rate) { + if(not doubles_are_equal(rate, get_master_clock_rate())) { + throw uhd::not_implemented_error("x3xx set dboard clock rate does not support setting an arbitrary clock rate"); + } +} + +std::vector<double> get_dboard_rates(const x300_clock_which_t) { + /* Right now, the only supported daughterboard clock rate is the master clock + * rate. TODO Implement divider settings for lower clock rates for legacy + * daughterboard support. */ + + std::vector<double> rates; + rates.push_back(get_master_clock_rate()); + return rates; +} + +void set_ref_out(const bool enable) { + // TODO Implement divider configuration to allow for configurable output + // rates + if (enable) + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; + else + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_P_DOWN; + this->write_regs(8); +} + +void write_regs(boost::uint8_t addr) { + boost::uint32_t data = _lmk04816_regs.get_reg(addr); + _spiface->write_spi(_slaveno, spi_config_t::EDGE_RISE, data,32); +} + + +private: + +void set_master_clock_rate(double clock_rate) { + /* The X3xx has two primary rates. The first is the + * _system_ref_rate, which is sourced from the "clock_source"/"value" field + * of the property tree, and whose value can be 10e6, 30.72e6, or 200e6. + * The _system_ref_rate is the input to the clocking system, and + * what comes out is a disciplined master clock running at the + * _master_clock_rate. As such, only certain combinations of + * system reference rates and master clock rates are supported. + * Additionally, a subset of these will operate in "zero delay" mode. */ + + enum opmode_t { INVALID, + m10M_200M_NOZDEL, // used for debug purposes only + m10M_200M_ZDEL, // Normal mode + m30_72M_184_32M_ZDEL, // LTE with external ref, aka CPRI Mode + m10M_184_32M_NOZDEL, // LTE with 10 MHz ref + m10M_120M_ZDEL }; // NI USRP 120 MHz Clocking + + /* The default clocking mode is 10MHz reference generating a 200 MHz master + * clock, in zero-delay mode. */ + opmode_t clocking_mode = INVALID; + + if(doubles_are_equal(_system_ref_rate, 10e6)) { + if(doubles_are_equal(clock_rate, 184.32e6)) { + /* 10MHz reference, 184.32 MHz master clock out, NOT Zero Delay. */ + clocking_mode = m10M_184_32M_NOZDEL; + } else if(doubles_are_equal(clock_rate, 200e6)) { + /* 10MHz reference, 200 MHz master clock out, Zero Delay */ + clocking_mode = m10M_200M_ZDEL; + } else if(doubles_are_equal(clock_rate, 120e6)) { + /* 10MHz reference, 120 MHz master clock rate, Zero Delay */ + clocking_mode = m10M_120M_ZDEL; + } + } else if(doubles_are_equal(_system_ref_rate, 30.72e6)) { + if(doubles_are_equal(clock_rate, 184.32e6)) { + /* 30.72MHz reference, 184.32 MHz master clock out, Zero Delay */ + clocking_mode = m30_72M_184_32M_ZDEL; + } + } + + if(clocking_mode == INVALID) { + throw uhd::runtime_error(str(boost::format("A master clock rate of %f cannot be derived from a system reference rate of %f") % clock_rate % _system_ref_rate)); + } + + // For 200 MHz output, the VCO is run at 2400 MHz + // For the LTE/CPRI rate of 184.32 MHz, the VCO runs at 2580.48 MHz + + int vco_div = 0; + + // Note: PLL2 N2 prescaler is enabled for all cases + // PLL2 reference doubler is enabled for all cases + + /* All LMK04816 settings are from the LMK datasheet for our clocking + * architecture. Please refer to the datasheet for more information. */ + switch (clocking_mode) { + case m10M_200M_NOZDEL: + vco_div = 12; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; + + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 48; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 48 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 25; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 4; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + + break; + + case m10M_200M_ZDEL: + vco_div = 12; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 100; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 96 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 5; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; + _lmk04816_regs.PLL2_R_28 = 2; + + if(_hw_rev <= 4) + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; + else + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + + break; + + case m30_72M_184_32M_ZDEL: + vco_div=14; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + + // PLL1 - 2.048 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 90; + _lmk04816_regs.PLL1_R_27 = 15; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 7.68 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 168; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 25; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + + _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + + _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_34PF; + + break; + + case m10M_184_32M_NOZDEL: + vco_div=14; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; + + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 48; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 7.68 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 168; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 25; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + + _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_4KILO_OHM; + _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + + _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_71PF; + + break; + + case m10M_120M_ZDEL: + vco_div = 20; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 60; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 96 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 5; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; + _lmk04816_regs.PLL2_R_28 = 2; + + if(_hw_rev <= 4) + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; + else + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + + break; + + default: + UHD_THROW_INVALID_CODE_PATH(); + break; + }; + + /* Reset the LMK clock controller. */ + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_RESET; + this->write_regs(0); + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_NO_RESET; + this->write_regs(0); + + /* Initial power-up */ + _lmk04816_regs.CLKout0_1_PD = lmk04816_regs_t::CLKOUT0_1_PD_POWER_UP; + this->write_regs(0); + _lmk04816_regs.CLKout0_1_DIV = vco_div; + this->write_regs(0); + + // Register 1 + _lmk04816_regs.CLKout2_3_PD = lmk04816_regs_t::CLKOUT2_3_PD_POWER_UP; + _lmk04816_regs.CLKout2_3_DIV = vco_div; + // Register 2 + _lmk04816_regs.CLKout4_5_PD = lmk04816_regs_t::CLKOUT4_5_PD_POWER_UP; + _lmk04816_regs.CLKout4_5_DIV = vco_div; + // Register 3 + _lmk04816_regs.CLKout6_7_DIV = vco_div; + _lmk04816_regs.CLKout6_7_OSCin_Sel = lmk04816_regs_t::CLKOUT6_7_OSCIN_SEL_VCO; + // Register 4 + _lmk04816_regs.CLKout8_9_DIV = vco_div; + // Register 5 + _lmk04816_regs.CLKout10_11_PD = lmk04816_regs_t::CLKOUT10_11_PD_NORMAL; + _lmk04816_regs.CLKout10_11_DIV = vco_div * static_cast<int>(clock_rate/X300_REF_CLK_OUT_RATE); + + // Register 6 + _lmk04816_regs.CLKout0_TYPE = lmk04816_regs_t::CLKOUT0_TYPE_LVDS; //FPGA + _lmk04816_regs.CLKout1_TYPE = lmk04816_regs_t::CLKOUT1_TYPE_P_DOWN; //CPRI feedback clock, use LVDS + _lmk04816_regs.CLKout2_TYPE = lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP; //DB_0_RX + _lmk04816_regs.CLKout3_TYPE = lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP; //DB_1_RX + // Register 7 + _lmk04816_regs.CLKout4_TYPE = lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP; //DB_1_TX + _lmk04816_regs.CLKout5_TYPE = lmk04816_regs_t::CLKOUT5_TYPE_LVPECL_700MVPP; //DB_0_TX + _lmk04816_regs.CLKout6_TYPE = lmk04816_regs_t::CLKOUT6_TYPE_LVPECL_700MVPP; //DB0_DAC + _lmk04816_regs.CLKout7_TYPE = lmk04816_regs_t::CLKOUT7_TYPE_LVPECL_700MVPP; //DB1_DAC + _lmk04816_regs.CLKout8_TYPE = lmk04816_regs_t::CLKOUT8_TYPE_LVPECL_700MVPP; //DB0_ADC + // Register 8 + _lmk04816_regs.CLKout9_TYPE = lmk04816_regs_t::CLKOUT9_TYPE_LVPECL_700MVPP; //DB1_ADC + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; //REF_CLKOUT + _lmk04816_regs.CLKout11_TYPE = lmk04816_regs_t::CLKOUT11_TYPE_P_DOWN; //Debug header, use LVPECL + + + // Register 10 + _lmk04816_regs.EN_OSCout0 = lmk04816_regs_t::EN_OSCOUT0_DISABLED; //Debug header + _lmk04816_regs.FEEDBACK_MUX = 0; //use output 0 (FPGA clock) for feedback + _lmk04816_regs.EN_FEEDBACK_MUX = lmk04816_regs_t::EN_FEEDBACK_MUX_ENABLED; + + // Register 11 + // MODE set in individual cases above + _lmk04816_regs.SYNC_QUAL = lmk04816_regs_t::SYNC_QUAL_FB_MUX; + _lmk04816_regs.EN_SYNC = lmk04816_regs_t::EN_SYNC_ENABLE; + _lmk04816_regs.NO_SYNC_CLKout0_1 = lmk04816_regs_t::NO_SYNC_CLKOUT0_1_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout2_3 = lmk04816_regs_t::NO_SYNC_CLKOUT2_3_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout4_5 = lmk04816_regs_t::NO_SYNC_CLKOUT4_5_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout8_9 = lmk04816_regs_t::NO_SYNC_CLKOUT8_9_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout10_11 = lmk04816_regs_t::NO_SYNC_CLKOUT10_11_CLOCK_XY_SYNC; + _lmk04816_regs.SYNC_EN_AUTO = lmk04816_regs_t::SYNC_EN_AUTO_SYNC_INT_GEN; + _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_LOW; + _lmk04816_regs.SYNC_TYPE = lmk04816_regs_t::SYNC_TYPE_INPUT; + + // Register 12 + _lmk04816_regs.LD_MUX = lmk04816_regs_t::LD_MUX_BOTH; + + /* Input Clock Configurations */ + // Register 13 + _lmk04816_regs.EN_CLKin0 = lmk04816_regs_t::EN_CLKIN0_NO_VALID_USE; // This is not connected + _lmk04816_regs.EN_CLKin2 = lmk04816_regs_t::EN_CLKIN2_NO_VALID_USE; // Used only for CPRI + _lmk04816_regs.Status_CLKin1_MUX = lmk04816_regs_t::STATUS_CLKIN1_MUX_UWIRE_RB; + _lmk04816_regs.CLKin_Select_MODE = lmk04816_regs_t::CLKIN_SELECT_MODE_CLKIN1_MAN; + _lmk04816_regs.HOLDOVER_MUX = lmk04816_regs_t::HOLDOVER_MUX_PLL1_R; + // Register 14 + _lmk04816_regs.Status_CLKin1_TYPE = lmk04816_regs_t::STATUS_CLKIN1_TYPE_OUT_PUSH_PULL; + _lmk04816_regs.Status_CLKin0_TYPE = lmk04816_regs_t::STATUS_CLKIN0_TYPE_OUT_PUSH_PULL; + + // Register 26 + // PLL2_CP_GAIN_26 set above in individual cases + _lmk04816_regs.PLL2_CP_POL_26 = lmk04816_regs_t::PLL2_CP_POL_26_NEG_SLOPE; + _lmk04816_regs.EN_PLL2_REF_2X = lmk04816_regs_t::EN_PLL2_REF_2X_DOUBLED_FREQ_REF; + + // Register 27 + // PLL1_CP_GAIN_27 set in individual cases above + // PLL1_R_27 set in the individual cases above + + // Register 28 + // PLL1_N_28 and PLL2_R_28 are set in the individual cases above + + // Register 29 + _lmk04816_regs.PLL2_N_CAL_29 = _lmk04816_regs.PLL2_N_30; // N_CAL should always match N + _lmk04816_regs.OSCin_FREQ_29 = lmk04816_regs_t::OSCIN_FREQ_29_63_TO_127MHZ; + + // Register 30 + // PLL2_P_30 set in individual cases above + // PLL2_N_30 set in individual cases above + + /* Write the configuration values into the LMK */ + for (size_t i = 1; i <= 16; ++i) { + this->write_regs(i); + } + for (size_t i = 24; i <= 31; ++i) { + this->write_regs(i); + } + + this->sync_clocks(); +} + +UHD_INLINE bool doubles_are_equal(double a, double b) { + return (std::fabs(a - b) < std::numeric_limits<double>::epsilon()); +} + +const spi_iface::sptr _spiface; +const size_t _slaveno; +const size_t _hw_rev; +const double _master_clock_rate; +const double _system_ref_rate; +lmk04816_regs_t _lmk04816_regs; +}; + +x300_clock_ctrl::sptr x300_clock_ctrl::make(uhd::spi_iface::sptr spiface, + const size_t slaveno, + const size_t hw_rev, + const double master_clock_rate, + const double system_ref_rate) { + return sptr(new x300_clock_ctrl_impl(spiface, slaveno, hw_rev, + master_clock_rate, system_ref_rate)); +} diff --git a/host/lib/usrp/x300/x300_clock_ctrl.hpp b/host/lib/usrp/x300/x300_clock_ctrl.hpp new file mode 100644 index 000000000..e9904d25c --- /dev/null +++ b/host/lib/usrp/x300/x300_clock_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_X300_CLOCK_CTRL_HPP +#define INCLUDED_X300_CLOCK_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + + +enum x300_clock_which_t +{ + X300_CLOCK_WHICH_ADC0, + X300_CLOCK_WHICH_ADC1, + X300_CLOCK_WHICH_DAC0, + X300_CLOCK_WHICH_DAC1, + X300_CLOCK_WHICH_DB0_RX, + X300_CLOCK_WHICH_DB0_TX, + X300_CLOCK_WHICH_DB1_RX, + X300_CLOCK_WHICH_DB1_TX, + X300_CLOCK_WHICH_TEST, +}; + +class x300_clock_ctrl : boost::noncopyable +{ +public: + + typedef boost::shared_ptr<x300_clock_ctrl> sptr; + + static sptr make(uhd::spi_iface::sptr spiface, + const size_t slaveno, + const size_t hw_rev, + const double master_clock_rate, + const double system_ref_rate); + + /*! Get the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double get_master_clock_rate(void) = 0; + + /*! Get the system reference rate of the device. + * \return the clock frequency in Hz + */ + virtual double get_sysref_clock_rate(void) = 0; + + /*! Get the current reference output rate + * \return the clock frequency in Hz + */ + virtual double get_refout_clock_rate(void) = 0; + + /*! Set the clock rate on the given daughterboard clock. + * \param rate the new clock rate + * \throw exception when rate invalid + */ + virtual void set_dboard_rate(const x300_clock_which_t which, double rate) = 0; + + /*! Get a list of possible daughterboard clock rates. + * \return a list of clock rates in Hz + */ + virtual std::vector<double> get_dboard_rates(const x300_clock_which_t which) = 0; + + /*! Turn the reference output on/off + * \param true = on, false = off + */ + virtual void set_ref_out(const bool) = 0; + + /*! Reset the clocks. + * Should be called if the reference clock changes + * to reduce the time required to achieve a lock. + */ + virtual void reset_clocks(void) = 0; +}; + +#endif /* INCLUDED_X300_CLOCK_CTRL_HPP */ diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp new file mode 100644 index 000000000..5eae9cc48 --- /dev/null +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -0,0 +1,146 @@ +// +// Copyright 2010-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 "x300_dac_ctrl.hpp" +#include "x300_regs.hpp" +#include <uhd/types/time_spec.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/exception.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/thread.hpp> //sleep + +using namespace uhd; + +#define write_ad9146_reg(addr, data) \ + _iface->write_spi(_slaveno, spi_config_t::EDGE_RISE, ((addr) << 8) | (data), 16) +#define read_ad9146_reg(addr) \ + (_iface->read_spi(_slaveno, spi_config_t::EDGE_RISE, ((addr) << 8) | (1 << 15), 16) & 0xff) + +/*! + * A X300 codec control specific to the ad9146 ic. + */ +class x300_dac_ctrl_impl : public x300_dac_ctrl +{ +public: + x300_dac_ctrl_impl(uhd::spi_iface::sptr iface, const size_t slaveno, const double refclk): + _iface(iface), _slaveno(slaveno) + { + write_ad9146_reg(0x00, 0x20); // Take DAC into reset. + write_ad9146_reg(0x00, 0x80); // Enable SPI reads and come out of reset + write_ad9146_reg(0x1e, 0x01); // Data path config - set for proper operation + + // Calculate N0 to be VCO friendly. + // Aim for VCO between 1 and 2GHz, assert otherwise. + // const int N1 = 4; + const int N1 = 4; + int N0_val, N0; + for (N0_val = 0; N0_val < 3; N0_val++) + { + N0 = (1 << N0_val); //1, 2, 4 + if ((refclk * N0 * N1) >= 1e9) break; + } + UHD_ASSERT_THROW((refclk * N0 * N1) >= 1e9); + UHD_ASSERT_THROW((refclk * N0 * N1) <= 2e9); + + /* Start PLL */ + //write_ad9146_reg(0x0C, 0xD1); // Narrow PLL loop filter, Midrange charge pump. + write_ad9146_reg(0x0D, 0xD1 | (N0_val << 2)); // N1=4, N2=16, N0 as calculated + //write_ad9146_reg(0x0D, 0x90 | (N0_val << 2)); // N1=2, N2=8, N0 as calculated + write_ad9146_reg(0x0A, 0xCF); // Auto init VCO band training as per datasheet + write_ad9146_reg(0x0A, 0xA0); // See above. + + // Verify PLL is Locked. 1 sec timeout. + // NOTE: Data sheet inconsistant about which pins give PLL lock status. FIXME! + const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0); + while (true) + { + const size_t reg_e = read_ad9146_reg(0x0E); /* Expect bit 7 = 1 */ + if ((exit_time < time_spec_t::get_system_time()) && ((reg_e & (1 << 7)) == 0)) + throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for DAC PLL to lock"); + else if ((reg_e & ((1 << 7) | (1 << 6))) != 0) break; + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } + + /* Skew DCI signal to find stable data eye */ + //write_ad9146_reg(0x16, 0x04); //Disable delay in DCI + //write_ad9146_reg(0x16, 0x00); //165ps delay in DCI + //write_ad9146_reg(0x16, 0x01); //375ps delay in DCI + write_ad9146_reg(0x16, 0x02); //615ps delay in DCI + //write_ad9146_reg(0x16, 0x03); //720ps delay in DCI + + write_ad9146_reg(0x03, 0x00); // 2's comp, I first, byte wide interface + + //fpga wants I,Q in the sample word: + //first transaction goes into low bits + //second transaction goes into high bits + //therefore, we want Q to go first (bit 6 == 1) + write_ad9146_reg(0x03, (1 << 6)); //2s comp, i first, byte mode + + write_ad9146_reg(0x10, 0x48); // Disable SYNC mode. + write_ad9146_reg(0x17, 0x04); // FIFO write pointer offset + write_ad9146_reg(0x18, 0x02); // Request soft FIFO align + write_ad9146_reg(0x18, 0x00); // (See above) + write_ad9146_reg(0x1B, 0xE4); // Bypass: Modulator, InvSinc, IQ Bal + + /* Configure interpolation filters */ + write_ad9146_reg(0x1C, 0x00); // Configure HB1 + write_ad9146_reg(0x1D, 0x00); // Configure HB2 + + } + + + ~x300_dac_ctrl_impl(void) + { + UHD_SAFE_CALL + ( + write_ad9146_reg(0x1, 0xf); //total power down + write_ad9146_reg(0x2, 0xf); //total power down + ) + } + + void arm_dac_sync(void) + { + // + // Attempt to synchronize AD9146's + // + write_ad9146_reg(0x10, 0xCF); // Enable SYNC mode. Sync Averaging set to 128. + + const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0); + while (true) + { + const size_t reg_12 = read_ad9146_reg(0x12); /* Expect bit 7 = 0, bit 6 = 1 */ + if ((exit_time < time_spec_t::get_system_time()) && (((reg_12 & (1 << 6)) == 0) || ((reg_12 & (1 << 7)) != 0))) + throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for backend synchronization"); + else if (((reg_12 & (1 << 6)) != 0) && ((reg_12 & (1 << 7)) == 0)) break; + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + } + } + +private: + uhd::spi_iface::sptr _iface; + const size_t _slaveno; +}; + +/*********************************************************************** + * Public make function for the DAC control + **********************************************************************/ +x300_dac_ctrl::sptr x300_dac_ctrl::make(uhd::spi_iface::sptr iface, const size_t slaveno, const double clock_rate) +{ + return sptr(new x300_dac_ctrl_impl(iface, slaveno, clock_rate)); +} diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp new file mode 100644 index 000000000..0db7e1e35 --- /dev/null +++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp @@ -0,0 +1,42 @@ +// +// Copyright 2010-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_X300_DAC_CTRL_HPP +#define INCLUDED_X300_DAC_CTRL_HPP + +#include <uhd/types/serial.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> + +class x300_dac_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<x300_dac_ctrl> sptr; + + /*! + * Make a codec control for the DAC. + * \param iface a pointer to the interface object + * \param spiface the interface to spi + * \return a new codec control object + */ + static sptr make(uhd::spi_iface::sptr iface, const size_t slaveno, const double clock_rate); + + // ! Arm the sync feature in DAC + virtual void arm_dac_sync(void) = 0; +}; + +#endif /* INCLUDED_X300_DAC_CTRL_HPP */ diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp new file mode 100644 index 000000000..43da7ca08 --- /dev/null +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -0,0 +1,333 @@ +// +// 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 "x300_impl.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_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 + **********************************************************************/ +x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config): + _config(config) +{ + //reset the aux dacs + _dac_regs[UNIT_RX] = ad5623_regs_t(); + _dac_regs[UNIT_TX] = ad5623_regs_t(); + BOOST_FOREACH(unit_t unit, _dac_regs.keys()) + { + _dac_regs[unit].data = 1; + _dac_regs[unit].addr = ad5623_regs_t::ADDR_ALL; + _dac_regs[unit].cmd = ad5623_regs_t::CMD_RESET; + this->_write_aux_dac(unit); + } + + this->set_clock_enabled(UNIT_RX, false); + this->set_clock_enabled(UNIT_TX, false); + + this->set_clock_rate(UNIT_RX, _config.clock->get_master_clock_rate()); + this->set_clock_rate(UNIT_TX, _config.clock->get_master_clock_rate()); + + + //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) +{ + UHD_SAFE_CALL + ( + this->set_clock_enabled(UNIT_RX, false); + this->set_clock_enabled(UNIT_TX, false); + ) +} + +/*********************************************************************** + * Clocks + **********************************************************************/ +void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) +{ + _clock_rates[unit] = rate; //set to shadow + switch(unit) + { + case UNIT_RX: + _config.clock->set_dboard_rate(_config.which_rx_clk, rate); + return; + case UNIT_TX: + _config.clock->set_dboard_rate(_config.which_tx_clk, rate); + return; + } +} + +double x300_dboard_iface::get_clock_rate(unit_t unit) +{ + return _clock_rates[unit]; //get from shadow +} + +std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) +{ + switch(unit) + { + case UNIT_RX: + return _config.clock->get_dboard_rates(_config.which_rx_clk); + case UNIT_TX: + return _config.clock->get_dboard_rates(_config.which_tx_clk); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) +{ + // TODO Variable DBoard clock control needs to be implemented for X300. +} + +double x300_dboard_iface::get_codec_rate(unit_t) +{ + return _config.clock->get_master_clock_rate(); +} + +/*********************************************************************** + * GPIO + **********************************************************************/ +void x300_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +{ + return _config.gpio->set_pin_ctrl(unit, value); +} + +void x300_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +{ + return _config.gpio->set_gpio_ddr(unit, value); +} + +void x300_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +{ + return _config.gpio->set_gpio_out(unit, value); +} + +boost::uint16_t x300_dboard_iface::read_gpio(unit_t unit) +{ + return _config.gpio->read_gpio(unit); +} + +void x300_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) +{ + return _config.gpio->set_atr_reg(unit, atr, value); +} + +void x300_dboard_iface::set_gpio_debug(unit_t, int) +{ + throw uhd::not_implemented_error("no set_gpio_debug implemented"); +} + +/*********************************************************************** + * 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 x300_dboard_iface::read_write_spi( + unit_t unit, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits +){ + return _config.spi->read_spi(toslaveno(unit), config, data, num_bits); +} + +/*********************************************************************** + * I2C + **********************************************************************/ +void x300_dboard_iface::write_i2c(boost::uint16_t addr, const byte_vector_t &bytes) +{ + return _config.i2c->write_i2c(addr, bytes); +} + +byte_vector_t x300_dboard_iface::read_i2c(boost::uint16_t addr, size_t num_bytes) +{ + return _config.i2c->read_i2c(addr, num_bytes); +} + +/*********************************************************************** + * Aux DAX/ADC + **********************************************************************/ +void x300_dboard_iface::_write_aux_dac(unit_t unit) +{ + static const uhd::dict<unit_t, int> unit_to_spi_dac = map_list_of + (UNIT_RX, DB_RX_LSDAC_SEN) + (UNIT_TX, DB_TX_LSDAC_SEN) + ; + _config.spi->write_spi( + unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, + _dac_regs[unit].get_reg(), 24 + ); +} + +void x300_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value) +{ + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); + _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; + + typedef uhd::dict<aux_dac_t, ad5623_regs_t::addr_t> aux_dac_to_addr; + static const uhd::dict<unit_t, aux_dac_to_addr> unit_to_which_to_addr = map_list_of + (UNIT_RX, map_list_of + (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_A) + (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_A) + ) + (UNIT_TX, map_list_of + (AUX_DAC_A, ad5623_regs_t::ADDR_DAC_A) + (AUX_DAC_B, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_C, ad5623_regs_t::ADDR_DAC_B) + (AUX_DAC_D, ad5623_regs_t::ADDR_DAC_A) + ) + ; + _dac_regs[unit].addr = unit_to_which_to_addr[unit][which]; + this->_write_aux_dac(unit); +} + +double x300_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which) +{ + static const uhd::dict<unit_t, int> unit_to_spi_adc = map_list_of + (UNIT_RX, DB_RX_LSADC_SEN) + (UNIT_TX, DB_TX_LSADC_SEN) + ; + + //setup spi config args + spi_config_t config; + config.mosi_edge = spi_config_t::EDGE_FALL; + config.miso_edge = spi_config_t::EDGE_RISE; + + //setup the spi registers + ad7922_regs_t ad7922_regs; + switch(which){ + case AUX_ADC_A: ad7922_regs.mod = 0; break; + case AUX_ADC_B: ad7922_regs.mod = 1; break; + } ad7922_regs.chn = ad7922_regs.mod; //normal mode: mod == chn + + //write and read spi + _config.spi->write_spi( + unit_to_spi_adc[unit], config, + ad7922_regs.get_reg(), 16 + ); + ad7922_regs.set_reg(boost::uint16_t(_config.spi->read_spi( + unit_to_spi_adc[unit], config, + ad7922_regs.get_reg(), 16 + ))); + + //convert to voltage and return + return 3.3*ad7922_regs.result/4095; +} diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h new file mode 100644 index 000000000..632391644 --- /dev/null +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -0,0 +1,125 @@ +// +// 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_X300_FW_COMMON_H +#define INCLUDED_X300_FW_COMMON_H + +#include <stdint.h> + +/*! + * Structs and constants for x300 communication. + * 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 X300_FW_COMPAT_MAJOR 3 +#define X300_FW_COMPAT_MINOR 0 +#define X300_FPGA_COMPAT_MAJOR 4 + +//shared memory sections - in between the stack and the program space +#define X300_FW_SHMEM_BASE 0x6000 +#define X300_FW_SHMEM_COMPAT_NUM 0 +#define X300_FW_SHMEM_GPSDO_STATUS 1 +#define X300_FW_SHMEM_UART_RX_INDEX 2 +#define X300_FW_SHMEM_UART_TX_INDEX 3 +#define X300_FW_SHMEM_CLAIM_STATUS 5 +#define X300_FW_SHMEM_CLAIM_TIME 6 +#define X300_FW_SHMEM_CLAIM_SRC 7 +#define X300_FW_SHMEM_UART_RX_ADDR 8 +#define X300_FW_SHMEM_UART_TX_ADDR 9 +#define X300_FW_SHMEM_UART_WORDS32 10 +#define X300_FW_SHMEM_ROUTE_MAP_ADDR 11 +#define X300_FW_SHMEM_ROUTE_MAP_LEN 12 + +#define X300_FW_NUM_BYTES (1 << 15) //64k +#define X300_FW_COMMS_MTU (1 << 13) //8k +#define X300_FW_COMMS_UDP_PORT 49152 + +#define X300_VITA_UDP_PORT 49153 +#define X300_GPSDO_UDP_PORT 49156 +#define X300_FPGA_PROG_UDP_PORT 49157 +#define X300_MTU_DETECT_UDP_PORT 49158 + +#define X300_DEFAULT_MAC_ADDR_0 {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff} +#define X300_DEFAULT_MAC_ADDR_1 {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33} + +#define X300_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0) + +#define X300_DEFAULT_IP_ETH0_1G (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0) +#define X300_DEFAULT_IP_ETH1_1G (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0) +#define X300_DEFAULT_IP_ETH0_10G (192 << 24 | 168 << 16 | 30 << 8 | 2 << 0) +#define X300_DEFAULT_IP_ETH1_10G (192 << 24 | 168 << 16 | 40 << 8 | 2 << 0) + +#define X300_DEFAULT_NETMASK_ETH0_1G (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define X300_DEFAULT_NETMASK_ETH1_1G (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define X300_DEFAULT_NETMASK_ETH0_10G (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define X300_DEFAULT_NETMASK_ETH1_10G (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) + +#define X300_FW_COMMS_FLAGS_ACK (1 << 0) +#define X300_FW_COMMS_FLAGS_ERROR (1 << 1) +#define X300_FW_COMMS_FLAGS_POKE32 (1 << 2) +#define X300_FW_COMMS_FLAGS_PEEK32 (1 << 3) + +#define X300_FPGA_PROG_FLAGS_ACK (1 << 0) +#define X300_FPGA_PROG_FLAGS_ERROR (1 << 1) +#define X300_FPGA_PROG_FLAGS_INIT (1 << 2) +#define X300_FPGA_PROG_FLAGS_CLEANUP (1 << 3) +#define X300_FPGA_PROG_FLAGS_ERASE (1 << 4) +#define X300_FPGA_PROG_FLAGS_VERIFY (1 << 5) +#define X300_FPGA_PROG_CONFIGURE (1 << 6) +#define X300_FPGA_PROG_CONFIG_STATUS (1 << 7) + +#define X300_MTU_DETECT_ECHO_REQUEST (1 << 0) +#define X300_MTU_DETECT_ECHO_REPLY (1 << 1) +#define X300_MTU_DETECT_ERROR (1 << 2) + +typedef struct +{ + uint32_t flags; + uint32_t sequence; + uint32_t addr; + uint32_t data; +} x300_fw_comms_t; + +typedef struct +{ + uint32_t flags; + uint32_t sector; + uint32_t index; + uint32_t size; + uint16_t data[128]; +} x300_fpga_prog_t; + +typedef struct +{ + uint32_t flags; +} x300_fpga_prog_flags_t; + +typedef struct +{ + uint32_t flags; + uint32_t size; +} x300_mtu_t; + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_X300_FW_COMMON_H */ diff --git a/host/lib/usrp/x300/x300_fw_ctrl.cpp b/host/lib/usrp/x300/x300_fw_ctrl.cpp new file mode 100644 index 000000000..67c314d3f --- /dev/null +++ b/host/lib/usrp/x300/x300_fw_ctrl.cpp @@ -0,0 +1,300 @@ +// +// 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 <uhd/types/wb_iface.hpp> +#include "x300_fw_common.h" +#include <uhd/transport/udp_simple.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/thread/mutex.hpp> +#include <uhd/transport/nirio/status.h> +#include <uhd/transport/nirio/niriok_proxy.h> +#include "x300_regs.hpp" +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; +using namespace uhd::niusrprio; + +class x300_ctrl_iface : public wb_iface +{ +public: + enum {num_retries = 3}; + + void flush(void) + { + boost::mutex::scoped_lock lock(reg_access); + __flush(); + } + + void poke32(const wb_addr_type addr, const boost::uint32_t data) + { + for (size_t i = 1; i <= num_retries; i++) + { + boost::mutex::scoped_lock lock(reg_access); + try + { + return this->__poke32(addr, data); + } + catch(const std::exception &ex) + { + const 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 (i == num_retries) throw uhd::io_error(error_msg); + } + } + } + + boost::uint32_t peek32(const wb_addr_type addr) + { + for (size_t i = 1; i <= num_retries; i++) + { + boost::mutex::scoped_lock lock(reg_access); + try + { + boost::uint32_t data = this->__peek32(addr); + return data; + } + catch(const std::exception &ex) + { + const 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 (i == num_retries) throw uhd::io_error(error_msg); + } + } + return 0; + } + +protected: + 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; + + boost::mutex reg_access; +}; + + +//----------------------------------------------------- +// Ethernet impl +//----------------------------------------------------- +class x300_ctrl_iface_enet : public x300_ctrl_iface +{ +public: + x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp): + udp(udp), seq(0) + { + try + { + this->peek32(0); + } + catch(...){} + } + +protected: + virtual void __poke32(const wb_addr_type addr, const boost::uint32_t data) + { + //load request struct + x300_fw_comms_t request = x300_fw_comms_t(); + request.flags = uhd::htonx<boost::uint32_t>(X300_FW_COMMS_FLAGS_ACK | X300_FW_COMMS_FLAGS_POKE32); + request.sequence = uhd::htonx<boost::uint32_t>(seq++); + request.addr = uhd::htonx(addr); + request.data = uhd::htonx(data); + + //send request + __flush(); + udp->send(boost::asio::buffer(&request, sizeof(request))); + + //recv reply + x300_fw_comms_t reply = x300_fw_comms_t(); + const size_t nbytes = udp->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("x300 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 & X300_FW_COMMS_FLAGS_ERROR)); + UHD_ASSERT_THROW(flags & X300_FW_COMMS_FLAGS_POKE32); + UHD_ASSERT_THROW(flags & X300_FW_COMMS_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + UHD_ASSERT_THROW(reply.data == request.data); + } + + virtual boost::uint32_t __peek32(const wb_addr_type addr) + { + //load request struct + x300_fw_comms_t request = x300_fw_comms_t(); + request.flags = uhd::htonx<boost::uint32_t>(X300_FW_COMMS_FLAGS_ACK | X300_FW_COMMS_FLAGS_PEEK32); + request.sequence = uhd::htonx<boost::uint32_t>(seq++); + request.addr = uhd::htonx(addr); + request.data = 0; + + //send request + __flush(); + udp->send(boost::asio::buffer(&request, sizeof(request))); + + //recv reply + x300_fw_comms_t reply = x300_fw_comms_t(); + const size_t nbytes = udp->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("x300 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 & X300_FW_COMMS_FLAGS_ERROR)); + UHD_ASSERT_THROW(flags & X300_FW_COMMS_FLAGS_PEEK32); + UHD_ASSERT_THROW(flags & X300_FW_COMMS_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); + } + + virtual void __flush(void) + { + char buff[X300_FW_COMMS_MTU] = {}; + while (udp->recv(boost::asio::buffer(buff), 0.0)){} //flush + } + +private: + uhd::transport::udp_simple::sptr udp; + size_t seq; +}; + + +//----------------------------------------------------- +// PCIe impl +//----------------------------------------------------- +class x300_ctrl_iface_pcie : public x300_ctrl_iface +{ +public: + x300_ctrl_iface_pcie(niriok_proxy& drv_proxy): + _drv_proxy(drv_proxy) + { + nirio_status status = 0; + nirio_status_chain(_drv_proxy.set_attribute(ADDRESS_SPACE, BUS_INTERFACE), status); + + //Verify that the Ettus FPGA loaded in the device. This may not be true if the + //user is switching to UHD after using LabVIEW FPGA. + boost::uint32_t pcie_fpga_signature = 0; + _drv_proxy.peek(FPGA_PCIE_SIG_REG, pcie_fpga_signature); + if (pcie_fpga_signature != FPGA_X3xx_SIG_VALUE) + throw uhd::io_error("cannot create x300_ctrl_iface_pcie. incorrect/no fpga image"); + + //Also, poll on the ZPU_STATUS bit to ensure all the state machines in the FPGA are + //ready to accept register transaction requests. + boost::uint32_t reg_data = 0xffffffff; + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + do { + boost::this_thread::sleep(boost::posix_time::microsec(500)); //Avoid flooding the bus + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + nirio_status_chain(_drv_proxy.peek(PCIE_ZPU_STATUS_REG(0), reg_data), status); + } while ( + nirio_status_not_fatal(status) && + (reg_data & PCIE_ZPU_STATUS_SUSPENDED) && + elapsed.total_milliseconds() < INIT_TIMEOUT_IN_MS); + + nirio_status_to_exception(status, "Could not initialize x300_ctrl_iface_pcie."); + + try + { + this->peek32(0); + } + catch(...){} + } + +protected: + virtual void __poke32(const wb_addr_type addr, const boost::uint32_t data) + { + nirio_status status = 0; + boost::uint32_t reg_data = 0xffffffff; + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + nirio_status_chain(_drv_proxy.poke(PCIE_ZPU_DATA_REG(addr), data), status); + if (nirio_status_not_fatal(status)) { + do { + boost::this_thread::sleep(boost::posix_time::microsec(50)); //Avoid flooding the bus + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + nirio_status_chain(_drv_proxy.peek(PCIE_ZPU_STATUS_REG(addr), reg_data), status); + } while ( + nirio_status_not_fatal(status) && + ((reg_data & (PCIE_ZPU_STATUS_BUSY | PCIE_ZPU_STATUS_SUSPENDED)) != 0) && + elapsed.total_milliseconds() < READ_TIMEOUT_IN_MS); + } + + if (nirio_status_fatal(status)) + throw uhd::io_error("x300 fw poke32 - hardware IO error"); + if (elapsed.total_milliseconds() > READ_TIMEOUT_IN_MS) + throw uhd::io_error("x300 fw poke32 - operation timed out"); + } + + virtual boost::uint32_t __peek32(const wb_addr_type addr) + { + nirio_status status = 0; + boost::uint32_t reg_data = 0xffffffff; + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + nirio_status_chain(_drv_proxy.poke(PCIE_ZPU_READ_REG(addr), PCIE_ZPU_READ_START), status); + if (nirio_status_not_fatal(status)) { + do { + boost::this_thread::sleep(boost::posix_time::microsec(50)); //Avoid flooding the bus + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + nirio_status_chain(_drv_proxy.peek(PCIE_ZPU_STATUS_REG(addr), reg_data), status); + } while ( + nirio_status_not_fatal(status) && + ((reg_data & (PCIE_ZPU_STATUS_BUSY | PCIE_ZPU_STATUS_SUSPENDED)) != 0) && + elapsed.total_milliseconds() < READ_TIMEOUT_IN_MS); + } + nirio_status_chain(_drv_proxy.peek(PCIE_ZPU_DATA_REG(addr), reg_data), status); + + if (nirio_status_fatal(status)) + throw uhd::io_error("x300 fw peek32 - hardware IO error"); + if (elapsed.total_milliseconds() > READ_TIMEOUT_IN_MS) + throw uhd::io_error("x300 fw peek32 - operation timed out"); + + return reg_data; + } + + virtual void __flush(void) + { + __peek32(0); + } + +private: + niriok_proxy& _drv_proxy; + static const uint32_t READ_TIMEOUT_IN_MS = 10; + static const uint32_t INIT_TIMEOUT_IN_MS = 5000; +}; + +wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp) +{ + return wb_iface::sptr(new x300_ctrl_iface_enet(udp)); +} + +wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy& drv_proxy) +{ + return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy)); +} diff --git a/host/lib/usrp/x300/x300_fw_uart.cpp b/host/lib/usrp/x300/x300_fw_uart.cpp new file mode 100644 index 000000000..943b2d9fa --- /dev/null +++ b/host/lib/usrp/x300/x300_fw_uart.cpp @@ -0,0 +1,104 @@ +// +// 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 "x300_impl.hpp" +#include <uhd/types/wb_iface.hpp> +#include "x300_regs.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/types/serial.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; + +struct x300_uart_iface : uart_iface +{ + x300_uart_iface(wb_iface::sptr iface): + rxoffset(0), txoffset(0), txword32(0), rxpool(0), txpool(0), poolsize(0) + { + _iface = iface; + rxoffset = _iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_RX_INDEX)); + txoffset = _iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_TX_INDEX)); + rxpool = _iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_RX_ADDR)); + txpool = _iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_TX_ADDR)); + poolsize = _iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_WORDS32)); + //this->write_uart("HELLO UART\n"); + //this->read_uart(0.1); + } + + void putchar(const char ch) + { + txoffset = (txoffset + 1) % (poolsize*4); + const int shift = ((txoffset%4) * 8); + if (shift == 0) txword32 = 0; + txword32 |= boost::uint32_t(ch) << shift; + _iface->poke32(SR_ADDR(txpool, txoffset/4), txword32); + _iface->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_TX_INDEX), txoffset); + } + + void write_uart(const std::string &buff) + { + BOOST_FOREACH(const char ch, buff) + { + if (ch == '\n') this->putchar('\r'); + this->putchar(ch); + } + } + + int getchar(void) + { + if (_iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_UART_RX_INDEX)) != rxoffset) + { + const int shift = ((rxoffset%4) * 8); + const char ch = _iface->peek32(SR_ADDR(rxpool, rxoffset/4)) >> shift; + rxoffset = (rxoffset + 1) % (poolsize*4); + return ch; + } + return -1; + } + + std::string read_uart(double timeout) + { + const boost::system_time exit_time = boost::get_system_time() + boost::posix_time::microseconds(long(timeout*1e6)); + std::string buff; + while (true) + { + const int ch = this->getchar(); + if (ch == -1) + { + if (boost::get_system_time() > exit_time) break; + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + continue; + } + if (ch == '\r') continue; + buff += std::string(1, (char)ch); + if (ch == '\n') break; + } + //UHD_VAR(buff); + return buff; + } + + wb_iface::sptr _iface; + boost::uint32_t rxoffset, txoffset, txword32, rxpool, txpool, poolsize; +}; + +uart_iface::sptr x300_make_uart_iface(wb_iface::sptr iface) +{ + return uart_iface::sptr(new x300_uart_iface(iface)); +} diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp new file mode 100644 index 000000000..e492b2238 --- /dev/null +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -0,0 +1,1661 @@ +// +// 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 "x300_impl.hpp" +#include "x300_regs.hpp" +#include "x300_lvbitx.hpp" +#include "x310_lvbitx.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/images.hpp> +#include <uhd/utils/safe_call.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <boost/foreach.hpp> +#include <boost/bind.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/nirio_zero_copy.hpp> +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/utils/platform.hpp> + +#define NIUSRPRIO_DEFAULT_RPC_PORT "5444" + +#define X300_REV(x) (x - "A" + 1) + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; +using namespace uhd::niusrprio; +namespace asio = boost::asio; + +/*********************************************************************** + * 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) +{ + udp_simple::sptr comm = udp_simple::make_broadcast( + hint["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)); + + //load request struct + x300_fw_comms_t request = x300_fw_comms_t(); + request.flags = uhd::htonx<boost::uint32_t>(X300_FW_COMMS_FLAGS_ACK); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + + //send request + comm->send(asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + device_addrs_t addrs; + while (true) + { + char buff[X300_FW_COMMS_MTU] = {}; + const size_t nbytes = comm->recv(asio::buffer(buff), 0.050); + if (nbytes == 0) break; + const x300_fw_comms_t *reply = (const x300_fw_comms_t *)buff; + if (request.flags != reply->flags) break; + if (request.sequence != reply->sequence) break; + device_addr_t new_addr; + new_addr["type"] = "x300"; + new_addr["addr"] = comm->get_recv_addr(); + + //Attempt to read the name from the EEPROM and perform filtering. + //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))); + if (x300_impl::is_claimed(zpu_ctrl)) continue; //claimed by another process + new_addr["fpga"] = get_fpga_option(zpu_ctrl); + + i2c_core_100_wb32::sptr zpu_i2c = i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); + i2c_iface::sptr eeprom16 = zpu_i2c->eeprom16(); + const mboard_eeprom_t mb_eeprom(*eeprom16, "X300"); + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_eeprom["serial"]; + switch (x300_impl::get_mb_type_from_eeprom(mb_eeprom)) { + case x300_impl::USRP_X300_MB: + new_addr["product"] = "X300"; + break; + case x300_impl::USRP_X310_MB: + new_addr["product"] = "X310"; + break; + default: + break; + } + } + 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"]) and + (not hint.has_key("product") or hint["product"] == new_addr["product"]) + ){ + addrs.push_back(new_addr); + } + } + + return addrs; +} + +static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_query) +{ + std::string rpc_port_name(NIUSRPRIO_DEFAULT_RPC_PORT); + if (hint.has_key("niusrpriorpc_port")) { + rpc_port_name = hint["niusrpriorpc_port"]; + } + + device_addrs_t addrs; + niusrprio_session::device_info_vtr dev_info_vtr; + nirio_status status = niusrprio_session::enumerate(rpc_port_name, dev_info_vtr); + if (explicit_query) nirio_status_to_exception(status, "x300_find_pcie: Error enumerating NI-RIO devices."); + + BOOST_FOREACH(niusrprio_session::device_info &dev_info, dev_info_vtr) + { + device_addr_t new_addr; + new_addr["type"] = "x300"; + new_addr["resource"] = dev_info.resource_name; + std::string resource_d(dev_info.resource_name); + boost::to_upper(resource_d); + + switch (x300_impl::get_mb_type_from_pcie(resource_d, rpc_port_name)) { + case x300_impl::USRP_X300_MB: + new_addr["product"] = "X300"; + break; + case x300_impl::USRP_X310_MB: + new_addr["product"] = "X310"; + break; + default: + continue; + } + + niriok_proxy kernel_proxy; + kernel_proxy.open(dev_info.interface_path); + + //Attempt to read the name from the EEPROM and perform filtering. + //This operation can throw due to compatibility mismatch. + try + { + //This call could throw an exception if the user is switching to using UHD + //after LabVIEW FPGA. In that case, skip reading the name and serial and pick + //a default FPGA flavor. During make, a new image will be loaded and everything + //will be OK + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy); + if (x300_impl::is_claimed(zpu_ctrl)) continue; //claimed by another process + + //Attempt to autodetect the FPGA type + if (not hint.has_key("fpga")) { + new_addr["fpga"] = get_fpga_option(zpu_ctrl); + } + + i2c_core_100_wb32::sptr zpu_i2c = i2c_core_100_wb32::make(zpu_ctrl, I2C1_BASE); + i2c_iface::sptr eeprom16 = zpu_i2c->eeprom16(); + const mboard_eeprom_t mb_eeprom(*eeprom16, "X300"); + new_addr["name"] = mb_eeprom["name"]; + new_addr["serial"] = mb_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 + if (not hint.has_key("fpga")) { + new_addr["fpga"] = "HGS"; + } + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + kernel_proxy.close(); + + //filter the discovered device below by matching optional keys + std::string resource_i = hint.has_key("resource") ? hint["resource"] : ""; + boost::to_upper(resource_i); + + if ( + (not hint.has_key("resource") or resource_i == resource_d) and + (not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]) and + (not hint.has_key("product") or hint["product"] == new_addr["product"]) + ){ + addrs.push_back(new_addr); + } + } + return addrs; +} + +static device_addrs_t x300_find(const device_addr_t &hint_) +{ + //handle the multi-device discovery + device_addrs_t hints = separate_device_addr(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 = x300_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 addrs; + if (hint.has_key("type") and hint["type"] != "x300") return addrs; + + + //use the address given + if (hint.has_key("addr")) + { + device_addrs_t reply_addrs; + try + { + reply_addrs = x300_find_with_addr(hint); + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "X300 Network discovery error " << ex.what() << std::endl; + } + catch(...) + { + UHD_MSG(error) << "X300 Network discovery unknown error " << std::endl; + } + BOOST_FOREACH(const device_addr_t &reply_addr, reply_addrs) + { + device_addrs_t new_addrs = x300_find_with_addr(reply_addr); + addrs.insert(addrs.begin(), new_addrs.begin(), new_addrs.end()); + } + return addrs; + } + + if (!hint.has_key("resource")) + { + //otherwise, no address was specified, send a broadcast on each interface + 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_addrs = x300_find(new_hint); + addrs.insert(addrs.begin(), new_addrs.begin(), new_addrs.end()); + } + } + + device_addrs_t pcie_addrs = x300_find_pcie(hint, hint.has_key("resource")); + if (not pcie_addrs.empty()) addrs.insert(addrs.end(), pcie_addrs.begin(), pcie_addrs.end()); + + return addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +static device::sptr x300_make(const device_addr_t &device_addr) +{ + return device::sptr(new x300_impl(device_addr)); +} + +UHD_STATIC_BLOCK(register_x300_device) +{ + device::register_device(&x300_find, &x300_make); +} + +static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_name) +{ + UHD_MSG(status) << "Loading firmware " << file_name << std::flush; + + //load file into memory + std::ifstream fw_file(file_name.c_str()); + boost::uint32_t fw_file_buff[X300_FW_NUM_BYTES/sizeof(boost::uint32_t)]; + fw_file.read((char *)fw_file_buff, sizeof(fw_file_buff)); + fw_file.close(); + + //Poke the fw words into the WB boot loader + fw_reg_ctrl->poke32(SR_ADDR(BOOT_LDR_BASE, BL_ADDRESS), 0); + for (size_t i = 0; i < X300_FW_NUM_BYTES; i+=sizeof(boost::uint32_t)) + { + //@TODO: FIXME: Since x300_ctrl_iface acks each write and traps exceptions, the first try for the last word + // written will print an error because it triggers a FW reload and fails to reply. + fw_reg_ctrl->poke32(SR_ADDR(BOOT_LDR_BASE, BL_DATA), uhd::byteswap(fw_file_buff[i/sizeof(boost::uint32_t)])); + if ((i & 0x1fff) == 0) UHD_MSG(status) << "." << std::flush; + } + + UHD_MSG(status) << " done!" << std::endl; +} + +x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) +{ + UHD_MSG(status) << "X300 initialization sequence..." << std::endl; + _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()); + for (size_t i = 0; i < device_args.size(); i++) + { + this->setup_mb(i, device_args[i]); + } +} + +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.addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; + mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth"; + mb.if_pkt_is_big_endian = mb.xport_path != "nirio"; + + if (mb.xport_path == "nirio") + { + nirio_status status = 0; + + std::string rpc_port_name(NIUSRPRIO_DEFAULT_RPC_PORT); + if (dev_addr.has_key("niusrpriorpc_port")) { + rpc_port_name = dev_addr["niusrpriorpc_port"]; + } + UHD_MSG(status) << boost::format("Connecting to niusrpriorpc at localhost:%s...\n") % rpc_port_name; + + //Instantiate the correct lvbitx object + nifpga_lvbitx::sptr lvbitx; + switch (get_mb_type_from_pcie(dev_addr["resource"], rpc_port_name)) { + case USRP_X300_MB: + lvbitx.reset(new x300_lvbitx(dev_addr["fpga"])); + break; + case USRP_X310_MB: + lvbitx.reset(new x310_lvbitx(dev_addr["fpga"])); + break; + default: + nirio_status_to_exception(status, "Motherboard detection error. Please ensure that you \ + have a valid USRP X3x0, NI USRP-294xR or NI USRP-295xR device and that all the device \ + driver have been loaded."); + } + + //Load the lvbitx onto the device + UHD_MSG(status) << boost::format("Using LVBITX bitfile %s...\n") % lvbitx->get_bitfile_path(); + mb.rio_fpga_interface.reset(new niusrprio_session(dev_addr["resource"], rpc_port_name)); + nirio_status_chain(mb.rio_fpga_interface->open(lvbitx, dev_addr.has_key("download-fpga")), status); + nirio_status_to_exception(status, "x300_impl: Could not initialize RIO session."); + + //Tell the quirks object which FIFOs carry TX stream data + const 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); + } + + BOOST_FOREACH(const std::string &key, dev_addr.keys()) + { + if (key.find("recv") != std::string::npos) mb.recv_args[key] = dev_addr[key]; + if (key.find("send") != std::string::npos) mb.send_args[key] = dev_addr[key]; + } + + if (mb.xport_path == "eth" ) { + /* This is an ETH connection. Figure out what the maximum supported frame + * size is for the transport in the up and down directions. The frame size + * depends on the host PIC's NIC's MTU settings. To determine the frame size, + * we test for support up to an expected "ceiling". If the user + * specified a frame size, we use that frame size as the ceiling. If no + * frame size was specified, we use the maximum UHD frame size. + * + * To optimize performance, the frame size should be greater than or equal + * to the frame size that UHD uses so that frames don't get split across + * multiple transmission units - this is why the limits passed into the + * 'determine_max_frame_size' function are actually frame sizes. */ + frame_size_t req_max_frame_size; + req_max_frame_size.recv_frame_size = (mb.recv_args.has_key("recv_frame_size")) \ + ? boost::lexical_cast<size_t>(mb.recv_args["recv_frame_size"]) \ + : X300_10GE_DATA_FRAME_MAX_SIZE; + req_max_frame_size.send_frame_size = (mb.send_args.has_key("send_frame_size")) \ + ? boost::lexical_cast<size_t>(mb.send_args["send_frame_size"]) \ + : X300_10GE_DATA_FRAME_MAX_SIZE; + + #if defined UHD_PLATFORM_LINUX + const std::string mtu_tool("ip link"); + #elif defined UHD_PLATFORM_WIN32 + const std::string mtu_tool("netsh"); + #else + const std::string mtu_tool("ifconfig"); + #endif + + // Detect the frame size on the path to the USRP + try { + _max_frame_sizes = determine_max_frame_size(mb.addr, req_max_frame_size); + } catch(std::exception &e) { + UHD_MSG(error) << e.what() << std::endl; + } + + if ((mb.recv_args.has_key("recv_frame_size")) + && (req_max_frame_size.recv_frame_size < _max_frame_sizes.recv_frame_size)) { + UHD_MSG(warning) + << boost::format("You requested a receive frame size of (%lu) but your NIC's max frame size is (%lu).") + % req_max_frame_size.recv_frame_size << _max_frame_sizes.recv_frame_size << std::endl + << boost::format("Please verify your NIC's MTU setting using '%s' or set the recv_frame_size argument appropriately.") + % mtu_tool << std::endl + << "UHD will use the auto-detected max frame size for this connection." + << std::endl; + } + + if ((mb.recv_args.has_key("send_frame_size")) + && (req_max_frame_size.send_frame_size < _max_frame_sizes.send_frame_size)) { + UHD_MSG(warning) + << boost::format("You requested a send frame size of (%lu) but your NIC's max frame size is (%lu).") + % req_max_frame_size.send_frame_size << _max_frame_sizes.send_frame_size << std::endl + << boost::format("Please verify your NIC's MTU setting using '%s' or set the send_frame_size argument appropriately.") + % mtu_tool << std::endl + << "UHD will use the auto-detected max frame size for this connection." + << std::endl; + } + } + + //create basic communication + UHD_MSG(status) << "Setup basic communication..." << std::endl; + if (mb.xport_path == "nirio") { + mb.zpu_ctrl = x300_make_ctrl_iface_pcie(mb.rio_fpga_interface->get_kernel_proxy()); + } else { + mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(mb.addr, + BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + } + + mb.claimer_task = uhd::task::make(boost::bind(&x300_impl::claimer_loop, this, mb.zpu_ctrl)); + + //extract the FW path for the X300 + //and live load fw over ethernet link + if (dev_addr.has_key("fw")) + { + const std::string x300_fw_image = find_image_path( + dev_addr.has_key("fw")? dev_addr["fw"] : X300_FW_FILE_NAME + ); + x300_load_fw(mb.zpu_ctrl, x300_fw_image); + } + + //check compat -- good place to do after conditional loading + this->check_fw_compat(mb_path, mb.zpu_ctrl); + this->check_fpga_compat(mb_path, mb.zpu_ctrl); + + //store which FPGA image is loaded + mb.loaded_fpga_image = get_fpga_option(mb.zpu_ctrl); + + //low speed perif access + mb.zpu_spi = spi_core_3000::make(mb.zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_SPI), + SR_ADDR(SET0_BASE, ZPU_RB_SPI)); + mb.zpu_i2c = i2c_core_100_wb32::make(mb.zpu_ctrl, I2C1_BASE); + mb.zpu_i2c->set_clock_rate(X300_BUS_CLOCK_RATE); + + //////////////////////////////////////////////////////////////////// + // print network routes mapping + //////////////////////////////////////////////////////////////////// + /* + const uint32_t routes_addr = mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_ROUTE_MAP_ADDR)); + const uint32_t routes_len = mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_ROUTE_MAP_LEN)); + UHD_VAR(routes_len); + for (size_t i = 0; i < routes_len; i+=1) + { + const uint32_t node_addr = mb.zpu_ctrl->peek32(SR_ADDR(routes_addr, i*2+0)); + const uint32_t nbor_addr = mb.zpu_ctrl->peek32(SR_ADDR(routes_addr, i*2+1)); + if (node_addr != 0 and nbor_addr != 0) + { + UHD_MSG(status) << boost::format("%u: %s -> %s") + % i + % asio::ip::address_v4(node_addr).to_string() + % asio::ip::address_v4(nbor_addr).to_string() + << std::endl; + } + } + */ + + //////////////////////////////////////////////////////////////////// + // setup the mboard eeprom + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Loading values from EEPROM..." << std::endl; + i2c_iface::sptr eeprom16 = mb.zpu_i2c->eeprom16(); + if (dev_addr.has_key("blank_eeprom")) + { + UHD_MSG(warning) << "Obliterating the motherboard EEPROM..." << std::endl; + eeprom16->write_eeprom(0x50, 0, byte_vector_t(256, 0xff)); + } + 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)); + + //////////////////////////////////////////////////////////////////// + // parse the product number + //////////////////////////////////////////////////////////////////// + std::string product_name = "X300?"; + switch (get_mb_type_from_eeprom(mb_eeprom)) { + case USRP_X300_MB: + product_name = "X300"; + break; + case USRP_X310_MB: + product_name = "X310"; + break; + default: + break; + } + _tree->create<std::string>(mb_path / "name").set(product_name); + _tree->create<std::string>(mb_path / "codename").set("Yetti"); + + //////////////////////////////////////////////////////////////////// + // 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); + } + + //////////////////////////////////////////////////////////////////// + // create clock control objects + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Setup RF frontend clocking..." << std::endl; + + mb.hw_rev = 0; + if(mb_eeprom.has_key("revision") and not mb_eeprom["revision"].empty()) { + try { + mb.hw_rev = boost::lexical_cast<size_t>(mb_eeprom["revision"]); + } catch(...) { + UHD_MSG(warning) << "Revision in EEPROM is invalid! Please reprogram your EEPROM." << std::endl; + } + } else { + UHD_MSG(warning) << "No revision detected MB EEPROM must be reprogrammed!" << std::endl; + } + + if(mb.hw_rev == 0) { + UHD_MSG(warning) << "Defaulting to X300 RevD Clock Settings. This will result in non-optimal lock times." << std::endl; + mb.hw_rev = X300_REV("D"); + } + + //Initialize clock control with internal references and GPSDO power on. + mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL; + mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL; + mb.clock_control_regs_pps_out_enb = 0; + mb.clock_control_regs_tcxo_enb = 1; + mb.clock_control_regs_gpsdo_pwr = 1; + this->update_clock_control(mb); + + //Create clock control + mb.clock = x300_clock_ctrl::make(mb.zpu_spi, + 1 /*slaveno*/, + mb.hw_rev, + dev_addr.cast<double>("master_clock_rate", X300_DEFAULT_TICK_RATE), + dev_addr.cast<double>("system_ref_rate", X300_DEFAULT_SYSREF_RATE)); + + //wait for reference clock to lock + if(mb.hw_rev > 4) + { + try { + //FIXME: Need to verify timeout value to make sure lock can be achieved in < 1.0 seconds + wait_for_ref_locked(mb.zpu_ctrl, 1.0); + } catch (uhd::runtime_error &e) { + //Silently fail for now, but fix after we have the correct timeout value + //UHD_MSG(warning) << "Clock failed to lock to internal source during initialization." << std::endl; + } + } + + //////////////////////////////////////////////////////////////////// + // 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"); + + UHD_MSG(status) << "Radio 1x clock:" << (mb.clock->get_master_clock_rate()/1e6) + << std::endl; + + //////////////////////////////////////////////////////////////////// + // Create the GPSDO control + //////////////////////////////////////////////////////////////////// + static const boost::uint32_t dont_look_for_gpsdo = 0x1234abcdul; + + //otherwise if not disabled, look for the internal GPSDO + if (mb.zpu_ctrl->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS)) != dont_look_for_gpsdo) + { + UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush; + try + { + mb.gps = gps_ctrl::make(x300_make_uart_iface(mb.zpu_ctrl)); + } + catch(std::exception &e) + { + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (mb.gps and mb.gps->gps_detected()) + { + 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)); + } + } + else + { + mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_GPSDO_STATUS), dont_look_for_gpsdo); + } + } + + //////////////////////////////////////////////////////////////////// + //clear router? + //////////////////////////////////////////////////////////////////// + for (size_t i = 0; i < 512; i++) { + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, i), 0); + } + + //////////////////////////////////////////////////////////////////// + // setup radios + //////////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Initialize Radio control..." << std::endl; + this->setup_radio(mb_i, "A"); + this->setup_radio(mb_i, "B"); + + //////////////////////////////////////////////////////////////////// + // front panel gpio + //////////////////////////////////////////////////////////////////// + mb.fp_gpio = gpio_core_200::make(mb.radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); + const std::vector<std::string> GPIO_ATTRS = boost::assign::list_of("CTRL")("DDR")("OUT")("ATR_0X")("ATR_RX")("ATR_TX")("ATR_XX"); + BOOST_FOREACH(const std::string &attr, GPIO_ATTRS) + { + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr) + .set(0) + .subscribe(boost::bind(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr, _1)); + } + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") + .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio, "READBACK")); + + //////////////////////////////////////////////////////////////////// + // 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(&time_core_3000::set_time_now, mb.radio_perifs[0].time64, _1)) + .subscribe(boost::bind(&time_core_3000::set_time_now, mb.radio_perifs[1].time64, _1)); + _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") + .subscribe(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)) + .set(true); + + //////////////////////////////////////////////////////////////////// + // setup clock sources and properties + //////////////////////////////////////////////////////////////////// + _tree->create<std::string>(mb_path / "clock_source" / "value") + .subscribe(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); + + //setup external reference options. default to 10 MHz input reference + _tree->create<std::string>(mb_path / "clock_source" / "external"); + static const std::vector<double> external_freq_options = boost::assign::list_of(10e6)(30.72e6)(200e6); + _tree->create<std::vector<double> >(mb_path / "clock_source" / "external" / "freq" / "options") + .set(external_freq_options); + _tree->create<double>(mb_path / "clock_source" / "external" / "value") + .set(mb.clock->get_sysref_clock_rate()); + // FIXME the external clock source settings need to be more robust + + //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)); + + + //////////////////////////////////////////////////////////////////// + // 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)); + + //////////////////////////////////////////////////////////////////// + // 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.zpu_ctrl)); + + //////////////////////////////////////////////////////////////////// + // create clock properties + //////////////////////////////////////////////////////////////////// + _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()); + + //////////////////////////////////////////////////////////////////// + // 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); + + UHD_MSG(status) << "Initializing clock and PPS references..." << std::endl; + try { + //First, try external source + _tree->access<std::string>(mb_path / "clock_source" / "value").set("external"); + wait_for_ref_locked(mb.zpu_ctrl, 1.0); + _tree->access<std::string>(mb_path / "time_source" / "value").set("external"); + UHD_MSG(status) << "References initialized to external sources" << std::endl; + } catch (uhd::exception::runtime_error &e) { + //No external source detected - set to the GPSDO if installed + if (mb.gps and mb.gps->gps_detected()) + { + _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo"); + try { + wait_for_ref_locked(mb.zpu_ctrl, 1.0); + } catch (uhd::exception::runtime_error &e) { + UHD_MSG(warning) << "Clock reference failed to lock to GPSDO during device initialization. " << + "Check for the lock before operation or ignore this warning if using another clock source." << std::endl; + } + _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo"); + UHD_MSG(status) << "References initialized to GPSDO sources" << std::endl; + UHD_MSG(status) << "Initializing time to the GPSDO time" << std::endl; + const time_t tp = time_t(mb.gps->get_sensor("gps_time").to_int()+1); + _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp)); + //wait for time to be set (timeout after 1 second) + for (int i = 0; i < 10 && tp != (_tree->access<time_spec_t>(mb_path / "time" / "pps").get()).get_full_secs(); i++) + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + } else { + _tree->access<std::string>(mb_path / "clock_source" / "value").set("internal"); + try { + wait_for_ref_locked(mb.zpu_ctrl, 1.0); + } catch (uhd::exception::runtime_error &e) { + UHD_MSG(warning) << "Clock reference failed to lock to internal source during device initialization. " << + "Check for the lock before operation or ignore this warning if using another clock source." << std::endl; + } + _tree->access<std::string>(mb_path / "time_source" / "value").set("internal"); + UHD_MSG(status) << "References initialized to internal sources" << std::endl; + } + } +} + +x300_impl::~x300_impl(void) +{ + try + { + BOOST_FOREACH(mboard_members_t &mb, _mb) + { + mb.radio_perifs[0].ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //disable/reset ADC/DAC + mb.radio_perifs[1].ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //disable/reset ADC/DAC + + //kill the claimer task and unclaim the device + mb.claimer_task.reset(); + mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_TIME), 0); + mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC), 0); + } + } + catch(...) + { + UHD_SAFE_CALL(throw;) + } +} + +static void check_adc(wb_iface::sptr iface, const boost::uint32_t val) +{ + boost::uint32_t adc_rb = iface->peek32(RB32_RX); + adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA + //UHD_MSG(status) << "adc_rb " << std::hex << adc_rb << " val " << std::hex << val << std::endl; + UHD_ASSERT_THROW(adc_rb == val); +} + +void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name) +{ + 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]; + + //////////////////////////////////////////////////////////////////// + // radio control + //////////////////////////////////////////////////////////////////// + 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.ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 2)); //reset adc + dac + perif.ctrl->poke32(TOREG(SR_MISC_OUTS), (1 << 1) | (1 << 0)); //out of reset + dac enable + + this->register_loopback_self_test(perif.ctrl); + + perif.spi = spi_core_3000::make(perif.ctrl, TOREG(SR_SPI), 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, TOREG(SR_LEDS)); + + _tree->access<time_spec_t>(mb_path / "time" / "cmd") + .subscribe(boost::bind(&radio_ctrl_core_3000::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)); + + //////////////////////////////////////////////////////////////// + // ADC self test + //////////////////////////////////////////////////////////////// + perif.adc->set_test_word("ones", "ones"); check_adc(perif.ctrl, 0xfffcfffc); + perif.adc->set_test_word("zeros", "zeros"); check_adc(perif.ctrl, 0x00000000); + perif.adc->set_test_word("ones", "zeros"); check_adc(perif.ctrl, 0xfffc0000); + perif.adc->set_test_word("zeros", "ones"); check_adc(perif.ctrl, 0x0000fffc); + for (size_t k = 0; k < 14; k++) + { + perif.adc->set_test_word("zeros", "custom", 1 << k); + check_adc(perif.ctrl, 1 << (k+2)); + } + for (size_t k = 0; k < 14; k++) + { + perif.adc->set_test_word("custom", "zeros", 1 << k); + check_adc(perif.ctrl, 1 << (k+18)); + } + perif.adc->set_test_word("normal", "normal"); + + //////////////////////////////////////////////////////////////// + // Sync DAC's for MIMO + //////////////////////////////////////////////////////////////// + UHD_MSG(status) << "Sync DAC's." << std::endl; + perif.dac->arm_dac_sync(); // Put DAC into data Sync mode + perif.ctrl->poke32(TOREG(SR_DACSYNC), 0x1); // Arm FRAMEP/N sync pulse + + + //////////////////////////////////////////////////////////////// + // 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 = rx_frontend_core_200::make(perif.ctrl, TOREG(SR_RX_FRONT)); + const fs_path rx_fe_path = mb_path / "rx_frontends" / slot_name; + _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, perif.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, perif.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, perif.rx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + + perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, TOREG(SR_TX_FRONT)); + const fs_path tx_fe_path = mb_path / "tx_frontends" / slot_name; + _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") + .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, perif.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, perif.tx_fe, _1)) + .set(std::complex<double>(0.0, 0.0)); + + + + //////////////////////////////////////////////////////////////////// + // 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)); + perif.ddc->set_link_rate(10e9/8); //whatever + _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)); + const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .subscribe(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) + .set(1e6); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(0.0); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //////////////////////////////////////////////////////////////////// + // create tx dsp control objects + //////////////////////////////////////////////////////////////////// + perif.deframer = tx_vita_core_3000::make(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 + _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)); + const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .subscribe(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) + .set(1e6); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(0.0); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //////////////////////////////////////////////////////////////////// + // create time control objects + //////////////////////////////////////////////////////////////////// + time_core_3000::readback_bases_type time64_rb_bases; + time64_rb_bases.rb_now = RB64_TIME_NOW; + time64_rb_bases.rb_pps = RB64_TIME_PPS; + perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases); + + //////////////////////////////////////////////////////////////////// + // 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, TOREG(SR_GPIO), 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; + _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_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)); + } +} + +void x300_impl::set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) +{ + apply_rx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); +} + +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); +} + + +x300_impl::both_xports_t x300_impl::make_transport( + const size_t mb_index, + const uint8_t& destination, + const uint8_t& prefix, + const uhd::device_addr_t& args, + boost::uint32_t& sid +) +{ + 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; + + zero_copy_xport_params default_buff_args; + + if (mb.xport_path == "nirio") { + default_buff_args.send_frame_size = + (prefix == X300_RADIO_DEST_PREFIX_TX) + ? X300_PCIE_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.recv_frame_size = + (prefix == X300_RADIO_DEST_PREFIX_RX) + ? X300_PCIE_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 = xports.recv; + + //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") { + + /* 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) { + eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; + } else if (mb.router_dst_here == X300_XB_DST_E1) { + eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; + } + } else if (mb.loaded_fpga_image == "XGS") { + eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; + } + + if (eth_data_rec_frame_size == 0) { + throw uhd::runtime_error("Unable to determine ETH link type."); + } + + /* Print a warning if the system's max available frame size is less than the most optimal + * frame size for this type of connection. */ + if (_max_frame_sizes.send_frame_size < eth_data_rec_frame_size) { + UHD_MSG(warning) + << boost::format("For this connection, UHD recommends a send frame size of at least %lu for best\nperformance, but your system's MTU will only allow %lu.") + % eth_data_rec_frame_size + % _max_frame_sizes.send_frame_size + << std::endl + << "This will negatively impact your maximum achievable sample rate." + << std::endl; + } + + if (_max_frame_sizes.recv_frame_size < eth_data_rec_frame_size) { + UHD_MSG(warning) + << boost::format("For this connection, UHD recommends a receive frame size of at least %lu for best\nperformance, but your system's MTU will only allow %lu.") + % eth_data_rec_frame_size + % _max_frame_sizes.recv_frame_size + << std::endl + << "This will negatively impact your maximum achievable sample rate." + << std::endl; + } + + size_t system_max_send_frame_size = (size_t) _max_frame_sizes.send_frame_size; + size_t system_max_recv_frame_size = (size_t) _max_frame_sizes.recv_frame_size; + + // 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) + ? 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) + ? 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) + ? X300_ETH_DATA_NUM_FRAMES + : X300_ETH_MSG_NUM_FRAMES; + + default_buff_args.num_recv_frames = + (prefix == X300_RADIO_DEST_PREFIX_RX) + ? 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, + BOOST_STRINGIZE(X300_VITA_UDP_PORT), + default_buff_args, + buff_params, + xport_args); + + xports.send = xports.recv; + + //For the UDP transport the buffer size if the size of the socket buffer + //in the kernel + xports.recv_buff_size = buff_params.recv_buff_size; + xports.send_buff_size = buff_params.send_buff_size; + + //clear the ethernet dispatcher's udp port + //NOT clearing this, the dispatcher is now intelligent + //_zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0+8+3)), 0); + + //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; + //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->commit(8); + buff.reset(); + + //reprogram the ethernet dispatcher's udp port (should be safe to always set) + UHD_LOG << "reprogram the ethernet dispatcher's udp port" << std::endl; + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT0+8+3)), X300_VITA_UDP_PORT); + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, (ZPU_SR_ETHINT1+8+3)), X300_VITA_UDP_PORT); + + //Do a peek to an arbitrary address to guarantee that the + //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; + + // 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); + // 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); + // 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") { + 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); + } + + UHD_LOG << std::hex + << "done router config for sid 0x" << sid + << std::dec << std::endl; + + //increment for next setup + _sid_framer++; + + 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 txrx_led = (1 << 1); + const int tx_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.time64->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 = time(NULL); + for (size_t i = 0; i < 100; i++) + { + boost::hash_combine(hash, i); + iface->poke32(TOREG(SR_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(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::set_time_source_out(mboard_members_t &mb, const bool enb) +{ + mb.clock_control_regs_pps_out_enb = enb? 1 : 0; + this->update_clock_control(mb); +} + +void x300_impl::update_clock_control(mboard_members_t &mb) +{ + const size_t reg = mb.clock_control_regs_clock_source + | (mb.clock_control_regs_pps_select << 2) + | (mb.clock_control_regs_pps_out_enb << 4) + | (mb.clock_control_regs_tcxo_enb << 5) + | (mb.clock_control_regs_gpsdo_pwr << 6) + ; + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_CLOCK_CTRL), reg); +} + +void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &source) +{ + mb.clock_control_regs_clock_source = 0; + mb.clock_control_regs_tcxo_enb = 0; + if (source == "internal") { + mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL; + mb.clock_control_regs_tcxo_enb = 1; + } else if (source == "external") { + mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL; + } else if (source == "gpsdo") { + mb.clock_control_regs_clock_source = ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO; + } else { + throw uhd::key_error("update_clock_source: unknown source: " + source); + } + + this->update_clock_control(mb); + + //reset the clock control + //without this, the lock time is long and can be as much as 30 seconds + mb.clock->reset_clocks(); + + /* FIXME: implement when we know the correct timeouts + * //wait for lock + * double timeout = 1.0; + * try { + * if (mb.hw_rev > 4) + * wait_for_ref_locked(mb.zpu_ctrl, timeout); + * } catch (uhd::runtime_error &e) { + * //failed to lock on reference + * throw uhd::runtime_error((boost::format("Clock failed to lock to %s source.") % source).str()); + * } + */ +} + +void x300_impl::update_time_source(mboard_members_t &mb, const std::string &source) +{ + if (source == "internal") { + mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL; + } else if (source == "external") { + mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_EXTERNAL; + } else if (source == "gpsdo") { + mb.clock_control_regs_pps_select = ZPU_SR_CLOCK_CTRL_PPS_SRC_GPSDO; + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + this->update_clock_control(mb); + + //check for valid pps + if (!is_pps_present(mb.zpu_ctrl)) + { + throw uhd::runtime_error((boost::format("The %d PPS was not detected. Please check the PPS source and try again.") % source).str()); + } +} + +void x300_impl::wait_for_ref_locked(wb_iface::sptr ctrl, double timeout) +{ + boost::system_time timeout_time = boost::get_system_time() + boost::posix_time::milliseconds(timeout * 1000.0); + do + { + if (get_ref_locked(ctrl).to_bool()) + return; + boost::this_thread::sleep(boost::posix_time::milliseconds(1)); + } while (boost::get_system_time() < timeout_time); + + //failed to lock on reference + throw uhd::runtime_error("The reference clock failed to lock."); +} + +sensor_value_t x300_impl::get_ref_locked(wb_iface::sptr ctrl) +{ + uint32_t clk_status = ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_CLK_STATUS)); + const bool lock = ((clk_status & ZPU_RB_CLK_STATUS_LMK_LOCK) != 0); + return sensor_value_t("Ref", lock, "locked", "unlocked"); +} + +bool x300_impl::is_pps_present(wb_iface::sptr ctrl) +{ + // The ZPU_RB_CLK_STATUS_PPS_DETECT bit toggles with each rising edge of the PPS. + // We monitor it for up to 1.5 seconds looking for it to toggle. + uint32_t pps_detect = ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_CLK_STATUS)) & ZPU_RB_CLK_STATUS_PPS_DETECT; + for (int i = 0; i < 15; i++) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + uint32_t clk_status = ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_CLK_STATUS)); + if (pps_detect != (clk_status & ZPU_RB_CLK_STATUS_PPS_DETECT)) + return true; + } + return false; +} + +void x300_impl::set_db_eeprom(i2c_iface::sptr i2c, const size_t addr, const uhd::usrp::dboard_eeprom_t &db_eeprom) +{ + db_eeprom.store(*i2c, addr); +} + +void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eeprom) +{ + i2c_iface::sptr eeprom16 = i2c->eeprom16(); + mb_eeprom.commit(*eeprom16, "X300"); +} + +boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio, const std::string &) +{ + return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); +} + +void x300_impl::set_fp_gpio(gpio_core_200::sptr gpio, const std::string &attr, const boost::uint32_t value) +{ + if (attr == "CTRL") return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value); + if (attr == "DDR") return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value); + if (attr == "OUT") return gpio->set_gpio_out(dboard_iface::UNIT_RX, value); + if (attr == "ATR_0X") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value); + if (attr == "ATR_RX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value); + if (attr == "ATR_TX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value); + if (attr == "ATR_XX") return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value); +} + +/*********************************************************************** + * claimer logic + **********************************************************************/ + +void x300_impl::claimer_loop(wb_iface::sptr iface) +{ + iface->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_TIME), time(NULL)); + iface->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC), get_process_hash()); + boost::this_thread::sleep(boost::posix_time::milliseconds(1500)); //1.5 seconds +} + +bool x300_impl::is_claimed(wb_iface::sptr iface) +{ + //If timed out then device is definitely unclaimed + if (iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_STATUS)) == 0) + return false; + + //otherwise check claim src to determine if another thread with the same src has claimed the device + return iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC)) != get_process_hash(); +} + +/*********************************************************************** + * Frame size detection + **********************************************************************/ +x300_impl::frame_size_t x300_impl::determine_max_frame_size(const std::string &addr, + const frame_size_t &user_frame_size) +{ + udp_simple::sptr udp = udp_simple::make_connected(addr, + BOOST_STRINGIZE(X300_MTU_DETECT_UDP_PORT)); + + std::vector<boost::uint8_t> buffer(std::max(user_frame_size.recv_frame_size, user_frame_size.send_frame_size)); + x300_mtu_t *request = reinterpret_cast<x300_mtu_t *>(&buffer.front()); + static const double echo_timeout = 0.020; //20 ms + + //test holler - check if its supported in this fw version + request->flags = uhd::htonx<boost::uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<boost::uint32_t>(sizeof(x300_mtu_t)); + udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); + udp->recv(boost::asio::buffer(buffer), echo_timeout); + if (!(uhd::ntohx<boost::uint32_t>(request->flags) & X300_MTU_DETECT_ECHO_REPLY)) + throw uhd::not_implemented_error("Holler protocol not implemented"); + + size_t min_recv_frame_size = sizeof(x300_mtu_t); + size_t max_recv_frame_size = user_frame_size.recv_frame_size; + size_t min_send_frame_size = sizeof(x300_mtu_t); + size_t max_send_frame_size = user_frame_size.send_frame_size; + + UHD_MSG(status) << "Determining maximum frame size... "; + while (min_recv_frame_size < max_recv_frame_size) + { + size_t test_frame_size = (max_recv_frame_size/2 + min_recv_frame_size/2 + 3) & ~3; + + request->flags = uhd::htonx<boost::uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<boost::uint32_t>(test_frame_size); + udp->send(boost::asio::buffer(buffer, sizeof(x300_mtu_t))); + + size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); + + if (len >= test_frame_size) + min_recv_frame_size = test_frame_size; + else + max_recv_frame_size = test_frame_size - 4; + } + + if(min_recv_frame_size < IP_PROTOCOL_MIN_MTU_SIZE-IP_PROTOCOL_UDP_PLUS_IP_HEADER) { + throw uhd::runtime_error("System receive MTU size is less than the minimum required by the IP protocol."); + } + + while (min_send_frame_size < max_send_frame_size) + { + size_t test_frame_size = (max_send_frame_size/2 + min_send_frame_size/2 + 3) & ~3; + + request->flags = uhd::htonx<boost::uint32_t>(X300_MTU_DETECT_ECHO_REQUEST); + request->size = uhd::htonx<boost::uint32_t>(sizeof(x300_mtu_t)); + udp->send(boost::asio::buffer(buffer, test_frame_size)); + + size_t len = udp->recv(boost::asio::buffer(buffer), echo_timeout); + if (len >= sizeof(x300_mtu_t)) + len = uhd::ntohx<boost::uint32_t>(request->size); + + if (len >= test_frame_size) + min_send_frame_size = test_frame_size; + else + max_send_frame_size = test_frame_size - 4; + } + + if(min_send_frame_size < IP_PROTOCOL_MIN_MTU_SIZE-IP_PROTOCOL_UDP_PLUS_IP_HEADER) { + throw uhd::runtime_error("System send MTU size is less than the minimum required by the IP protocol."); + } + + frame_size_t frame_size; + // There are cases when NICs accept oversized packets, in which case we'd falsely + // detect a larger-than-possible frame size. A safe and sensible value is the minimum + // of the recv and send frame sizes. + frame_size.recv_frame_size = std::min(min_recv_frame_size, min_send_frame_size); + frame_size.send_frame_size = std::min(min_recv_frame_size, min_send_frame_size); + UHD_MSG(status) << frame_size.send_frame_size << " bytes." << std::endl; + return frame_size; +} + +/*********************************************************************** + * compat checks + **********************************************************************/ + +void x300_impl::check_fw_compat(const fs_path &mb_path, wb_iface::sptr iface) +{ + boost::uint32_t compat_num = iface->peek32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_COMPAT_NUM)); + boost::uint32_t compat_major = (compat_num >> 16); + boost::uint32_t compat_minor = (compat_num & 0xffff); + + if (compat_major != X300_FW_COMPAT_MAJOR) + { + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number 0x%x, but got 0x%x.%x:\n" + "The firmware build is not compatible with the host code build.\n" + "%s" + ) % int(X300_FW_COMPAT_MAJOR) % compat_major % compat_minor + % print_images_error())); + } + _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u") + % compat_major % compat_minor)); +} + +void x300_impl::check_fpga_compat(const fs_path &mb_path, wb_iface::sptr iface) +{ + boost::uint32_t compat_num = iface->peek32(SR_ADDR(SET0_BASE, ZPU_RB_COMPAT_NUM)); + boost::uint32_t compat_major = (compat_num >> 16); + boost::uint32_t compat_minor = (compat_num & 0xffff); + + if (compat_major != X300_FPGA_COMPAT_MAJOR) + { + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number 0x%x, but got 0x%x.%x:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major % compat_minor + % print_images_error())); + } + _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") + % compat_major % compat_minor)); +} + +x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port) +{ + x300_mboard_t mb_type = UNKNOWN; + + //Detect the PCIe product ID to distinguish between X300 and X310 + nirio_status status = NiRio_Status_Success; + boost::uint32_t pid; + niriok_proxy::sptr discovery_proxy = + niusrprio_session::create_kernel_proxy(resource, rpc_port); + if (discovery_proxy) { + nirio_status_chain(discovery_proxy->get_attribute(PRODUCT_NUMBER, pid), status); + discovery_proxy->close(); + if (nirio_status_not_fatal(status)) { + //The PCIe ID -> MB mapping may be different from the EEPROM -> MB mapping + switch (pid) { + case X300_USRP_PCIE_SSID: + mb_type = USRP_X300_MB; break; + case X310_USRP_PCIE_SSID: + case X310_2940R_PCIE_SSID: + case X310_2942R_PCIE_SSID: + case X310_2943R_PCIE_SSID: + case X310_2944R_PCIE_SSID: + case X310_2950R_PCIE_SSID: + case X310_2952R_PCIE_SSID: + case X310_2953R_PCIE_SSID: + case X310_2954R_PCIE_SSID: + mb_type = USRP_X310_MB; break; + default: + mb_type = UNKNOWN; break; + } + } + } + + return mb_type; +} + +x300_impl::x300_mboard_t x300_impl::get_mb_type_from_eeprom(const uhd::usrp::mboard_eeprom_t& mb_eeprom) +{ + x300_mboard_t mb_type = UNKNOWN; + if (not mb_eeprom["product"].empty()) + { + boost::uint16_t product_num = 0; + try { + product_num = boost::lexical_cast<boost::uint16_t>(mb_eeprom["product"]); + } catch (const boost::bad_lexical_cast &) { + product_num = 0; + } + + switch (product_num) { + //The PCIe ID -> MB mapping may be different from the EEPROM -> MB mapping + case X300_USRP_PCIE_SSID: + mb_type = USRP_X300_MB; break; + case X310_USRP_PCIE_SSID: + case X310_2940R_PCIE_SSID: + case X310_2942R_PCIE_SSID: + case X310_2943R_PCIE_SSID: + case X310_2944R_PCIE_SSID: + case X310_2950R_PCIE_SSID: + case X310_2952R_PCIE_SSID: + case X310_2953R_PCIE_SSID: + case X310_2954R_PCIE_SSID: + mb_type = USRP_X310_MB; break; + default: + UHD_MSG(warning) << "X300 unknown product code in EEPROM: " << product_num << std::endl; + mb_type = UNKNOWN; break; + } + } + return mb_type; +} + diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp new file mode 100644 index 000000000..259ea253d --- /dev/null +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -0,0 +1,342 @@ +// +// 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_X300_IMPL_HPP +#define INCLUDED_X300_IMPL_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/device.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_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 "recv_packet_demuxer_3000.hpp" + +static const std::string X300_FW_FILE_NAME = "usrp_x300_fw.bin"; + +static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz +static const double X300_BUS_CLOCK_RATE = 175e6; //Hz + +static const size_t X300_TX_HW_BUFF_SIZE = 0x90000; //576KiB +static const size_t X300_TX_FC_RESPONSE_FREQ = 8; //per flow-control window + +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 + +static const size_t X300_PCIE_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 = 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 size_t X300_ETH_MSG_NUM_FRAMES = 32; +static const size_t X300_ETH_DATA_NUM_FRAMES = 32; +static const double X300_DEFAULT_SYSREF_RATE = 10e6; + +#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 +{ + 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, +}; + +struct x300_dboard_iface_config_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::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& drv_proxy); + +class x300_impl : public uhd::device +{ +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 bool is_claimed(uhd::wb_iface::sptr); + + enum x300_mboard_t { + USRP_X300_MB, USRP_X310_MB, UNKNOWN + }; + 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 + { + 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; + }; + + //overflow recovery impl + void handle_overflow(radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer); + + //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; + + uhd::task::sptr claimer_task; + std::string addr; + std::string xport_path; + int router_dst_here; + uhd::device_addr_t send_args; + uhd::device_addr_t recv_args; + bool if_pkt_is_big_endian; + uhd::niusrprio::niusrprio_session::sptr rio_fpga_interface; + + //perifs in the zpu + uhd::wb_iface::sptr zpu_ctrl; + spi_core_3000::sptr zpu_spi; + i2c_core_100_wb32::sptr zpu_i2c; + + //perifs in each radio + radio_perifs_t radio_perifs[2]; //!< 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; + + //clock control register bits + int clock_control_regs_clock_source; + int clock_control_regs_pps_select; + int clock_control_regs_pps_out_enb; + int clock_control_regs_tcxo_enb; + int clock_control_regs_gpsdo_pwr; + + //which FPGA image is loaded + std::string loaded_fpga_image; + + size_t hw_rev; + }; + 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); + + /*! \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); + + 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 uint8_t& destination, + const uint8_t& prefix, + const uhd::device_addr_t& args, + boost::uint32_t& sid); + + struct frame_size_t + { + size_t recv_frame_size; + size_t send_frame_size; + }; + frame_size_t _max_frame_sizes; + + /*! + * Automatically determine the maximum frame size available by sending a UDP packet + * to the device and see which packet sizes actually work. This way, we can take + * switches etc. into account which might live between the device and the host. + */ + frame_size_t determine_max_frame_size(const std::string &addr, const frame_size_t &user_mtu); + + //////////////////////////////////////////////////////////////////// + // + //Caching for transport interface re-use -- like sharing a DMA. + //The cache is optionally used by make_transport by use-case. + //The cache maps an ID string to a transport-ish object. + //The ID string identifies a purpose for the transport. + // + //For recv, there is a demux cache, which maps a ID string + //to a recv demux object. When a demux is used, the underlying transport + //must never be used outside of the demux. Use demux->make_proxy(sid). + // + uhd::dict<std::string, uhd::usrp::recv_packet_demuxer_3000::sptr> _demux_cache; + // + //For send, there is a shared send xport, which maps an ID string + //to a transport capable of sending buffers. Send transports + //can be shared amongst multiple callers, unlike recv. + // + uhd::dict<std::string, uhd::transport::zero_copy_if::sptr> _send_cache; + // + //////////////////////////////////////////////////////////////////// + + 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); + + /*! 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 set_time_source_out(mboard_members_t&, const bool); + void update_clock_source(mboard_members_t&, const std::string &); + void update_time_source(mboard_members_t&, const std::string &); + + uhd::sensor_value_t get_ref_locked(uhd::wb_iface::sptr); + void wait_for_ref_locked(uhd::wb_iface::sptr, double timeout = 0.0); + bool is_pps_present(uhd::wb_iface::sptr); + + void set_db_eeprom(uhd::i2c_iface::sptr i2c, const size_t, const uhd::usrp::dboard_eeprom_t &); + void set_mb_eeprom(uhd::i2c_iface::sptr i2c, const uhd::usrp::mboard_eeprom_t &); + + 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, uhd::wb_iface::sptr iface); + + void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); + boost::uint32_t get_fp_gpio(gpio_core_200::sptr, const std::string &); + void set_fp_gpio(gpio_core_200::sptr, const std::string &, const boost::uint32_t); +}; + +#endif /* INCLUDED_X300_IMPL_HPP */ diff --git a/host/lib/usrp/x300/x300_init.sh b/host/lib/usrp/x300/x300_init.sh new file mode 100644 index 000000000..96c346c12 --- /dev/null +++ b/host/lib/usrp/x300/x300_init.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +utils/usrp_burn_mb_eeprom --key=serial --val="x310rC12" +utils/usrp_burn_mb_eeprom --key=name --val="dillen" + +utils/usrp_burn_mb_eeprom --key=product --val="30410" +utils/usrp_burn_mb_eeprom --key=revision --val="3" + +mac0=$(python -c "import random; mac = [0x00, 0x16, 0x3e, random.randint(0x00, 0x7f),random.randint(0x00, 0xff),random.randint(0x00, 0xff)]; print ':'.join(map(lambda x: '%02x' % x, mac))") +mac1=$(python -c "import random; mac = [0x00, 0x16, 0x3e, random.randint(0x00, 0x7f),random.randint(0x00, 0xff),random.randint(0x00, 0xff)]; print ':'.join(map(lambda x: '%02x' % x, mac))") + +utils/usrp_burn_mb_eeprom --key=mac-addr0 --val="${mac0}" +utils/usrp_burn_mb_eeprom --key=mac-addr1 --val="${mac1}" + +utils/usrp_burn_mb_eeprom --key=gateway --val="192.168.10.1" + +utils/usrp_burn_mb_eeprom --key=subnet0 --val="255.255.255.0" +utils/usrp_burn_mb_eeprom --key=subnet1 --val="255.255.255.0" +utils/usrp_burn_mb_eeprom --key=subnet2 --val="255.255.255.0" +utils/usrp_burn_mb_eeprom --key=subnet3 --val="255.255.255.0" + +utils/usrp_burn_mb_eeprom --key=ip-addr0 --val="192.168.10.2" +utils/usrp_burn_mb_eeprom --key=ip-addr1 --val="192.168.10.3" +utils/usrp_burn_mb_eeprom --key=ip-addr2 --val="192.168.10.2" +utils/usrp_burn_mb_eeprom --key=ip-addr3 --val="192.168.10.3" diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp new file mode 100644 index 000000000..85de34a54 --- /dev/null +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -0,0 +1,647 @@ +// +// 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 "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 <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +/*********************************************************************** + * update streamer rates + **********************************************************************/ +void x300_impl::update_tick_rate(mboard_members_t &mb, const double rate) +{ + 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()) + { + 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); + } + } + + _tree->access<std::vector<size_t> >(mb_root / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); +} + + +/*********************************************************************** + * VITA stuff + **********************************************************************/ +static void x300_if_hdr_unpack_be( + 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); +} + +static void x300_if_hdr_pack_be( + 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); +} + +static void x300_if_hdr_unpack_le( + 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_le(packet_buff, if_packet_info); +} + +static void x300_if_hdr_pack_le( + 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_le(packet_buff, if_packet_info); +} + +/*********************************************************************** + * 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; +} + +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) + x300_if_hdr_pack_be(pkt, packet_info); + else + x300_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; +}; + +static size_t get_tx_flow_control_window(size_t frame_size, const device_addr_t& tx_args) +{ + 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; +} + +static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero_copy_if::sptr xport, bool big_endian, x300_clock_ctrl::sptr clock) +{ + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + uint32_t (*endian_conv)(uint32_t) = uhd::ntohx; + try + { + if (big_endian) + { + x300_if_hdr_unpack_be(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } + else + { + x300_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; + } + + //catch the flow control packets and react + if (endian_conv(packet_buff[if_packet_info.num_header_words32+0]) == 0) + { + const size_t seq = endian_conv(packet_buff[if_packet_info.num_header_words32+1]); + guts->seq_queue.push_with_haste(seq); + 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); + 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) + { + const size_t delta = (guts->last_seq_out & 0xfff) - (guts->last_seq_ack & 0xfff); + if ((delta & 0xfff) <= fc_pkt_window) break; + + 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(); + } + } + + // 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 + } + } + + //allocate sid and create transport + 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; + + //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.recv->get_recv_frame_size() - hdr_size; // 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(&x300_if_hdr_unpack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_unpacker(&x300_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); + + 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_rx_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*/)); + + 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]; + + //setup the dsp transport hints (TODO) + device_addr_t device_addr = mb.send_args; + + //allocate sid and create transport + 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; + + //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.send->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()); + + std::string conv_endianness; + if (mb.if_pkt_is_big_endian) { + my_streamer->set_vrt_packer(&x300_if_hdr_pack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_packer(&x300_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(); + } + + return my_streamer; +} diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp new file mode 100644 index 000000000..fb1786deb --- /dev/null +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -0,0 +1,166 @@ +// +// 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_X300_REGS_HPP +#define INCLUDED_X300_REGS_HPP + +#include <boost/cstdint.hpp> + +#define TOREG(x) ((x)*4) + +#define localparam static const int + +localparam SR_DACSYNC = 5; +localparam SR_LOOPBACK = 6; +localparam SR_TEST = 7; +localparam SR_SPI = 8; +localparam SR_GPIO = 16; +localparam SR_MISC_OUTS = 24; +localparam SR_READBACK = 32; +localparam SR_TX_CTRL = 64; +localparam SR_RX_CTRL = 96; +localparam SR_TIME = 128; +localparam SR_RX_DSP = 144; +localparam SR_TX_DSP = 184; +localparam SR_LEDS = 196; +localparam SR_FP_GPIO = 200; +localparam SR_RX_FRONT = 208; +localparam SR_TX_FRONT = 216; + +localparam RB32_GPIO = 0; +localparam RB32_SPI = 4; +localparam RB64_TIME_NOW = 8; +localparam RB64_TIME_PPS = 16; +localparam RB32_TEST = 24; +localparam RB32_RX = 28; +localparam RB32_FP_GPIO = 32; + +localparam BL_ADDRESS = 0; +localparam BL_DATA = 1; + +//wishbone settings map - relevant to host code +#define SET0_BASE 0xa000 +#define SETXB_BASE 0xb000 +#define BOOT_LDR_BASE 0xFA00 +#define I2C0_BASE 0xfe00 +#define I2C1_BASE 0xff00 +#define SR_ADDR(base, offset) ((base) + (offset)*4) + +localparam ZPU_SR_LEDS = 00; +localparam ZPU_SR_PHY_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; + +//clock controls +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_EXTERNAL 0x00 +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_INTERNAL 0x02 +#define ZPU_SR_CLOCK_CTRL_CLK_SRC_GPSDO 0x03 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_EXTERNAL 0x00 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_INTERNAL 0x02 +#define ZPU_SR_CLOCK_CTRL_PPS_SRC_GPSDO 0x03 + +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; + +//clock status +#define ZPU_RB_CLK_STATUS_LMK_STATUS (0x3 << 0) +#define ZPU_RB_CLK_STATUS_LMK_LOCK (0x1 << 2) +#define ZPU_RB_CLK_STATUS_LMK_HOLDOVER (0x1 << 3) +#define ZPU_RB_CLK_STATUS_PPS_DETECT (0x1 << 4) + +//spi slaves on radio +#define DB_DAC_SEN (1 << 7) +#define DB_ADC_SEN (1 << 6) +#define DB_RX_LSADC_SEN (1 << 5) +#define DB_RX_LSDAC_SEN (1 << 4) +#define DB_TX_LSADC_SEN (1 << 3) +#define DB_TX_LSDAC_SEN (1 << 2) +#define DB_RX_SEN (1 << 1) +#define DB_TX_SEN (1 << 0) + +//------------------------------------------------------------------- +// PCIe Registers +//------------------------------------------------------------------- + +static const uint32_t X300_PCIE_VID = 0x1093; +static const uint32_t X300_PCIE_PID = 0xC4C4; +static const uint32_t X300_USRP_PCIE_SSID = 0x7736; +static const uint32_t X310_USRP_PCIE_SSID = 0x76CA; +static const uint32_t X310_2940R_PCIE_SSID = 0x772B; +static const uint32_t X310_2942R_PCIE_SSID = 0x772C; +static const uint32_t X310_2943R_PCIE_SSID = 0x772D; +static const uint32_t X310_2944R_PCIE_SSID = 0x772E; +static const uint32_t X310_2950R_PCIE_SSID = 0x772F; +static const uint32_t X310_2952R_PCIE_SSID = 0x7730; +static const uint32_t X310_2953R_PCIE_SSID = 0x7731; +static const uint32_t X310_2954R_PCIE_SSID = 0x7732; + +static const uint32_t FPGA_X3xx_SIG_VALUE = 0x58333030; + +static const uint32_t PCIE_FPGA_ADDR_BASE = 0xC0000; +#define PCIE_FPGA_REG(X) (PCIE_FPGA_ADDR_BASE + X) + +static const uint32_t FPGA_PCIE_SIG_REG = PCIE_FPGA_REG(0x0000); +static const uint32_t FPGA_CNTR_LO_REG = PCIE_FPGA_REG(0x0004); +static const uint32_t FPGA_CNTR_HI_REG = PCIE_FPGA_REG(0x0008); +static const uint32_t FPGA_CNTR_FREQ_REG = PCIE_FPGA_REG(0x000C); +static const uint32_t FPGA_USR_SIG_REG_BASE = PCIE_FPGA_REG(0x0030); +static const uint32_t FPGA_USR_SIG_REG_SIZE = 16; + +static const uint32_t PCIE_TX_DMA_REG_BASE = PCIE_FPGA_REG(0x0200); +static const uint32_t PCIE_RX_DMA_REG_BASE = PCIE_FPGA_REG(0x0400); + +static const uint32_t DMA_REG_GRP_SIZE = 16; +static const uint32_t DMA_CTRL_STATUS_REG = 0x0; +static const uint32_t DMA_FRAME_SIZE_REG = 0x4; +static const uint32_t DMA_SAMPLE_COUNT_REG = 0x8; +static const uint32_t DMA_PKT_COUNT_REG = 0xC; + +#define PCIE_TX_DMA_REG(REG, CHAN) (PCIE_TX_DMA_REG_BASE + (CHAN*DMA_REG_GRP_SIZE) + REG) +#define PCIE_RX_DMA_REG(REG, CHAN) (PCIE_RX_DMA_REG_BASE + (CHAN*DMA_REG_GRP_SIZE) + REG) + +static const uint32_t DMA_CTRL_RESET = 1; +static const uint32_t DMA_CTRL_SW_BUF_U64 = (3 << 4); +static const uint32_t DMA_CTRL_SW_BUF_U32 = (2 << 4); +static const uint32_t DMA_CTRL_SW_BUF_U16 = (1 << 4); +static const uint32_t DMA_CTRL_SW_BUF_U8 = (0 << 4); +static const uint32_t DMA_STATUS_ERROR = 1; + +static const uint32_t PCIE_ROUTER_REG_BASE = PCIE_FPGA_REG(0x0500); +#define PCIE_ROUTER_REG(X) (PCIE_ROUTER_REG_BASE + X) + +static const uint32_t PCIE_ZPU_DATA_BASE = 0x30000; +static const uint32_t PCIE_ZPU_READ_BASE = 0x20000; //Trig and Status share the same base +static const uint32_t PCIE_ZPU_STATUS_BASE = 0x20000; + +#define PCIE_ZPU_DATA_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_DATA_BASE) + X) +#define PCIE_ZPU_READ_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_READ_BASE) + X) +#define PCIE_ZPU_STATUS_REG(X) (PCIE_FPGA_REG(PCIE_ZPU_STATUS_BASE) + X) + +static const uint32_t PCIE_ZPU_READ_START = 0x0; +static const uint32_t PCIE_ZPU_READ_CLOBBER = 0x80000000; +static const uint32_t PCIE_ZPU_STATUS_BUSY = 0x1; +static const uint32_t PCIE_ZPU_STATUS_SUSPENDED = 0x80000000; + + +#endif /* INCLUDED_X300_REGS_HPP */ diff --git a/host/lib/utils/CMakeLists.txt b/host/lib/utils/CMakeLists.txt index a28e1f9ef..2252f3221 100644 --- a/host/lib/utils/CMakeLists.txt +++ b/host/lib/utils/CMakeLists.txt @@ -87,6 +87,7 @@ CHECK_CXX_SOURCE_COMPILES(" UNSET(CMAKE_REQUIRED_LIBRARIES) CHECK_CXX_SOURCE_COMPILES(" + #define WIN32_LEAN_AND_MEAN #include <windows.h> int main(){ LoadLibrary(0); @@ -136,6 +137,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/log.cpp ${CMAKE_CURRENT_SOURCE_DIR}/msg.cpp ${CMAKE_CURRENT_SOURCE_DIR}/paths.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/static.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tasks.cpp ${CMAKE_CURRENT_SOURCE_DIR}/thread_priority.cpp diff --git a/host/lib/utils/gain_group.cpp b/host/lib/utils/gain_group.cpp index 85f4977a6..d422b3d52 100644 --- a/host/lib/utils/gain_group.cpp +++ b/host/lib/utils/gain_group.cpp @@ -64,7 +64,7 @@ public: } gain_range_t get_range(const std::string &name){ - if (not name.empty()) return _name_to_fcns[name].get_range(); + if (not name.empty()) return _name_to_fcns.get(name).get_range(); double overall_min = 0, overall_max = 0, overall_step = 0; BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ @@ -79,7 +79,7 @@ public: } double get_value(const std::string &name){ - if (not name.empty()) return _name_to_fcns[name].get_value(); + if (not name.empty()) return _name_to_fcns.get(name).get_value(); double overall_gain = 0; BOOST_FOREACH(const gain_fcns_t &fcns, get_all_fcns()){ @@ -89,7 +89,7 @@ public: } void set_value(double gain, const std::string &name){ - if (not name.empty()) return _name_to_fcns[name].set_value(gain); + if (not name.empty()) return _name_to_fcns.get(name).set_value(gain); std::vector<gain_fcns_t> all_fcns = get_all_fcns(); if (all_fcns.size() == 0) return; //nothing to set! diff --git a/host/lib/utils/platform.cpp b/host/lib/utils/platform.cpp new file mode 100644 index 000000000..e2f92039e --- /dev/null +++ b/host/lib/utils/platform.cpp @@ -0,0 +1,58 @@ +// +// Copyright 2010-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 <uhd/utils/platform.hpp> +#include <uhd/config.hpp> +#include <boost/functional/hash.hpp> +#ifdef UHD_PLATFORM_WIN32 +#include <Windows.h> +#else +#include <unistd.h> +#endif + +namespace uhd { + + boost::int32_t get_process_id() { +#ifdef UHD_PLATFORM_WIN32 + return boost::int32_t(GetCurrentProcessId()); +#else + return boost::int32_t(getpid()); +#endif + } + + boost::uint32_t get_host_id() { +#ifdef UHD_PLATFORM_WIN32 + //extract volume serial number + char szVolName[MAX_PATH+1], szFileSysName[MAX_PATH+1]; + DWORD dwSerialNumber, dwMaxComponentLen, dwFileSysFlags; + GetVolumeInformation("C:\\", szVolName, MAX_PATH, + &dwSerialNumber, &dwMaxComponentLen, + &dwFileSysFlags, szFileSysName, sizeof(szFileSysName)); + + return boost::uint32_t(dwSerialNumber); +#else + return boost::uint32_t(gethostid()); +#endif + } + + boost::uint32_t get_process_hash() { + size_t hash = 0; + boost::hash_combine(hash, uhd::get_process_id()); + boost::hash_combine(hash, uhd::get_host_id()); + return boost::uint32_t(hash); + } +} diff --git a/host/tests/convert_test.cpp b/host/tests/convert_test.cpp index 4b0226e3d..8ef1e74cc 100644 --- a/host/tests/convert_test.cpp +++ b/host/tests/convert_test.cpp @@ -50,8 +50,8 @@ template <typename Range> static void loopback( const int prio_in = -1, const int prio_out = -1 ){ - //item32 is largest device type - std::vector<boost::uint32_t> interm(nsamps); + //make this buffer large enough for all test types + std::vector<boost::uint64_t> interm(nsamps); std::vector<const void *> input0(1, &input[0]), input1(1, &interm[0]); std::vector<void *> output0(1, &interm[0]), output1(1, &output[0]); @@ -148,8 +148,8 @@ static void test_convert_types_for_floats( BOOST_FOREACH(const int_pair_t &prio, prios){ loopback(nsamps, in_id, out_id, input, output, prio.first, prio.second); for (size_t i = 0; i < nsamps; i++){ - MY_CHECK_CLOSE(input[i].real(), output[i].real(), value_type(1./32767)); - MY_CHECK_CLOSE(input[i].imag(), output[i].imag(), value_type(1./32767)); + MY_CHECK_CLOSE(input[i].real(), output[i].real(), value_type(1./(1 << 14))); + MY_CHECK_CLOSE(input[i].imag(), output[i].imag(), value_type(1./(1 << 14))); } } } @@ -207,6 +207,66 @@ BOOST_AUTO_TEST_CASE(test_convert_types_le_fc64){ } /*********************************************************************** + * Test float to/from sc12 conversion loopback + **********************************************************************/ + +BOOST_AUTO_TEST_CASE(test_convert_types_le_sc12_with_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc12_item32_le"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id, 1./16); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_be_sc12_with_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "sc12_item32_be"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id, 1./16); + } +} + +/*********************************************************************** + * Test float to/from fc32 conversion loopback + **********************************************************************/ + +BOOST_AUTO_TEST_CASE(test_convert_types_le_fc32_with_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "fc32_item32_le"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id); + } +} + +BOOST_AUTO_TEST_CASE(test_convert_types_be_fc32_with_fc32){ + convert::id_type id; + id.input_format = "fc32"; + id.num_inputs = 1; + id.output_format = "fc32_item32_be"; + id.num_outputs = 1; + + //try various lengths to test edge cases + for (size_t nsamps = 1; nsamps < 16; nsamps++){ + test_convert_types_for_floats<fc32_t>(nsamps, id); + } +} + +/*********************************************************************** * Test float to short conversion loopback **********************************************************************/ BOOST_AUTO_TEST_CASE(test_convert_types_fc32_to_sc16){ diff --git a/host/tests/sph_recv_test.cpp b/host/tests/sph_recv_test.cpp index 316e24779..5dd0761db 100644 --- a/host/tests/sph_recv_test.cpp +++ b/host/tests/sph_recv_test.cpp @@ -227,6 +227,7 @@ BOOST_AUTO_TEST_CASE(test_sph_recv_one_channel_sequence_error){ if (i == NUM_PKTS_TO_TEST/2){ //must get the soft overflow here BOOST_REQUIRE(metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + BOOST_REQUIRE(metadata.out_of_sequence == true); BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); num_accum_samps += 10 + i%10; } @@ -495,6 +496,7 @@ BOOST_AUTO_TEST_CASE(test_sph_recv_multi_channel_sequence_error){ if (i == NUM_PKTS_TO_TEST/2){ //must get the soft overflow here BOOST_REQUIRE(metadata.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW); + BOOST_REQUIRE(metadata.out_of_sequence == true); BOOST_CHECK_TS_CLOSE(metadata.time_spec, uhd::time_spec_t::from_ticks(num_accum_samps, SAMP_RATE)); num_accum_samps += 10 + i%10; } diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index abf2a546b..7604a7d37 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright 2010-2012 Ettus Research LLC +# Copyright 2010-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 @@ -15,6 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # +SET(CMAKE_C_COMPILE_OBJECT ${CMAKE_CXX_COMPILE_OBJECT}) + ######################################################################## # Utilities that get installed into the runtime path ######################################################################## @@ -25,6 +27,12 @@ SET(util_runtime_sources uhd_cal_tx_dc_offset.cpp uhd_cal_tx_iq_balance.cpp usrp_n2xx_simple_net_burner.cpp + nirio_programmer.cpp +) + +SET(x3xx_burner_sources + usrp_x3xx_fpga_burner.cpp + cdecode.c ) #for each source: build an executable and install @@ -35,6 +43,10 @@ FOREACH(util_source ${util_runtime_sources}) UHD_INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) ENDFOREACH(util_source) +ADD_EXECUTABLE(usrp_x3xx_fpga_burner ${x3xx_burner_sources}) +TARGET_LINK_LIBRARIES(usrp_x3xx_fpga_burner uhd ${Boost_LIBRARIES}) +UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) + ######################################################################## # Utilities that get installed into the share path ######################################################################## @@ -43,7 +55,6 @@ SET(util_share_sources usrp_burn_db_eeprom.cpp usrp_burn_mb_eeprom.cpp ) - IF(ENABLE_USB) LIST(APPEND util_share_sources fx2_init_eeprom.cpp @@ -71,6 +82,7 @@ FOREACH(util_source ${util_share_sources}) ENDFOREACH(util_source) UHD_INSTALL(TARGETS usrp_n2xx_simple_net_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +UHD_INSTALL(TARGETS usrp_x3xx_fpga_burner RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) #UHD images downloader configuration CONFIGURE_FILE( diff --git a/host/utils/b2xx_fx3_utils.cpp b/host/utils/b2xx_fx3_utils.cpp index 96cd1f3e7..d5f829c80 100644 --- a/host/utils/b2xx_fx3_utils.cpp +++ b/host/utils/b2xx_fx3_utils.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// 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 @@ -79,7 +79,10 @@ template <class T> struct to_hex{ //!parse hex-formatted ASCII text into an int boost::uint16_t atoh(const std::string &string){ if (string.substr(0, 2) == "0x"){ - return boost::lexical_cast<to_hex<boost::uint16_t> >(string); + std::stringstream interpreter(string); + to_hex<boost::uint16_t> hh; + interpreter >> hh; + return hh.value; } return boost::lexical_cast<boost::uint16_t>(string); } diff --git a/host/utils/cdecode.c b/host/utils/cdecode.c new file mode 100644 index 000000000..0a9b5c46b --- /dev/null +++ b/host/utils/cdecode.c @@ -0,0 +1,80 @@ +/*
+cdecoder.c - c source to a base64 decoding algorithm implementation
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#include "cdecode.h"
+
+int base64_decode_value(char value_in){
+ static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
+ static const char decoding_size = sizeof(decoding);
+ value_in -= 43;
+ if ((signed char)value_in < 0 || value_in > decoding_size) return -1;
+ return decoding[(int)value_in];
+}
+
+void base64_init_decodestate(base64_decodestate* state_in){
+ state_in->step = step_a;
+ state_in->plainchar = 0;
+}
+
+size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in){
+ const char* codechar = code_in;
+ char* plainchar = plaintext_out;
+ char fragment;
+
+ *plainchar = state_in->plainchar;
+
+ switch (state_in->step){
+ while (1){
+ case step_a:
+ do{
+ if (codechar == code_in+length_in){
+ state_in->step = step_a;
+ state_in->plainchar = *plainchar;
+ return plainchar - plaintext_out;
+ }
+ fragment = (char)base64_decode_value(*codechar++);
+ } while ((signed char)fragment < 0);
+ *plainchar = (fragment & 0x03f) << 2;
+
+ case step_b:
+ do{
+ if (codechar == code_in+length_in){
+ state_in->step = step_b;
+ state_in->plainchar = *plainchar;
+ return plainchar - plaintext_out;
+ }
+ fragment = (char)base64_decode_value(*codechar++);
+ } while ((signed char)fragment < 0);
+ *plainchar++ |= (fragment & 0x030) >> 4;
+ *plainchar = (fragment & 0x00f) << 4;
+ case step_c:
+ do{
+ if (codechar == code_in+length_in)
+ {
+ state_in->step = step_c;
+ state_in->plainchar = *plainchar;
+ return plainchar - plaintext_out;
+ }
+ fragment = (char)base64_decode_value(*codechar++);
+ } while ((signed char)fragment < 0);
+ *plainchar++ |= (fragment & 0x03c) >> 2;
+ *plainchar = (fragment & 0x003) << 6;
+ case step_d:
+ do{
+ if (codechar == code_in+length_in){
+ state_in->step = step_d;
+ state_in->plainchar = *plainchar;
+ return plainchar - plaintext_out;
+ }
+ fragment = (char)base64_decode_value(*codechar++);
+ } while ((signed char)fragment < 0);
+ *plainchar++ |= (fragment & 0x03f);
+ }
+ }
+ /* control should not reach here */
+ return plainchar - plaintext_out;
+}
diff --git a/host/utils/cdecode.h b/host/utils/cdecode.h new file mode 100644 index 000000000..e1eee301f --- /dev/null +++ b/host/utils/cdecode.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +#include <stddef.h> + +typedef enum{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +size_t base64_decode_block(const char* code_in, const size_t length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/host/utils/nirio_programmer.cpp b/host/utils/nirio_programmer.cpp new file mode 100644 index 000000000..98db862eb --- /dev/null +++ b/host/utils/nirio_programmer.cpp @@ -0,0 +1,275 @@ + +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/transport/nirio/niriok_proxy.h> +#include <uhd/transport/nirio/nifpga_lvbitx.h> +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <iostream> +#include <fstream> +#include <streambuf> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/regex.hpp> + +using namespace uhd::niusrprio; +using namespace uhd::usrprio_rpc; + +class dummy_lvbitx : public nifpga_lvbitx { +public: + dummy_lvbitx(const std::string& fpga_lvbitx_path) : _fpga_lvbitx_path(fpga_lvbitx_path) { + std::ifstream lvbitx_stream(_fpga_lvbitx_path.c_str()); + if (lvbitx_stream.is_open()) { + std::string lvbitx_contents; + lvbitx_stream.seekg(0, std::ios::end); + lvbitx_contents.reserve(static_cast<size_t>(lvbitx_stream.tellg())); + lvbitx_stream.seekg(0, std::ios::beg); + lvbitx_contents.assign((std::istreambuf_iterator<char>(lvbitx_stream)), std::istreambuf_iterator<char>()); + try { + boost::smatch md5_match; + if (boost::regex_search(lvbitx_contents, md5_match, boost::regex("<BitstreamMD5>([a-zA-Z0-9]{32})<\\/BitstreamMD5>", boost::regex::icase))) { + _bitstream_checksum = std::string(md5_match[1].first, md5_match[1].second); + } + boost::to_upper(_bitstream_checksum); + } catch (boost::exception&) { + _bitstream_checksum = ""; + } + try { + boost::smatch sig_match; + if (boost::regex_search(lvbitx_contents, sig_match, boost::regex("<SignatureRegister>([a-zA-Z0-9]{32})<\\/SignatureRegister>", boost::regex::icase))) { + _signature = std::string(sig_match[1].first, sig_match[1].second); + } + boost::to_upper(_signature); + } catch (boost::exception&) { + _signature = ""; + } + } + } + ~dummy_lvbitx() {} + + virtual const char* get_bitfile_path() { return _fpga_lvbitx_path.c_str(); } + virtual const char* get_signature() { return _signature.c_str(); } + virtual const char* get_bitstream_checksum() { return _bitstream_checksum.c_str(); } + + virtual size_t get_input_fifo_count() { return 0; } + virtual const char** get_input_fifo_names() { return NULL; } + + virtual size_t get_output_fifo_count() { return 0; } + virtual const char** get_output_fifo_names() { return NULL; } + + virtual size_t get_control_count() { return 0; } + virtual const char** get_control_names() { return NULL; } + + virtual size_t get_indicator_count() { return 0; } + virtual const char** get_indicator_names() { return NULL; } + + virtual void init_register_info(nirio_register_info_vtr& vtr) { vtr.clear(); } + virtual void init_fifo_info(nirio_fifo_info_vtr& vtr) { vtr.clear(); } + +private: + std::string _fpga_lvbitx_path; + std::string _bitstream_checksum; + std::string _signature; +}; + +int main(int argc, char *argv[]) +{ + nirio_status status = NiRio_Status_Success; + + //Setup the program options + uint32_t interface_num, peek_addr, poke_addr, poke_data; + std::string rpc_port, fpga_lvbitx_path, flash_path, peek_tokens_str, poke_tokens_str; + + namespace po = boost::program_options; + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("interface", po::value<uint32_t>(&interface_num)->default_value(0), "The interface number to communicate with.") + ("port", po::value<std::string>(&rpc_port)->default_value("5444"), "Port to communicate with RPC server.") + ("fpga", po::value<std::string>(&fpga_lvbitx_path)->default_value(""), "The absolute path to the LVBITX file to download to the FPGA.") + ("flash", po::value<std::string>(&flash_path)->default_value(""), "The path to the image to download to the flash OR 'erase' to erase the FPGA image from flash.") + ("peek", po::value<std::string>(&peek_tokens_str)->default_value(""), "Peek32.") + ("poke", po::value<std::string>(&poke_tokens_str)->default_value(""), "Poke32.") + ("stats", "Dump interface and DMA stats.") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //Print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP-NIRIO-Programmer\n\n %s") % desc << std::endl; + return ~0; + } + + std::string resource_name = boost::str(boost::format("RIO%u") % interface_num); + + //Download LVBITX image + if (fpga_lvbitx_path != "") + { + printf("Downloading image %s to FPGA as %s...", fpga_lvbitx_path.c_str(), resource_name.c_str()); + fflush(stdout); + uhd::niusrprio::niusrprio_session fpga_session(resource_name, rpc_port); + uhd::niusrprio::nifpga_lvbitx::sptr lvbitx(new dummy_lvbitx(fpga_lvbitx_path)); + nirio_status_chain(fpga_session.open(lvbitx, true), status); + //Download BIN to flash or erase + if (flash_path != "erase") { + if (flash_path != "") { + printf("Writing FPGA image %s to flash...", flash_path.c_str()); + fflush(stdout); + nirio_status_chain(fpga_session.download_bitstream_to_flash(flash_path), status); + printf("DONE\n"); + } + } else { + printf("Erasing FPGA image from flash..."); + fflush(stdout); + nirio_status_chain(fpga_session.download_bitstream_to_flash(""), status); + printf("DONE\n"); + } + fpga_session.close(); + printf("DONE\n"); + } + + fflush(stdout); + usrprio_rpc_client temp_rpc_client("localhost", rpc_port); + std::string interface_path; + nirio_status_chain(temp_rpc_client.niusrprio_get_interface_path(resource_name, interface_path), status); + if (interface_path.empty()) { + printf("ERROR: Could not open a proxy to interface %u. If it exists, try downloading an LVBITX to the FPGA first.\n", interface_num); + exit(EXIT_FAILURE); + } + + niriok_proxy dev_proxy; + dev_proxy.open(interface_path); + + if (poke_tokens_str != ""){ + std::stringstream ss; + std::vector<std::string> poke_tokens; + boost::split(poke_tokens, poke_tokens_str, boost::is_any_of(":")); + ss.clear(); + ss << std::hex << poke_tokens[1]; + ss >> poke_addr; + ss.clear(); + ss << std::hex << poke_tokens[2]; + ss >> poke_data; + + niriok_scoped_addr_space(dev_proxy, poke_tokens[0]=="c"?BUS_INTERFACE:FPGA, status); + if (poke_tokens[0]=="z") { + nirio_status_chain(dev_proxy.poke(poke_addr, (uint32_t)0x70000 + poke_addr), status); + } else { + nirio_status_chain(dev_proxy.poke(poke_addr, poke_data), status); + } + printf("[POKE] %s:0x%x <= 0x%x (%u)\n", poke_tokens[0]=="c"?"Chinch":(poke_tokens[0]=="z"?"ZPU":"FPGA"), poke_addr, poke_data, poke_data); + } + + if (peek_tokens_str != ""){ + std::stringstream ss; + std::vector<std::string> peek_tokens; + boost::split(peek_tokens, peek_tokens_str, boost::is_any_of(":")); + ss.clear(); + ss << std::hex << peek_tokens[1]; + ss >> peek_addr; + + niriok_scoped_addr_space(dev_proxy, peek_tokens[0]=="c"?BUS_INTERFACE:FPGA, status); + uint32_t reg_val; + if (peek_tokens[0]=="z") { + nirio_status_chain(dev_proxy.poke((uint32_t)0x60000 + peek_addr, (uint32_t)0), status); + do { + nirio_status_chain(dev_proxy.peek((uint32_t)0x60000 + peek_addr, reg_val), status); + } while (reg_val != 0); + nirio_status_chain(dev_proxy.peek((uint32_t)0x70000 + peek_addr, reg_val), status); + } else { + nirio_status_chain(dev_proxy.peek(peek_addr, reg_val), status); + } + + printf("[PEEK] %s:0x%x = 0x%x (%u)\n", peek_tokens[0]=="c"?"Chinch":(peek_tokens[0]=="z"?"ZPU":"FPGA"), peek_addr, reg_val, reg_val); + } + + //Display attributes + if (vm.count("stats")){ + printf("[Interface %u]\n", interface_num); + uint32_t attr_val; + nirio_status_chain(dev_proxy.get_attribute(IS_FPGA_PROGRAMMED, attr_val), status); + printf("* Is FPGA Programmed? = %s\n", (attr_val==1)?"YES":"NO"); + + std::string signature; + for (int i = 0; i < 4; i++) { + nirio_status_chain(dev_proxy.peek(0x3FFF4, attr_val), status); + signature += boost::str(boost::format("%08x") % attr_val); + } + printf("* FPGA Signature = %s\n", signature.c_str()); + + std::string checksum; + for (int i = 0; i < 4; i++) { + nirio_status_chain(dev_proxy.peek(0x40030 + (i * 4), attr_val), status); + checksum += boost::str(boost::format("%08x") % attr_val); + } + printf("* FPGA Bitstream Checksum = %s\n", checksum.c_str()); + + uint32_t reg_val; + nirio_status_chain(dev_proxy.set_attribute(ADDRESS_SPACE, BUS_INTERFACE), status); + nirio_status_chain(dev_proxy.peek(0, reg_val), status); + printf("* Chinch Signature = %x\n", reg_val); + nirio_status_chain(dev_proxy.set_attribute(ADDRESS_SPACE, FPGA), status); + nirio_status_chain(dev_proxy.peek(0, reg_val), status); + printf("* PCIe FPGA Signature = %x\n", reg_val); + + printf("\n[DMA Stream Stats]\n"); + + nirio_status_chain(dev_proxy.set_attribute(ADDRESS_SPACE, FPGA), status); + + printf("------------------------------------------------------------------------------------------------"); + printf("\nChannel => |"); + for (uint32_t i = 0; i < 6; i++) { + printf("%11u |", i); + } + printf("\n------------------------------------------------------------------------------------------------"); + printf("\nTX Status |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40200 + (i * 16), reg_val), status); + printf("%s |", reg_val==0 ? " Good" : " Error"); + } + printf("\nRX Status |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40400 + (i * 16), reg_val), status); + printf("%s |", reg_val==0 ? " Good" : " Error"); + } + printf("\nTX Frm Size |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40204 + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\nRX Frm Size |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40404 + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\nTX Pkt Count |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x4020C + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\nTX Samp Count |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40208 + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\nRX Pkt Count |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x4040C + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\nRX Samp Count |"); + for (uint32_t i = 0; i < 6; i++) { + nirio_status_chain(dev_proxy.peek(0x40408 + (i * 16), reg_val), status); + printf("%11u |", reg_val); + } + printf("\n------------------------------------------------------------------------------------------------\n"); + } + + exit(EXIT_SUCCESS); +} + + diff --git a/host/utils/uhd_cal_rx_iq_balance.cpp b/host/utils/uhd_cal_rx_iq_balance.cpp index 5fb494114..551da7544 100644 --- a/host/utils/uhd_cal_rx_iq_balance.cpp +++ b/host/utils/uhd_cal_rx_iq_balance.cpp @@ -20,6 +20,7 @@ #include <uhd/utils/safe_main.hpp> #include <uhd/utils/paths.hpp> #include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/usrp/multi_usrp.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> @@ -93,7 +94,7 @@ static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double rx_l * Main **********************************************************************/ int UHD_SAFE_MAIN(int argc, char *argv[]){ - std::string args; + std::string args, subdev, serial; double tx_wave_ampl, tx_offset; double freq_start, freq_stop, freq_step; size_t nsamps; @@ -102,7 +103,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ desc.add_options() ("help", "help message") ("verbose", "enable some verbose") - ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("args", po::value<std::string>(&args)->default_value(""), "Device address args [default = \"\"]") + ("subdev", po::value<std::string>(&subdev), "Subdevice specification (default: first subdevice, often 'A')") ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") ("tx_offset", po::value<double>(&tx_offset)->default_value(.9344e6), "TX LO offset from the RX LO in Hz") ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") @@ -129,6 +131,15 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + // Configure subdev + if (vm.count("subdev")) { + usrp->set_tx_subdev_spec(subdev); + usrp->set_rx_subdev_spec(subdev); + } + UHD_MSG(status) << "Running calibration for " << usrp->get_tx_subdev_name(0) << std::endl; + serial = get_serial(usrp, "tx"); + UHD_MSG(status) << "Daughterboard serial: " << serial << std::endl; + //set the antennas to cal if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); @@ -158,6 +169,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ if (not vm.count("freq_start")) freq_start = usrp->get_rx_freq_range().start() + 50e6; if (not vm.count("freq_stop")) freq_stop = usrp->get_rx_freq_range().stop() - 50e6; + UHD_MSG(status) << boost::format("Calibration frequency range: %d MHz -> %d MHz") % (freq_start/1e6) % (freq_stop/1e6) << std::endl; for (double rx_lo_i = freq_start; rx_lo_i <= freq_stop; rx_lo_i += freq_step){ const double rx_lo = tune_rx_and_tx(usrp, rx_lo_i, tx_offset); @@ -238,7 +250,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ threads.interrupt_all(); threads.join_all(); - store_results(usrp, results, "RX", "rx", "iq"); + store_results(results, "RX", "rx", "iq", serial); return EXIT_SUCCESS; } diff --git a/host/utils/uhd_cal_tx_dc_offset.cpp b/host/utils/uhd_cal_tx_dc_offset.cpp index c9cf757f4..eb82db826 100644 --- a/host/utils/uhd_cal_tx_dc_offset.cpp +++ b/host/utils/uhd_cal_tx_dc_offset.cpp @@ -20,6 +20,7 @@ #include <uhd/utils/safe_main.hpp> #include <uhd/utils/paths.hpp> #include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/usrp/multi_usrp.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> @@ -94,7 +95,7 @@ static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_l * Main **********************************************************************/ int UHD_SAFE_MAIN(int argc, char *argv[]){ - std::string args; + std::string args, subdev, serial; double tx_wave_freq, tx_wave_ampl, rx_offset; double freq_start, freq_stop, freq_step; size_t nsamps; @@ -104,6 +105,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("help", "help message") ("verbose", "enable some verbose") ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("subdev", po::value<std::string>(&subdev), "Subdevice specification (default: first subdevice, often 'A')") ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") @@ -131,6 +133,15 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + // Configure subdev + if (vm.count("subdev")) { + usrp->set_tx_subdev_spec(subdev); + usrp->set_rx_subdev_spec(subdev); + } + UHD_MSG(status) << "Running calibration for " << usrp->get_tx_subdev_name(0) << std::endl; + serial = get_serial(usrp, "tx"); + UHD_MSG(status) << "Daughterboard serial: " << serial << std::endl; + //set the antennas to cal if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); @@ -160,6 +171,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + UHD_MSG(status) << boost::format("Calibration frequency range: %d MHz -> %d MHz") % (freq_start/1e6) % (freq_stop/1e6) << std::endl; for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); @@ -235,7 +247,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ threads.interrupt_all(); threads.join_all(); - store_results(usrp, results, "TX", "tx", "dc"); + store_results(results, "TX", "tx", "dc", serial); return EXIT_SUCCESS; } diff --git a/host/utils/uhd_cal_tx_iq_balance.cpp b/host/utils/uhd_cal_tx_iq_balance.cpp index 20d018edf..786aac061 100644 --- a/host/utils/uhd_cal_tx_iq_balance.cpp +++ b/host/utils/uhd_cal_tx_iq_balance.cpp @@ -20,6 +20,7 @@ #include <uhd/utils/safe_main.hpp> #include <uhd/utils/paths.hpp> #include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/usrp/multi_usrp.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> @@ -95,7 +96,7 @@ static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_l * Main **********************************************************************/ int UHD_SAFE_MAIN(int argc, char *argv[]){ - std::string args; + std::string args, subdev, serial; double tx_wave_freq, tx_wave_ampl, rx_offset; double freq_start, freq_stop, freq_step; size_t nsamps; @@ -105,6 +106,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ ("help", "help message") ("verbose", "enable some verbose") ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("subdev", po::value<std::string>(&subdev), "Subdevice specification (default: first subdevice, often 'A')") ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") @@ -122,7 +124,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ if (vm.count("help")){ std::cout << boost::format("USRP Generate TX IQ Balance Calibration Table %s") % desc << std::endl; std::cout << - "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + "This application measures leakage between RX and TX on a daughterboard to self-calibrate.\n" << std::endl; return EXIT_FAILURE; } @@ -132,6 +134,15 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + // Configure subdev + if (vm.count("subdev")) { + usrp->set_tx_subdev_spec(subdev); + usrp->set_rx_subdev_spec(subdev); + } + UHD_MSG(status) << "Running calibration for " << usrp->get_tx_subdev_name(0) << std::endl; + serial = get_serial(usrp, "tx"); + UHD_MSG(status) << "Daughterboard serial: " << serial << std::endl; + //set the antennas to cal if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); @@ -161,6 +172,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + UHD_MSG(status) << boost::format("Calibration frequency range: %d MHz -> %d MHz") % (freq_start/1e6) % (freq_stop/1e6) << std::endl; for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); @@ -241,7 +253,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ threads.interrupt_all(); threads.join_all(); - store_results(usrp, results, "TX", "tx", "iq"); + store_results(results, "TX", "tx", "iq", serial); return EXIT_SUCCESS; } + diff --git a/host/utils/usrp_cal_utils.hpp b/host/utils/usrp_cal_utils.hpp index bab6ddd91..5aff5e22f 100644 --- a/host/utils/usrp_cal_utils.hpp +++ b/host/utils/usrp_cal_utils.hpp @@ -53,7 +53,7 @@ static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp){ const uhd::fs_path mb_path = "/mboards/0"; const std::string mb_name = tree->access<std::string>(mb_path / "name").get(); - if (mb_name.find("USRP2") != std::string::npos or mb_name.find("N200") != std::string::npos or mb_name.find("N210") != std::string::npos){ + if (mb_name.find("USRP2") != std::string::npos or mb_name.find("N200") != std::string::npos or mb_name.find("N210") != std::string::npos or mb_name.find("X300") != std::string::npos or mb_name.find("X310") != std::string::npos){ usrp->set_tx_rate(12.5e6); usrp->set_rx_rate(12.5e6); } @@ -179,27 +179,37 @@ static inline void write_samples_to_file( outfile.close(); } + /*********************************************************************** - * Store data to file + * Retrieve d'board serial **********************************************************************/ -static void store_results( +static std::string get_serial( uhd::usrp::multi_usrp::sptr usrp, - const std::vector<result_t> &results, - const std::string &XX, - const std::string &xx, - const std::string &what + const std::string &tx_rx ){ - //extract eeprom serial uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); - const uhd::fs_path db_path = "/mboards/0/dboards/A/" + xx + "_eeprom"; + uhd::usrp::subdev_spec_t subdev_spec = usrp->get_rx_subdev_spec(); + const uhd::fs_path db_path = "/mboards/0/dboards/" + subdev_spec[0].db_name + "/" + tx_rx + "_eeprom"; const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + return db_eeprom.serial; +} +/*********************************************************************** + * Store data to file + **********************************************************************/ +static void store_results( + const std::vector<result_t> &results, + const std::string &XX, // "TX" or "RX" + const std::string &xx, // "tx" or "rx" + const std::string &what, // Type of test, e.g. "iq", + const std::string &serial +){ //make the calibration file path fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd"; fs::create_directory(cal_data_path); cal_data_path = cal_data_path / "cal"; fs::create_directory(cal_data_path); - cal_data_path = cal_data_path / str(boost::format("%s_%s_cal_v0.2_%s.csv") % xx % what % db_eeprom.serial); + cal_data_path = cal_data_path / str(boost::format("%s_%s_cal_v0.2_%s.csv") % xx % what % serial); if (fs::exists(cal_data_path)){ fs::rename(cal_data_path, cal_data_path.string() + str(boost::format(".%d") % time(NULL))); } @@ -207,7 +217,7 @@ static void store_results( //fill the calibration file std::ofstream cal_data(cal_data_path.string().c_str()); cal_data << boost::format("name, %s Frontend Calibration\n") % XX; - cal_data << boost::format("serial, %s\n") % db_eeprom.serial; + cal_data << boost::format("serial, %s\n") % serial; cal_data << boost::format("timestamp, %d\n") % time(NULL); cal_data << boost::format("version, 0, 1\n"); cal_data << boost::format("DATA STARTS HERE\n"); @@ -259,3 +269,4 @@ static void capture_samples( throw std::runtime_error("did not get all the samples requested"); } } + diff --git a/host/utils/usrp_n2xx_simple_net_burner.cpp b/host/utils/usrp_n2xx_simple_net_burner.cpp index 1898ee9ae..277e807d9 100644 --- a/host/utils/usrp_n2xx_simple_net_burner.cpp +++ b/host/utils/usrp_n2xx_simple_net_burner.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2013 Ettus Research LLC +// Copyright 2012-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 @@ -15,6 +15,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#include <csignal> #include <iostream> #include <map> #include <fstream> @@ -46,6 +47,25 @@ using namespace boost::algorithm; using namespace uhd; using namespace uhd::transport; +/*********************************************************************** + * Signal handlers + **********************************************************************/ +static int num_ctrl_c = 0; +void sig_int_handler(int){ + num_ctrl_c++; + if(num_ctrl_c == 1){ + std::cout << std::endl << "Are you sure you want to abort the image burning? If you do, your " + "USRP-N Series unit will be bricked!" << std::endl + << "Press Ctrl+C again to abort the image burning procedure." << std::endl << std::endl; + } + else{ + std::cout << std::endl << "Aborting. Your USRP-N Series unit will be bricked." << std::endl + << "Refer to http://files.ettus.com/uhd_docs/manual/html/usrp2.html#device-recovery-and-bricking" << std::endl + << "for details on restoring your device." << std::endl; + exit(EXIT_FAILURE); + } +} + //Mapping revision numbers to filenames std::map<boost::uint32_t, std::string> filename_map = boost::assign::map_list_of (0xa, "n200_r3") @@ -498,7 +518,7 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ std::cout << boost::format(" * Sector size: %3.2f\n\n") % flash_info[0]; //Burning images - + std::signal(SIGINT, &sig_int_handler); if(burn_fpga){ erase_image(udp_transport, false, flash_info[1]); write_image(udp_transport, false, fpga_image, flash_info[1], fpga_image_size); @@ -511,6 +531,8 @@ int UHD_SAFE_MAIN(int argc, char *argv[]){ verify_image(udp_transport, true, fw_image, flash_info[1], fw_image_size); } + delete(flash_info); + //Reset USRP N2XX bool reset = false; if(auto_reboot) reset = true; diff --git a/host/utils/usrp_x3xx_fpga_burner.cpp b/host/utils/usrp_x3xx_fpga_burner.cpp new file mode 100644 index 000000000..2f0202e84 --- /dev/null +++ b/host/utils/usrp_x3xx_fpga_burner.cpp @@ -0,0 +1,513 @@ +// +// 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 <csignal> +#include <iostream> +#include <map> +#include <fstream> +#include <stdexcept> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <vector> + +#include <boost/foreach.hpp> +#include <boost/asio.hpp> +#include <boost/program_options.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <boost/assign.hpp> +#include <boost/cstdint.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string/erase.hpp> +#include <boost/filesystem.hpp> +#include <boost/thread/thread.hpp> + +#include <uhd/exception.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/nirio/niusrprio_session.h> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/device.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/images.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/safe_call.hpp> + +#ifdef _MSC_VER +extern "C" { +#endif +#include "cdecode.h" +#ifdef _MSC_VER +} +#endif + +#define X300_FPGA_BIN_SIZE_BYTES 15877916 +#define X300_FPGA_BIT_MAX_SIZE_BYTES 15878022 +#define X300_FPGA_PROG_UDP_PORT 49157 +#define X300_FLASH_SECTOR_SIZE 131072 +#define X300_PACKET_SIZE_BYTES 256 +#define X300_FPGA_SECTOR_START 32 +#define X300_MAX_RESPONSE_BYTES 128 +#define UDP_TIMEOUT 3 +#define FPGA_LOAD_TIMEOUT 15 + +#define X300_FPGA_PROG_FLAGS_ACK 1 +#define X300_FPGA_PROG_FLAGS_ERROR 2 +#define X300_FPGA_PROG_FLAGS_INIT 4 +#define X300_FPGA_PROG_FLAGS_CLEANUP 8 +#define X300_FPGA_PROG_FLAGS_ERASE 16 +#define X300_FPGA_PROG_FLAGS_VERIFY 32 +#define X300_FPGA_PROG_CONFIGURE 64 +#define X300_FPGA_PROG_CONFIG_STATUS 128 + +namespace fs = boost::filesystem; +namespace po = boost::program_options; + +using namespace uhd; +using namespace uhd::transport; + +static int num_ctrl_c = 0; +void sig_int_handler(int){ + num_ctrl_c++; + if(num_ctrl_c == 1){ + std::cout << std::endl << "Are you sure you want to abort the image burning? If you do, your " + "USRP-X series device will be bricked!" << std::endl + << "Press Ctrl+C again to abort the image burning procedure." << std::endl << std::endl; + } + else{ + std::cout << std::endl << "Aborting. Your USRP X-Series device will be bricked." << std::endl + << "Refer to http://files.ettus.com/uhd_docs/manual/html/usrp_x3x0.html#use-jtag-to-load-fpga-images" << std::endl + << "for details on restoring your device." << std::endl; + exit(EXIT_FAILURE); + } +} + +typedef struct { + boost::uint32_t flags; + boost::uint32_t sector; + boost::uint32_t index; + boost::uint32_t size; + boost::uint16_t data[128]; +} x300_fpga_update_data_t; + +boost::uint8_t x300_data_in_mem[udp_simple::mtu]; +boost::uint8_t intermediary_packet_data[X300_PACKET_SIZE_BYTES]; + +boost::uint8_t bitswap(uint8_t b){ + b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4); + b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2); + b = ((b & 0xAA) >> 1) | ((b & 0x55) << 1); + + return b; +} + +void list_usrps(){ + device_addrs_t found_devices = device::find(device_addr_t("type=x300")); + + std::cout << "Available X3x0 devices:" << std::endl; + BOOST_FOREACH(const device_addr_t &dev, found_devices){ + std::string dev_string; + if(dev.has_key("addr")){ + dev_string = str(boost::format(" * %s (%s, addr: %s)") + % dev["product"] + % dev["fpga"] + % dev["addr"]); + } + else{ + dev_string = str(boost::format(" * %s (%s, resource: %s)") + % dev["product"] + % dev["fpga"] + % dev["resource"]); + } + std::cout << dev_string << std::endl; + } +} + +device_addr_t find_usrp_with_ethernet(std::string ip_addr, bool output){ + if(output) std::cout << "Attempting to find X3x0 with IP address: " << ip_addr << std::endl; + const device_addr_t dev = device_addr_t(str(boost::format("addr=%s") % ip_addr)); + device_addrs_t found_devices = device::find(dev); + + if(found_devices.size() < 1) { + throw std::runtime_error("Could not find X3x0 with the specified address!"); + } + else if(found_devices.size() > 1) { + throw std::runtime_error("Found multiple X3x0 units with the specified address!"); + } + else { + if(output) std::cout << (boost::format("Found %s (%s).\n\n") + % found_devices[0]["product"] + % found_devices[0]["fpga"]); + } + return found_devices[0]; +} + +device_addr_t find_usrp_with_pcie(std::string resource, bool output){ + if(output) std::cout << "Attempting to find X3x0 with resource: " << resource << std::endl; + const device_addr_t dev = device_addr_t(str(boost::format("resource=%s") % resource)); + device_addrs_t found_devices = device::find(dev); + + if(found_devices.size() < 1) { + throw std::runtime_error("Could not find X3x0 with the specified resource!"); + } + else { + if(output) std::cout << (boost::format("Found %s (%s).\n\n") + % found_devices[0]["product"] + % found_devices[0]["fpga"]); + } + return found_devices[0]; +} + +std::string get_default_image_path(std::string model, std::string image_type){ + std::transform(model.begin(), model.end(), model.begin(), ::tolower); + + std::string image_name = str(boost::format("usrp_%s_fpga_%s.bit") + % model.c_str() % image_type.c_str()); + + return find_image_path(image_name); +} + +void extract_from_lvbitx(std::string lvbitx_path, std::vector<char> &bitstream){ + boost::property_tree::ptree pt; + boost::property_tree::xml_parser::read_xml(lvbitx_path.c_str(), pt, + boost::property_tree::xml_parser::no_comments | + boost::property_tree::xml_parser::trim_whitespace); + std::string const encoded_bitstream(pt.get<std::string>("Bitfile.Bitstream")); + std::vector<char> decoded_bitstream(encoded_bitstream.size()); + + base64_decodestate decode_state; + base64_init_decodestate(&decode_state); + size_t const decoded_size = base64_decode_block(encoded_bitstream.c_str(), + encoded_bitstream.size(), &decoded_bitstream.front(), &decode_state); + decoded_bitstream.resize(decoded_size); + bitstream.swap(decoded_bitstream); +} + +void ethernet_burn(udp_simple::sptr udp_transport, std::string fpga_path, bool verify){ + boost::uint32_t max_size; + std::vector<char> bitstream; + + if(fs::extension(fpga_path) == ".bit") max_size = X300_FPGA_BIT_MAX_SIZE_BYTES; + else max_size = X300_FPGA_BIN_SIZE_BYTES; //Use for both .bin and .lvbitx + + bool is_lvbitx = (fs::extension(fpga_path) == ".lvbitx"); + + size_t fpga_image_size; + FILE* file; + if((file = fopen(fpga_path.c_str(), "rb"))){ + fseek(file, 0, SEEK_END); + if(is_lvbitx){ + extract_from_lvbitx(fpga_path, bitstream); + fpga_image_size = bitstream.size(); + } + else fpga_image_size = ftell(file); + if(fpga_image_size > max_size){ + fclose(file); + throw std::runtime_error(str(boost::format("FPGA size is too large (%d > %d).") + % fpga_image_size % max_size)); + } + rewind(file); + } + else{ + throw std::runtime_error(str(boost::format("Could not find FPGA image at location: %s") + % fpga_path.c_str())); + } + + const x300_fpga_update_data_t *update_data_in = reinterpret_cast<const x300_fpga_update_data_t *>(x300_data_in_mem); + + x300_fpga_update_data_t ack_packet; + ack_packet.flags = htonx<boost::uint32_t>(X300_FPGA_PROG_FLAGS_ACK | X300_FPGA_PROG_FLAGS_INIT); + ack_packet.sector = 0; + ack_packet.size = 0; + ack_packet.index = 0; + memset(ack_packet.data, 0, sizeof(ack_packet.data)); + udp_transport->send(boost::asio::buffer(&ack_packet, sizeof(ack_packet))); + + udp_transport->recv(boost::asio::buffer(x300_data_in_mem), UDP_TIMEOUT); + if((ntohl(update_data_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) != X300_FPGA_PROG_FLAGS_ERROR){ + std::cout << "Burning image: " << fpga_path << std::endl; + if(verify) std::cout << "NOTE: Verifying image. Burning will take much longer." << std::endl; + std::cout << std::endl; + } + else{ + throw std::runtime_error("Failed to start image burning! Did you specify the correct IP address? If so, power-cycle the device and try again."); + } + + size_t current_pos = 0; + size_t sectors = fpga_image_size / X300_FLASH_SECTOR_SIZE; + + //Each sector + for(size_t i = 0; i < fpga_image_size; i += X300_FLASH_SECTOR_SIZE){ + + //Print progress percentage at beginning of each sector + std::cout << "\rProgress: " << int(double(i)/double(fpga_image_size)*100) + << "% (" << (i / X300_FLASH_SECTOR_SIZE) << "/" + << sectors << " sectors)" << std::flush; + + //Each packet + for(size_t j = i; (j < fpga_image_size and j < (i+X300_FLASH_SECTOR_SIZE)); j += X300_PACKET_SIZE_BYTES){ + x300_fpga_update_data_t send_packet; + + send_packet.flags = X300_FPGA_PROG_FLAGS_ACK; + if(verify) send_packet.flags |= X300_FPGA_PROG_FLAGS_VERIFY; + if(j == i) send_packet.flags |= X300_FPGA_PROG_FLAGS_ERASE; //Erase the sector before writing + send_packet.flags = htonx<boost::uint32_t>(send_packet.flags); + + send_packet.sector = htonx<boost::uint32_t>(X300_FPGA_SECTOR_START + (i/X300_FLASH_SECTOR_SIZE)); + send_packet.index = htonx<boost::uint32_t>((j % X300_FLASH_SECTOR_SIZE) / 2); + send_packet.size = htonx<boost::uint32_t>(X300_PACKET_SIZE_BYTES / 2); + memset(intermediary_packet_data,0,X300_PACKET_SIZE_BYTES); + memset(send_packet.data,0,X300_PACKET_SIZE_BYTES); + if(!is_lvbitx) current_pos = ftell(file); + + if(current_pos + X300_PACKET_SIZE_BYTES > fpga_image_size){ + if(is_lvbitx){ + memcpy(intermediary_packet_data, (&bitstream[current_pos]), (bitstream.size()-current_pos+1)); + } + else{ + size_t len = fread(intermediary_packet_data, sizeof(boost::uint8_t), (fpga_image_size-current_pos), file); + if(len != (fpga_image_size-current_pos)){ + throw std::runtime_error("Error reading from file!"); + } + } + } + else{ + if(is_lvbitx){ + memcpy(intermediary_packet_data, (&bitstream[current_pos]), X300_PACKET_SIZE_BYTES); + current_pos += X300_PACKET_SIZE_BYTES; + } + else{ + size_t len = fread(intermediary_packet_data, sizeof(boost::uint8_t), X300_PACKET_SIZE_BYTES, file); + if(len != X300_PACKET_SIZE_BYTES){ + throw std::runtime_error("Error reading from file!"); + } + } + } + + for(size_t k = 0; k < X300_PACKET_SIZE_BYTES; k++){ + intermediary_packet_data[k] = bitswap(intermediary_packet_data[k]); + } + + memcpy(send_packet.data, intermediary_packet_data, X300_PACKET_SIZE_BYTES); + + for(size_t k = 0; k < (X300_PACKET_SIZE_BYTES/2); k++){ + send_packet.data[k] = htonx<boost::uint16_t>(send_packet.data[k]); + } + + udp_transport->send(boost::asio::buffer(&send_packet, sizeof(send_packet))); + + if (udp_transport->recv(boost::asio::buffer(x300_data_in_mem), UDP_TIMEOUT) == 0) + throw std::runtime_error("Timed out waiting for ACK!"); + + const x300_fpga_update_data_t *update_data_in = reinterpret_cast<const x300_fpga_update_data_t *>(x300_data_in_mem); + + if((ntohl(update_data_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) == X300_FPGA_PROG_FLAGS_ERROR){ + throw std::runtime_error("Transfer or data verification failed!"); + } + } + } + fclose(file); + + //Send clean-up signal + x300_fpga_update_data_t cleanup_packet; + cleanup_packet.flags = htonx<boost::uint32_t>(X300_FPGA_PROG_FLAGS_ACK | X300_FPGA_PROG_FLAGS_CLEANUP); + cleanup_packet.sector = 0; + cleanup_packet.size = 0; + cleanup_packet.index = 0; + memset(cleanup_packet.data, 0, sizeof(cleanup_packet.data)); + udp_transport->send(boost::asio::buffer(&cleanup_packet, sizeof(cleanup_packet))); + + if (udp_transport->recv(boost::asio::buffer(x300_data_in_mem), UDP_TIMEOUT) == 0) + throw std::runtime_error("Timed out waiting for ACK!"); + const x300_fpga_update_data_t *cleanup_data_in = reinterpret_cast<const x300_fpga_update_data_t *>(x300_data_in_mem); + + if((ntohl(cleanup_data_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) == X300_FPGA_PROG_FLAGS_ERROR){ + throw std::runtime_error("Transfer or data verification failed!"); + } + + std::cout << "\rProgress: " << "100% (" << sectors << "/" << sectors << " sectors)" << std::endl; +} + +void pcie_burn(std::string resource, std::string rpc_port, std::string fpga_path) +{ + std::cout << "Burning image: " << fpga_path << std::endl; + std::cout << "This will take 3-10 minutes." << std::endl; + + nirio_status status = NiRio_Status_Success; + + uhd::niusrprio::niusrprio_session fpga_session(resource, rpc_port); + nirio_status_chain(fpga_session.download_bitstream_to_flash(fpga_path), status); + + if(nirio_status_fatal(status)) throw std::runtime_error("Failed to burn FPGA image!"); +} + +bool configure_fpga(udp_simple::sptr udp_transport, std::string ip_addr){ + x300_fpga_update_data_t configure_packet; + configure_packet.flags = htonx<boost::uint32_t>(X300_FPGA_PROG_CONFIGURE | X300_FPGA_PROG_FLAGS_ACK); + configure_packet.sector = 0; + configure_packet.size = 0; + configure_packet.index = 0; + memset(configure_packet.data, 0, sizeof(configure_packet.data)); + udp_transport->send(boost::asio::buffer(&configure_packet, sizeof(configure_packet))); + + udp_transport->recv(boost::asio::buffer(x300_data_in_mem), UDP_TIMEOUT); + const x300_fpga_update_data_t *configure_data_in = reinterpret_cast<const x300_fpga_update_data_t *>(x300_data_in_mem); + bool successful = false; + + if((ntohl(configure_data_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) == X300_FPGA_PROG_FLAGS_ERROR){ + throw std::runtime_error("Transfer or data verification failed!"); + } + else{ + std::cout << std::endl << "Waiting for X3x0 to configure FPGA image and reload." << std::endl; + boost::this_thread::sleep(boost::posix_time::milliseconds(5000)); + + x300_fpga_update_data_t config_status_packet; + configure_packet.flags = htonx<boost::uint32_t>(X300_FPGA_PROG_CONFIG_STATUS); + config_status_packet.sector = 0; + config_status_packet.size = 0; + config_status_packet.index = 0; + memset(config_status_packet.data, 0, sizeof(config_status_packet.data)); + for(int i = 0; i < 5; i++){ + udp_transport->send(boost::asio::buffer(&config_status_packet, sizeof(config_status_packet))); + udp_transport->recv(boost::asio::buffer(x300_data_in_mem), 1); + const x300_fpga_update_data_t *config_status_data_in = reinterpret_cast<const x300_fpga_update_data_t *>(x300_data_in_mem); + + if((ntohl(config_status_data_in->flags) & X300_FPGA_PROG_FLAGS_ERROR) != X300_FPGA_PROG_FLAGS_ERROR + and udp_transport->get_recv_addr() == ip_addr){ + successful = true; + break; + } + successful = false; //If it worked, the break would skip this + } + } + return successful; +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + memset(intermediary_packet_data, 0, X300_PACKET_SIZE_BYTES); + std::string ip_addr, resource, fpga_path, image_type, rpc_port; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "Display this help message.") + ("addr", po::value<std::string>(&ip_addr), "Specify an IP address.") + ("resource", po::value<std::string>(&resource), "Specify an NI-RIO resource.") + ("rpc-port", po::value<std::string>(&rpc_port)->default_value("5444"), "Specify a port to communicate with the RPC server.") + ("type", po::value<std::string>(&image_type), "Specify an image type (1G, HGS, XGS), leave blank for current type.") + ("fpga-path", po::value<std::string>(&fpga_path), "Specify an FPGA path (overrides --type option).") + ("configure", "Initialize FPGA with image currently burned to flash (Ethernet only).") + ("verify", "Verify data downloaded to flash (Ethernet only, download will take much longer)") + ("list", "List all available X3x0 devices.") + ; + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //Print help message + if(vm.count("help")){ + std::cout << "USRP X3x0 FPGA Burner" << std::endl << std::endl; + + std::cout << "Burns an FPGA image onto a USRP X300/X310. To burn the image" << std::endl + << "over Ethernet, specify an IP address with the --addr option," << std::endl + << "or to burn over PCIe, specify an NI-RIO resource (ex. RIO0)" << std::endl + << "with the --resource option." << std::endl << std::endl; + + std::cout << desc << std::endl; + return EXIT_SUCCESS; + } + + //List all available devices + if(vm.count("list")){ + list_usrps(); + return EXIT_SUCCESS; + } + + /* + * The user must specify whether to burn the image over Ethernet or PCI-e. + */ + if(not (vm.count("addr") xor vm.count("resource"))){ + throw std::runtime_error("You must specify addr OR resource!"); + } + + /* + * With settings validated, find X3x0 with specified arguments. + */ + device_addr_t dev = (vm.count("addr")) ? find_usrp_with_ethernet(ip_addr, true) + : find_usrp_with_pcie(resource, true); + + /* + * If custom FPGA path is given, ignore specified type and let FPGA + * figure it out. + */ + if(vm.count("fpga-path")){ + //Expand tilde usage if applicable + #ifndef UHD_PLATFORM_WIN32 + if(fpga_path.find("~/") == 0) fpga_path.replace(0,1,getenv("HOME")); + #endif + } + else{ + if(vm.count("type")){ + //Make sure the specified type is 1G, HGS, or XGS + if((image_type != "1G") and (image_type != "HGS") and (image_type != "XGS")){ + throw std::runtime_error("--type must be 1G, HGS, or XGS!"); + } + else fpga_path = get_default_image_path(dev["product"], image_type); + } + else{ + //Use default image of currently present FPGA type + fpga_path = get_default_image_path(dev["product"], dev["fpga"]); + } + } + + /* + * Check validity of image through extension + */ + std::string ext = fs::extension(fpga_path.c_str()); + if(ext != ".bin" and ext != ".bit" and ext != ".lvbitx"){ + throw std::runtime_error("The image filename must end in .bin, .bit, or .lvbitx."); + } + + std::signal(SIGINT, &sig_int_handler); + if(vm.count("addr")){ + udp_simple::sptr udp_transport = udp_simple::make_connected(ip_addr, BOOST_STRINGIZE(X300_FPGA_PROG_UDP_PORT)); + + ethernet_burn(udp_transport, fpga_path, vm.count("verify")); + + if(vm.count("configure")){ + if(configure_fpga(udp_transport, ip_addr)) std::cout << "Successfully configured FPGA!" << std::endl; + else throw std::runtime_error("FPGA configuring failed!"); + } + } + else pcie_burn(resource, rpc_port, fpga_path); + + /* + * Attempt to find USRP after burning + */ + std::cout << std::endl << "Attempting to find device..." << std::flush; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); //Sometimes needed for Ethernet to reconnect + device_addr_t found_usrp = (vm.count("addr")) ? find_usrp_with_ethernet(ip_addr, false) + : find_usrp_with_pcie(resource, false); + std::cout << "found!" << std::endl; //If unsuccessful, runtime error would occur in find functions + std::cout << "Successfully burned FPGA image!" << std::endl << std::endl; + + if(vm.count("addr")) std::cout << str(boost::format("Power-cycle the USRP %s to use the new image.") % found_usrp["product"]) << std::endl; + else std::cout << str(boost::format("Power-cycle the USRP %s and reboot your machine to use the new image.") % found_usrp["product"]) << std::endl; + + return EXIT_SUCCESS; +} |