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 board

  • custom_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.xsa is exported from Vivado after completing your block design and generating the bitstream.

  • custom_zynq7000.dtsi is written by hand. SDTGen does not generate a board DTSI for a custom board, which is why it accepts one through set_dt_param -user_dts. You create it in Customize Device Tree below.

Tutorial Structure

  1. Environment Setup

  2. Hardware Description Analysis

  3. Device Tree Generation

  4. Machine Configuration

  5. Boot Image Build

  6. Linux Image Build

  7. WIC Image Creation

  8. Flashing the SD Card and Booting

  9. Complete Build Workflow

  10. Hardware Customization Scenarios

  11. Troubleshooting

  12. File Reference

  13. Next Steps

  14. Additional Resources

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 = <&ethernet_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

  1. Look up the SD Card boot mode MIO strapping values in the Zynq-7000 TRM (UG585).

  2. Set your board’s boot mode switches accordingly.

  3. 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 B — Balena Etcher

  1. Download and install Balena Etcher.

  2. Click Flash from file and select the .wic.xz image.

  3. 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

  1. Insert the SD card into the board’s SD slot.

  2. Connect a USB-to-UART cable to the board’s serial console port.

  3. Open a serial terminal at 115200 8N1.

  4. 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:

  1. Double-click the ZYNQ7 Processing System IP block.

  2. Navigate to DDR Configuration.

  3. Set the Memory Part to match your physical DRAM device (this populates timing parameters and the effective address range).

  4. Confirm the Memory Size reflects your target (for example, 512 MB or 1 GB).

  5. 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?

system-top.dts

memory@0 node reg property (base address + size)

No

pcw.dtsi

DDR timing parameters (xlnx,ddr-* properties)

No

custom_zynq7000.dtsi

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

  1. Double-click the ZYNQ7 Processing System IP block.

  2. Navigate to Clock Configuration.

  3. Adjust the relevant clock — for example:

    • CPU 6x4x: Cortex-A9 core frequency (for example, 667 MHz → 800 MHz)

    • FCLK_CLK0–3: PL clocks used by custom IP

  4. Click OK, re-export the XSA.

Files Updated by SDTGen

Clock changes flow entirely into the PS-generated files:

File

What changes

Manual edit needed?

pcw.dtsi

clocks nodes updated with new frequency values

No

system-top.dts

clocks phandle assignments may change

No

custom_zynq7000.dtsi

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.

Note

If your board DTSI contains a fixed-clock node (e.g., for an external oscillator or PHY reference clock), that node is not auto-generated and must be updated manually to reflect any external clock change.

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 = <&ethernet_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 = <&ethernet_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

reg

Always — must match the PHY’s MDIO address set by hardware strapping pins

compatible

Only if the new PHY needs a vendor driver (for example, Marvell or TI DP83867); omit for generic PHY

phy-mode

Only if changing between RGMII variants (rgmii, rgmii-id, rgmii-rxid, rgmii-txid)

reset-gpios

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

  1. Open your block design and double-click the ZYNQ7 Processing System IP block.

  2. Navigate to MIO Configuration > I/O Peripherals.

  3. Locate I2C 0 and uncheck it to disable the controller.

  4. Click OK, then Validate Design, regenerate the bitstream, and re-export the XSA.

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

pcw.dtsi

Yes

Regenerated by SDTGen

None

system-top.dts

Yes — alias + MMIO entry removed

Regenerated by SDTGen

None

custom_zynq7000.dtsi

No — stale reference remains

Not touched by SDTGen

Manual edit required

zynq-7000.dtsi

No change

Common SoC file

None

pl.dtsi

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

custom_board.xsa

Hardware description

Project root

custom_zynq7000.dtsi

Custom device tree overlay

hw_project_sdt/

custom-zynq7000.conf

Machine configuration

sources/meta-custom-zynq7000/conf/machine/

boot.bin

Boot image

tmp/deploy/images/custom-zynq7000/

edf-linux-disk-image-*.rootfs.wic.xz

Compressed WIC disk image

tmp/deploy/images/amd-cortexa9thf-neon-common/

system.dtb

Compiled device tree

tmp/deploy/images/custom-zynq7000/

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:

  1. Deploy to Real Hardware: Copy boot.bin and rootfs to SD card

  2. Configure the Kernel: Adjust kernel features and drivers with MACHINE=amd-cortexa9thf-neon-common bitbake -c menuconfig virtual/kernel

  3. Add Packages to the Image: Extend IMAGE_INSTALL in your layer or in local.conf

  4. Add Custom Applications: Create recipes in your meta layer

  5. Optimize Boot Time: Configure U-Boot and kernel parameters

  6. Set Up Remote Development: Configure SSH and development tools

Additional Resources