● Live notes + builds
Uncategorized
Reading mode

From Silicon to Boot Prompt: Building a Custom Yocto BSP for the Zybo Z7-10


Most Zynq-7000 tutorials stop at PetaLinux. Xilinx’s own tooling wraps Yocto in a friendly CLI, generates your BSP for you, and mostly just works until you need something PetaLinux doesn’t give you: a layer you fully own, pinned to upstream meta-xilinx/poky revisions of your choosing, structured so a CI pipeline or another engineer can reproduce it without a PetaLinux license or installer.

This post walks through building embedded Linux for the Digilent Zybo Z7-10 (Zynq-7000, XC7Z010) the long way: a Vivado hardware design, a Vitis-generated first-stage bootloader, and a hand-built Yocto layer structured deliberately to mirror PetaLinux’s project-spec layout, so the hardware and software concerns stay cleanly separated.

The pipeline, end to end

Vivado (block design) → .xsa + .bit
Vitis (platform + FSBL) → zynq_fsbl.elf
Yocto (meta-xilinx BSP) → BOOT.bin, u-boot, zImage, rootfs

Each stage hands the next a small number of artifacts. Understanding exactly what those artifacts are and where they plug into Yocto’s recipe graph is what makes the whole thing reproducible.


Stage 1: Vivado — the hardware design

The starting point is a Vivado block design (design_1) built around the Zynq-7000 Processing System IP. For a board bring-up like this, the design is intentionally minimal:

  • ZYNQ7 Processing System IP, configured via the board preset for the Zybo Z7-10 (DDR3 controller, UART, SD/MMC, USB, GPIO, and clocking pulled straight from Digilent’s board files).
  • Any project-specific programmable-logic IP sits alongside the PS — but even with zero custom PL, the PS still needs to be wrapped and built, because Zynq won’t boot without a bitstream configuring its PL-side clocking and AXI interconnects.
  • Run Block Automation and Run Connection Automation in the IP integrator to wire up the PS’s fixed I/O and DDR interfaces automatically.
  • Generate the top-level wrapper (design_1_wrapper.v) and run synthesis + implementation to produce design_1_wrapper.bit.

Once the bitstream is in hand, the export that actually matters for everything downstream is:

File → Export → Export Hardware, with include bitstream checked, producing design_1_wrapper.xsa.

The XSA (Xilinx Support Archive) is the single source of truth for the rest of the pipeline, it’s a zip containing the bitstream, the processor configuration, address map, and (critically) enough metadata for downstream tools to regenerate a device tree. Every device tree node describing PS peripherals, DDR base/size, and clock inputs downstream is derived from this file, not hand-written.

Stage 2: Vitis — first-stage bootloader

The XSA feeds directly into Vitis to generate the FSBL (First Stage Boot Loader), the tiny bare-metal program the Zynq’s on-chip boot ROM runs first. It configures the PS (DDR, clocks, MIO), loads the bitstream to program the PL, and hands off to U-Boot.

The flow:

  1. Create Platform Project from design_1_wrapper.xsa.
  2. Under that platform, Create Application Project using the built-in Zynq FSBL template.
  3. Build the output is zynq_fsbl.elf.

That single ELF, alongside the .bit and .xsa, is everything Yocto needs from the hardware side. No Vivado or Vitis installation is required on the Yocto build host this is deliberate, since the build machine here has neither tool installed and never needs to regenerate hardware artifacts on its own.

Stage 3: Yocto — the BSP layer

This is where PetaLinux normally takes over and hides the machinery. Building it by hand means recreating, explicitly, what PetaLinux does implicitly: turning the XSA’s exported device-tree sources into a real meta-* BSP layer against upstream meta-xilinx.

Layer stack

  • poky (core, scarthgap/5.0 LTS)
  • meta-arm / meta-arm-toolchain
  • meta-xilinx-core + meta-xilinx-standalone — the modern SDT-based embedded-software flow, not the deprecated xsct/meta-xilinx-tools path
  • meta-openembedded/meta-oe
  • a custom BSP layer, meta-user

