aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2024-01-26 16:57:20 +0100
committerAki Tomita <121511582+atomita-ni@users.noreply.github.com>2024-02-28 09:38:28 -0600
commit6f892f797a3e0ffd1042a0137cf683ff47e39403 (patch)
treea4c933ae70a59707c682373bdb3f172bb5ca9770
parentpython: Make block_controller_factory_python.hpp public (diff)
downloaduhd-6f892f797a3e0ffd1042a0137cf683ff47e39403.tar.xz
uhd-6f892f797a3e0ffd1042a0137cf683ff47e39403.zip
examples: Add Python support to rfnoc-example
- Generate Pybind11 bindings for the C++ code - Generate a Python module (rfnoc_example) that can be imported and used - Add an example to show how the gain block affects dBFS readings
-rw-r--r--host/cmake/Modules/UHDPython.cmake2
-rw-r--r--host/examples/rfnoc-example/CMakeLists.txt50
-rw-r--r--host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake370
-rwxr-xr-xhost/examples/rfnoc-example/examples/rx_gain_estimate_power.py105
-rw-r--r--host/examples/rfnoc-example/lib/gain_block_control_python.hpp23
-rw-r--r--host/examples/rfnoc-example/python/CMakeLists.txt84
-rw-r--r--host/examples/rfnoc-example/python/pyrfnoc-example.cpp38
-rw-r--r--host/examples/rfnoc-example/python/rfnoc_example/__init__.py14
-rwxr-xr-xhost/examples/rfnoc-example/python/setup.py.in34
9 files changed, 710 insertions, 10 deletions
diff --git a/host/cmake/Modules/UHDPython.cmake b/host/cmake/Modules/UHDPython.cmake
index e53e63824..4fdacc243 100644
--- a/host/cmake/Modules/UHDPython.cmake
+++ b/host/cmake/Modules/UHDPython.cmake
@@ -5,6 +5,8 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
+# Note: When modifying this file, check if the changes also need to go into
+# host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake.
if (POLICY CMP0094)
# See https://cmake.org/cmake/help/v3.15/policy/CMP0094.html
diff --git a/host/examples/rfnoc-example/CMakeLists.txt b/host/examples/rfnoc-example/CMakeLists.txt
index 738b5ae8e..2c7e238ec 100644
--- a/host/examples/rfnoc-example/CMakeLists.txt
+++ b/host/examples/rfnoc-example/CMakeLists.txt
@@ -8,20 +8,22 @@ cmake_minimum_required(VERSION 3.8)
project(rfnoc-example CXX C)
#make sure our local CMake Modules path comes first
-#list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules)
+list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules)
-#install to PyBOMBS target prefix if defined
-#if(DEFINED ENV{PYBOMBS_PREFIX})
-# set(CMAKE_INSTALL_PREFIX $ENV{PYBOMBS_PREFIX})
-# message(STATUS "PyBOMBS installed GNU Radio. Setting CMAKE_INSTALL_PREFIX to $ENV{PYBOMBS_PREFIX}")
-#endif()
+# Set the version information here
+set(VERSION_MAJOR 4)
+set(VERSION_API 9)
+set(VERSION_ABI 0)
########################################################################
# Setup install directories
########################################################################
-set(RFNOC_DATA_DIR share CACHE PATH "Base location for data")
-set(RFNOC_PKG_DATA_DIR ${RFNOC_DATA_DIR}/uhd/rfnoc/ CACHE PATH "Path to install RFNoC package data")
-set(PROJECT_DATA_DIR ${RFNOC_PKG_DATA_DIR}/example/ CACHE PATH "Path for this project's package data")
+set(RFNOC_DATA_DIR share
+ CACHE PATH "Base location for data")
+set(RFNOC_PKG_DATA_DIR ${RFNOC_DATA_DIR}/uhd/rfnoc/
+ CACHE PATH "Path to install RFNoC package data")
+set(PROJECT_DATA_DIR ${RFNOC_PKG_DATA_DIR}/example/
+ CACHE PATH "Path for this project's package data")
if(NOT DEFINED LIB_SUFFIX AND REDHAT AND CMAKE_SYSTEM_PROCESSOR MATCHES "64$")
set(LIB_SUFFIX 64)
@@ -49,9 +51,10 @@ endif()
###########################################################################
# Find UHD
###########################################################################
-find_package(UHD)
+find_package(UHD 4.6)
if(UHD_FOUND)
message(STATUS "Found UHD:")
+ message(STATUS " * Version: ${UHD_VERSION}")
include_directories(${UHD_INCLUDE_DIRS})
message(STATUS " * INCLUDES = ${UHD_INCLUDE_DIRS}")
link_directories(${UHD_LIBRARIES})
@@ -68,6 +71,30 @@ else()
endif()
###########################################################################
+# Find Python and uhd Python module
+###########################################################################
+include(UHDPython)
+
+PYTHON_CHECK_MODULE(
+ "UHD Python API"
+ "uhd"
+ "uhd.__version__ == '${UHD_VERSION}'"
+ HAVE_PYTHON_MODULE_UHD
+)
+
+if(pybind11_FOUND AND HAVE_PYTHON_MODULE_UHD)
+ set(ENABLE_PYTHON_API TRUE
+ CACHE BOOL "Enable Python API")
+else()
+ set(ENABLE_PYTHON_API FALSE
+ CACHE BOOL "Enable Python API")
+endif()
+
+if(ENABLE_PYTHON_API)
+ message(STATUS "Enabling Python API for this module.")
+endif()
+
+###########################################################################
# Find FPGA
###########################################################################
set(UHD_FPGA_DIR "" CACHE PATH "Path to FPGA source directory")
@@ -179,3 +206,6 @@ if(UHD_FOUND)
add_subdirectory(lib)
add_subdirectory(apps)
endif()
+if(ENABLE_PYTHON_API)
+ add_subdirectory(python)
+endif()
diff --git a/host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake b/host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake
new file mode 100644
index 000000000..4fdacc243
--- /dev/null
+++ b/host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake
@@ -0,0 +1,370 @@
+#
+# Copyright 2010-2011 Ettus Research LLC
+# Copyright 2018 Ettus Research, a National Instruments Company
+# Copyright 2019 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# Note: When modifying this file, check if the changes also need to go into
+# host/examples/rfnoc-example/cmake/Modules/UHDPython.cmake.
+
+if (POLICY CMP0094)
+ # See https://cmake.org/cmake/help/v3.15/policy/CMP0094.html
+ # set Python3_FIND_STRATEGY to LOCATION - this ensures that Python from
+ # sysroot is used first when cross-compiling
+ # note: policy CMP0094 is available starting with CMake 3.15
+ cmake_policy(SET CMP0094 NEW)
+endif()
+
+if(NOT DEFINED INCLUDED_UHD_PYTHON_CMAKE)
+set(INCLUDED_UHD_PYTHON_CMAKE TRUE)
+
+########################################################################
+# Setup Python Part 0: Pybind11
+#
+# We do this first so it doesn't interfere with the other steps. In
+# particular, searching for pybind11 will mess with PYTHON_VERSION.
+########################################################################
+find_package(pybind11 ${PYBIND11_MIN_VERSION} QUIET)
+
+########################################################################
+# Setup Python Part 1: Find the interpreters
+########################################################################
+message(STATUS "")
+message(STATUS "Configuring the Python interpreter...")
+#this allows the user to override PYTHON_EXECUTABLE
+if(PYTHON_EXECUTABLE)
+ set(PYTHONINTERP_FOUND TRUE)
+endif(PYTHON_EXECUTABLE)
+
+if(NOT PYTHONINTERP_FOUND)
+ find_package(Python3 ${PYTHON_MIN_VERSION} QUIET)
+ if(Python3_Interpreter_FOUND)
+ set(PYTHON_VERSION ${Python3_VERSION})
+ set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
+ set(PYTHONINTERP_FOUND TRUE)
+ endif(Python3_Interpreter_FOUND)
+endif(NOT PYTHONINTERP_FOUND)
+
+if(NOT PYTHONINTERP_FOUND)
+ find_package(PythonInterp ${PYTHON_MIN_VERSION} QUIET)
+ if(PYTHONINTERP_FOUND)
+ set(PYTHON_VERSION ${PYTHON_VERSION_STRING})
+ endif(PYTHONINTERP_FOUND)
+endif(NOT PYTHONINTERP_FOUND)
+
+# If that fails, try using the build-in find program routine.
+if(NOT PYTHONINTERP_FOUND)
+ message(STATUS "Attempting to find Python without CMake...")
+ find_program(PYTHON_EXECUTABLE NAMES python3 python3.6 python3.7 python3.8 python3.9)
+ if(PYTHON_EXECUTABLE)
+ set(PYTHONINTERP_FOUND TRUE)
+ endif(PYTHON_EXECUTABLE)
+endif(NOT PYTHONINTERP_FOUND)
+
+if(NOT PYTHON_VERSION)
+ message(STATUS "Manually determining build Python version...")
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "
+import sys
+print('{}.{}.{}'.format(
+ sys.version_info.major,
+ sys.version_info.minor,
+ sys.version_info.micro))"
+ OUTPUT_VARIABLE PYTHON_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+endif(NOT PYTHON_VERSION)
+
+# If we still haven't found a Python interpreter, then we're done.
+if(NOT PYTHONINTERP_FOUND)
+ message(FATAL_ERROR "Error: Python interpreter required by the build system.")
+endif(NOT PYTHONINTERP_FOUND)
+if(NOT PYTHON_EXECUTABLE)
+ message(FATAL_ERROR "Error: Python interpreter required by the build system.")
+endif(NOT PYTHON_EXECUTABLE)
+
+#make the path to the executable appear in the cmake gui
+set(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH
+ "python buildtime interpreter")
+
+message(STATUS "Python interpreter: ${PYTHON_EXECUTABLE} Version: ${PYTHON_VERSION}")
+message(STATUS "Override with: -DPYTHON_EXECUTABLE=<path-to-python>")
+
+#this allows the user to override RUNTIME_PYTHON_EXECUTABLE
+if(NOT RUNTIME_PYTHON_EXECUTABLE)
+ if(CMAKE_CROSSCOMPILING)
+ message(STATUS "Cross compiling, setting python runtime to /usr/bin/python3")
+ message(STATUS "and interpreter to min. required version ${PYTHON_MIN_VERSION}")
+ message(STATUS "If this is not what you want, please set RUNTIME_PYTHON_EXECUTABLE")
+ message(STATUS "and RUNTIME_PYTHON_VERSION manually")
+ set(RUNTIME_PYTHON_EXECUTABLE "/usr/bin/python3")
+ set(RUNTIME_PYTHON_VERSION ${PYTHON_MIN_VERSION})
+ set(EXACT_ARGUMENT "")
+ else(CMAKE_CROSSCOMPILING)
+ #default to the buildtime interpreter
+ set(RUNTIME_PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE})
+ set(RUNTIME_PYTHON_VERSION ${PYTHON_VERSION})
+ set(EXACT_ARGUMENT "EXACT")
+ endif(CMAKE_CROSSCOMPILING)
+else(NOT RUNTIME_PYTHON_EXECUTABLE)
+ set(EXACT_ARGUMENT "EXACT")
+endif(NOT RUNTIME_PYTHON_EXECUTABLE)
+
+if(NOT RUNTIME_PYTHON_VERSION)
+ message(STATUS "Manually determining runtime Python version...")
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "
+from __future__ import print_function
+import sys
+print('{}.{}.{}'.format(
+ sys.version_info.major,
+ sys.version_info.minor,
+ sys.version_info.micro))"
+ OUTPUT_VARIABLE RUNTIME_PYTHON_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+endif(NOT RUNTIME_PYTHON_VERSION)
+
+#make the path to the executable appear in the cmake gui
+set(RUNTIME_PYTHON_EXECUTABLE ${RUNTIME_PYTHON_EXECUTABLE} CACHE FILEPATH
+ "python runtime interpreter")
+
+message(STATUS "Python runtime interpreter: ${RUNTIME_PYTHON_EXECUTABLE} Version: ${RUNTIME_PYTHON_VERSION}")
+message(STATUS "Override with: -DRUNTIME_PYTHON_EXECUTABLE=<path-to-python>")
+
+###############################################################################
+# Determine if a Python module is installed, or, more generally, determine
+# if some condition that Python can report through a Boolean expression is
+# met. This macro allows one or more modules to be imported and a Python
+# Boolean expression to be evaluated.
+#
+# - desc:
+# Description of what's being checked (for user feedback)
+# - module:
+# The module(s) to be passed to the `import` command
+# - bool_expr:
+# A Python expression to be evaluated that returns True or False based on
+# the presence or absence of the module (or in the general case, the
+# condition being checked)
+# - have_ver:
+# The variable name to be set to TRUE if the Python expression returns True,
+# or FALSE otherwise
+macro(PYTHON_CHECK_MODULE desc module bool_expr have_var)
+ message(STATUS "")
+ message(STATUS "Python checking for ${desc}")
+ execute_process(
+ COMMAND ${PYTHON_EXECUTABLE} -c "
+#########################################
+try:
+ import ${module}
+except:
+ exit(1)
+try:
+ assert ${bool_expr}
+except:
+ exit(2)
+exit(0)
+#########################################"
+ RESULT_VARIABLE python_result
+ )
+ if(python_result EQUAL 0)
+ message(STATUS "Python checking for ${desc} - found")
+ set(${have_var} TRUE)
+ elseif(python_result EQUAL 1)
+ message(STATUS "Python checking for ${desc} - \"import ${module}\" failed (is it installed?)")
+ set(${have_var} FALSE)
+ elseif(python_result EQUAL 2)
+ message(STATUS "Python checking for ${desc} - \"assert ${bool_expr}\" failed")
+ set(${have_var} FALSE)
+ else()
+ message(STATUS "Python checking for ${desc} - unknown error")
+ set(${have_var} FALSE)
+ endif()
+endmacro(PYTHON_CHECK_MODULE)
+
+
+###############################################################################
+# Determine if a Python module is installed and if it meets a minimum required
+# version.
+#
+# - desc:
+# Description of what's being checked (for user feedback)
+# - module:
+# The module to be `import`ed
+# - module_version_expr:
+# A Python expression to be evaluated that returns the module version string
+# (usually "module_name.__version__", but may be tailored for non-conformant
+# modules, or other custom use cases)
+# - min_module_version:
+# The minimum version required of the module as a canonical Python version
+# string ("major.minor.micro") as defined in PEP 440
+# - have_ver:
+# The variable name to be set to TRUE if the module is present and meets
+# the minimum version requirement or FALSE otherwise
+macro(PYTHON_CHECK_MODULE_VERSION desc module module_version_expr min_module_version have_var)
+ message(STATUS "")
+ message(STATUS "Python checking for ${desc}")
+ execute_process(
+ COMMAND ${PYTHON_EXECUTABLE} -c "
+#########################################
+try:
+ import ${module}
+except:
+ exit(1)
+try:
+ version = ${module_version_expr}
+ print(version)
+except:
+ exit(2)
+exit(0)
+#########################################"
+ RESULT_VARIABLE python_result
+ OUTPUT_VARIABLE version_output
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ if(python_result EQUAL 0)
+ if(${version_output} VERSION_GREATER_EQUAL ${min_module_version})
+ message(STATUS "Python checking for ${desc} - ${version_output} satisfies minimum required version ${min_module_version}")
+ set(${have_var} TRUE)
+ else()
+ message(STATUS "Python checking for ${desc} - ${version_output} does not satisfy minimum required version ${min_module_version}")
+ set(${have_var} FALSE)
+ endif()
+ elseif(python_result EQUAL 1)
+ message(STATUS "Python checking for ${desc} - \"import ${module}\" failed (is it installed?)")
+ set(${have_var} FALSE)
+ elseif(python_result EQUAL 2)
+ message(STATUS "Python checking for ${desc} - evaluation of \"${module_version_expr}\" failed")
+ set(${have_var} FALSE)
+ else()
+ message(STATUS "Python checking for ${desc} - unknown error")
+ set(${have_var} FALSE)
+ endif()
+endmacro(PYTHON_CHECK_MODULE_VERSION)
+
+
+###############################################################################
+# Install a Python module into a system/prefix/virtualenv location.
+#
+# - LIBTARGET: Is there a library target included in this module (e.g., pyuhd)?
+# If so, state its name here. It will update RPATH on Linux/Unix
+# systems.
+# - MODULE: Name of module (e.g., 'uhd')
+macro(PYTHON_INSTALL_MODULE)
+ cmake_parse_arguments(
+ _py_install_mod
+ "" "LIBTARGET;MODULE" ""
+ ${ARGN}
+ )
+
+ # Check if we're in a virtual environment -- the rules are a bit different
+ # there.
+ PYTHON_CHECK_MODULE(
+ "virtual environment"
+ "sys"
+ "sys.prefix != sys.base_prefix"
+ HAVE_PYTHON_VIRTUALENV
+ )
+
+ if(HAVE_PYTHON_VIRTUALENV)
+ message(
+ STATUS
+ "Python virtual environment detected -- Ignoring UHD_PYTHON_DIR.")
+ # In virtualenvs, let setuptools do its thing
+ install(CODE "message(\"Installing ${_py_install_mod_MODULE} Python module into venv via pip.\")")
+ install(CODE
+ "execute_process(COMMAND pip3 install . --force-reinstall WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})")
+ else()
+ # Otherwise, use sysconfig to determine the correct relative path for Python
+ # packages, and install to our prefix
+ if(NOT DEFINED UHD_PYTHON_DIR)
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c
+ # Avoid the posix_local install scheme
+ "import os,sysconfig;\
+ install_scheme = 'posix_user';\
+ platlib = sysconfig.get_path('platlib', scheme=install_scheme);\
+ prefix = sysconfig.get_config_var('prefix');\
+ print(os.path.relpath(platlib, prefix));"
+ OUTPUT_VARIABLE UHD_PYTHON_DIR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ endif(NOT DEFINED UHD_PYTHON_DIR)
+ file(TO_CMAKE_PATH ${UHD_PYTHON_DIR} UHD_PYTHON_DIR)
+
+ message(
+ STATUS
+ "Installing '${_py_install_mod_MODULE}' Python module to: "
+ "${CMAKE_INSTALL_PREFIX}/${UHD_PYTHON_DIR}")
+ # We use sysconfig (above) to figure out the destination path, and then
+ # we simply copy this module recursively into its final destination.
+ install(DIRECTORY
+ ${CMAKE_CURRENT_BINARY_DIR}/${_py_install_mod_MODULE}
+ DESTINATION ${UHD_PYTHON_DIR}
+ COMPONENT pythonapi
+ )
+ # On Linux/Unix systems, we must properly install the library file.
+ # install(DIRECTORY) will treat the .so file like any other file, which
+ # means it won't update its RPATH, and thus the RPATH would be stuck to the
+ # build directory.
+ if(UNIX AND _py_install_mod_LIBTARGET)
+ install(TARGETS ${_py_install_mod_LIBTARGET}
+ DESTINATION ${UHD_PYTHON_DIR}/${_py_install_mod_MODULE}
+ )
+ endif()
+ endif(HAVE_PYTHON_VIRTUALENV)
+endmacro(PYTHON_INSTALL_MODULE)
+
+###############################################################################
+# Part 2: Python Libraries
+###############################################################################
+# The libraries must match the RUNTIME_PYTHON_EXECUTABLE's version.
+# - Figure out version
+# - See if Python3_LIBRARIES is already set (or Python2_LIBRARIES)
+if(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ message(STATUS "Finding Python Libraries...")
+ find_package(PythonLibs ${RUNTIME_PYTHON_VERSION} ${EXACT_ARGUMENT} QUIET)
+ if(NOT RUNTIME_PYTHON_VERSION VERSION_LESS 3)
+ if(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ find_package(Python3 ${RUNTIME_PYTHON_VERSION}
+ ${EXACT_ARGUMENT}
+ QUIET
+ COMPONENTS Interpreter Development)
+ if(Python3_Development_FOUND)
+ set(PYTHON_LIBRARIES ${Python3_LIBRARIES})
+ set(PYTHON_INCLUDE_DIRS ${Python3_INCLUDE_DIRS})
+ endif(Python3_Development_FOUND)
+ endif(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ else(NOT RUNTIME_PYTHON_VERSION VERSION_LESS 3)
+ if(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ find_package(Python2 ${RUNTIME_PYTHON_VERSION}
+ ${EXACT_ARGUMENT}
+ QUIET
+ COMPONENTS Interpreter Development)
+ if(Python2_Development_FOUND)
+ set(PYTHON_LIBRARIES ${Python2_LIBRARIES})
+ set(PYTHON_INCLUDE_DIRS ${Python2_INCLUDE_DIRS})
+ endif(Python2_Development_FOUND)
+ endif(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ endif(NOT RUNTIME_PYTHON_VERSION VERSION_LESS 3)
+ if(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+ message(STATUS "Could not find Python Libraries.")
+ endif(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+endif(NOT PYTHON_LIBRARIES OR NOT PYTHON_INCLUDE_DIRS)
+
+if(PYTHON_LIBRARIES AND PYTHON_INCLUDE_DIRS)
+ set(HAVE_PYTHON_LIBS TRUE)
+ message(STATUS "Python Libraries: ${PYTHON_LIBRARIES}")
+ message(STATUS "Python include directories: ${PYTHON_INCLUDE_DIRS}")
+else(PYTHON_LIBRARIES AND PYTHON_INCLUDE_DIRS)
+ set(HAVE_PYTHON_LIBS FALSE)
+endif(PYTHON_LIBRARIES AND PYTHON_INCLUDE_DIRS)
+
+if(NOT PYTHON_LIBRARY)
+ set(PYTHON_LIBRARIES ${PYTHON_LIBRARIES} CACHE FILEPATH
+ "Python libraries")
+ mark_as_advanced(PYTHON_LIBRARIES)
+endif(NOT PYTHON_LIBRARY)
+if(NOT PYTHON_INCLUDE_DIR)
+ set(PYTHON_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS} CACHE FILEPATH
+ "Python include dirs")
+ mark_as_advanced(PYTHON_INCLUDE_DIRS)
+endif(NOT PYTHON_INCLUDE_DIR)
+
+endif(NOT DEFINED INCLUDED_UHD_PYTHON_CMAKE)
diff --git a/host/examples/rfnoc-example/examples/rx_gain_estimate_power.py b/host/examples/rfnoc-example/examples/rx_gain_estimate_power.py
new file mode 100755
index 000000000..27018e756
--- /dev/null
+++ b/host/examples/rfnoc-example/examples/rx_gain_estimate_power.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#
+# Copyright 2024 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Test the gain block with the RFNoC Python API
+"""
+
+
+import argparse
+import sys
+import uhd
+import rfnoc_example
+
+def parse_args():
+ """Parse the command line arguments"""
+ parser = argparse.ArgumentParser()
+ parser.add_argument("-a", "--args", default="",
+ help="USRP Device Args")
+ parser.add_argument("--gain-block", "-G", type=str, default="0/Gain#0",
+ help="Gain block to use. Defaults to \"0/Gain#0\".")
+ parser.add_argument("--radio-block", "-R", type=str, default="0/Radio#0",
+ help="Radio block to use. Defaults to \"0/Radio#0\".")
+ parser.add_argument("-f", "--freq", type=float, required=True,
+ help="Center Frequency")
+ parser.add_argument("-g", "--gain", type=float, required=True,
+ help="Analog gain")
+ parser.add_argument("-d", "--digital-gain", type=int, required=True,
+ help="Digital gain")
+ parser.add_argument("-c", "--channel", type=int, default=0,
+ help="Radio block channel index")
+ parser.add_argument("-t", "--antenna",
+ help="USRP RX Antenna")
+ parser.add_argument("-r", "--rate", default=1e6, type=float,
+ help="Sampling Rate")
+ parser.add_argument("-b", "--bandwidth", type=float,
+ help="Analog filter bandwidth (if supported)")
+ parser.add_argument("-n", "--samps-per-est", type=float, default=1e6,
+ help="Samples per estimate.")
+ return parser.parse_args()
+
+
+def main():
+ """Go go go!"""
+ args = parse_args()
+ # Create graph and block references
+ graph = uhd.rfnoc.RfnocGraph(args.args)
+ gain_block_base = graph.get_block(args.gain_block)
+ gain_block = rfnoc_example.GainBlockControl(gain_block_base)
+ radio_block = uhd.rfnoc.RadioControl(graph.get_block(args.radio_block))
+ radio_chan = args.channel
+ assert radio_chan < radio_block.get_num_output_ports()
+ rx_streamer = graph.create_rx_streamer(1, uhd.usrp.StreamArgs("fc32", "sc16"))
+ # Set up graph
+ blocks_in_graph = uhd.rfnoc.connect_through_blocks(
+ graph,
+ radio_block.get_unique_id(), radio_chan,
+ gain_block_base.get_unique_id(), 0)
+ ddc_block_id, ddc_port = next((
+ (x.dst_blockid, x.dst_port)
+ for x in blocks_in_graph
+ if uhd.rfnoc.BlockID(x.dst_blockid).get_block_name() == 'DDC'
+ ), (None, None))
+ graph.connect(
+ gain_block_base.get_unique_id(), 0,
+ rx_streamer, 0)
+ graph.commit()
+ # Apply settings
+ radio_block.set_rx_frequency(args.freq, radio_chan)
+ print(
+ f"Requested RX frequency: {args.freq/1e9:.3f} GHz, "
+ f"actual RX frequency: {radio_block.get_rx_frequency(radio_chan)/1e9:.3f} GHz")
+ radio_block.set_rx_antenna(args.antenna, radio_chan)
+ print(
+ f"Requested RX antenna: {args.antenna}, "
+ f"actual RX antenna: {radio_block.get_rx_antenna(radio_chan)}")
+ radio_block.set_rx_gain(args.gain, radio_chan)
+ print(
+ f"Requested analog RX gain: {args.gain:.1f} dB, "
+ f"actual analog RX gain: {radio_block.get_rx_gain(radio_chan):.1f} dB")
+ gain_block.set_gain_value(args.digital_gain)
+ print(
+ f"Requested digital RX gain factor: x{args.digital_gain}, "
+ f"actual digital RX gain factor: x{gain_block.get_gain_value()}")
+ if ddc_block_id:
+ ddc_block = uhd.rfnoc.DdcBlockControl(graph.get_block(ddc_block_id))
+ ddc_block.set_output_rate(args.rate, ddc_port)
+ rate = ddc_block.get_output_rate(ddc_port)
+ else:
+ radio_block.set_rate(args.rate)
+ rate = radio_block.get_rate()
+ print(
+ f"Requested RX rate: {args.rate/1e6} Msps, "
+ f"actual RX rate: {rate} Msps")
+ # Now do a power reading
+ print(f"Estimating RX power from {args.samps_per_est/rate} s worth of signal...")
+ power_dbfs = uhd.dsp.signals.get_usrp_power(
+ rx_streamer, num_samps=int(args.samps_per_est))
+ print(f"Received power: {power_dbfs:+6.2f} dBFS")
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/host/examples/rfnoc-example/lib/gain_block_control_python.hpp b/host/examples/rfnoc-example/lib/gain_block_control_python.hpp
new file mode 100644
index 000000000..c6079b5a5
--- /dev/null
+++ b/host/examples/rfnoc-example/lib/gain_block_control_python.hpp
@@ -0,0 +1,23 @@
+//
+// Copyright 2024 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <uhd/rfnoc/block_controller_factory_python.hpp>
+#include <rfnoc/example/gain_block_control.hpp>
+
+using namespace rfnoc::example;
+
+void export_gain_block_control(py::module& m)
+{
+ py::class_<gain_block_control, gain_block_control::sptr>(m, "gain_block_control")
+ .def(py::init(
+ &uhd::rfnoc::block_controller_factory<gain_block_control>::make_from))
+ .def("set_gain_value", &gain_block_control::set_gain_value, py::arg("gain"))
+ .def("get_gain_value", &gain_block_control::get_gain_value)
+
+ ;
+}
diff --git a/host/examples/rfnoc-example/python/CMakeLists.txt b/host/examples/rfnoc-example/python/CMakeLists.txt
new file mode 100644
index 000000000..3290611f4
--- /dev/null
+++ b/host/examples/rfnoc-example/python/CMakeLists.txt
@@ -0,0 +1,84 @@
+#
+# Copyright 2024 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+###############################################################################
+# Build Python wrapper module for C++ -> Python bindings
+###############################################################################
+# Global Python API constants
+set(PYMODULE_NAME rfnoc_example)
+set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
+set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py")
+set(TIMESTAMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp")
+# convert binary directory to native format to use in SETUP_PY file.
+file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR} NATIVE_CURRENT_BINARY_DIR)
+configure_file(${SETUP_PY_IN} ${SETUP_PY})
+
+
+###############################################################################
+# Build Python wrapper module for C++ -> Python bindings
+###############################################################################
+pybind11_add_module(${PYMODULE_NAME}_python
+ MODULE
+ pyrfnoc-example.cpp
+)
+target_include_directories(${PYMODULE_NAME}_python
+ PUBLIC
+ ${CMAKE_SOURCE_DIR}/lib
+ ${CMAKE_SOURCE_DIR}/include
+)
+target_link_libraries(
+ ${PYMODULE_NAME}_python
+ PRIVATE
+ pybind11::pybind11
+ uhd
+)
+
+# Copy pybind bindings library to the staging directory (it will get copied to
+# its final destination further down)
+add_custom_command(TARGET ${PYMODULE_NAME}_python
+ POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${PYMODULE_NAME}_python> ${CMAKE_CURRENT_BINARY_DIR}/${PYMODULE_NAME}/$<TARGET_FILE_NAME:${PYMODULE_NAME}_python>)
+
+
+# List of Python files that are part of the module but don't get
+# generated during build time.
+# Note: When adding Python files into the module, they don't get added to the
+# dependency list until CMake is re-run.
+file(GLOB_RECURSE PYMODULE_FILE
+ ${CMAKE_CURRENT_SOURCE_DIR}/${PYMODULE_NAME}/*.py
+)
+
+# If we're not in a virtual environment, then we need to figure out where to
+# install the Python module.
+if(NOT DEFINED UHD_PYTHON_DIR)
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c
+ # Avoid the posix_local install scheme
+ "import os,sysconfig;\
+ install_scheme = 'posix_prefix';\
+ platlib = sysconfig.get_path('platlib', scheme=install_scheme);\
+ prefix = sysconfig.get_config_var('prefix');\
+ print(os.path.relpath(platlib, prefix));"
+ OUTPUT_VARIABLE UHD_PYTHON_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+endif(NOT DEFINED UHD_PYTHON_DIR)
+
+# This copies the contents of the Python module into the build directory. We will
+# use that as a staging ground for installing the final module to the system.
+# We make sure that we always have an up-to-date copy in here.
+add_custom_command(OUTPUT ${TIMESTAMP_FILE}
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/${PYMODULE_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${PYMODULE_NAME}
+ COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} -q build
+ COMMAND ${CMAKE_COMMAND} -E touch ${TIMESTAMP_FILE}
+ DEPENDS ${PYMODULE_FILE})
+
+add_custom_target(pymodule_library
+ ALL DEPENDS ${TIMESTAMP_FILE} ${PYMODULE_NAME}_python)
+
+# Now install the Python module from the build directory to the final destination.
+PYTHON_INSTALL_MODULE(
+ MODULE "${PYMODULE_NAME}"
+ LIBTARGET "${PYMODULE_NAME}_python"
+)
diff --git a/host/examples/rfnoc-example/python/pyrfnoc-example.cpp b/host/examples/rfnoc-example/python/pyrfnoc-example.cpp
new file mode 100644
index 000000000..9dc69cb2c
--- /dev/null
+++ b/host/examples/rfnoc-example/python/pyrfnoc-example.cpp
@@ -0,0 +1,38 @@
+//
+// Copyright 2024 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+// NOTE: Most of these includes, as well as the numpy support, are not required
+// for rfnoc-example, but are commonly required
+#include <pybind11/complex.h>
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#include <numpy/arrayobject.h>
+
+namespace py = pybind11;
+
+#include "gain_block_control_python.hpp"
+
+// We need this hack because import_array() returns NULL
+// for newer Python versions.
+// This function is also necessary because it ensures access to the C API
+// and removes a warning.
+void* init_numpy()
+{
+ import_array();
+ return NULL;
+}
+
+PYBIND11_MODULE(rfnoc_example_python, m)
+{
+ // Initialize the numpy C API
+ // (otherwise we will see segmentation faults)
+ init_numpy();
+
+ // uhd::rfnoc::python::export_noc_block_base(m);
+ export_gain_block_control(m);
+}
diff --git a/host/examples/rfnoc-example/python/rfnoc_example/__init__.py b/host/examples/rfnoc-example/python/rfnoc_example/__init__.py
new file mode 100644
index 000000000..b85ef6f9d
--- /dev/null
+++ b/host/examples/rfnoc-example/python/rfnoc_example/__init__.py
@@ -0,0 +1,14 @@
+#
+# Copyright 2024 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+rfnoc-example: Example module for Python support
+"""
+
+# Import all bindings from C++
+from . import rfnoc_example_python as lib
+
+# In UHD, we use CamelCase for names in Python, so we'll do the same here
+GainBlockControl = lib.gain_block_control
diff --git a/host/examples/rfnoc-example/python/setup.py.in b/host/examples/rfnoc-example/python/setup.py.in
new file mode 100755
index 000000000..7989a71de
--- /dev/null
+++ b/host/examples/rfnoc-example/python/setup.py.in
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+# Copyright 2024 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""Setup file for rfnoc-example module"""
+
+from setuptools import setup, find_packages
+
+packages = find_packages()
+
+print("Including packages in rfnoc-example:", packages)
+
+setup(name='rfnoc_example',
+ version='${VERSION_MAJOR}.${VERSION_API}.${VERSION_ABI}',
+ description='rfnoc-example: An example module for RFNoC OOT Python support',
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
+ 'Programming Language :: C++',
+ 'Programming Language :: Python',
+ 'Topic :: System :: Hardware :: Hardware Drivers',
+ ],
+ keywords='SDR UHD USRP',
+ author='Ettus Research',
+ author_email='packages@ettus.com',
+ url='https://www.ettus.com/',
+ license='GPLv3',
+ package_dir={'': r'${NATIVE_CURRENT_BINARY_DIR}'},
+ package_data={'uhd': ['*.so']},
+ zip_safe=False,
+ packages=packages,
+ install_requires=[])