Skip to main content

TECH VEDA

Embedded Linux on Edge-AI 23rd Sept 2026 enrollingLinux kernel & Device drivers starts on 24th Oct 2026 enrollingCorporate on-site training - Submit proposal Pick your modulessignup for free monthly live Masterclass Register
Deep Dives

System Device Trees and Yocto for Heterogeneous SoCs

A system device tree plus Yocto multiconfig builds Linux, bare-metal, and firmware for heterogeneous SoCs like ZynqMP from one hardware description.

System Device Trees and Yocto for Heterogeneous SoCs

A modern SoC such as the Zynq UltraScale+ runs Linux on its Cortex-A cores, a real-time or bare-metal image on its Cortex-R cores, and separate firmware on a management microcontroller, all sharing the same memory and peripherals. A system device tree describes that whole chip in one hardware model, with no operating system assumed. A tool called Lopper then transforms it into the individual device trees each image needs, and a Yocto multiconfig build produces the Linux root filesystem, the bare-metal applications, and the firmware from that single description.

Building software for a single-core board is a well-understood problem: one kernel, one device tree, one root filesystem. A heterogeneous SoC breaks that assumption. On an AMD (Xilinx) Zynq UltraScale+, for example, you have Cortex-A53 application cores, a Cortex-R5 real-time cluster, and a MicroBlaze platform management unit, and each of these runs its own software built by its own toolchain. This article explains how a system device tree and a Yocto multiconfig project let you describe and build that entire system from one place, using the flow AMD and OpenAMP tooling is built around.

Why heterogeneous SoCs complicate the build

A heterogeneous system is a set of processors that share resources but run independent software. The pieces work separately, yet the product only functions when they are combined. That creates two problems that a single-core workflow never has to solve.

The first is that no single build system produces everything. The Linux side is a Yocto build with GCC and glibc. The bare-metal and RTOS images use a different toolchain, a different C library (newlib rather than glibc), and a different notion of what an “image” is. The management firmware may come as a prebuilt binary. Someone has to build each part with the correct tool and then combine the outputs into one deliverable, which is often a set of images rather than a single disk partition.

The second problem is describing the hardware. A device tree normally describes hardware as one operating environment sees it — one address space, the memory map and peripherals visible to that specific kernel or bootloader. On a heterogeneous SoC the Cortex-A cluster, the Cortex-R cluster, and the MicroBlaze each see a different view of the same silicon. The traditional answer is to maintain a separate device tree per environment, but then any change to the board has to be applied by hand to several trees, and keeping them consistent becomes a source of errors.

What a system device tree describes

A system device tree extends the standard device tree so that it describes the whole SoC without assuming any one operating environment. It adds two things the base specification does not have: a way to describe multiple CPU clusters, and a way to describe execution domains that assign parts of the hardware to particular software images.

CPU clusters are described with a cpus,cluster compatible node, one per processing unit type. Execution domains are placed under a top-level /domains node and use the openamp,domain-v1 binding defined by the OpenAMP project. A domain node names the CPUs its image runs on, the memory ranges assigned to it, and the devices it is allowed to touch:

domains {
    openamp_r5 {
        compatible = "openamp,domain-v1";
        cpus = <&cpus_r5 0x2 0x80000000>;

        #memory-flags-cells = <0>;
        memory = <0x0 0x0 0x0 0x8000000>;

        #access-flags-cells = <1>;
        access = <&can0 0x3 &ethernet0 0x7>;

        os,type = "baremetal";
        id = <0x1>;
    };
};

Reading that node: the cpus property is a triplet of a cluster phandle, a CPU bit-mask, and an execution level; memory is a list of (start, size, flags) ranges; access is a list of (device, flags) pairs the domain has exclusive access to; and os,type records what will run there. The permitted os,type values include baremetal, linux, freertos and zephyr, which is exactly the information a build system needs to decide what to produce for each domain. Keeping the domain configuration separate from the hardware description is deliberate: hardware and software allocation change at different rates and are usually edited by different people.

From system device tree to per-domain device trees

The system device tree is not consumed directly by the Linux kernel or by a bare-metal application. It is transformed into the traditional, environment-specific device tree each image expects. The tool that does this is Lopper, which applies a sequence of transforms (called “lops”) to prune and rewrite the tree for one domain.

On the AMD flow the hardware starts in Vivado, which emits a hardware description file (an .xsa). A device tree generator (DTG++) turns the XSA into a system device tree whose top file, system-top.dts, includes the SoC and board fragments. Lopper then produces the Linux device tree from it:

raghu@techveda.org:~$ LOPPER_DTC_FLAGS="-b 0 -@" lopper -f --enhanced \
  -i lops/lop-a53-imux.dts \
  -i lops/lop-domain-linux-a53.dts \
  -i lops/lop-domain-linux-a53-prune.dts \
  system-top.dts cortexa53-zynqmp-linux.dtb

A different set of lops produces the device tree for the Cortex-R5 bare-metal domain, and Lopper can also read the MicroBlaze cluster node and emit a Yocto tune configuration for it, because a MicroBlaze’s capabilities (barrel shifter, pattern compare, FPU) are recorded as properties in the device tree rather than fixed by the architecture alone. One source of truth, several generated outputs.

Configuring Yocto for a multi-image build

Yocto builds one configuration at a time by default: one MACHINE, one DISTRO. To build several targets in one project it uses multiconfig. Each additional configuration is listed in BBMULTICONFIG and lives in its own conf/multiconfig/<name>.conf with, at minimum, its own TMPDIR, and usually its own MACHINE and DISTRO. The Linux side stays on the default distro; the bare-metal side switches DISTRO to xilinx-standalone.