All of poky, meta-openembedded, meta-arm, and meta-xilinx are cloned and pinned to exact commits, rather than tracking branch tips, this is what makes a from-scratch Yocto BSP reproducible in a way a moving PetaLinux install isn’t. This is available here github.com/oluseyivictor/zybo-z7-yocto

Project layout

Rather than dropping hardware artifacts and layer files wherever felt convenient, the project mirrors PetaLinux’s own project-spec convention — hardware and layer concerns get separate directories, and the hardware directory is never hand-edited:


project-spec/
hw-description/ # everything hardware-generated, untouched
design_1_wrapper.bit
design_1_wrapper.xsa
zynq_fsbl.elf
system-top.dts pcw.dtsi pl.dtsi zynq-7000.dtsi
meta-user/
recipes-bsp/device-tree/
device-tree.bbappend # FILESEXTRAPATHS pulls from both dirs
files/
system-user.dtsi # board fixes, hand-maintained
pl-custom.dtsi # dormant scaffold for future PL IP
configs/
board.conf # MACHINE, BITSTREAM_PATH, FSBL_FILE, ...

The key design decision is in device-tree.bbappend: it never modifies system-top.dts directly. Instead it appends system-user.dtsi onto the DTG-generated tree at do_configure time via EXTRA_DT_INCLUDE_FILES. That means the entire contents of hw-description/ can be overwritten wholesale by a fresh Vivado/Vitis export — a new PS configuration, added PL IP, a bumped board revision — without losing a single board-specific fix. All hand-maintained logic lives in meta-user; everything in hw-description is treated as disposable, regenerable output.

board.conf is the other piece that keeps build/ itself disposable: it’s the one file local.conf pulls in via include, setting MACHINE, LICENSE_FLAGS_ACCEPTED, XILINX_WITH_ESW, and pointers (BITSTREAM_PATH, FSBL_FILE, CONFIG_DTFILE) into hw-description/. Delete build/ entirely, re-run oe-init-build-env, re-point bblayers.conf at the four upstream sources plus meta-user, and the whole image reproduces.

What Yocto actually builds

With XILINX_WITH_ESW = "1" set, meta-xilinx-core‘s device-tree recipe becomes the preferred provider for virtual/dtb, compiling system-top.dts (plus the system-user.dtsi fixes) into system-top.dtb. u-boot-xlnx picks that DTB up automatically as its EXT_DTB. The FSBL and bitstream from hw-description/ get packaged by meta-xilinx‘s bitstream and fsbl recipes into the final BOOT.bin, alongside U-Boot.

A single bitbake core-image-minimal then produces the full artifact set for the SD card:

  • BOOT.bin — boot-ROM-loadable image containing FSBL, bitstream, and U-Boot
  • u-boot.elf / u-boot.dtb
  • zImage and system.dtb for the kernel
  • a root filesystem image


Why go this route instead of PetaLinux

PetaLinux is the path of least resistance for a single developer bringing up one board once. This approach earns its extra setup cost when:

  • You need the BSP pinned to exact, auditable upstream commits rather than whatever a PetaLinux release snapshot bundled.
  • CI needs to rebuild the image without a PetaLinux license or a multi-GB installer.
  • The hardware and software teams genuinely need to iterate independently, a new XSA export should never require touching Yocto recipes, and it doesn’t here.
  • You want the resulting layer to be a normal, portable meta-* layer that composes with any other Yocto-based project instead of a PetaLinux-specific artifact.

The tradeoff is real: PetaLinux hides device-tree generation, FSBL packaging, and BSP wiring behind a CLI. Rebuilding that by hand means understanding and owning every one of those steps yourself. For a one-off prototype, that’s overhead you don’t need. For a BSP that has to outlive the person who built it, it’s the difference between a reproducible layer and a black box.


Board: Digilent Zybo Z7-10 (Zynq-7000 XC7Z010) · Yocto: scarthgap (5.0 LTS) · Layers: poky, meta-arm, meta-xilinx-core/meta-xilinx-standalone, meta-openembedded

Discover more from NeuralonEdge

Subscribe now to keep reading and get access to the full archive.

Continue reading