# Digilent Arty Z7 (Zynq-7000)

**URL:** <https://hub.mender.io/t/digilent-arty-z7-zynq-7000/8324>\
**Category:** Yocto Project\
**Created:** [July 20, 2026, 11:16am UTC](https://hub.mender.io/t/digilent-arty-z7-zynq-7000/8324 "2026-07-20T11:16:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![TheYoctoJester](https://yyz2.discourse-cdn.com/flex036/user_avatar/hub.mender.io/theyoctojester/32/1444_2.png) [@TheYoctoJester](https://hub.mender.io/u/TheYoctoJester)\
**Post date:** [July 20, 2026, 11:16am UTC](https://hub.mender.io/t/digilent-arty-z7-zynq-7000/8324/1 "2026-07-20T11:16:51Z")

</div>

## Introduction

The Digilent Arty Z7 is a popular entry into the Zynq-7000 world, but as far as current software support goes, it does not exist: there is no devicetree for it in mainline Linux or U-Boot, no machine in meta-xilinx, and Digilent’s own Yocto layer dates back to the jethro era. The vendor-blessed path to a bootable image runs through Vivado, exporting a hardware description and building the FSBL from it. That is a multi-gigabyte tool installation for a board whose hardware configuration has not changed since 2016.

This tutorial takes the other path. We bring the board up on plain upstream Yocto scarthgap with the meta-xilinx `zynq-generic` machine, let U-Boot SPL replace the FSBL so that no Vivado output is needed anywhere in the build, and then add the standard Mender A/B integration on top. The result is a reproducible, commit-pinned build with exactly two board-specific inputs: the generated PS initialization code and the devicetree.

The board here is the Arty Z7-20. The approach applies to any Zynq-7000 board in the same situation, and the U-Boot environment pitfall in the Mender section applies to all of them.

## The board

 ![ARTY-Z7_-_Obl_-_600__70201](https://canada1.discourse-cdn.com/flex036/uploads/mender/original/2X/9/96076df304b593bf420fcbb238152115f6c33e6d.jpeg)  
Image source: [https://digilent.com/shop/arty-z7-zynq-7000-soc-development-board/](https://digilent.com/shop/arty-z7-zynq-7000-soc-development-board/)

- Vendor: [Digilent Arty Z7](https://digilent.com/reference/programmable-logic/arty-z7/start)
- SoC: Zynq-7000 (xc7z020clg400-1), dual Cortex-A9 processing system plus Artix-7 programmable logic, 512 MB DDR3
- Yocto `MACHINE`: `arty-z7-20`
- Config in `mender-community-images`: `yocto/scarthgap/{tagged,floating}/arty-z7-20.yml`

## Test results

Built and runtime-verified on the following Yocto Project release:

| Yocto Project | Build | Runtime |
| --- | --- | --- |
| scarthgap (5.0 LTS) | ✓ | ✓ |

_Build_ means the Yocto build completes and produces images. _Runtime_ means a Mender deployment installs, reboots, commits, and rolls back on the board.

The verified component set:

| Component | Version |
| --- | --- |
| meta-xilinx | scarthgap branch, `XILINX_RELEASE_VERSION = "v2025.1"` (u-boot-xlnx 2025.01, linux-xlnx 6.12) |
| meta-mender | scarthgap branch, Mender client 6.0.0 (`mender-update`) |
| Board | Digilent Arty Z7-20 (Zynq-7000 xc7z020clg400-1, 512 MB DDR3) |
| Build setup | kas 5.1 (optional; the kas file below translates 1:1 to a bblayers.conf/local.conf setup) |

## Prerequisites

- A Yocto-capable build host.
- A Mender server; this tutorial uses [hosted Mender](https://hosted.mender.io).
- The board facts, gathered from schematics or vendor sources: console UART, DDR configuration, PS clock, ethernet PHY. For the Arty Z7-20 these are collected below, for other boards this is the actual research work of the bring-up.

## Goal

A close-to-upstream Yocto build that boots the Arty Z7-20 from SD card to a login prompt with working ethernet, built without any vendor tool. On top of it sits the standard Mender integration: A/B rootfs layout, redundant U-Boot environment, and verified rollback.

## The starting point

What exists for this board:

- Digilent’s Petalinux BSP, [Petalinux-Arty-Z7-20](https://github.com/Digilent/Petalinux-Arty-Z7-20), which contains the generated PS initialization files (`ps7_init_gpl.c/.h`, HSI 2017.4).
- A devicetree in Digilent’s 2016 U-Boot fork, [u-boot-digilent](https://github.com/Digilent/u-boot-digilent) (`arch/arm/dts/zynq-artyz7.dts`).

A tempting shortcut is the in-tree sibling Zybo Z7, which shares the xc7z020. Its files are not reusable, though: the Zybo runs a 33.33 MHz PS clock where the Arty has 50 MHz, a 32-bit/1 GB DDR3 configuration where the Arty has 16-bit/512 MB, and its console is UART1 where the Arty uses UART0. Zynq PS initialization is board-level, not SoC-level. For the same reason, owners of the smaller Arty Z7-10 should take their `ps7_init_gpl` files from Digilent’s [Petalinux-Arty-Z7-10](https://github.com/Digilent/Petalinux-Arty-Z7-10) BSP rather than reusing the Z7-20 ones shown here; everything else in this tutorial carries over.

So the board facts, cross-checked between the two Digilent sources:

- Console: UART0 (`ttyPS0`, 115200)
- Memory: 512 MB DDR3, 16-bit
- PS clock: 50 MHz
- Ethernet: GEM0, RGMII-ID, Realtek RTL8211E or F (board-revision dependent) at MDIO address 1
- No factory-programmed MAC address (more on that below)
- USB0 host with a `usb-nop-xceiv` reset on GPIO 46

## Booting a Zynq without the FSBL

The classic Zynq boot chain is BootROM → FSBL → U-Boot, where the FSBL is built from Vivado’s exported hardware description. U-Boot SPL can take the FSBL’s place entirely: it links the same generated `ps7_init_gpl.c` and produces `boot.bin` itself. The boot flow becomes:

```auto
BootROM -> boot.bin (U-Boot SPL + ps7_init) -> u-boot.img -> kernel + dtb

```

meta-xilinx supports this through the `zynq-generic` machine, it just needs to be told where the PS initialization comes from. The board support lives in a small custom layer with this layout:

```auto
meta-arty-z7/
├── conf/
│ ├── layer.conf
│ └── machine/arty-z7-20.conf
└── recipes-bsp/
    ├── device-tree/
    │ ├── device-tree.bbappend
    │ └── files/zynq-arty-z7-20.dts
    ├── platform-init/
    │ ├── platform-init.bbappend
    │ └── platform-init/arty-z7-20/ps7_init_gpl.{c,h}
    └── u-boot/
        ├── u-boot-xlnx_%.bbappend
        └── u-boot-xlnx/
            ├── 0001-arm-dts-add-zynq-arty-z7-20.patch
            └── arty-z7-20.cfg

```

The `conf/layer.conf` is entirely conventional:

```auto
BBPATH .= ":${LAYERDIR}"

BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "arty-z7"
BBFILE_PATTERN_arty-z7 = "^${LAYERDIR}/"
BBFILE_PRIORITY_arty-z7 = "7"

LAYERDEPENDS_arty-z7 = "core xilinx"
LAYERSERIES_COMPAT_arty-z7 = "scarthgap"

```

The machine configuration, `conf/machine/arty-z7-20.conf`:

```auto
#@TYPE: Machine
#@NAME: arty-z7-20
#@DESCRIPTION: Digilent Arty Z7-20 (Zynq-7000 xc7z020, 512 MB DDR3, console ttyPS0/UART0)

require conf/machine/zynq-generic.conf

# Boot flow: BootROM -> spl/boot.bin (U-Boot SPL + Arty ps7_init, replaces the
# FSBL) -> u-boot.img -> boot.scr -> uImage + system.dtb from the FAT partition.
SPL_BINARY = "spl/boot.bin"
# The weak default (xilinx-bootbin) needs a Vivado-built FSBL and silently
# clears SPL_BINARY otherwise; u-boot-xlnx must provide boot.bin itself.
PREFERRED_PROVIDER_virtual/boot-bin = "u-boot-xlnx"
# xilinx_zynq_virt_defconfig is listed in HAS_PLATFORM_INIT, so the ps7_init
# injection from virtual/xilinx-platform-init has to be forced.
FORCE_PLATFORM_INIT = "1"
# the u-boot build tree names its ELF plainly "u-boot"; the Xilinx include
# default (u-boot.elf) does not exist in this flow
UBOOT_ELF = "u-boot"
# SPL loads CONFIG_SPL_FS_LOAD_PAYLOAD_NAME = u-boot.img, not u-boot.bin
UBOOT_SUFFIX = "img"
# U-Boot control device tree, added to the u-boot-xlnx source by our patch
UBOOT_USER_SPECIFIED_DTS = "zynq-arty-z7-20"

# Runtime device tree from the out-of-tree device-tree recipe; the stock
# u-boot-xlnx-scr boot script loads /system.dtb from the boot partition.
PREFERRED_PROVIDER_virtual/dtb = "device-tree"
CONFIG_DTFILE = "zynq-arty-z7-20.dts"

IMAGE_BOOT_FILES += "boot.bin"

# Plain SD card image.
IMAGE_FSTYPES = "tar.gz wic wic.bmap"

```

(The Mender-specific additions to this file follow in the Mender section.)

The PS initialization files are taken verbatim from Digilent’s BSP, `Arty-Z7-20/project-spec/hw-description/ps7_init_gpl.{c,h}`, and handed to meta-xilinx’s `platform-init` recipe through a bbappend, with the files placed in `platform-init/arty-z7-20/` next to it:

```auto
FILESEXTRAPATHS:prepend := "${THISDIR}/platform-init:"

# ps7_init_gpl.c/.h are taken verbatim from Digilent's own BSP:
# github.com/Digilent/Petalinux-Arty-Z7-20 @ 43dca7d (HSI 2017.4), file
# Arty-Z7-20/project-spec/hw-description/ps7_init_gpl.{c,h}.
COMPATIBLE_MACHINE:arty-z7-20 = "arty-z7-20"

# The base recipe points LIC_FILES_CHKSUM at common-licenses/GPL-2.0, which
# modern poky no longer ships (renamed to GPL-2.0-only/-or-later in honister).
# The shipped ps7_init_gpl.c/.h carry the Xilinx GPL-2.0-or-later header.
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0-or-later;md5=fed54355545ffd980b814dab4a3b312c"

```

The license checksum override works around a real bug in meta-xilinx scarthgap: the recipe references a license file that poky removed several releases ago, so it fails `do_populate_lic` for everyone who actually uses it.

## Two devicetrees

The build carries the board devicetree twice: once as the U-Boot control devicetree (added to the u-boot-xlnx source by a patch, since SPL and U-Boot proper are configured by it), and once for the kernel at runtime, compiled by meta-xilinx’s out-of-tree `device-tree` recipe as `system.dtb`. Both are derived from Digilent’s 2016 `zynq-artyz7.dts`, restructured after the mainline `zynq-zybo-z7.dts`. The runtime version:

```auto
// SPDX-License-Identifier: GPL-2.0+
/*
 * Digilent Arty Z7-20 board (Zynq-7000 xc7z020, 512 MB DDR3)
 *
 * Copyright (C) 2016 Digilent
 */
/dts-v1/;
#include "zynq-7000.dtsi"
#include <dt-bindings/gpio/gpio.h>

/ {
	model = "Digilent Arty Z7-20 board";
	compatible = "digilent,zynq-artyz7", "xlnx,zynq-7000";

	aliases {
		ethernet0 = &gem0;
		serial0 = &uart0;
		spi0 = &qspi;
		mmc0 = &sdhci0;
	};

	memory@0 {
		device_type = "memory";
		reg = <0x0 0x20000000>;
	};

	chosen {
		stdout-path = "serial0:115200n8";
	};

	usb_phy0: phy0 {
		#phy-cells = <0>;
		compatible = "usb-nop-xceiv";
		reset-gpios = <&gpio0 46 GPIO_ACTIVE_LOW>;
	};
};

&clkc {
	ps-clk-frequency = <50000000>;
};

&gem0 {
	status = "okay";
	phy-mode = "rgmii-id";
	phy-handle = <&ethernet_phy>;
	/* No factory MAC on the board: fixed locally administered address */
	local-mac-address = [02 44 5a 37 32 30];

	ethernet_phy: ethernet-phy@1 { /* RTL8211E or F, board-revision dependent */
		reg = <1>;
	};
};

&qspi {
	status = "okay";
};

&sdhci0 {
	status = "okay";
};

&uart0 {
	status = "okay";
};

&usb0 {
	status = "okay";
	dr_mode = "host";
	usb-phy = <&usb_phy0>;
};

```

The U-Boot copy is identical except for `bootph-all` markers on `&uart0` and `&sdhci0`, which keep those nodes in the SPL devicetree; the SoC-level nodes SPL also needs (slcr, clock controller, timer) already carry their markers in U-Boot’s `zynq-7000.dtsi`, so two board-level markers are all it takes. If you maintain both copies by hand as done here, put a comment in each pointing at the other: the two values that must never diverge are the MAC address and the PHY address.

The MAC address deserves a word. The Arty Z7 has no factory-programmed MAC. With nothing set, every boot invents a random one, which means a new DHCP lease on every boot and, once Mender enters the picture, a new device identity on every boot. A fixed [locally administered address](https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local) (second-least-significant bit of the first octet set, `02:...`) in the devicetree solves both: U-Boot picks it up from its control devicetree, the kernel from `system.dtb`, and it doubles as a stable Mender device identity.

Two glue pieces complete the picture. The `device-tree` recipe defaults its kernel include path to empty, so a bbappend restores it. The dts above includes `zynq-7000.dtsi` and dt-bindings headers from the kernel source:

```auto
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

KERNEL_INCLUDE:arty-z7-20 = " \
    ${STAGING_KERNEL_DIR}/arch/${ARCH}/boot/dts \
    ${STAGING_KERNEL_DIR}/arch/${ARCH}/boot/dts/* \
    ${STAGING_KERNEL_DIR}/scripts/dtc/include-prefixes \
    "

```

And the u-boot-xlnx bbappend adds the control devicetree patch plus a config fragment. The patch must add _two_ things: the dts as `arch/arm/dts/zynq-arty-z7-20.dts`, and its entry in the dts Makefile:

```auto
dtb-$(CONFIG_ARCH_ZYNQ) += \
	zynq-arty-z7-20.dtb \

```

That Makefile line is easy to forget. Without it the dtb is never built, and the build dies with a kbuild “no rule to make target” error that does not exactly shout “you forgot a Makefile hunk”.

`xilinx_zynq_virt_defconfig` builds a multi-dtb U-Boot with seventeen boards in `CONFIG_OF_LIST` and defaults to the zc706; the config fragment prunes the list to the one devicetree we actually have:

```auto
FILESEXTRAPATHS:prepend := "${THISDIR}/u-boot-xlnx:"

SRC_URI:append:arty-z7-20 = " \
    file://0001-arm-dts-add-zynq-arty-z7-20.patch \
    file://arty-z7-20.cfg \
    "

# With UBOOT_SUFFIX = "img" (SPL payload) no /boot/u-boot*.bin exists, which
# leaves ${PN}-bin empty while ${PN} RDEPENDS on it; let it take the .img.
FILES:${PN}-bin:append:arty-z7-20 = " /boot/u-boot*.img"

```

```auto
CONFIG_DEFAULT_DEVICE_TREE="zynq-arty-z7-20"
CONFIG_OF_LIST="zynq-arty-z7-20"

```

With this in place, `bitbake core-image-minimal` produces a `.wic` image that boots the board from SD to a login prompt: BootROM loads SPL from `boot.bin`, SPL runs the Digilent `ps7_init`, loads `u-boot.img`, and U-Boot boots the kernel with `system.dtb`. Not a single Vivado-generated artifact appears anywhere in the chain.

## Adding Mender

The Mender integration follows the [standard Yocto setup](https://docs.mender.io/operating-system-updates-yocto-project/board-integration) with `MENDER_FEATURES_ENABLE = "mender-uboot mender-image-sd"`: an A/B rootfs pair on the SD card, a data partition, and the redundant U-Boot environment at raw offsets. Kept in a separate layer so the plain bring-up build stays untouched, the configuration is unspectacular:

```auto
INIT_MANAGER = "systemd"
INHERIT += "mender-full"
MENDER_FEATURES_ENABLE:append = " mender-uboot mender-image-sd"
MENDER_FEATURES_DISABLE:append = " mender-grub mender-image-uefi"
# meta-mender depends on "u-boot"; our fork provides it (bbappend adds PROVIDES)
PREFERRED_PROVIDER_u-boot = "u-boot-xlnx"
MENDER_ARTIFACT_NAME = "arty-z7-20-v1"

```

Because the bootloader is a fork (u-boot-xlnx) rather than upstream U-Boot, meta-mender’s bbappend does not attach to it; the fork integration is a bbappend of our own, following the pattern of the meta-mender-community board layers:

```auto
# Mender U-Boot integration for u-boot-xlnx (meta-mender only appends to the
# plain "u-boot" recipe). Pattern per meta-mender-community fork integrations.
require recipes-bsp/u-boot/u-boot-mender.inc

# meta-mender depends on "u-boot" in several places (EXTRA_IMAGEDEPENDS,
# part-image deps, libubootenv do_compile); make the fork satisfy them.
PROVIDES += "u-boot"
RPROVIDES:${PN} += "u-boot"

FILESEXTRAPATHS:prepend := "${THISDIR}/u-boot-xlnx:"

# meta-mender's 0002 patch only applies with fuzz against u-boot-xlnx
# 2025.01 (patch-fuzz QA is fatal); carry a rebased copy instead. The env
# patch is numbered 0004: meta-mender's 0001 and 0003 apply before it.
SRC_URI:remove:arty-z7-20 = "file://0002-Integration-of-Mender-boot-code-into-U-Boot.patch"

SRC_URI:append:arty-z7-20 = " \
    file://0002-Integration-of-Mender-boot-code-into-U-Boot-xlnx-2025.01.patch \
    file://0004-zynq-env-get-location-mmc.patch \
    file://mender-bootargs.cfg \
    "

```

The rebased integration patch is nothing exotic: it is meta-mender’s own boot-code patch, refreshed. Apply `meta-mender-core/recipes-bsp/u-boot/patches/0002-Integration-of-Mender-boot-code-into-U-Boot.patch` to a u-boot-xlnx checkout, fix up the offset-shifted hunks, and export the result with `git format-patch`. The remaining two files are where the Zynq-specific substance is.

### The environment pitfall

This is the part that will bite anyone doing Mender on a Zynq-7000, and it bites silently. Mender’s A/B mechanism lives in the U-Boot environment: `mender_boot_part`, `upgrade_available` and the boot counter are written by the client through libubootenv and read by U-Boot on the next boot. With `mender-uboot`, that environment sits at raw MMC offsets (`CONFIG_ENV_IS_IN_MMC`), redundantly.

The Zynq board code, however, overrides `env_get_location()`, and its SD-boot case only ever considers FAT and EXT4 environment backends. `CONFIG_ENV_IS_IN_MMC` is silently ignored and the function falls through to `ENVL_NOWHERE`, so U-Boot happily boots with its built-in default environment on every single boot. The failure mode is nasty precisely because everything else works: the image boots, the client connects, a deployment installs and reboots the device, which comes back on the _old_ rootfs, because U-Boot never saw `upgrade_available=1`. The deployment reports a rollback that was never supposed to happen, and nothing in any log says why.

The fix is a two-line patch to `board/xilinx/zynq/board.c`:

```auto
--- a/board/xilinx/zynq/board.c
+++ b/board/xilinx/zynq/board.c
@@ -147,6 +147,8 @@ enum env_location env_get_location(enum env_operation op, int prio)
 			return ENVL_FAT;
 		if (IS_ENABLED(CONFIG_ENV_IS_IN_EXT4))
 			return ENVL_EXT4;
+ if (IS_ENABLED(CONFIG_ENV_IS_IN_MMC))
+ return ENVL_MMC;
 		return ENVL_NOWHERE;
 	case ZYNQ_BM_NAND:
 		if (IS_ENABLED(CONFIG_ENV_IS_IN_NAND))

```

Be careful how you verify this: `fw_printenv` proves nothing. It reads the raw offsets directly through `/etc/fw_env.config` and works perfectly on a broken build. Since the image is created with `mender_boot_part=2` and an unpatched U-Boot boots the first slot from its built-in defaults, the values even _agree_. The conclusive check is a single line in the U-Boot console output. Broken:

```auto
Loading Environment from nowhere... OK

```

Fixed:

```auto
Loading Environment from MMC... OK

```

For a second opinion, set a scratch variable from Linux with `fw_setenv`, reboot, and interrupt autoboot: `printenv` at the U-Boot prompt must show it.

### The kernel command line

The stock meta-xilinx boot flow sets the kernel command line in `boot.scr`; the Mender boot code replaces the boot script entirely and constructs the root device itself, so the rest of the command line has to come from the U-Boot configuration:

```auto
CONFIG_USE_BOOTARGS=y
CONFIG_BOOTARGS="console=ttyPS0,115200 rootwait"

```

### Machine configuration additions

The Mender image types change where files live: the kernel and `system.dtb` move into the rootfs `/boot` of each A/B slot, and the FAT boot partition carries only SPL and its payload. Gated behind the Mender overrides, in the same machine conf as above:

```auto
IMAGE_BOOT_FILES:mender-uboot = "boot.bin u-boot.img"
# The runtime dtb is not kernel-built (KERNEL_DEVICETREE unset): name it for
# the Mender boot code and ship the device-tree package in the rootfs.
MENDER_DTB_NAME_FORCE:mender-uboot = "system.dtb"
MACHINE_ESSENTIAL_EXTRA_RDEPENDS:append:mender-image = " device-tree"
# mender-image would add kernel-devicetree, which is never emitted here
MACHINE_ESSENTIAL_EXTRA_RDEPENDS:remove:mender-image = "kernel-devicetree"
IMAGE_FSTYPES:remove:mender-image = "wic wic.bmap"
# the Mender boot code replaces boot.scr entirely; stop building the stock one
EXTRA_IMAGEDEPENDS:remove:mender-image = "u-boot-xlnx-scr"
# bootimg populates from DEPLOY_DIR_IMAGE
do_image_bootimg[depends] += "virtual/boot-bin:do_deploy virtual/bootloader:do_deploy"

```

The overrides only exist when the Mender features are enabled, so the plain bring-up build is unaffected by this block.

## Pinning it down with kas

Reproducibility is cheap to add and expensive to skip. The whole setup is driven by a kas configuration with every upstream repository pinned to a commit; the essential parts:

```auto
header:
  version: 14

machine: arty-z7-20
distro: poky
target: core-image-minimal

repos:
  workspace:
    layers:
      meta-arty-z7:

  poky:
    url: https://git.yoctoproject.org/poky
    branch: scarthgap
    commit: e146cbbc73e10fecfb7f1c26e8567a5a2873c51b
    layers:
      meta:
      meta-poky:

  # hard LAYERDEPENDS of meta-xilinx-core
  meta-arm:
    url: https://git.yoctoproject.org/meta-arm
    branch: scarthgap
    commit: 23b572c40ea6709ab3f8bd7abbb5a795fc3631c2
    layers:
      meta-arm:
      meta-arm-toolchain:

  meta-xilinx:
    url: https://github.com/Xilinx/meta-xilinx
    branch: scarthgap
    commit: 936845b97ab1ff035108f219f4e7cfef5401eb02
    layers:
      meta-xilinx-core:

local_conf_header:
  base: |
    # Pin the xlnx component set (linux-xlnx 6.12, u-boot-xlnx 2025.01) so a
    # future meta-xilinx default bump cannot change component versions.
    XILINX_RELEASE_VERSION = "v2025.1"

```

One kas construct here deserves a warning: the `workspace` repository has no `url`, which tells kas to use the enclosing git repository, the one the kas file itself lives in. This is what lets the kas configuration and meta-arty-z7 travel together in a single repository, but it only works when kas is invoked from inside a git checkout; in a bare directory it fails with a rather unhelpful error about the repository path.

The Mender pieces live in a second kas file, whose `local_conf_header` carries exactly the configuration block from the Mender section:

```auto
header:
  version: 14

repos:
  workspace:
    layers:
      meta-arty-z7-mender:

  meta-mender:
    url: https://github.com/mendersoftware/meta-mender.git
    branch: scarthgap
    commit: de0f577c0e612afeed22c06015d590ade6ea0b96
    layers:
      meta-mender-core:

  # the C++ mender client DEPENDS on lmdb and RDEPENDS on jq, both meta-oe
  meta-openembedded:
    url: https://git.openembedded.org/meta-openembedded
    branch: scarthgap
    commit: 29a044218285fdc7fcdd63d5f0929cb3a27b6fed
    layers:
      meta-oe:

local_conf_header:
  mender: |
    # ... the INIT_MANAGER / mender-full block shown above ...

```

With that,

```auto
kas build kas/arty-z7-20.yml

```

builds the plain bring-up image and

```auto
kas build kas/arty-z7-20.yml:kas/mender.yml

```

builds the Mender-enabled one, producing `core-image-minimal-arty-z7-20.sdimg` for the initial flash and `.mender` as the deployable artifact.

## Seeing it work

After flashing the sdimg and booting, the device requests authorization on the Mender server with the fixed MAC as its identity. Accept it, and the inventory reports `device_type: arty-z7-20` with client 6.0.0. From there it is the standard flow: upload the `.mender` artifact of the next build (with a bumped `MENDER_ARTIFACT_NAME`), create the deployment, and watch the device switch rootfs slots: install to the inactive partition, reboot, commit. On this board the full cycle takes about three minutes, and pulling power mid-update leaves the device booting the old rootfs, which is the entire point of the exercise.

## Conclusion

Bringing up a Zynq-7000 board with no current vendor support turned out to require exactly two board-specific inputs, the generated `ps7_init_gpl` files and a devicetree, both extractable from old vendor sources, plus one genuinely non-obvious fix. That fix is the `env_get_location()` patch, without which the Mender integration appears to work right up until the first real deployment quietly rolls back.

With the A/B foundation in place, the same board can update more than its rootfs: building an FPGA bitstream for the programmable logic with the open source openXC7 toolchain and packaging it as a Mender Artifact is covered in the follow-up tutorial “Building and packaging FPGA bitstreams for the Zynq-7000 without Vivado”.

## References

- [Digilent Arty Z7 reference](https://digilent.com/reference/programmable-logic/arty-z7/start)
- [Xilinx meta-xilinx layer](https://github.com/Xilinx/meta-xilinx)
- [Mender Yocto board integration](https://docs.mender.io/operating-system-updates-yocto-project/board-integration)
- [meta-mender-xilinx in meta-mender-community](https://github.com/mendersoftware/meta-mender-community/tree/scarthgap/meta-mender-xilinx)
- [`arty-z7-20` build config in mender-community-images](https://github.com/theyoctojester/mender-community-images/blob/main/yocto/scarthgap/floating/arty-z7-20.yml)
- [kas](https://kas.readthedocs.io/)

## Known issues

- Board support is out-of-tree and experimental: there is no mainline or meta-xilinx machine for the Arty Z7, so the machine, devicetree and PS initialization live in the `meta-mender-xilinx` layer rather than upstream.
- The ethernet PHY is board-revision dependent (Realtek RTL8211E on earlier boards, RTL8211F on later ones). Both answer at MDIO address 1 and work with the single `rgmii-id` devicetree node shown here, so no revision-specific handling is needed, but the PHY marking on the board will differ between revisions.
- The locally administered MAC address is baked into the image, so every unit flashed from the same build shares it. For more than one board on a network, override `local-mac-address` per device or provision a MAC at first boot.
