rfnoc oot: Move fpga/ subdir into rfnoc/

During redesign of the OOT module directory layout, the block YAMLs and
dts files were moved under `rfnoc/`, but the gateware was not. This
causes an inconsistency in the directory layout before and after
installation, making it hard to switch between installed and
not-installed versions of RFNoC modules.

For example, block YAMLs are in `./rfnoc/blocks/*.yml`, and gateware is
in `fpga/gain/rfnoc_block_gain/*`. After installation, block YAMLs are
in `$PREFIX/share/uhd/rfnoc/blocks/*.yml`, and gateware is in
`$PREFIX/share/uhd/rfnoc/fpga/gain/rfnoc_block_gain/*` -- note the
discrepancy.

This fixes two immediate issues:

- When building the X410 example in-tree, the DTS file was not being
  found because it used the same finding logic as the gateware source files.
- When building any image core file post-installation, none of the
  gateware could be found because it used the same logic for finding
  gateware inside the OOT, which is in a different location relative to
  the block YAMLs.

By removing the inconsistencies, both these use cases work.

A note on backward compatibility:

- Existing OOT modules already have the issue that building bitfiles
  post-install does not work, and that will continue to not work.
  However, otherwise, they will keep working as before.
- Because people may have gotten accustomed to specifying the RFNoC OOT
  module as the include directory (e.g., `rfnoc_image_builder -I
  /path/to/rfnoc-gain`), we allow for this use case, and auto-correct
  the include path in this case with some backward compatibility code.
- Building the rfnoc-gain/icores/x410_XG_200_rfnoc_image_core.yml
  example image core file now also works, even with older versions of
  rfnoc-gain.
This commit is contained in:
Martin Braun 2025-04-08 17:18:55 +02:00 committed by Jörg Hofrichter
parent 332646f91f
commit 8684d3c15a
17 changed files with 127 additions and 17 deletions

View File

@ -17,6 +17,9 @@ Change Log for Releases
* Bug Fixes
- Calling `recv()` on an RX streamer connected to an RFNoC device with a
zero timeout value now correctly returns immediately.
- rfnoc-gain: Moved the fpga/ subdirectory under rfnoc/. This way, the
directory structure inside the OOT module is identical to the directory
structure after installation.
* Removals / Deprecations
- Removed tools/uhd_dump. This tool is obsolete, the USRP X3x0 is not even
compatible with it any more. Dumps can be recorded and analyzed with wireshark

View File

@ -11,8 +11,8 @@ steps:
- "apps/*.cpp"
- "build"
- "examples/*.py"
- "fpga/gain/ip"
- "fpga/gain/rfnoc_block_gain"
- "rfnoc/fpga/gain/ip"
- "rfnoc/fpga/gain/rfnoc_block_gain"
- "icores/*.yml"
- "lib/*.cpp"
- "gr-*"
@ -39,14 +39,14 @@ steps:
pattern: "^ .+py$"
repl: " # Insert your example sources here if you want them to be installed"
- comment_out:
file: fpga/newmod/CMakeLists.txt
file: rfnoc/fpga/newmod/CMakeLists.txt
range: [9, 13]
- search_and_replace:
file: fpga/newmod/CMakeLists.txt
file: rfnoc/fpga/newmod/CMakeLists.txt
pattern: "add_subdirectory\\(([a-z0-9_]+)\\)"
repl: "#add_subdirectory(rfnoc_block_myblock)"
- insert_before:
file: fpga/newmod/CMakeLists.txt
file: rfnoc/fpga/newmod/CMakeLists.txt
pattern: "# IP gets"
text: "# Uncomment the following lines to add your own IP:\n"
- search_and_replace:

View File

@ -215,7 +215,6 @@ endif(NOT CMAKE_MODULES_DIR)
########################################################################
if(UHD_FPGA_DIR)
add_subdirectory(rfnoc)
add_subdirectory(fpga/gain)
add_subdirectory(icores)
endif()
if(UHD_FOUND)

