mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-14 20:58:09 +00:00
Add rfnoc-modtool
This replaces rfnoc_modtool that was previously shipped with gr-ettus. It is fully RFNoC/UHD4.x compatible. There is no shared code between this version and the old one; this is a clean rewrite. As of now, it lacks many of the features that were available with the previous version, most importantly, the ability to generate GNU Radio bindings.
This commit is contained in:
parent
1d8026bcfe
commit
1fb022a35f
42 changed files with 1026 additions and 260 deletions
50
host/cmake/Modules/copy_rfnoc_newmod.py
Normal file
50
host/cmake/Modules/copy_rfnoc_newmod.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# /usr/bin/env python3
|
||||
"""Image Builder Helper: Create the template for RFNoC OOT modules.
|
||||
|
||||
Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Create argument parser and return args."""
|
||||
arg_parser = argparse.ArgumentParser(description="Create the template for RFNoC OOT modules.")
|
||||
arg_parser.add_argument("--source", required=True, help="Source directory")
|
||||
arg_parser.add_argument("--dest", required=True, help="Destination directory")
|
||||
arg_parser.add_argument("--module-dir", required=True, help="Module directory")
|
||||
return arg_parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
"""Do the copying."""
|
||||
args = parse_args()
|
||||
# Load the step configuratio as a Python object
|
||||
yaml = YAML(typ="safe", pure=True)
|
||||
step_yml = os.path.join(os.path.dirname(os.path.realpath(__file__)), "create_newmod.yml")
|
||||
with open(step_yml, "r", encoding="utf-8") as f:
|
||||
cmd = yaml.load(f)
|
||||
# Load the step executor from within the tree
|
||||
sys.path.insert(0, os.path.normpath(args.module_dir))
|
||||
importlib.import_module("rfnoc_utils.log").init_logging(color_mode="off", log_level="info")
|
||||
executor_vars = {
|
||||
"SOURCE_DIR": args.source,
|
||||
"DEST_DIR": args.dest,
|
||||
}
|
||||
# Now we can run the steps
|
||||
executor = importlib.import_module("rfnoc_utils.step_executor").StepExecutor(
|
||||
executor_vars, args, cmd
|
||||
)
|
||||
executor.run(cmd["steps"])
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
109
host/cmake/Modules/create_newmod.yml
Normal file
109
host/cmake/Modules/create_newmod.yml
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
steps:
|
||||
- rmtree:
|
||||
dir: "${ DEST_DIR }"
|
||||
- chdir:
|
||||
dir: "${ SOURCE_DIR }"
|
||||
- copy_dir:
|
||||
src: "${ SOURCE_DIR }"
|
||||
dst: "${ DEST_DIR }"
|
||||
ignore_globs:
|
||||
- "*.hpp"
|
||||
- "apps/*.cpp"
|
||||
- "build"
|
||||
- "examples/*.py"
|
||||
- "fpga/gain/ip"
|
||||
- "fpga/gain/rfnoc_block_gain"
|
||||
- "icores/*.yml"
|
||||
- "lib/*.cpp"
|
||||
- "gr-*"
|
||||
- "rfnoc/**/*.yml"
|
||||
- "rfnoc/dts"
|
||||
- "xsim_proj"
|
||||
- "modelsim_proj"
|
||||
- chdir:
|
||||
dir: "${ DEST_DIR }"
|
||||
- multi_rename:
|
||||
glob: "**/*gain*"
|
||||
pattern: gain
|
||||
repl: newmod
|
||||
- comment_out:
|
||||
file: apps/CMakeLists.txt
|
||||
range: [7, -1]
|
||||
character: "#"
|
||||
- insert_after:
|
||||
file: apps/CMakeLists.txt
|
||||
pattern: "or-later..."
|
||||
text: "\n# Edit this to add your own apps!\n"
|
||||
- search_and_replace:
|
||||
file: examples/CMakeLists.txt
|
||||
pattern: "^ .+py$"
|
||||
repl: " # Insert your example sources here if you want them to be installed"
|
||||
- comment_out:
|
||||
file: fpga/newmod/CMakeLists.txt
|
||||
range: [9, 13]
|
||||
- search_and_replace:
|
||||
file: fpga/newmod/CMakeLists.txt
|
||||
pattern: "add_subdirectory\\(([a-z0-9_]+)\\)"
|
||||
repl: "#add_subdirectory(rfnoc_block_myblock)"
|
||||
- insert_before:
|
||||
file: fpga/newmod/CMakeLists.txt
|
||||
pattern: "# IP gets"
|
||||
text: "# Uncomment the following lines to add your own IP:\n"
|
||||
- search_and_replace:
|
||||
file: icores/CMakeLists.txt
|
||||
pattern: "RFNOC_REGISTER.*$"
|
||||
repl: "# Add one line for every image core here:\n#RFNOC_REGISTER_BLOCK(SRC my_image_core)"
|
||||
- search_and_replace:
|
||||
file: include/rfnoc/newmod/CMakeLists.txt
|
||||
pattern: "gain_block_control.hpp"
|
||||
repl: "#my_block_control.hpp"
|
||||
- search_and_replace:
|
||||
file: lib/CMakeLists.txt
|
||||
pattern: "gain_block_control.cpp"
|
||||
repl: "#my_block_control.cpp"
|
||||
- search_and_replace:
|
||||
file: python/rfnoc_newmod/__init__.py
|
||||
pattern: "GainBlockControl.*$"
|
||||
repl: "#MyBlockControl = lib.my_block_control"
|
||||
- search_and_replace:
|
||||
file: python/pyrfnoc-newmod.cpp
|
||||
pattern: '#include "gain_block_control_python.hpp"\n'
|
||||
repl: ""
|
||||
- search_and_replace:
|
||||
file: python/pyrfnoc-newmod.cpp
|
||||
pattern: "export_gain_block_control\\(m\\);\n"
|
||||
repl: ""
|
||||
- search_and_replace:
|
||||
file: README.md
|
||||
pattern: ".*(?=## Directory Structure)"
|
||||
repl: |
|
||||
# RFNoC Out-of-Tree Module
|
||||
|
||||
This is a template for creating an RFNoC out-of-tree module. It is based on the
|
||||
rfnoc-gain example that ships with UHD.
|
||||
|
||||
We recommend sticking to this directory structure and file layout.\n
|
||||
count: 1
|
||||
- search_and_replace:
|
||||
file: rfnoc/CMakeLists.txt
|
||||
pattern: "add_subdirectory\\(dts\\)"
|
||||
repl: "#add_subdirectory(dts)"
|
||||
- search_and_replace:
|
||||
files:
|
||||
- CMakeLists.txt
|
||||
- apps/CMakeLists.txt
|
||||
- examples/CMakeLists.txt
|
||||
- python/rfnoc_newmod/__init__.py
|
||||
- python/CMakeLists.txt
|
||||
pattern: r"rfnoc(.)gain"
|
||||
repl: r"rfnoc\1newmod"
|
||||
- search_and_replace:
|
||||
files:
|
||||
- CMakeLists.txt
|
||||
- include/rfnoc/newmod/CMakeLists.txt
|
||||
- lib/CMakeLists.txt
|
||||
- python/pyrfnoc-newmod.cpp
|
||||
- python/setup.py.in
|
||||
- README.md
|
||||
pattern: "gain"
|
||||
repl: "newmod"
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
debian/NetworkManager-USRP /etc/NetworkManager/system-connections/
|
||||
usr/lib/uhd* /usr/lib/
|
||||
usr/bin/rfnoc_image_builder
|
||||
usr/bin/rfnoc_modtool
|
||||
usr/bin/uhd_adc_self_cal
|
||||
usr/bin/uhd_cal_rx_iq_balance
|
||||
usr/bin/uhd_cal_tx_dc_offset
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ set(man_page_sources
|
|||
uhd_usrp_probe.1
|
||||
usrp_n2xx_simple_net_burner.1
|
||||
usrp2_card_burner.1
|
||||
rfnoc_modtool.1
|
||||
)
|
||||
|
||||
if (ENABLE_PYTHON_API)
|
||||
|
|
|
|||
69
host/docs/rfnoc_modtool.1
Normal file
69
host/docs/rfnoc_modtool.1
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
.TH "rfnoc_modtool" 1 "4.8.0" UHD "User Commands"
|
||||
.SH NAME
|
||||
rfnoc_modtool - RFNoC OOT module management tool
|
||||
|
||||
.SH DESCRIPTION
|
||||
Create and manage RFNoC OOT modules.
|
||||
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B rfnoc_modtool [COMMAND] [OPTIONS]
|
||||
|
||||
.SH COMMANDS
|
||||
Run rfnoc_modtool COMMAND --help for more information on a specific command.
|
||||
|
||||
.IP "create"
|
||||
Create a new RFNoC OOT module.
|
||||
.IP "add"
|
||||
Add a new block to an existing RFNoC OOT module.
|
||||
|
||||
.SH CREATING NEW MODULES
|
||||
.sp
|
||||
When running rfnoc_modtool create, a new directory will be created that contains
|
||||
the necessary files for a new RFNoC OOT module. This directory will only contain
|
||||
boilerplate code, and the user will need to add their own blocks to the module.
|
||||
|
||||
.SH ADDING BLOCKS
|
||||
.sp
|
||||
When running rfnoc_modtool add, a new block will be added to an existing RFNoC OOT module.
|
||||
This requires previously having run rfnoc_modtool create to create the module.
|
||||
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
.SS Create a new RFNoC OOT module called "filter"
|
||||
.sp
|
||||
rfnoc_modtool create filter
|
||||
.ft
|
||||
|
||||
.SS Add a block called "fir" to the "filter" module (this requires having create a fir.yml block descriptor file)
|
||||
.sp
|
||||
rfnoc_modtool -C ./rfnoc-filter add fir -y rfnoc-foo/rfnoc/blocks/fir.yml
|
||||
.ft
|
||||
|
||||
.fi
|
||||
|
||||
.SH SEE ALSO
|
||||
UHD documentation:
|
||||
.B http://files.ettus.com/manual/
|
||||
.LP
|
||||
GR-UHD documentation:
|
||||
.B http://gnuradio.org/doc/doxygen/page_uhd.html
|
||||
.LP
|
||||
Other UHD programs:
|
||||
.sp
|
||||
rfnoc_image_builder(1)
|
||||
.SH AUTHOR
|
||||
This manual page was written by Martin Braun.
|
||||
.SH COPYRIGHT
|
||||
Copyright (c) 2015-2022 Ettus Research, A National Instruments Brand
|
||||
.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.
|
||||
|
|
@ -42,6 +42,7 @@ if(NOT ENABLE_PYTHON_API AND ENABLE_PYMOD_UTILS)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/dsp/*.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/rfnoc_utils/*.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/rfnoc_utils/*.mako
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/rfnoc_utils/*.yml
|
||||
)
|
||||
# This copies the contents of host/python/uhd into the build directory. We will
|
||||
# use that as a staging ground for installing the final module to the system.
|
||||
|
|
@ -133,6 +134,8 @@ add_custom_command(TARGET pyuhd
|
|||
# dependency list until CMake is re-run.
|
||||
file(GLOB_RECURSE PYUHD_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/*.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/*.mako
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/uhd/*.yml
|
||||
)
|
||||
|
||||
if(ENABLE_SIM)
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@
|
|||
set(BINARY_DIR "" CACHE STRING "")
|
||||
set(SOURCE_DIR "" CACHE STRING "")
|
||||
file(COPY "${SOURCE_DIR}/uhd/" DESTINATION "${BINARY_DIR}/uhd"
|
||||
FILES_MATCHING PATTERN "*.py" PATTERN "*.mako")
|
||||
FILES_MATCHING PATTERN "*.py" PATTERN "*.mako" PATTERN "*.yml")
|
||||
|
|
|
|||
|
|
@ -1,38 +1,50 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
"""Setup file for uhd module"""
|
||||
"""Setup file for uhd module.
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
Copyright 2017-2018 Ettus Research, a National Instruments Company
|
||||
|
||||
packages = find_packages() + ['uhd.rfnoc_utils.templates', 'uhd.rfnoc_utils.templates.modules']
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
packages = find_packages() + [
|
||||
"uhd.rfnoc_utils.templates",
|
||||
"uhd.rfnoc_utils.templates.modules",
|
||||
"uhd.rfnoc_utils.modtool_commands",
|
||||
]
|
||||
|
||||
print("Including packages in pyuhd:", packages)
|
||||
|
||||
setup(name='uhd',
|
||||
version='${UHD_VERSION_MAJOR}.${UHD_VERSION_API}.${UHD_VERSION_ABI}',
|
||||
description='Universal Software Radio Peripheral (USRP) Hardware Driver Python API',
|
||||
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'],
|
||||
'uhd.rfnoc_utils.templates': ['*.mako'],
|
||||
'uhd.rfnoc_utils.templates.modules': ['*.mako'],
|
||||
},
|
||||
zip_safe=False,
|
||||
packages=packages,
|
||||
install_requires=['numpy'])
|
||||
setup(
|
||||
name="uhd",
|
||||
version="${UHD_VERSION_MAJOR}.${UHD_VERSION_API}.${UHD_VERSION_ABI}",
|
||||
description="Universal Software Radio Peripheral (USRP) Hardware Driver Python API",
|
||||
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"],
|
||||
"uhd.rfnoc_utils.templates": ["*.mako"],
|
||||
"uhd.rfnoc_utils.templates.modules": ["*.mako"],
|
||||
"uhd.rfnoc_utils.modtool_commands": ["*.yml"],
|
||||
},
|
||||
zip_safe=False,
|
||||
packages=packages,
|
||||
install_requires=["numpy", "ruamel.yaml", "mako"],
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"rfnoc_modtool = uhd.rfnoc_utils.rfnoc_modtool:main",
|
||||
]
|
||||
},
|
||||
)
|
||||
|
|
|
|||
77
host/python/uhd/rfnoc_utils/log.py
Normal file
77
host/python/uhd/rfnoc_utils/log.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""RFNoC Utilities: Logging.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
class ColorFormatter(logging.Formatter):
|
||||
"""Logging Formatter to add colors and icons."""
|
||||
|
||||
RESET = 0
|
||||
BOLD = 1
|
||||
DIM = 2
|
||||
GREY = "38;20"
|
||||
BLACK = "30;20"
|
||||
YELLOW = "33;20"
|
||||
RED = "31;20"
|
||||
BRIGHTRED = 91
|
||||
BRIGHT = 99
|
||||
|
||||
def c(colors): # noqa -- should be staticmethod but that requires Python 3.10
|
||||
"""Format escape sequence from list of colors."""
|
||||
return f"\x1b[{';'.join(str(c) for c in colors)}m"
|
||||
|
||||
debug_color = c([DIM])
|
||||
info_color = c([BRIGHT, BOLD])
|
||||
warning_color = c([YELLOW])
|
||||
error_color = c([BRIGHTRED])
|
||||
crit_color = c([RED, BOLD])
|
||||
reset = c([RESET])
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: debug_color + "[debug] %(message)s" + reset,
|
||||
logging.INFO: info_color + "%(message)s" + reset,
|
||||
logging.WARNING: warning_color + "⚠ %(message)s" + reset,
|
||||
logging.ERROR: error_color + "⛔ %(message)s" + reset,
|
||||
logging.CRITICAL: crit_color + "⛔ %(message)s" + reset,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
"""Format a record the way we like it."""
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
return logging.Formatter(log_fmt).format(record)
|
||||
|
||||
|
||||
class SimpleFormatter(logging.Formatter):
|
||||
"""Logging Formatter for non-interactive shells."""
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: "[debug] %(message)s",
|
||||
logging.INFO: "%(message)s",
|
||||
logging.WARNING: "[warning] %(message)s",
|
||||
logging.ERROR: "[error] %(message)s",
|
||||
logging.CRITICAL: "[critical] %(message)s",
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
"""Format a record the way we like it."""
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
return logging.Formatter(log_fmt).format(record)
|
||||
|
||||
|
||||
def init_logging(color_mode="auto", log_level="info"):
|
||||
"""Initialize the logging interface for RFNoC utilities."""
|
||||
use_color = (color_mode == "always") or (
|
||||
color_mode == "auto" and sys.__stdout__.isatty() and sys.__stderr__.isatty()
|
||||
)
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(ColorFormatter() if use_color else SimpleFormatter())
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(handler)
|
||||
|
||||
if log_level:
|
||||
logging.root.setLevel(log_level.upper())
|
||||
107
host/python/uhd/rfnoc_utils/modtool_commands/add.yml
Normal file
107
host/python/uhd/rfnoc_utils/modtool_commands/add.yml
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#
|
||||
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
help: |
|
||||
Add a new block to an RFNoC OOT module based on a descriptor file.
|
||||
|
||||
Note: Must be called from within a valid RFNoC OOT module directory. Use -C to
|
||||
specify the module directory if necessary.
|
||||
|
||||
# These get turned into command line arguments for this command. May not contain
|
||||
# variable references (they get evaluated later).
|
||||
args:
|
||||
blockname:
|
||||
name_or_flags: blockname
|
||||
type: str
|
||||
help: Name of the new block to add to the module
|
||||
yaml_descriptor:
|
||||
name_or_flags: ["-y", "--yaml-descriptor"]
|
||||
type: str
|
||||
metavar: YAML_FILE
|
||||
help: >
|
||||
Path to the YAML descriptor file for the new block. Defaults to rfnoc/blocks/<blockname>.yml.
|
||||
If this file does not exist, the command will fail with an error.
|
||||
copyright_holder:
|
||||
name_or_flags: "--copyright-holder"
|
||||
default: "<author>"
|
||||
license:
|
||||
name_or_flags: "--license"
|
||||
default: "SPDX-License-Identifier: GPL-3.0-or-later"
|
||||
skip_testbench:
|
||||
name_or_flags: "--skip-testbench"
|
||||
action: store_true
|
||||
help: Skip generating the testbench for the new block
|
||||
|
||||
# Note: Variables get resolved in order, that means later vars can reference earlier ones
|
||||
variables:
|
||||
# Type is block or transport_adapter (latter not yet implemented)
|
||||
type: block
|
||||
type_d: blocks
|
||||
blockname: "${ args.blockname }"
|
||||
descriptor: "${ args.yaml_descriptor if args.yaml_descriptor else 'rfnoc/' + type_d + '/' + args.blockname + '.yml' }"
|
||||
blockname_full: "${ f'rfnoc_{type}_{blockname}' }"
|
||||
copyright_holder: "${ args.copyright_holder }"
|
||||
license: "${ args.license }"
|
||||
|
||||
|
||||
# Steps:
|
||||
# - Create controller C++
|
||||
# - Create Python bindings
|
||||
|
||||
|
||||
# This tells us that templates come from the blocktool/ subdirectory
|
||||
template_namespace: blocktool
|
||||
|
||||
steps:
|
||||
- parse_descriptor:
|
||||
source: "${ descriptor }"
|
||||
# RFNoC block gateware
|
||||
- write_template:
|
||||
template: noc_shell_template.sv.mako
|
||||
dest: "fpga/${ MODULE_NAME }/${ blockname_full }/noc_shell_${ blockname }.sv"
|
||||
- write_template:
|
||||
template: rfnoc_block_template.sv.mako
|
||||
dest: "fpga/${ MODULE_NAME }/${ blockname_full }/${ blockname_full }.sv"
|
||||
- write_template:
|
||||
template: Makefile.srcs.mako
|
||||
dest: "fpga/${ MODULE_NAME }/${ blockname_full }/Makefile.srcs"
|
||||
# RFNoC block testbench
|
||||
- run_if:
|
||||
condition: "${ not args.skip_testbench }"
|
||||
steps:
|
||||
- write_template:
|
||||
template: rfnoc_block_template_tb.sv.mako
|
||||
dest: "fpga/${ MODULE_NAME }/${ blockname_full }/${ blockname_full }_tb.sv"
|
||||
- write_template:
|
||||
template: Makefile.mako
|
||||
dest: "fpga/${ MODULE_NAME }/${ blockname_full }/Makefile"
|
||||
# RFNoC block C++ controller
|
||||
- write_template:
|
||||
template: template_block_control.hpp.mako
|
||||
dest: "include/rfnoc/${ MODULE_NAME }/${ blockname }_block_control.hpp"
|
||||
- write_template:
|
||||
template: template_block_control.cpp.mako
|
||||
dest: "lib/${ blockname }_block_control.cpp"
|
||||
- insert_after:
|
||||
file: "include/rfnoc/${ MODULE_NAME }/CMakeLists.txt"
|
||||
pattern: "install.*FILES"
|
||||
text: "\n ${ blockname }_block_control.hpp"
|
||||
- insert_after:
|
||||
file: "lib/CMakeLists.txt"
|
||||
pattern: "APPEND *rfnoc_${ MODULE_NAME }_sources"
|
||||
text: "\n ${ blockname }_block_control.cpp"
|
||||
# RFNoC block Python bindings
|
||||
- write_template:
|
||||
template: template_block_control_python.hpp.mako
|
||||
dest: "python/${ blockname }_block_control_python.hpp"
|
||||
- insert_after:
|
||||
file: "python/pyrfnoc-${ MODULE_NAME}.cpp"
|
||||
pattern: "PYBIND11_MODULE[^}]*"
|
||||
text: " export_${ blockname }_block_control(m);\n"
|
||||
- insert_before:
|
||||
file: "python/pyrfnoc-${ MODULE_NAME}.cpp"
|
||||
pattern: "\nPYBIND11_MODULE"
|
||||
text: '#include "${ blockname }_block_control_python.hpp"\n'
|
||||
45
host/python/uhd/rfnoc_utils/modtool_commands/create.yml
Normal file
45
host/python/uhd/rfnoc_utils/modtool_commands/create.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
# Because this is for creating a new module, we skip the step of finding a valid
|
||||
# module to operate in. This requires us to go switch directories further down.
|
||||
skip_identify_module: true
|
||||
|
||||
help: |
|
||||
Create a new RFNoC OOT module
|
||||
|
||||
# These get turned into command line arguments for this command. May not contain
|
||||
# variable references (they get evaluated later).
|
||||
args:
|
||||
module_name:
|
||||
name_or_flags: module_name
|
||||
type: str
|
||||
help: Name of the new RFNoC OOT module, without the leading "rfnoc-"
|
||||
|
||||
# These variables may be used in the steps below
|
||||
variables:
|
||||
module_name: "${ args.module_name }"
|
||||
module_name_full: "${ args.module_name if args.module_name.startswith('rfnoc-') else 'rfnoc-' + args.module_name }"
|
||||
|
||||
steps:
|
||||
- copy_dir:
|
||||
src: "${ RFNOC_PKG_DIR }/rfnoc-newmod"
|
||||
dst: "${ CWD }/${ module_name_full }"
|
||||
- chdir:
|
||||
dir: "${ CWD }/${ module_name_full }"
|
||||
- search_and_replace:
|
||||
glob: "**"
|
||||
pattern: 'newmod'
|
||||
repl: "${ module_name }"
|
||||
quiet: true
|
||||
- search_and_replace:
|
||||
glob: README.md
|
||||
pattern: "${ module_name } example that ships with"
|
||||
repl: "gain example that ships with"
|
||||
- multi_rename:
|
||||
glob: "**/*newmod*"
|
||||
pattern: 'newmod'
|
||||
repl: "${ module_name }"
|
||||
130
host/python/uhd/rfnoc_utils/rfnoc_modtool.py
Normal file
130
host/python/uhd/rfnoc_utils/rfnoc_modtool.py
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env python3
|
||||
"""RFNoC Modtool: The tool to create and manipulate RFNoC OOT modules."""
|
||||
#
|
||||
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from .log import init_logging
|
||||
from .step_executor import StepExecutor
|
||||
from .utils import resolve
|
||||
|
||||
|
||||
def get_command_repo_dir():
|
||||
"""Return the path to the command repository directory (where the command YAMLS are stored)."""
|
||||
this_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
return os.path.join(this_dir, "modtool_commands")
|
||||
|
||||
|
||||
def collect_commands():
|
||||
"""Load all available commands.
|
||||
|
||||
The return value is a dictionary where the keys are the command names and
|
||||
the values are raw contents of the YAML as Python structures.
|
||||
"""
|
||||
yamls = glob.glob(os.path.join(get_command_repo_dir(), "*.yml"))
|
||||
yaml = YAML()
|
||||
commands = {}
|
||||
for y in yamls:
|
||||
cmd_name = os.path.basename(y).replace(".yml", "")
|
||||
with open(y, "r", encoding="utf-8") as f:
|
||||
commands[cmd_name] = yaml.load(f)
|
||||
return commands
|
||||
|
||||
|
||||
def parse_args(cmds):
|
||||
"""Parse args.
|
||||
|
||||
This will dynamically populate main- and subparsers from the YAML configs
|
||||
given in cmds.
|
||||
"""
|
||||
arg_parser = argparse.ArgumentParser(description=__doc__)
|
||||
arg_parser.add_argument(
|
||||
"-C", "--directory", help="Change to this directory before running the command"
|
||||
)
|
||||
arg_parser.add_argument(
|
||||
"-l", "--log-level", default="INFO", help="Set the log level (default: INFO)"
|
||||
)
|
||||
subparsers = arg_parser.add_subparsers(dest="command")
|
||||
parser_list = []
|
||||
for cmd in cmds:
|
||||
new_parser = subparsers.add_parser(cmd, help=cmds[cmd]["help"])
|
||||
for arg_name, arg_info in cmds[cmd]["args"].items():
|
||||
name_or_flags = (
|
||||
f"{arg_name}" if "name_or_flags" not in arg_info else arg_info.pop("name_or_flags")
|
||||
)
|
||||
if not isinstance(name_or_flags, list):
|
||||
name_or_flags = [name_or_flags]
|
||||
# We don't want to use eval(), but for most types we need to do
|
||||
# just that, so manually map
|
||||
if "type" in arg_info:
|
||||
try:
|
||||
arg_info["type"] = {
|
||||
"str": str,
|
||||
"int": int,
|
||||
"float": float,
|
||||
}[arg_info["type"]]
|
||||
except KeyError:
|
||||
pass
|
||||
new_parser.add_argument(*name_or_flags, **arg_info)
|
||||
parser_list.append(new_parser)
|
||||
return arg_parser.parse_args()
|
||||
|
||||
|
||||
def get_global_vars(pkg_data_dir):
|
||||
"""Create a dictionary with global variables for the key resolution."""
|
||||
return {
|
||||
"RFNOC_PKG_DIR": pkg_data_dir,
|
||||
"CWD": os.getcwd(),
|
||||
}
|
||||
|
||||
|
||||
def resolve_vars(cmd, global_vars, args):
|
||||
"""Resolve all variables in the command."""
|
||||
for var_name, var_val in cmd.get("variables", {}).items():
|
||||
cmd["variables"][var_name] = resolve(
|
||||
var_val, args=args, **global_vars, **cmd.get("variables", {})
|
||||
)
|
||||
return cmd
|
||||
|
||||
|
||||
def check_valid_oot_dir(oot_dir):
|
||||
"""Check if the given directory contains a valid OOT module."""
|
||||
return os.path.isfile(os.path.join(oot_dir, "CMakeLists.txt")) and os.path.isdir(
|
||||
os.path.join(oot_dir, "rfnoc")
|
||||
)
|
||||
|
||||
|
||||
def main(pkg_data_dir):
|
||||
"""Run main rfnoc_modtool function."""
|
||||
cmds = collect_commands()
|
||||
args = parse_args(cmds)
|
||||
init_logging(log_level=args.log_level)
|
||||
if args.directory:
|
||||
os.chdir(args.directory)
|
||||
cmd = cmds[args.command]
|
||||
global_vars = get_global_vars(pkg_data_dir)
|
||||
cmd = resolve_vars(cmd, global_vars, args)
|
||||
if not cmd.get("skip_identify_module", False):
|
||||
if not check_valid_oot_dir(os.getcwd()):
|
||||
print("Error: Not a valid OOT module directory")
|
||||
return 1
|
||||
oot_dir_name = os.path.split(os.getcwd())[-1]
|
||||
module_name = oot_dir_name.replace("rfnoc-", "")
|
||||
global_vars["MODULE_NAME"] = module_name
|
||||
global_vars["MODULE_NAME_FULL"] = oot_dir_name
|
||||
executor = StepExecutor(global_vars, args, cmd)
|
||||
executor.run(cmd["steps"])
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
229
host/python/uhd/rfnoc_utils/step_executor.py
Normal file
229
host/python/uhd/rfnoc_utils/step_executor.py
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
#!/usr/bin/env python3
|
||||
"""RFNoC Modtool: Step Executor.
|
||||
|
||||
Contains the class that can run individual steps of a command script.
|
||||
"""
|
||||
#
|
||||
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
import datetime
|
||||
import fnmatch
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import mako.lookup
|
||||
import mako.template
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from .utils import resolve
|
||||
|
||||
|
||||
def get_file_list(**kwargs):
|
||||
"""Extract a list of filenames from kwargs.
|
||||
|
||||
The following rules apply:
|
||||
- If "glob" is present, use it to generate a list of filenames.
|
||||
- If "files" is present, use it as a list of filenames.
|
||||
- If "file" is present, use it as a single filename.
|
||||
"""
|
||||
file_list = []
|
||||
if "glob" in kwargs:
|
||||
glob_pattern = kwargs["glob"]
|
||||
# FIXME: Maybe the user wants to glob somewhere else
|
||||
glob_pattern = os.path.join(os.getcwd(), glob_pattern)
|
||||
file_list = [
|
||||
f
|
||||
for f in glob.glob(kwargs["glob"], recursive=kwargs.get("glob_recursive", True))
|
||||
if Path(f).is_file()
|
||||
]
|
||||
file_list += kwargs.get("files", [])
|
||||
if "file" in kwargs:
|
||||
file_list.append(kwargs["file"])
|
||||
return file_list
|
||||
|
||||
|
||||
class StepExecutor:
|
||||
"""Main engine for executing steps in a command script."""
|
||||
|
||||
def __init__(self, global_vars, args, cmd):
|
||||
"""Initialize the executor."""
|
||||
self.cmd = cmd
|
||||
self.args = args
|
||||
self.global_vars = global_vars
|
||||
self.template_base = os.path.join(os.path.dirname(__file__), "templates")
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
def _resolve(self, value):
|
||||
"""Shorthand for resolving a value."""
|
||||
return resolve(value, args=self.args, **self.global_vars, **self.cmd.get("variables", {}))
|
||||
|
||||
def run(self, steps):
|
||||
"""Run all the steps in the command."""
|
||||
for step in steps:
|
||||
for step_type, step_args in step.items():
|
||||
getattr(self, step_type)(**{k: self._resolve(v) for k, v in step_args.items()})
|
||||
|
||||
def run_if(self, condition, steps):
|
||||
"""Run a block of steps if a condition is true."""
|
||||
if self._resolve(condition):
|
||||
self.run(steps)
|
||||
|
||||
def copy_dir(self, src, dst, **kwargs):
|
||||
"""Copy a directory from src to dest, recursively."""
|
||||
self.log.debug("Copying directory %s to %s", src, dst)
|
||||
ignore = None
|
||||
if "ignore_globs" in kwargs:
|
||||
|
||||
def ignore_patterns(path, names):
|
||||
relpath = os.path.relpath(path, os.getcwd())
|
||||
return [
|
||||
name
|
||||
for name in names
|
||||
if any(
|
||||
fnmatch.fnmatch(os.path.normpath(os.path.join(relpath, name)), pattern)
|
||||
for pattern in kwargs["ignore_globs"]
|
||||
)
|
||||
]
|
||||
|
||||
ignore = ignore_patterns
|
||||
copytree_kwargs = {"ignore": ignore}
|
||||
if sys.version_info >= (3, 8):
|
||||
copytree_kwargs["dirs_exist_ok"] = True
|
||||
shutil.copytree(src, dst, **copytree_kwargs)
|
||||
|
||||
def search_and_replace(self, **kwargs):
|
||||
"""Search and replace text in a file."""
|
||||
file_list = get_file_list(**kwargs)
|
||||
for file in file_list:
|
||||
self.log.debug(
|
||||
"Editing file %s (replacing `%s' with `%s')",
|
||||
file,
|
||||
kwargs["pattern"],
|
||||
kwargs["repl"],
|
||||
)
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
count = kwargs.get("count", 0)
|
||||
contents, sub_count = re.subn(
|
||||
kwargs["pattern"],
|
||||
kwargs["repl"],
|
||||
contents,
|
||||
count=count,
|
||||
flags=re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
if sub_count == 0 and not kwargs.get("quiet", False):
|
||||
self.log.warning("Pattern not found in file %s", os.path.abspath(file))
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(contents)
|
||||
|
||||
def chdir(self, dir):
|
||||
"""Change the working directory."""
|
||||
os.chdir(os.path.normpath(dir))
|
||||
self.global_vars["CWD"] = os.getcwd()
|
||||
self.log.debug("Changed working directory to %s", os.getcwd())
|
||||
|
||||
def multi_rename(self, pattern, repl, **kwargs):
|
||||
"""Rename multiple files with a regex pattern."""
|
||||
paths_to_rename = glob.glob(kwargs["glob"], recursive=kwargs.get("glob_recursive", True))
|
||||
for path in paths_to_rename:
|
||||
head, tail = os.path.split(path)
|
||||
new_path = os.path.join(head, re.sub(pattern, repl, tail))
|
||||
if new_path != path:
|
||||
self.log.debug("Renaming %s to %s", path, new_path)
|
||||
shutil.move(path, new_path)
|
||||
|
||||
def parse_descriptor(self, source, var="config", **kwargs):
|
||||
"""Load a block descriptor file."""
|
||||
yaml = YAML(typ="safe", pure=True)
|
||||
self.log.debug("Loading descriptor file %s into variable %s", source, var)
|
||||
with open(source, "r", encoding="utf-8") as f:
|
||||
self.global_vars[var] = yaml.load(f)
|
||||
|
||||
def write_template(self, template, dest, **kwargs):
|
||||
"""Write a template file."""
|
||||
template_dir = os.path.join(
|
||||
self.template_base, kwargs.get("namespace", self.cmd.get("template_namespace", ""))
|
||||
)
|
||||
lookup = mako.lookup.TemplateLookup(directories=[template_dir])
|
||||
Path(dest).parent.mkdir(parents=True, exist_ok=True)
|
||||
tpl = mako.template.Template(
|
||||
filename=os.path.join(template_dir, template), lookup=lookup, strict_undefined=True
|
||||
)
|
||||
vars = self.cmd.get("variables", {}).copy()
|
||||
# Make sure standard template variables are available
|
||||
if "year" in kwargs:
|
||||
vars["year"] = kwargs["year"]
|
||||
elif "year" in vars:
|
||||
pass
|
||||
else:
|
||||
vars["year"] = datetime.datetime.now().year
|
||||
self.log.debug("Writing template %s to %s", template, dest)
|
||||
with open(dest, "w", encoding="utf-8") as f:
|
||||
f.write(tpl.render(**self.global_vars, **vars))
|
||||
|
||||
def insert_after(self, pattern, text, **kwargs):
|
||||
"""Insert text after a pattern in a file."""
|
||||
file_list = get_file_list(**kwargs)
|
||||
for file in file_list:
|
||||
self.log.debug("Editing file %s (inserting `%s')", file, text.strip())
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
count = kwargs.get("count", 1)
|
||||
contents, sub_count = re.subn(
|
||||
pattern, r"\g<0>" + text, contents, count=count, flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
if sub_count == 0:
|
||||
self.log.warning("Pattern not found in file %s", file)
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(contents)
|
||||
|
||||
def insert_before(self, pattern, text, **kwargs):
|
||||
"""Insert text after a pattern in a file."""
|
||||
file_list = get_file_list(**kwargs)
|
||||
for file in file_list:
|
||||
self.log.debug("Editing file %s (inserting `%s')", file, text.strip())
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
count = kwargs.get("count", 1)
|
||||
contents, sub_count = re.subn(
|
||||
pattern, text + r"\g<0>", contents, count=count, flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
if sub_count == 0:
|
||||
self.log.warning("Pattern not found in file %s", file)
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.write(contents)
|
||||
|
||||
def comment_out(self, character="#", **kwargs):
|
||||
"""Modify all lines in range to prepend character.
|
||||
|
||||
Note that the line range starts at 1, as it would with sed or in an
|
||||
editor.
|
||||
"""
|
||||
line_range = kwargs.get("range", [1, -1])
|
||||
file_list = get_file_list(**kwargs)
|
||||
for file in file_list:
|
||||
self.log.debug("Commenting out lines in %s", file)
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
contents = f.readlines()
|
||||
for line_no_minus_one, line in enumerate(contents):
|
||||
if line_no_minus_one + 1 >= line_range[0] and (
|
||||
line_no_minus_one + 1 <= line_range[1] or line_range[1] == -1
|
||||
):
|
||||
contents[line_no_minus_one] = character + line
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
f.writelines(contents)
|
||||
|
||||
def rmtree(self, **kwargs):
|
||||
"""Remove a directory tree."""
|
||||
dir_to_remove = kwargs["dir"]
|
||||
self.log.debug("Removing directory %s", dir_to_remove)
|
||||
if os.path.exists(dir_to_remove) and os.path.isdir(dir_to_remove):
|
||||
shutil.rmtree(dir_to_remove)
|
||||
|
|
@ -26,9 +26,10 @@ include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs
|
|||
include Makefile.srcs
|
||||
|
||||
DESIGN_SRCS += $(abspath ${"\\"}
|
||||
$(RFNOC_SRCS) ${"\\"}
|
||||
$(RFNOC_CORE_SRCS) ${"\\"}
|
||||
$(RFNOC_UTIL_SRCS) ${"\\"}
|
||||
$(RFNOC_OOT_SRCS) ${"\\"}
|
||||
$(${ blockname_full.upper() }_SRCS) ${"\\"}
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
|
|
@ -16,7 +16,7 @@ ${"##################################################"}
|
|||
# calling this file. RFNOC_OOT_SRCS needs to be a simply expanded variable
|
||||
# (not a recursively expanded variable), and we take care of that in the build
|
||||
# infrastructure.
|
||||
RFNOC_OOT_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), ${"\\"}
|
||||
${ blockname_full.upper() }_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), ${"\\"}
|
||||
rfnoc_block_${config['module_name']}.v ${"\\"}
|
||||
noc_shell_${config['module_name']}.v ${"\\"}
|
||||
)
|
||||
|
|
@ -3,9 +3,9 @@ import math
|
|||
%>\
|
||||
<%namespace name="func" file="/functions.mako"/>\
|
||||
//
|
||||
// Copyright ${year} Ettus Research, a National Instruments Brand
|
||||
// Copyright ${year} ${copyright_holder}
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// ${license}
|
||||
//
|
||||
// Module: rfnoc_block_${config['module_name']}
|
||||
//
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<%namespace name="func" file="/functions.mako"/>\
|
||||
//
|
||||
// Copyright ${year} Ettus Research, a National Instruments Brand
|
||||
// Copyright ${year} ${copyright_holder}
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// ${license}
|
||||
//
|
||||
// Module: rfnoc_block_${config['module_name']}_tb
|
||||
//
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright ${year} ${copyright_holder}
|
||||
//
|
||||
// ${license}
|
||||
//
|
||||
|
||||
// Include our own header:
|
||||
#include <rfnoc/${MODULE_NAME}/${blockname}_block_control.hpp>
|
||||
|
||||
// These two includes are the minimum required to implement a block:
|
||||
#include <uhd/rfnoc/defaults.hpp>
|
||||
#include <uhd/rfnoc/registry.hpp>
|
||||
|
||||
using namespace rfnoc::${MODULE_NAME};
|
||||
using namespace uhd::rfnoc;
|
||||
|
||||
// Define register addresses here:
|
||||
//const uint32_t ${blockname}_block_control::REG_NAME = 0x1234;
|
||||
|
||||
class ${blockname}_block_control_impl : public ${blockname}_block_control
|
||||
{
|
||||
public:
|
||||
RFNOC_BLOCK_CONSTRUCTOR(${blockname}_block_control) {}
|
||||
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
UHD_RFNOC_BLOCK_REGISTER_DIRECT(
|
||||
${blockname}_block_control, ${config["noc_id"]}, "Gain", CLOCK_KEY_GRAPH, "bus_clk")
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Copyright ${year} ${copyright_holder}
|
||||
//
|
||||
// ${license}
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <uhd/config.hpp>
|
||||
#include <uhd/rfnoc/noc_block_base.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace rfnoc { namespace ${MODULE_NAME} {
|
||||
|
||||
/*! Block controller: Describe me!
|
||||
*/
|
||||
class UHD_API ${blockname}_block_control : public uhd::rfnoc::noc_block_base
|
||||
{
|
||||
public:
|
||||
RFNOC_DECLARE_BLOCK(${blockname}_block_control)
|
||||
|
||||
// List all registers here if you need to know their address in the block controller:
|
||||
////! The register address of the gain value
|
||||
//static const uint32_t REG_NAME;
|
||||
|
||||
};
|
||||
|
||||
}} // namespace rfnoc::gain
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Copyright ${year} ${copyright_holder}
|
||||
//
|
||||
// ${license}
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <uhd/rfnoc/block_controller_factory_python.hpp>
|
||||
#include <rfnoc/${MODULE_NAME}/${blockname}_block_control.hpp>
|
||||
|
||||
using namespace rfnoc::gain;
|
||||
|
||||
void export_${blockname}_block_control(py::module& m)
|
||||
{
|
||||
py::class_<${blockname}_block_control, ${blockname}_block_control::sptr>(m, "${blockname}_block_control")
|
||||
.def(py::init(
|
||||
&uhd::rfnoc::block_controller_factory<${blockname}_block_control>::make_from))
|
||||
|
||||
;
|
||||
}
|
||||
|
|
@ -23,6 +23,47 @@ if(ENABLE_PYMOD_UTILS OR ENABLE_UTILS)
|
|||
COMPONENT utilities
|
||||
)
|
||||
|
||||
### RFNoC modtool. Note this also requires the RFNoC gain OOT.
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/rfnoc_modtool.py"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/rfnoc_modtool"
|
||||
)
|
||||
UHD_INSTALL(PROGRAMS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rfnoc_modtool
|
||||
RENAME rfnoc_modtool
|
||||
DESTINATION ${RUNTIME_DIR}
|
||||
COMPONENT utilities
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE RFNOC_GAIN_OOT_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/apps/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/cmake/*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/examples/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/fpga/gain/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/include/rfnoc/gain/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/lib/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/python/*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/README.md
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/rfnoc/CMakeLists.txt
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain/rfnoc/blocks/CMakeLists.txt
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
rfnoc_newmod ALL
|
||||
DEPENDS ${RFNOC_GAIN_OOT_FILES}
|
||||
COMMAND
|
||||
"${PYTHON_EXECUTABLE}" "${UHD_SOURCE_DIR}/cmake/Modules/copy_rfnoc_newmod.py"
|
||||
"--source" "${CMAKE_CURRENT_SOURCE_DIR}/../examples/rfnoc-gain"
|
||||
"--dest" "${CMAKE_CURRENT_BINARY_DIR}/rfnoc-newmod"
|
||||
"--module-dir" "${CMAKE_CURRENT_SOURCE_DIR}/../python/uhd"
|
||||
COMMENT "Generating rfnoc-newmod"
|
||||
)
|
||||
install(DIRECTORY
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rfnoc-newmod
|
||||
DESTINATION ${PKG_DATA_DIR}
|
||||
COMPONENT utilities)
|
||||
|
||||
### UHD Images downloader
|
||||
# Configure the scripts
|
||||
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../images/manifest.txt CMAKE_MANIFEST_CONTENTS)
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
set(BINARY_DIR "" CACHE STRING "")
|
||||
set(SOURCE_DIR "" CACHE STRING "")
|
||||
file(COPY "${SOURCE_DIR}/rfnoc/" DESTINATION ${BINARY_DIR}/rfnoc
|
||||
FILES_MATCHING PATTERN *.py)
|
||||
file(COPY "${SOURCE_DIR}/rfnoc/" DESTINATION ${BINARY_DIR}/rfnoc
|
||||
FILES_MATCHING PATTERN *.v.mako)
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013-2015 Ettus Research LLC
|
||||
# Copyright 2018 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
"""
|
||||
Create a task for each file to generate from templates. A task is based on
|
||||
a named tuple to ease future extensions.
|
||||
Each task generates on result file by utilizing the BlockGenerator class.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
import mako.template
|
||||
import mako.lookup
|
||||
from mako import exceptions
|
||||
from ruamel import yaml
|
||||
|
||||
|
||||
class BlockGenerator:
|
||||
"""
|
||||
Generates a new file from a template utilizing a YAML configuration passed
|
||||
as argument.
|
||||
|
||||
Each BlockGenerator generate one file out of one template.
|
||||
All substitution parameter used in the template must be given in the YAML
|
||||
configuration. Exceptions: year (generated on the fly) and destination
|
||||
(given as argument). The root object parsed from the YAML configuration is
|
||||
config. All configuration items are represented as object members of config
|
||||
(YAML dictionaries are resolved recursively).
|
||||
"""
|
||||
|
||||
def __init__(self, template_file):
|
||||
"""
|
||||
Initializes a new generator based on template_file
|
||||
:param template_file: file used as root template during rendering,
|
||||
template file is assumed to be located in folder
|
||||
'modules' relative to this Python file.
|
||||
"""
|
||||
self.template_file = template_file
|
||||
self.year = datetime.datetime.now().year
|
||||
self.parser = None
|
||||
self.config = None
|
||||
self.destination = None
|
||||
|
||||
def setup_parser(self):
|
||||
"""
|
||||
Setup argument parser.
|
||||
|
||||
ArgumentParser is able to receive destination path and a file location
|
||||
of the YAML configuration.
|
||||
"""
|
||||
self.parser = argparse.ArgumentParser(
|
||||
description="Generate RFNoC module out of yml description")
|
||||
self.parser.add_argument("-c", "--config", required=True,
|
||||
help="Path to yml configuration file")
|
||||
self.parser.add_argument("-d", "--destination", required=True,
|
||||
help="Destination path to write output files")
|
||||
|
||||
def setup(self):
|
||||
"""
|
||||
Prepare generator for template substitution.
|
||||
|
||||
Results of setup are save in member variables and are accessible for
|
||||
further template processing.
|
||||
self.year current year (for copyright headers)
|
||||
self.destination root path where template generation results are placed
|
||||
self.config everything that was passed as YAML format
|
||||
configuration
|
||||
"""
|
||||
args = self.parser.parse_args()
|
||||
self.year = datetime.datetime.now().year
|
||||
self.destination = args.destination
|
||||
os.makedirs(self.destination, exist_ok=True)
|
||||
with open(args.config) as stream:
|
||||
self.config = yaml.safe_load(stream)
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Do template substitution for destination template using YAML
|
||||
configuration passed by arguments.
|
||||
|
||||
Template substitution is done in memory. The result is written to a file
|
||||
where the destination folder is given by the argument parser and the
|
||||
final filename is derived from the template file by substitute template
|
||||
by the module name from the YAML configuration.
|
||||
"""
|
||||
# Create absolute paths for templates so run location doesn't matter
|
||||
template_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
"templates"))
|
||||
lookup = mako.lookup.TemplateLookup(directories=[template_dir])
|
||||
filename = os.path.join(template_dir, self.template_file)
|
||||
tpl = mako.template.Template(filename=filename, lookup=lookup,
|
||||
strict_undefined=True)
|
||||
# Render and return
|
||||
try:
|
||||
block = tpl.render(**{"config": self.config,
|
||||
"year": self.year,
|
||||
"destination": self.destination,})
|
||||
except:
|
||||
print(exceptions.text_error_template().render())
|
||||
sys.exit(1)
|
||||
|
||||
filename = self.template_file
|
||||
# pylint: disable=no-member
|
||||
filename = re.sub(r"template(.*)\.mako",
|
||||
r"%s\1" % self.config['module_name'],
|
||||
filename)
|
||||
fullpath = os.path.join(self.destination, filename)
|
||||
|
||||
with open(fullpath, "w") as stream:
|
||||
stream.write(block)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Task = namedtuple("Task", "name")
|
||||
TASKS = [Task(name="noc_shell_template.v.mako"),
|
||||
Task(name="rfnoc_block_template.v.mako"),
|
||||
Task(name="rfnoc_block_template_tb.sv.mako"),
|
||||
Task(name="Makefile"),
|
||||
Task(name="Makefile.srcs")]
|
||||
for task in TASKS:
|
||||
generator = BlockGenerator(task.name)
|
||||
generator.setup_parser()
|
||||
generator.setup()
|
||||
generator.run()
|
||||
|
|
@ -1,20 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
"""RFNoC Image Builder: Create RFNoC FPGA bitfiles from YAML input.
|
||||
|
||||
Copyright 2019 Ettus Research, A National Instrument Brand
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
@ -24,63 +11,7 @@ import os
|
|||
import re
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
from uhd.rfnoc_utils import grc, image_builder, yaml_utils
|
||||
|
||||
|
||||
class ColorFormatter(logging.Formatter):
|
||||
"""Logging Formatter to add colors and icons."""
|
||||
|
||||
RESET = 0
|
||||
BOLD = 1
|
||||
DIM = 2
|
||||
GREY = "38;20"
|
||||
BLACK = "30;20"
|
||||
YELLOW = "33;20"
|
||||
RED = "31;20"
|
||||
BRIGHTRED = 91
|
||||
BRIGHT = 99
|
||||
|
||||
def c(colors): # noqa -- should be staticmethod but that requires Python 3.10
|
||||
"""Format escape sequence from list of colors."""
|
||||
return f"\x1b[{';'.join(str(c) for c in colors)}m"
|
||||
|
||||
debug_color = c([DIM])
|
||||
info_color = c([BRIGHT, BOLD])
|
||||
warning_color = c([YELLOW])
|
||||
error_color = c([BRIGHTRED])
|
||||
crit_color = c([RED, BOLD])
|
||||
reset = c([RESET])
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: debug_color + "[debug] %(message)s" + reset,
|
||||
logging.INFO: info_color + "%(message)s" + reset,
|
||||
logging.WARNING: warning_color + "⚠ %(message)s" + reset,
|
||||
logging.ERROR: error_color + "⛔ %(message)s" + reset,
|
||||
logging.CRITICAL: crit_color + "⛔ %(message)s" + reset,
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
"""Format a record the way we like it."""
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
return logging.Formatter(log_fmt).format(record)
|
||||
|
||||
class SimpleFormatter(logging.Formatter):
|
||||
"""Logging Formatter for non-interactive shells."""
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: "[debug] %(message)s",
|
||||
logging.INFO: "%(message)s",
|
||||
logging.WARNING: "[warning] %(message)s",
|
||||
logging.ERROR: "[error] %(message)s",
|
||||
logging.CRITICAL: "[critical] %(message)s",
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
"""Format a record the way we like it."""
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
return logging.Formatter(log_fmt).format(record)
|
||||
|
||||
from uhd.rfnoc_utils import grc, image_builder, log as rfnoc_log, yaml_utils
|
||||
|
||||
|
||||
def setup_parser():
|
||||
|
|
@ -238,7 +169,8 @@ def setup_parser():
|
|||
choices=("never", "auto", "always"),
|
||||
default="auto",
|
||||
help="Enable colorful output. When set to 'auto' will only show color "
|
||||
"output in TTY environments (e.g., interactive shells)")
|
||||
"output in TTY environments (e.g., interactive shells)",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
|
@ -262,6 +194,9 @@ def image_config(args):
|
|||
if image_core_name is None:
|
||||
image_core_name = os.path.splitext(os.path.basename(args.yaml_config))[0]
|
||||
return config, args.yaml_config, device, image_core_name, target
|
||||
# FIXME replace with ruamel.yaml
|
||||
import yaml
|
||||
|
||||
with open(args.grc_config, encoding="utf-8") as grc_file:
|
||||
config = yaml.load(grc_file)
|
||||
logging.info("Converting GNU Radio Companion file to image builder format")
|
||||
|
|
@ -342,16 +277,7 @@ def main():
|
|||
:return: exit code
|
||||
"""
|
||||
args = setup_parser().parse_args()
|
||||
use_color = (args.color == "always") or \
|
||||
(args.color == "auto" and sys.__stdout__.isatty() and sys.__stderr__.isatty())
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(ColorFormatter() if use_color else SimpleFormatter())
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(handler)
|
||||
|
||||
if args.log_level is not None:
|
||||
logging.root.setLevel(args.log_level.upper())
|
||||
rfnoc_log.init_logging(args.color, args.log_level)
|
||||
|
||||
config, source, device, image_core_name, target = image_config(args)
|
||||
source_hash = hashlib.sha256()
|
||||
|
|
|
|||
24
host/utils/rfnoc_modtool.py
Normal file
24
host/utils/rfnoc_modtool.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python3
|
||||
"""RFNoC Modtool: The tool to create and manipulate RFNoC OOT modules.
|
||||
|
||||
Copyright 2024 Ettus Research, A National Instrument Brand
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from uhd.rfnoc_utils import rfnoc_modtool
|
||||
|
||||
|
||||
def get_pkg_dir():
|
||||
"""Return package data directory.
|
||||
|
||||
This is the main UHD installation path for package data (e.g., /usr/share/uhd).
|
||||
"""
|
||||
return os.path.normpath("@CONFIG_PATH@")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(rfnoc_modtool.main(get_pkg_dir()))
|
||||
Loading…
Reference in a new issue