- PetaLinux provides a kernel, device tree, and rootfs ready for customization on Zynq/ZynqMP.
- The actual workflow combines Vivado (XSA), PetaLinux (image) and Vitis/SDK (app).
- The Device Tree reflects the address and interrupt map defined in Vivado.
- UIO/drivers facilitate access to IP; FPGA Manager streamlines PL iterations.
If you're struggling with PetaLinux, don't worry: you're not alone. It has a steep learning curve , but there's a clear path to understanding how Vivado, PetaLinux, and Vitis work together, and what role each component plays. Here you'll find a practical and organized explanation to help you go from a Vivado design to running your first Linux application on Zynq or Zynq UltraScale+ without losing your mind.
Many people wonder if it's possible to "live" solely within Vitis. You need PetaLinux for the kernel, device tree, and rootfs . The typical workflow you'll see on real-world systems is: generate the .xsa file in Vivado, create and configure the PetaLinux project (sometimes from a BSP), compile and package the image (WIC for SD), and then export the sysroot to develop the app in Vitis or with an external toolchain. Later, I'll explain alternatives, tips for avoiding endless rebuilds, and what to do with things like FPGA Manager or TCF Agent.
What is PetaLinux and what does it include by default?
PetaLinux is AMD's reference distribution designed for its SoCs and MPSoCs, providing a ready-to-customize Linux environment with optimized bootloaders and kernels , libraries and utilities, C/C++ support, and debugging tools. It also includes threading options, FPU support, and a web-based management server for easy network and firmware configuration.
Official BSPs usually come with pre-made boot and configuration images. Deploy and run the binaries on real hardware or in QEMU , the full system emulator that comes with PetaLinux. This speeds up initial testing and is ideal for verifying that the project boots before you start working with apps or drivers.
Recommended hardware-software workflows
In professional environments, two versions of the Linux workflow are used: one focused on the PetaLinux command line, and another that integrates Vitis for application development, generating the .xsa file and, if applicable, the bitstream.
Can I do everything in Vitis? For Linux, no. Vitis is great for building platforms when you need a custom kernel, device tree, and rootfs. The usual approach is: PetaLinux to build the system and export the SDK/sysroot; and Vitis or an external toolchain to compile and debug your application.
From XSA to the boot system
The minimum viable itinerary is usually as follows: you generate the .xsa file in Vivado , create the project with petalinux-create (or starting from a BSP), update the hardware in PetaLinux with that .xsa file, and adjust the configuration (kernel, device tree, and rootfs). Then you compile, package to .wic format, and flash the SD card.
With the system booting, it's time to add the application. You export the sysroot/SDK from PetaLinux and, in Vitis, create a platform from the .xsa file. From there, you generate your C/C++ application project pointing to the sysroot to properly link with the rootfs libraries, and run the app on the target system using tcf-agent, gdbserver, or your preferred method.
A small real-life warning: Vivado/Vitis/PetaLinux versions have their quirks. For example, a problem was reported in Vitis Unified 2023.2 that prevented connecting to tcf-agent, and the quick fix was to upgrade to 2024.1. Always check the release notes and, if possible, freeze your tool stack during a project.
Hardware design in Vivado (Zynq-7000 and Zynq UltraScale+)
Your technology base is in Vivado's Block Design. Add the "ZYNQ7 Processing System" block (or its equivalent in ZynqMP) and run Run Block Automation to wire the DRAM and clock. From there, adjust what you need.
- Enable external interrupts: In PS customization, activate PS-PL Interrupts and Shared Interrupts if you plan to share IRQ lines between PL peripherals.
- Synchronize clocks: a common practice is to connect FCLK_CLK0 to the M_AXI_GP0_ACLK port so that the AXI bus clock is consistent with the PS clock.
With the PL IPs, rely on automation. Run Connection Automation establishes AXI interconnections and arbitration, but be aware: it doesn't connect interrupts or all clock trees. For IRQs, use a concat block and route its output to the PS.
- Connect each IRQ line of your peripheral IPs to the Concat block.
- Connect the Concat output to the PS interrupt input.
If you need fast memory in the PL, add BRAM. Insert the BRAM Controller and Block Memory Generator , define the number of ports, width, and capacity, and, very importantly, check its mapping in the Address Editor.
- Check and adjust the address map: there cannot be any range overlaps; if there are, Vivado colors the conflict in red.
- Consider other masters (for example, a coprocessor in the PL). Assigning addresses is mandatory to prevent synthesis from failing.
To finalize the design, create the HDL wrapper for the .bd file, synthesize it, and implement it. Generating the bitstream can take a while, and when it's finished, export the hardware, including the bit if you want to program the PL from startup.
Some useful points: Exposing PS signals to PL with EMIO for GPIO or I2C is very practical if, for example, you're going to use a PYNQ shield. Be careful with integrated peripherals: they are usually hardwired to fixed pins on the device, and it's not always possible to replace them with fabric-level IPs without changing connections. If the implementation fails, check restrictions (XDC) and assignments.
Device tree and hardware mapping
The million-dollar question: how far does Linux abstract you? The Device Tree describes device addresses, IRQs, and compatibilities. This mapping isn't "dynamic" at boot unless you use overlays; it's usually static and derives from Vivado's Address Editor and the DTS generated by PetaLinux.
The control portion (registers) and the data portion (e.g., DMA accesses) should be separated conceptually and physically. Use "reg" ranges and interrupt lines . The driver (or UIO) will use this information to map memory and interrupt logic. The size and alignment of the ranges must match what you defined in Vivado.
If you compile with PetaLinux, the DTS fragments are usually automatically generated from the .xsa file. Update the .xsa file when you change the hardware (new IPs or addresses) and rebuild at least the device tree and kernel. The rootfs file doesn't always need changes, so the sysroot file can still work if the user libraries haven't been modified.
Options for accessing hardware from C/C++
There's no single way; choose based on your project's stage and the effort you're willing to dedicate to drivers. /dev/mem is used for MMIO mappings, but it's not recommended in production due to security, portability, and potential interruptions.
An intermediate alternative: UIO (Userspace I/O) allows you to map IP regions in user space and handle interrupts with select/poll. It requires adding the UIO nodes to the Device Tree, but avoids writing a complete driver.
The professional option is to create a character driver or a platform driver for your IP. A character driver gives you fine control over DMA, IRQ, power, and a stable interface in /dev, as well as better integration with the kernel ecosystem. If your IP uses AXI-DMA or standard frameworks, rely on existing drivers.
Regarding the toolchain: you can use Vitis or compile with the SDK exported by PetaLinux. Run `petalinux-build --sdk` and link your project to that sysroot to ensure you're linking to the same libraries as in the target's rootfs. Vitis simplifies building the platform from the .xsa file and launching remote debugging.
Debugging and remote execution
To run the app on the target, you have several options. tcf-agent works well for launching and debugging from Vitis, provided the agent is installed in the rootfs (add it from petalinux-config -c rootfs) and you don't have a version conflict.
Alternatively, gdbserver is simple and robust. You copy your binary to the target, run `gdbserver :port ./yourapp`, and connect from the host using the multi-architecture gdb. Many teams combine gdbserver with VS Code Remote or Python scripts.
As a way to go beyond printf, take a look at kernel profiling and tracing techniques. The Zynq community has shared best practices for efficient debugging that will save you time when bugs aren't obvious.
Upgrade hardware without rebuilding everything
It's normal to think that a change in the PL file requires "remaking the world," but there's room for maneuver. Interfaces visible to the kernel (addresses, IRQs, compatibility), rootfs, and sysroot usually remain in place. In that case, you can reprogram the PL file and you're good to go.
To load bitstreams on demand, Linux offers the FPGA Manager framework ( fpgautil) or the sysfs/configfs interface for programming the PL from user space. This avoids regenerating entire images and speeds up logic iterations.
If the hardware change introduces new IPs or modifies ranges/IRQs, then yes: rebuild the device tree and regenerate BOOT.BIN/image. Even so, use the sstate cache to prevent the compilation from repeating tasks that have already been completed.
Tips to speed up builds and gain stability
The best remedy against "rebuild everything" is caching and organization. Use an sstate mirror and a shared downloads directory for Yocto/PetaLinux; this prevents you from recompiling packages that haven't changed. This makes a big difference on large systems and in-house environments.
Maintain reproducibility: anchor Vivado/Vitis/PetaLinux versions and document patches. If possible, avoid version changes mid-project unless it's due to a confirmed critical bug. And before jumping to a new version, test on a branch.
QEMU is your ally. Boot the image in QEMU to verify that the rootfs and kernel are working as expected without needing to flash the SD card. It doesn't replace the actual hardware, but it saves boot cycles.
After all of the above, the picture no longer seems like a puzzle. With a complete reference distro (bootloader, kernel, rootfs, and tools) , Vivado defines the physical map, and Vitis accelerates the app and debugging cycle. From there, leveraging Device Tree, UIO, or drivers makes the difference between a "Hello World" and a robust application that seamlessly utilizes your hardware.
Passionate writer about the world of bytes and technology in general. I love sharing my knowledge through writing, and that's what I'll do on this blog, show you all the most interesting things about gadgets, software, hardware, tech trends, and more. My goal is to help you navigate the digital world in a simple and entertaining way.