Hello everyone,
A Quick Note: Why Yocto and not PetaLinux?
You might be thinking, “Isn’t PetaLinux the standard for this?” Well, it seems the winds are changing. PetaLinux itself no longer seems to prioritize PYNQ, and more importantly, AMD/Xilinx is officially deprecating PetaLinux’s core BSP-generation feature starting in 2025.1, pushing users directly towards Yocto.
You can read the official note here: Yocto Project Machine Configuration Support
So, figuring out a “pure” Yocto build for PYNQ isn’t just a fun academic exercise—it’s quickly becoming the future-proof path. This guide is our attempt to document that path.
After a significant amount of debugging, we successfully got PYNQ v3.1.2 running on our custom ZynqMP-based board using a custom Yocto layer. The documentation for this specific combination is sparse, so I wanted to share our solution.
I cannot share the complete source code (because it’s too messy, split in several dynamic layers, probably too dirty to be shared…), but I can provide a step-by-step guide on the layer structure and the two critical fixes that were required to make it work.
1. The Custom PYNQ Layer Structure
We created a dedicated layer (e.g., meta-custom-pynq) to hold all our PYNQ-related recipes. Here is the file structure and the purpose of each part:
meta-custom-pynq/
├── classes
│ └── xilinx-pynq.bbclass # A helper class to define common PYNQ dependencies (cma, pynqutils, etc.)
├── conf
│ └── layer.conf # Standard layer configuration file
├── recipes-bsp
│ └── device-tree
│ ├── device-tree.bbappend # CRITICAL FIX #1: Injects board ID into device tree
│ └── files/ # Standard PYNQ device tree overlays (UIO, ZOCL)
│ ├── pynq_uio_*.dtsi
│ └── pynq_zocl_*.dtsi
├── recipes-images
│ ├── images
│ │ └── custom-pynq-image.bb # Our main image recipe
│ └── packagegroups
│ └── packagegroup-custom-pynq.bb # Pulls all PYNQ packages together
├── recipes-python
│ ├── python3-pynq # PYNQ v3.1.2 recipe
│ ├── python3-pynqmetadata # PYNQ dependency
│ └── python3-pynqutils # PYNQ dependency
├── recipes-support
│ ├── libcma # PYNQ dependency (Contiguous Memory Allocator)
│ ├── pynq-runtime-config # Custom recipe for board setup (hostname, etc.)
│ │ ├── files/
│ │ │ ├── boardname.sh # Script for /etc/profile.d
│ │ │ └── pynq_hostname.sh # Script to set hostname
│ │ └── pynq-runtime-config_1.0.bb
│ └── xrt-environment # Helper recipe to install test notebooks/kernels
│ └── python3-ipykernel_%.bbappend # (Optional) Tweak for Jupyter kernels
└── recipes-xrt
└── xrt
├── files
│ └── 0001-Force-python-bindings-build-for-embedded.patch # Patch for XRT
└── xrt_%.bbappend # CRITICAL FIX #2: Builds and installs pyxrt.so
2. The Two Critical Fixes
Out of the box, PYNQ failed with two major issues. Here is how we fixed them.
Fix #1: The ON_TARGET = False Problem (Device Tree)
PYNQ failed to recognize our board, which we traced back to os.path.isfile('/proc/device-tree/chosen/pynq_board') returning False.
The Solution: We must inject this property into the device tree.
In recipes-bsp/device-tree/device-tree.bbappend, we added a task to run before the device tree is compiled. This task appends the required chosen node to the system-top.dts file.
device-tree.bbappend:
# Include PYNQ's dtsi files (UIO, ZOCL)
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += " \
file://pynq_uio_zynqmp.dtsi \
file://pynq_zocl_intc_zynqmp.dtsi \
"
# THIS IS THE FIX:
# Append to the do_configure task to modify the dts before compilation
do_configure:append() {
# Check if MACHINE is set, then echo the property into the dts
if [ -n "${MACHINE}" ]; then
echo "/ { chosen { pynq_board = \"${MACHINE}\"; }; };" >> ${B}/device-tree/system-top.dts
else
bbwarn "[PYNQ] MACHINE variable is empty, cannot set pynq_board property."
fi
}
Fix #2: The NameError: name 'pyxrt' is not defined Problem (XRT)
Even with the correct device tree, PYNQ failed because it still tries to import pyxrt. The modern XRT recipe (e.g., 2024.1) does not build the pyxrt.so Python bindings for embedded targets by default.
The Solution: We must force XRT to build the Python bindings and install them correctly.
recipes-xrt/xrt/xrt_%.bbappend: This file is complex, but it does three things:
-
Apply a Patch: We use
SRC_URI += "file://0001-Force-python-bindings-build-for-embedded.patch"to patch XRT’sCMakeLists.txt. The patch forces thepythonsubdirectory to be included in the embedded build.
diff --git a/CMake/embedded_system.cmake b/CMake/embedded_system.cmake
index 4b298fced6..cdee7e7a77 100644
— a/CMake/embedded_system.cmake
+++ b/CMake/embedded_system.cmake
@@ -134,3 +134,7 @@ if (DEFINED ENV{DKMS_FLOW})
set (XRT_DKMS_DRIVER_SRC_BASE_DIR “${CMAKE_CURRENT_SOURCE_DIR}/runtime_src/core”)
include (CMake/dkms-edge.cmake)
endif()
+
+# — PYNQv3.1 - patches to force cmake to build pyxrt
+set (XRT_INSTALL_PYTHON_DIR “${XRT_INSTALL_DIR}/python”)
+add_subdirectory(python) -
Force Build Flags: We add
EXTRA_OECMAKEflags to ensure CMake enables the Python bindings. -
Package
pyxrt.so: This is the most important part. Thepyxrt.sofile is built but not packaged. We use ado_install:appendto:-
Find the compiled Python binding (e.g.,
pyxrt.cpython-312-aarch64-linux-gnu.so). -
Rename it to
pyxrt.so. -
Install it to the correct path:
/usr/lib/pythonX.Y/pyxrt.so. Note: It must go here, not insite-packages. -
We create a new package,
python3-xrt, to contain this single file and add it to our image’s dependencies.
-
3. PYNQ Runtime & Board Configuration
To finish the setup, we created a small helper recipe pynq-runtime-config_1.0.bb. This recipe handles board-specific configuration:
-
Creates
/etc/xocl.txt: Writes the${MACHINE}name into/etc/xocl.txt. -
Sets Hostname: Installs the
pynq_hostname.shscript and a simplesystemdservice (pynq-set-hostname.service) that runs once on first boot to set the hostname topynq-${MACHINE}. -
Sets Profile: Installs
boardname.shinto/etc/profile.d/to provide a nice shell prompt.
4. Bonus: Interactive Plotting in Jupyter
As a final touch, to get interactive “Matlab-style” plotting inside Jupyter notebooks, we simply added the python3-ipympl package to our main image recipe (custom-pynq-image.bb). This enables the Matplotlib “widget” backend.
I hope this guide helps others who are trying to get PYNQ working in a custom Yocto environment.
P.S. (A Note on Open Sourcing)
I see some of you might be wondering, “This is great, but… where’s the GitHub repo?” ![]()
If this guide generates a lot of interest, I might be convinced to clean up this layer and post it publicly.
One tiny problem: I simply don’t have the time or the deep Yocto expertise to keep it alive and updated with every new release.
So, if I do post it, consider it a “use-at-your-own-risk,” “no-warranty-expressed-or-implied” kind of starting point!
Let me know if it’s something you’d still find useful. Cheers!
Good luck!