PYNQ SD Card image
This page explains how SD card images can be built for PYNQ embedded platforms.
Note: the PYNQ images for supported boards are provided as precompiled downloadable SD card images and do not need rebuilt. The SD card build flow is only required to modify SD cards’ contents or target a new board.
Specifically, the SD card build flow creates BOOT.BIN, the U-Boot
bootloader, the Linux device tree blob, the Linux kernel and the
PYNQ-Linux root filesystem.
The source files for the PYNQ image flow build can be found here:
<PYNQ repository>/sdbuild
Prepare the Building Environment
The image build runs in a Docker container based on the following Ubuntu release:
Supported OS |
Code name |
|---|---|
Ubuntu 24.04 |
Noble |
Use Docker to prepare the build environment
Docker manages the image build dependencies and environment. AMD tools remain on the host and are mounted into the container.
Install Docker on your host OS by following the official Docker installation instructions.
Install the required AMD tools on the host:
Vivado and Vitis, version 2025.2 (Petalinux is no longer required)
Ensure your host OS is supported by the AMD tools (see UG973)
Note
AMD tools must be installed on the host system, not inside the Docker container.
Clone the PYNQ repository and build the Docker image:
git clone --recursive https://github.com/Xilinx/PYNQ.git PYNQ cd PYNQ/sdbuild docker build \ --build-arg USERNAME=$(whoami) \ --build-arg USER_UID=$(id -u) \ --build-arg USER_GID=$(id -g) \ -t pynqdock:latest .The
--build-argvalues ensure that files created inside the container will be owned by your user on the host system, avoiding permission issues.From the top of the PYNQ repository, set the paths to the AMD tools and licence, then run the container:
export XILINX_TOOLS=/tools/Xilinx/2025.2 export XILINXD_LICENSE_FILE="$HOME/.Xilinx" docker run --init --rm \ --network host \ -e XILINX_TOOLS -e XILINXD_LICENSE_FILE \ -v "$XILINX_TOOLS:$XILINX_TOOLS:ro" \ -v "$XILINXD_LICENSE_FILE:$XILINXD_LICENSE_FILE" \ -v "$PWD:/workspace" \ --privileged \ pynqdock:latest \ bash -lc 'cd /workspace/sdbuild && make BOARDS=ZCU104 REBUILD_PYNQ_SDIST=1 REBUILD_PYNQ_ROOTFS=1'Set
BOARDS=VCK190to build a VCK190 image. Multiple board names can be supplied at once.
Building the Image
The build flow expects a prebuilt board-agnostic root filesystem and PYNQ source
distribution unless they are rebuilt from source. Place prebuilt files in the
sdbuild/prebuilt folder:
cp pynq_rootfs.aarch64.tar.gz <PYNQ repository>/sdbuild/prebuilt/
cp pynq-<version>.tar.gz <PYNQ repository>/sdbuild/prebuilt/pynq_sdist.tar.gz
To build one of the supported images using the prebuilt files:
cd <PYNQ repository>/sdbuild/
make BOARDS=ZCU104
make BOARDS=VCK190
To build the PYNQ source distribution and root filesystem from source:
make BOARDS=ZCU104 REBUILD_PYNQ_SDIST=1 REBUILD_PYNQ_ROOTFS=1
The image is written to sdbuild/output/<BOARD>-4.0.0.img and its boot
artefacts are written to sdbuild/output/boot/<BOARD>/.
Rebuilding the prebuilt board-agnostic image
ZCU104 and VCK190 use the aarch64 root filesystem. Reusing the prebuilt
root filesystem skips the board-agnostic stage.
You can force a root filesystem build by setting the REBUILD_PYNQ_ROOTFS variable
when invoking make:
cd <PYNQ repository>/sdbuild/
make REBUILD_PYNQ_ROOTFS=1 BOARDS=<board>
Rebuilding the PYNQ source distribution tarball
Reuse the prebuilt PYNQ source distribution package unless the package or included overlays have changed.
You can force a PYNQ source distribution rebuild by setting the REBUILD_PYNQ_SDIST variable
when invoking make
cd <PYNQ repository>/sdbuild/
make REBUILD_PYNQ_SDIST=1 BOARDS=<board>
Cleaning image build output
To remove the build and output directories while retaining the EDF cache:
cd <PYNQ repository>/sdbuild/
make clean
The first EDF build creates an sdbuild/edf-cache directory of approximately
30 GB. Set EDF_CACHE=/path/to/edf-cache to share it between checkouts or
store it on another filesystem.
Retargeting to a Different Board
Additional boards can be supplied through an external board directory. Each
board is a directory containing a <BOARD>.spec file and an edf.env file.
Those two are the only required files; everything else is added as the board
needs it. The external directory is treated like the
<PYNQ repository>/boards directory.
Board directory layout
Myboard/
├── Myboard.spec # required: make variables for the board
├── edf.env # required: boot artefact configuration
├── edf_bsp/ # optional
│ ├── board.dtsi # optional: device tree nodes for the board
│ ├── kernel.cfg # optional: kernel config fragment
│ └── u-boot/ # optional: u-boot patches and config fragments
├── base/ # required if BITSTREAM_<BOARD> is set: overlay
│ # sources and a Makefile that builds them
├── notebooks/ # optional: notebooks, delivered to the image
└── packages/ # optional: extra packages for the root filesystem
The directory name is the board name. It must match the .spec file name and
the <BOARD> suffix of the variables inside it, and it becomes the BOARD
environment variable in the image.
Elements of the specification file
The specification file should be named <BOARD>.spec, where BOARD is the name
of the board directory. All paths in it are relative to the board directory.
Variable |
Meaning |
|
|---|---|---|
|
required |
|
|
optional |
Overlay loaded at boot, for example
|
|
optional |
|
|
optional |
Packages installed into the board’s
root filesystem. Without |
|
optional |
Extra packages for a PYNQ.remote root filesystem |
ARCH_Myboard := aarch64
BITSTREAM_Myboard := base/base.bit
FPGA_MANAGER_Myboard := 1
STAGE4_PACKAGES_Myboard := xrt pynq ethernet selftest
Boot artefacts: edf.env
BOOT.BIN, the kernel Image, system.dtb, the kernel modules and
zocl.ko are built with AMD’s EDF (Yocto/bitbake) flow. edf.env selects
the Yocto machine to build them for.
Key |
Meaning |
|
|---|---|---|
|
required |
Machine used for |
|
optional |
|
|
optional |
Machine used for the kernel. Defaults
to |
|
optional |
EDF manifest tag to sync. Defaults to the tag used by the build scripts |
|
custom mode |
The XSA to generate the machine from.
Without it, |
|
optional |
Custom mode only: board DTS to pass to
|
|
optional |
|
Use prebuilt mode when AMD’s layers already contain a machine for the board, as for the ZCU104:
EDF_MANIFEST_TAG=amd-edf-rel-v25.11.1
EDF_MODE=prebuilt
EDF_BOOT_MACHINE=zynqmp-zcu104-sdt-full
EDF_LINUX_MACHINE=zynqmp-zcu104-sdt-full
Use custom mode when the boot image has to match a design built here, as on
Versal, where BOOT.BIN carries the golden reference design that runtime
overlays are segmented children of. The build runs sdtgen on
BSP_XSA_PATH to produce a system device tree, then gen-machine-conf to
turn that into a machine layer, so Vivado must be available. The VCK190:
EDF_MANIFEST_TAG=amd-edf-rel-v25.11.1
EDF_MODE=custom
EDF_BOOT_MACHINE=versal-vck190-pynq-seg
EDF_LINUX_MACHINE=amd-cortexa72-common
EDF_BOARD_DTS=versal-vck190-reva
BSP_XSA_PATH=golden/golden.xsa
In custom mode EDF_BOOT_MACHINE must not match a machine name in AMD’s
layers, or bitbake resolves theirs and ignores the generated one.
Kernel, device tree and U-Boot: edf_bsp
All three are optional, and a board that needs none of them can omit
edf_bsp entirely.
board.dtsiis appended to the machine’s device tree. Board-specific nodes belong here: memory, PHYs, I2C devices, pin settings.
kernel.cfgis added to the kernel’s sources as a config fragment when present, and merged into its.config.
u-boot/is added to U-Boot’s sources when present..patchand.difffiles are applied, and.cfgfragments are merged into U-Boot’s.config.
Editing board.dtsi invalidates the device tree’s shared state, so the change
is picked up on the next build.
Board-specific packages
A packages directory can be included in the board directory with the same
layout as the <PYNQ repository>/sdbuild/packages directory. A directory
under it becomes installable by naming it in STAGE4_PACKAGES_<BOARD>, and
packages from the standard sdbuild library can be named there too. A package
consists of up to four optional files: a Makefile for work done on the host,
pre.sh and post.sh which run outside the target root filesystem and are
passed its path, and qemu.sh which runs inside it under QEMU. Scripts should
write temporary files to $BUILD_ROOT.
Leveraging boot.py to modify SD card boot behavior
Starting from the v2.6.0 release, PYNQ SD card images include a boot.py
file in the boot partition that runs automatically after the board has been
booted. Whatever is inside this file runs during boot and can be modified
any time for a custom next-boot behavior (e.g. changing the host name,
connecting the board to WiFi, etc.).
This file can be accessed using a SD Card reader on your host machine or
from a running PYNQ board - if you are live on the board inside Linux, the
file is located in the /boot folder. Note that /boot is the
boot partition of the board and no other files should be modified.
If you see some existing code running inside the boot.py file, it probably came
from a PYNQ sdbuild package that modified that file. To see an example of an
sdbuild package writing the boot.py file see the ZCU104’s boot_leds package
in <PYNQ repository>/boards/ZCU104/packages/boot_leds, which simply flashes
the boards LEDs to signify Linux has booted on the board.
Using the PYNQ package
The pynq package will treat your board directory the same as any of the
officially supported boards. This means, in particular, that:
A
notebooksfolder, if it exists, will be copied into thejupyter_notebooksfolder in the image. Notebooks here will overwrite any of the default ones.Any directory containing a
.bitor.pdifile will be treated as an overlay and copied into the overlays folder of the PYNQ installation. Any notebooks will likewise by installed in an overlay-specific subdirectory.
Building from a board repository
To build from a third-party board repository, pass the BOARDDIR variable to the
sdbuild makefile.
cd <PYNQ repository>/sdbuild/
make BOARDDIR=${BOARD_REPO}
The board repo should be provided as an absolute path. The BOARDDIR variable
can be combined with the BOARDS variable if the repository contains multiple
boards and only a subset should be built.