Custom Zynq 7000 Board EDF Tutorial
Overview
This tutorial demonstrates how to create an Embedded Development Framework (EDF) image for a custom Zynq 7000-based board. The process involves generating custom machine configurations, device trees, and boot images using the AMD toolchain.
Prerequisites
AMD Vivado 2026.1.1 or later
Yocto build environment with EDF sources
Custom board hardware description (XSA file)
Access to build machine with appropriate storage (80-170 GB recommended)
Learning Objectives
After completing this tutorial, you can:
Generate custom machine configurations for Zynq 7000 boards
Create device tree files for custom hardware
Build EDF boot images and Linux disk images
Flash an SD card and boot Linux on custom Zynq 7000 hardware
Understand the two-stage build process for custom boards
Make targeted hardware changes without breaking device tree consistency
Design Files
This tutorial uses the following design files:
custom_board.xsa— Hardware description file for the custom boardcustom_zynq7000.dtsi— Device tree overlay for a custom Zynq 7000 board
Note
These are example file names used throughout the tutorial. Replace them with the actual file names from your own Vivado project when following the steps.
custom_board.xsais exported from Vivado after completing your block design and generating the bitstream.custom_zynq7000.dtsiis written by hand. SDTGen does not generate a board DTSI for a custom board, which is why it accepts one throughset_dt_param -user_dts. You create it in Customize Device Tree below.
Tutorial Structure
Environment Setup
Initialize Build Environment
First, set up your Yocto build environment with EDF support (see Setting Up the Yocto Environment):
$ source /tools/Xilinx/Vitis/2026.1.1/settings64.sh
$ repo init -u https://github.com/Xilinx/yocto-manifests.git \
-b refs/tags/amd-edf-rel-v26.06.1 -m default-edf.xml
$ repo sync
$ source edf-init-build-env
Create Working Directory Structure
Sourcing edf-init-build-env has already created the build directory
and left you in it. Create the SDTGen output directory there:
$ mkdir -p hw_project_sdt
Hardware Description Analysis
Extract Hardware Description
Place your custom board XSA file in the working directory and extract the system device tree:
$ cp /path/to/custom_board.xsa .
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
Verify Generated Files
After SDTGen completes, hw_project_sdt/ contains the generated
hardware description files: custom_board.bit, pcw.dtsi,
pl.dtsi, ps7_init.c, ps7_init.h, ps7_init.html,
ps7_init.tcl, ps7_init_gpl.c, ps7_init_gpl.h,
system-top.dts, zynq-7000.dtsi, and the include/ bindings
directory.
SDT File Behavior by Peripheral Configuration
Disabling a PS peripheral in Vivado removes its node from the generated files, but does not automatically prune your board DTSI. This mismatch is the most common source of device tree inconsistencies on custom boards. See Hardware Customization Scenarios for the complete walk-through covering each scenario.
Customize Device Tree
Create the board device tree file. SDTGen reads it through
-user_dts in the next step, so it must exist before you continue:
$ cat > hw_project_sdt/custom_zynq7000.dtsi << 'EOF'
/include/ "zynq-7000.dtsi"
/include/ "pcw.dtsi"
/ {
model = "Custom Zynq 7000 Board";
compatible = "xlnx,zynq-7000";
aliases {
ethernet0 = &gem0;
serial0 = &uart1;
};
};
&gem0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <ðernet_phy>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethernet_phy: ethernet-phy@0 {
reg = <0>;
};
};
};
EOF
Device Tree Generation
Configure SDTGen Parameters
Use SDTGen to apply your custom device tree:
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -user_dts ./hw_project_sdt/custom_zynq7000.dtsi
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
Verify Device Tree Output
After SDTGen completes, hw_project_sdt/ contains updated device tree
files: pcw.dtsi, pl.dtsi, system-top.dts,
zynq-7000.dtsi, custom_zynq7000.dtsi, ps7_init.c,
ps7_init.h, ps7_init.html, ps7_init.tcl,
ps7_init_gpl.c, ps7_init_gpl.h, custom_board.bit, and
the include/ bindings directory.
Machine Configuration
Generate Machine Configuration
Create a custom machine configuration using the gen-machine-conf
utility (see
Custom Hardware Development).
First, create the layer skeleton:
$ bitbake-layers create-layer ./sources/meta-custom-zynq7000
Then generate the machine configuration:
$ gen-machine-conf parse-sdt \
--hw-description ./hw_project_sdt \
-c ./sources/meta-custom-zynq7000/conf \
--machine-name custom-zynq7000 \
-g full
Verify Machine Configuration
Check the generated machine configuration:
$ ls sources/meta-custom-zynq7000/conf/machine/
custom-zynq7000.conf
$ cat sources/meta-custom-zynq7000/conf/machine/custom-zynq7000.conf
Add Meta Layer to Build
Add your custom meta layer to the build configuration:
$ bitbake-layers add-layer ./sources/meta-custom-zynq7000
Boot Image Build
Build Boot Components
Build the boot image using the xilinx-bootbin recipe:
$ MACHINE=custom-zynq7000 bitbake xilinx-bootbin
Verify Boot Image Output
Check that the boot image was produced:
$ ls tmp/deploy/images/custom-zynq7000/boot.bin
tmp/deploy/images/custom-zynq7000/boot.bin
boot.bin is a symlink to a datestamped
BOOT-custom-zynq7000-<timestamp>.bin, and
BOOT-custom-zynq7000.bin is a second symlink to the same file, so all
three names refer to one image. The directory also holds
boot.bin-extracted/ with the unpacked components, system.dtb,
fsbl-custom-zynq7000.elf, and the U-Boot binaries.
Boot Image Components
The generated boot.bin contains:
FSBL (First Stage Boot Loader): Hardware initialization
U-Boot: Second stage bootloader
Bitstream: FPGA configuration (if present)
Device Tree: Hardware description for Linux kernel
Linux Image Build
Build Linux Disk Image
Build the complete Linux filesystem image:
$ MACHINE=amd-cortexa9thf-neon-common bitbake edf-linux-disk-image
Note
The two-stage build process uses a custom machine
(custom-zynq7000) for board-specific boot components and a
common machine (amd-cortexa9thf-neon-common) for generic Linux
components.
Verify Linux Image Output
$ ls tmp/deploy/images/amd-cortexa9thf-neon-common/
edf-linux-disk-image-*.rootfs.tar.gz
edf-linux-disk-image-*.rootfs.wic
edf-linux-disk-image-*.rootfs.wic.bmap
edf-linux-disk-image-*.rootfs.wic.xz
uImage
modules-*.tgz
WIC Image Creation
When the boot image and Linux disk image are ready, copy
boot.bin into the boot partition of the WIC image using the
wic cp command. This produces a single SD card image containing
the bootloader, kernel, and root filesystem.
Verify the WIC Partition Layout
$ wic ls tmp/deploy/images/amd-cortexa9thf-neon-common/edf-linux-disk-image-amd-cortexa9thf-neon-common.rootfs.wic
Num Start End Size Fstype
1 16384 ... ... fat32
2 ... ... ... ext4
Copy the Boot Image Into the WIC Image
$ wic cp tmp/deploy/images/custom-zynq7000/boot.bin \
tmp/deploy/images/amd-cortexa9thf-neon-common/edf-linux-disk-image-amd-cortexa9thf-neon-common.rootfs.wic:1
This copies boot.bin into partition 1 (the FAT32 boot partition)
of the WIC image. The resulting .wic file is now ready to flash to
an SD card.
Note
The wic tool requires mtools to be installed on the host
(sudo apt install mtools on Ubuntu).
Flashing the SD Card and Booting
Set the Zynq 7000 Boot Mode
Look up the SD Card boot mode MIO strapping values in the Zynq-7000 TRM (UG585).
Set your board’s boot mode switches accordingly.
Power-cycle the board before inserting the SD card.
Flash the SD Card
Use an 8 GB SD card. The build produces a .wic.xz compressed
image that can be written directly with any of the tools below.
Option A — Raspberry Pi Imager (recommended)
Download and install Raspberry Pi Imager.
Click Choose OS → Use custom and select
edf-linux-disk-image-amd-cortexa9thf-neon-common.rootfs.wic.xzfromtmp/deploy/images/amd-cortexa9thf-neon-common/.Click Choose Storage and select your SD card.
Click Write. The imager decompresses and writes in one step.
Option B — Balena Etcher
Download and install Balena Etcher.
Click Flash from file and select the
.wic.xzimage.Select your SD card and click Flash.
Caution
Recent versions of Balena Etcher have been found unreliable — they may fail during the verify stage. If flashing fails, switch to Raspberry Pi Imager.
Option C — Command line (dd)
$ xz -dc tmp/deploy/images/amd-cortexa9thf-neon-common/edf-linux-disk-image-amd-cortexa9thf-neon-common.rootfs.wic.xz \
| sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
$ sync && sudo eject /dev/sdX
Insert and Boot
Insert the SD card into the board’s SD slot.
Connect a USB-to-UART cable to the board’s serial console port.
Open a serial terminal at 115200 8N1.
Power on the board. You should see U-Boot output followed by the Linux kernel boot log.
Login
The default EDF image boots to a login prompt:
AMD Embedded Development Framework Linux distribution amd-edf-rel-v26.06.1 amd-edf ttyPS0
amd-edf login: amd-edf
You are required to change your password immediately (administrator enforced).
New password:
Retype new password:
WARNING: AMD Embedded Development Framework is a reference Yocto Project
distribution that should be used for testing and development purposes only.
It is recommended that you create your own distribution for production use.
amd-edf:~$
Complete Build Workflow
Here is the complete workflow for reference:
$ source /tools/Xilinx/Vitis/2026.1.1/settings64.sh
$ repo init -u https://github.com/Xilinx/yocto-manifests.git \
-b refs/tags/amd-edf-rel-v26.06.1 -m default-edf.xml
$ repo sync
$ source edf-init-build-env
$ mkdir -p hw_project_sdt
$ cp /path/to/custom_board.xsa .
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
Create hw_project_sdt/custom_zynq7000.dtsi for your board (see
Customize Device Tree), then continue:
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -user_dts ./hw_project_sdt/custom_zynq7000.dtsi
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
$ bitbake-layers create-layer ./sources/meta-custom-zynq7000
$ gen-machine-conf parse-sdt \
--hw-description ./hw_project_sdt \
-c ./sources/meta-custom-zynq7000/conf \
--machine-name custom-zynq7000 \
-g full
$ bitbake-layers add-layer ./sources/meta-custom-zynq7000
$ MACHINE=custom-zynq7000 bitbake xilinx-bootbin
$ MACHINE=amd-cortexa9thf-neon-common bitbake edf-linux-disk-image
$ wic cp tmp/deploy/images/custom-zynq7000/boot.bin \
tmp/deploy/images/amd-cortexa9thf-neon-common/edf-linux-disk-image-amd-cortexa9thf-neon-common.rootfs.wic:1
Hardware Customization Scenarios
This section covers common hardware changes when adapting a reference design to your custom board. Each scenario describes what to change in Vivado, which SDT files are affected automatically, and whether the board DTSI requires manual edits.
Scenario A: Changing the Memory Amount
Change in Vivado
Open the PS7 Configuration wizard in your block design:
Double-click the ZYNQ7 Processing System IP block.
Navigate to DDR Configuration.
Set the Memory Part to match your physical DRAM device (this populates timing parameters and the effective address range).
Confirm the Memory Size reflects your target (for example, 512 MB or 1 GB).
Click OK, then regenerate the bitstream and re-export the XSA.
Files Updated by SDTGen
After re-running SDTGen with the new XSA, the following files change automatically:
File |
What changes |
Manual edit needed? |
|---|---|---|
|
|
No |
|
DDR timing parameters ( |
No |
|
No change |
No |
The memory@0 node lives in system-top.dts and is fully
generated by SDTGen. A 1 GB board produces reg = <0x0 0x40000000>;;
a board with 512 MB produces reg = <0x0 0x20000000>;. No board DTSI
edit is needed.
Note
If your board DTSI explicitly overrides the memory@0 node (to
add custom properties), that override must also be updated to match
the new size.
Verify the Change
$ grep -A 3 "memory@0" hw_project_sdt/system-top.dts
memory@0 {
device_type = "memory";
reg = <0x0 0x20000000>;
};
Scenario B: Changing a Clock Frequency
Change in Vivado
Files Updated by SDTGen
Clock changes flow entirely into the PS-generated files:
File |
What changes |
Manual edit needed? |
|---|---|---|
|
|
No |
|
|
No |
|
No change in most cases |
No |
The pcw.dtsi contains entries such as:
clkc: clkc@f8000000 {
...
fclk-enable = <0x1>;
ps-clk-frequency = <33333333>;
};
These values are regenerated from the XSA. The board DTSI does not reference clock frequencies directly, so no manual edit is needed.
Verify the Change
$ grep -i "frequency\|fclk" hw_project_sdt/pcw.dtsi
Scenario C: Swapping an Ethernet PHY
PHY swaps require no Vivado changes — the PS GEM interface and RGMII pinout remain the same. All changes are confined to the board DTSI.
No Change Needed in Vivado
The PS GEM0/GEM1 configuration in Vivado only defines the Ethernet interface type (RGMII, GMII). The PHY device on the other end of that interface is a board-level detail described in the DTSI, not in the XSA.
Update the Board Device Tree
The &gem0 node in custom_zynq7000.dtsi contains the PHY
description. A typical RGMII entry looks like this:
&gem0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <ðernet_phy>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethernet_phy: ethernet-phy@0 {
reg = <0>;
};
};
};
To swap to a different RGMII PHY, change the reg value (MDIO
address) and, if the new PHY requires a vendor-specific driver, add a
compatible string:
&gem0 {
status = "okay";
phy-mode = "rgmii-id";
phy-handle = <ðernet_phy>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethernet_phy: ethernet-phy@1 {
reg = <1>;
compatible = "marvell,88e1512";
reset-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
reset-assert-us = <10000>;
reset-deassert-us = <50000>;
};
};
};
Key Properties to Update
Property |
When to change |
|---|---|
|
Always — must match the PHY’s MDIO address set by hardware strapping pins |
|
Only if the new PHY needs a vendor driver (for example, Marvell or TI DP83867); omit for generic PHY |
|
Only if changing between RGMII variants ( |
|
Only if the new PHY has a hard reset line connected to a GPIO |
Files That Do Not Change
pcw.dtsi, system-top.dts, and zynq-7000.dtsi are
unaffected by a PHY swap. SDTGen does not need to be re-run unless
the XSA itself changed.
Rebuild the Device Tree After Editing
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -user_dts ./hw_project_sdt/custom_zynq7000.dtsi
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
$ gen-machine-conf parse-sdt \
--hw-description ./hw_project_sdt \
-c ./sources/meta-custom-zynq7000/conf \
--machine-name custom-zynq7000 \
-g full
$ MACHINE=custom-zynq7000 bitbake xilinx-bootbin
Re-running gen-machine-conf parse-sdt is what carries the edit into
the layer. The device tree BitBake compiles is the copy under
sources/meta-custom-zynq7000/conf/dts/custom-zynq7000/, not the
SDT that produced it, so regenerating the SDT alone
leaves the build unchanged.
Scenario D: Disabling a PS MIO Peripheral (I2C0 Example)
This is the most important scenario to understand because it is the only one where generated and board-maintained files can go out of sync without warning.
Change in Vivado
What SDTGen Regenerates Automatically
After running SDTGen with the updated XSA, two files are rewritten.
pcw.dtsi — I2C0 controller node is removed
With I2C0 enabled:
ps7_i2c_0: i2c@e0004000 {
compatible = "cdns,i2c-r1p10";
status = "okay";
clocks = <&clkc 38>;
interrupt-parent = <&intc>;
interrupts = <0 25 4>;
reg = <0xe0004000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
};
With I2C0 disabled — the entire block above is absent from
pcw.dtsi.
system-top.dts — alias and MMIO entry are removed
With I2C0 enabled:
aliases {
i2c0 = &ps7_i2c_0;
...
};
With I2C0 disabled — the i2c0 alias and the MMIO mapping at
0xe0004000 are both absent.
What the Board Device Tree Still Contains (Stale)
custom_zynq7000.dtsi is not rewritten by SDTGen. If it
contains board-level devices attached to I2C0, those nodes remain
after the peripheral is disabled:
&i2c0 {
i2c-mux@70 {
compatible = "nxp,pca9546";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
i2c@1 {
#address-cells = <1>;
#size-cells = <0>;
rtc@68 {
compatible = "dallas,ds1307";
reg = <0x68>;
};
};
};
};
Because pcw.dtsi no longer defines ps7_i2c_0, the &i2c0
reference in your board DTSI has no target. This produces a device
tree compiler warning (Reference to non-existent node) and can
cause runtime probe failures.
Required Manual Edit to Board Device Tree
Choose one of two options depending on whether I2C0 is temporarily or permanently removed.
Option 1 — Disable the subtree (preferred if I2C0 may be re-enabled later)
&i2c0 {
status = "disabled";
};
Option 2 — Remove the subtree entirely (permanent removal)
Delete or comment out the entire &i2c0 { ... }; block from
custom_zynq7000.dtsi.
Summary: Which Files Change and Who Owns Them
File |
Changes when I2C0 disabled? |
How |
Action required |
|---|---|---|---|
|
Yes |
Regenerated by SDTGen |
None |
|
Yes — alias + MMIO entry removed |
Regenerated by SDTGen |
None |
|
No — stale reference remains |
Not touched by SDTGen |
Manual edit required |
|
No change |
Common SoC file |
None |
|
Minor — bitstream name / PL node delta |
Regenerated by SDTGen |
None |
Verify and Rebuild
$ grep -n "i2c0\|&i2c" hw_project_sdt/custom_zynq7000.dtsi
$ sdtgen
sdtgen% set_dt_param -xsa ./custom_board.xsa
sdtgen% set_dt_param -user_dts ./hw_project_sdt/custom_zynq7000.dtsi
sdtgen% set_dt_param -dir ./hw_project_sdt
sdtgen% generate_sdt
sdtgen% exit
$ gen-machine-conf parse-sdt \
--hw-description ./hw_project_sdt \
-c ./sources/meta-custom-zynq7000/conf \
--machine-name custom-zynq7000 \
-g full
$ MACHINE=custom-zynq7000 bitbake xilinx-bootbin
Troubleshooting
Machine Configuration Not Found
Error:
MACHINE=custom-zynq7000 is invalid. Please set a valid MACHINE in your
local.conf, environment or other configuration file.
$ ls sources/meta-custom-zynq7000/conf/machine/
$ bitbake-layers show-layers | grep custom-zynq7000
$ bitbake-layers add-layer ./sources/meta-custom-zynq7000
SDTGen Failures
Error: set_dt_param bad option - -userdts
Use the correct parameter name:
sdtgen% set_dt_param -user_dts ./hw_project_sdt/custom_zynq7000.dtsi
Build Failures
Error: Missing dependencies or parse errors
$ MACHINE=custom-zynq7000 bitbake -c cleanall xilinx-bootbin
$ MACHINE=custom-zynq7000 bitbake xilinx-bootbin
$ bitbake-layers show-layers
Device Tree Inconsistency After Disabling PS Peripherals
See Scenario D: Disabling a PS MIO Peripheral (I2C0 Example) for
the complete walk-through covering Vivado, pcw.dtsi,
system-top.dts, and the required manual edit to
custom_zynq7000.dtsi.
File Reference
Key Files and Their Purposes
File |
Purpose |
Location |
|---|---|---|
|
Hardware description |
Project root |
|
Custom device tree overlay |
|
|
Machine configuration |
|
|
Boot image |
|
|
Compressed WIC disk image |
|
|
Compiled device tree |
|
Build Artifacts Directory Structure
tmp/deploy/images/
├── custom-zynq7000/
│ ├── BOOT-custom-zynq7000-*.bin
│ ├── BOOT-custom-zynq7000.bin
│ ├── boot.bin
│ ├── boot.bin-extracted/
│ ├── fsbl-custom-zynq7000.elf
│ ├── system.dtb
│ └── u-boot.elf
└── amd-cortexa9thf-neon-common/
├── edf-linux-disk-image-*.rootfs.tar.gz
├── edf-linux-disk-image-*.rootfs.wic
├── edf-linux-disk-image-*.rootfs.wic.bmap
├── edf-linux-disk-image-*.rootfs.wic.xz
├── uImage
└── modules-*.tgz
Next Steps
After completing this tutorial, you can:
Deploy to Real Hardware: Copy
boot.binand rootfs to SD cardConfigure the Kernel: Adjust kernel features and drivers with
MACHINE=amd-cortexa9thf-neon-common bitbake -c menuconfig virtual/kernelAdd Packages to the Image: Extend
IMAGE_INSTALLin your layer or inlocal.confAdd Custom Applications: Create recipes in your meta layer
Optimize Boot Time: Configure U-Boot and kernel parameters
Set Up Remote Development: Configure SSH and development tools