View File

@ -18,7 +18,7 @@ out-of-tree blocks.
Examples that are listed in CMake get installed into `share/rfnoc-gain/examples`
within the installation prefix.
* `fpga/gain`: This directory contains the gateware for the HDL modules
* `rfnoc/fpga/gain`: This directory contains the gateware for the HDL modules
of the individual RFNoC blocks, along with their testbenches, and additional
modules required to build the blocks. There is one subdirectory for every
block (or module, or transport adapter). This is also where to define IP that
@ -60,13 +60,81 @@ out-of-tree blocks.
the UHD Python API is not found (or Python dependencies for building bindings,
such as pybind11, are not found) no Python bindings will be generated.
* `rfnoc`: This directory contains all the block definitions (YAML files).
* `rfnoc/blocks`: This directory contains all the block definitions (YAML files).
These block definitions can be read by the RFNoC tools, and will get
installed into the system for use by other out-of-tree modules.
Within this directory, there needs to be one subdirectory for every type of
RFNoC component that this out-of-tree module contains: `blocks` for regular
RFNoC blocks, `modules` for generic Verilog modules, and `transport_adapters`
for RFNoC transport adapters.
If an out-of-tree module contains other types of RFNoC components, such as
modules or transport adapters, they should be placed in the appropriate
subdirectory of `rfnoc` (e.g., `rfnoc/modules` or `rfnoc/transport_adapters`).
Like with the gateware, these get installed into the RFNoC package data
directory, e.g., `/usr/share/uhd/rfnoc/blocks/*.yml`.
* `rfnoc/dts`: This is an example folder for when blocks or transport adapters
require additional .dtsi files (DTS include files). The only file in here is
empty, and is included for example purposes only.
A note on the directory structure: After installation, all files that are
required by `rfnoc_image_builder` get installed into a common directory, e.g.
`/usr/share/uhd/rfnoc`. The directory structure underneath is identical to the
directory structure in this example, e.g., `/usr/share/uhd/rfnoc/fpga/gain` will
have the same content as `./fpga/gain`. This allows the image builder to work
with the OOT module as an include directory.
Files that are required for the software component (e.g., everything under `include`,
`lib`, `apps` and so on) are installed like any other software (e.g., include
files may go to `/usr/include/rfnoc`, shared libraries might go to `/usr/lib`,
and so on).
## Building FPGA bitfiles from this OOT module
Building a bitfile requires an image core file, and those are stored under the
`icores` directory. The image core files are YAML files that define the contents
of the RFNoC image, including the blocks that are part of the image.
Inside the `icores` directory is a CMakeLists.txt file, inside which bitfile
targets can be defined using the `RFNOC_REGISTER_IMAGE_CORE()` macro. For example,
if we register the image core file `x310_rfnoc_image_core.yml` in the CMakeLists.txt
file, we can build the bitfile using the following command (assuming CMake was
executed beforehand):
```sh
cd /path/to/rfnoc-gain/build
make x310_rfnoc_image_core
```
Note that this will build the bitfile with source files from this directory. This
workflow is therefore a sensible way to build bitfiles during development.
Using `make` is not a requirement, it is merely a convenience. You can also
directly run `rfnoc_image_builder` and point it to the `rfnoc` subdirectory:
```sh
cd /path/to/rfnoc-gain/
rfnoc_image_builder -y ./icores/x310_rfnoc_image_core.yml -I ./rfnoc
```
This has the same effect, but let's you control the precise arguments to the
image builder.
However, the design of the out-of-tree module is such that it is also possible
to install, and then build bitfiles using the installed files. This is of value
when installing multiple OOT modules, and not planning to modify their source
files.
For example, the following workflow is valid:
```sh
cd /path/to/rfnoc-gain # Switch to this file's directory
mkdir build && cd build && cmake .. -F /path/to/uhd/fpga/usrp3 # Configure the project
# Now, launch an image build and use all files from this directory:
rfnoc_image_builder -y ../icores/x310_rfnoc_image_core.yml -I ../rfnoc
make install # Install software and gateware
# Build again, without -I:
rfnoc_image_builder -y ../icores/x310_rfnoc_image_core.yml
```
In this workflow, we launch `rfnoc_image_builder` once with `-I` on the current
directory, and once without. In the latter case, the image builder will only
use files that were installed into the right location.