With the meta-xilinx-standalone layer in place, enabling the boot firmware for a ZynqMP build is a single addition to conf/local.conf, after which the firmware is built on demand alongside Linux:

# conf/local.conf  (ZynqMP)
BBMULTICONFIG += "fsbl-fw zynqmp-pmufw"
raghu@techveda.org:~$ MACHINE=zynqmp-generic bitbake fsbl pmufw

The equivalent for a Versal device builds its Platform Loader and Manager (PLM) and Processing System Management (PSM) firmware instead:

# conf/local.conf  (Versal)
BBMULTICONFIG += "versal-fw"
raghu@techveda.org:~$ MACHINE=versal-generic bitbake plmfw psmfw

What ties the pieces together is a cross-configuration dependency. A recipe in one configuration can depend on a task in another using the mcdepends flag, whose form is mc:from_multiconfig:to_multiconfig:recipe:task (the first field, the source multiconfig, may be left empty, as in the examples below). So the recipe that assembles the final boot image can pull in the first-stage bootloader and the management firmware that other multiconfigs deploy:

FSBL_MCDEPENDS = "mc::cortexa53-zynqmp-fsbl-baremetal:fsb-firmware:do_deploy"
PMU_MCDEPENDS  = "mc::microblaze-pmu:pmu-firmware:do_deploy"

The default configuration acts as the overall system integrator: it builds the Linux image and then depends on the firmware and bare-metal outputs the multiconfigs deposit in their deploy directories.

Keeping proprietary and binary-only parts isolated

Not every component is built from source. Vendor firmware is sometimes delivered as a binary, and pulling that blob into an open Yocto build has to be done carefully so its licence terms stay contained. The practical approach is to give each binary-only component its own recipe whose only real job is a do_deploy that places the artefact where the integrator expects it. The consumer then references it through the same mcdepends and deploy-directory mechanism used for locally built firmware, so the rest of the project does not care whether a given image was compiled from source or supplied as a binary. This keeps the open and closed parts of the build separate while still producing one combined system image.

A single project targeting Cortex-A53, Cortex-R5 and MicroBlaze

Putting the flow end to end for a Zynq UltraScale+ design: Vivado produces the XSA, DTG++ turns it into system-top.dts, and Lopper generates a Linux device tree for the Cortex-A53 domain, a bare-metal device tree for the Cortex-R5 domain, and a tune configuration for the MicroBlaze PMU. Yocto is then configured with the default Linux build plus multiconfigs for the Cortex-R5 bare-metal image, the FreeRTOS image, the first-stage bootloader, and the MicroBlaze firmware. A single bitbake invocation on the integrating image builds the lot in dependency order, and the boot-image step packages the firmware, the bootloader, and the Linux artefacts into what the board actually boots.

The cost to be aware of is machine resources: a multiconfig build compiles several toolchains at once, and it is common to run out of memory on a developer laptop when the Linux, bare-metal, and firmware toolchains build in parallel. Provision the build host accordingly. If you want structured, hands-on practice with this kind of layered build, we cover it in our Embedded Linux and Yocto training.

Key takeaways

  • A system device tree describes an entire heterogeneous SoC in one model, with no operating environment assumed, using cpus,cluster nodes and openamp,domain-v1 execution domains.
  • An execution domain assigns CPUs, memory ranges, device access, and an os,type to one software image, which is the information a build system needs to target it.
  • Lopper transforms the system device tree into the per-domain device trees (and tune configs) that Linux, bare-metal, and firmware builds each consume.
  • Yocto multiconfig builds the Linux root filesystem, bare-metal applications, and firmware in one project; mcdepends wires the cross-configuration dependencies together.
  • Binary-only components are isolated in their own deploy-only recipes and consumed through the same dependency mechanism as source-built parts.
Was this worth your time?

Frequently asked questions

What is the difference between a device tree and a system device tree?
A normal device tree describes hardware as one operating environment sees it, in a single address space. A system device tree describes the whole SoC without assuming any operating environment, adding CPU cluster nodes and /domains execution domains so that Linux, an RTOS, bare-metal images, and firmware can all be derived from one description.

What does Lopper do?
Lopper applies a sequence of transforms to a system device tree and prunes and rewrites it into a traditional, environment-specific device tree for one domain. From a single system-top.dts it can generate the Linux device tree, a bare-metal device tree, and configuration such as a MicroBlaze tune file.

How does Yocto build Linux, bare-metal, and firmware in one project?
Through multiconfig. Additional configurations are listed in BBMULTICONFIG, each with its own TMPDIR and usually its own MACHINE and DISTRO (bare-metal uses the xilinx-standalone distro). The mcdepends task flag lets the integrating image depend on firmware and bare-metal outputs that other multiconfigs deploy.

How are proprietary binary-only components handled?
Each binary is wrapped in its own recipe whose main task is a do_deploy that places the artefact in a deploy directory. The rest of the build consumes it through the same dependency and deploy mechanism used for source-built firmware, which keeps closed components isolated from the open parts of the project.

Further reading

RB
Raghu Bharadwaj

Founder, TECH VEDA — 20+ years teaching the Linux kernel, device drivers and embedded systems.

Follow on LinkedIn

Get new posts by email

Kernel, embedded Linux and AI-era engineering — a few sharp reads a month. No spam.

We email occasionally and never share your address.