View File

@ -4,6 +4,8 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This subdir contains all the gateware
add_subdirectory(fpga/gain)
# Add one subdirectory for every type of RFNoC components you have YAML files
# for (e.g., blocks, transport_adapters, modules, ...)
add_subdirectory(blocks)

View File

@ -25,6 +25,7 @@ from mako import exceptions
from . import grc, yaml_utils
from .builder_config import ImageBuilderConfig
from .template import Template
from .utils import check_include_paths_backward_compat
### DATA ######################################################################
# Directory under the FPGA repo where the device directories are
@ -337,8 +338,7 @@ def load_module_yamls(include_paths):
def load_module_descs(module_type):
"""Load separate block/module defs."""
paths = [
os.path.abspath(os.path.normpath(os.path.join(x, "rfnoc", module_type)))
for x in include_paths
os.path.abspath(os.path.normpath(os.path.join(x, module_type))) for x in include_paths
]
logging.debug("Looking for %s descriptors in:", module_type[:-1])
for path in paths:
@ -423,9 +423,9 @@ def build_image(repo_fpga_path, config_path, device, **args):
"""
# We start by loading some core/standard files that are always needed.
core_config_path = yaml_utils.get_core_config_path(config_path)
# TODO does this work for both block yamls and HDL sources?
include_paths = args.get("include_paths", []) + [config_path]
include_paths = check_include_paths_backward_compat(args.get("include_paths", [])) + [
os.path.join(config_path, "rfnoc")
]
logging.debug("Include paths: %s", ";".join(include_paths))
# A list of all known module descriptors
known_modules = load_module_yamls(include_paths)

View File

@ -163,3 +163,41 @@ def find_include_file(filename, include_paths):
return os.path.realpath(candidate)
raise FileNotFoundError(f"File {filename} not found in include paths: {include_paths}")
def check_include_paths_backward_compat(include_paths):
"""Check if the include paths are plausible or maybe following an old style.
In an older version of the image builder, the include paths for OOT builds
pointed to the OOT modules itself (e.g., `-I ./rfnoc-gain`). However, the
correct path should be the `rfnoc` subdir (e.g., `-I ./rfnoc-gain/rfnoc`).
To make people's lives not unnecessarily hard, we allow the old behavior.
This requires a heuristic to figure out if the user actually meant to use
include_path + 'rfnoc' or not. This is done by checking if the include path
contains expected subdirs (e.g., `blocks`, `fpga`) or if the path contains
subdirs that are not expected (e.g., `cmake`, `rfnoc`).
"""
def path_is_probably_old_style(path):
"""Return True if a path is missing an 'rfnoc' subdir."""
expected_subdirs_new_style = ["blocks", "fpga", "transport_adapters", "modules"]
expected_subdirs_old_style = ["cmake", "rfnoc"]
if any(os.path.isdir(os.path.join(path, p)) for p in expected_subdirs_new_style):
return False
if all(os.path.isdir(os.path.join(path, p)) for p in expected_subdirs_old_style):
return True
return False
def check_and_fix(path):
if path_is_probably_old_style(path):
new_path = os.path.join(path, "rfnoc")
logging.info(
"Include path %s is likely missing an 'rfnoc' subdir. Changing to: %s",
path,
new_path,
)
return os.path.join(path, "rfnoc")
return path
return [check_and_fix(path) for path in include_paths]