Help with creating xilinx zynq zedboard image

I’ve been struggling to deploy to a zed board.

I have the 6/rtems-arm bset built.

and the test cases for the arm/xilinx_zynq_zedboard bsp built.

I have uboot cloned in and built.

I tried the rtems-boot-image tool, but there seems to be some typos in the .ini under share/rtems/tools/config/rtems-boot.init

stuff like “second_state” vs “second_stage”


at this part I’m out of depth, but here what I’ve fumbled through thus far.

my best shot at fixing the .ini

[u-boot-arm-xilinx-zynq-common]
arch = arm
vendor = xilinx
board = zynq
config_name = zynq-common
first_stage = %{ubootdir}/spl/boot.bin
second_stage = %{ubootdir}/u-boot.img
boot_device = mmc 0:1
kernel_loadaddr = 0x02000000
fdt_loadaddr = 0x08000000
start_address = 0x00104040
entry_address = 0x00104040
kernel_converter = %{objcopy} -R -S --strip-debug -O binary @KERNEL@ @KERNEL@.bin,
                   cat @KERNEL@.bin | gzip -9 > @KERNEL@.gz,
                   %{mkimage} -A arm -O linux -T kernel -C gzip -a %{start_address}
                    -e %{entry_address} -n "RTEMS" -d @KERNEL@.gz @KERNEL@.img
kernel_image = @KERNEL@.img

I run the tool with:
rtems-boot-image -o /tmp/ticker.img -b u-boot-zedboard -k $TELF ~/work/rtems/projects/zynq_demo/uboot/

and i copy the image to the SD card.

boot fails so I tried interrupting booting manually (I got the device tree from a linux image from AMD)

U-Boot 2021.01 (Apr 03 2026 - 16:53:23 -0400)

CPU:   Zynq 7z020
Silicon: v3.1
Model: Avnet ZedBoard board
DRAM:  ECC disabled 512 MiB
Flash: 0 Bytes
NAND:  0 MiB
MMC:   mmc@e0100000: 0
Loading Environment from FAT... *** Warning - bad CRC, using default environment

In:    serial@e0001000
Out:   serial@e0001000
Err:   serial@e0001000
Net:
ZYNQ GEM: e000b000, mdio bus e000b000, phyaddr 0, interface rgmii-id

Warning: ethernet@e000b000 (eth0) using random MAC address - ce:a0:df:34:c6:53
eth0: ethernet@e000b000
Hit any key to stop autoboot:  0
Zynq> fatload mmc 0:1 0x02000000 ticker.exe.img
34045 bytes read in 22 ms (1.5 MiB/s)
Zynq> fatload mmc 0:1 0x08000000 system.dtb
20560 bytes read in 22 ms (912.1 KiB/s)
Zynq> bootm 0x02000000 - 0x08000000
## Booting kernel from Legacy Image at 02000000 ...
   Image Name:   RTEMS
   Image Type:   ARM Linux Kernel Image (gzip compressed)
   Data Size:    33981 Bytes = 33.2 KiB
   Load Address: 00104040
   Entry Point:  00104040
   Verifying Checksum ... OK
## Flattened Device Tree blob at 08000000
   Booting using the fdt blob at 0x8000000
   Uncompressing Kernel Image
   Loading Device Tree to 1eae0000, end 1eae804f ... OK

Starting kernel ...


Hoping someone has a working .ini a different path

Please raise a MR with the change to the INI file? :grinning:

Checking a script I have pasted below the START_ADDR is different. Please try that and let us know?

#! /bin/sh
set -e

RTEMS_VERSION=7
OBJCOPY_FOR_TARGET=/opt/work/rtems/${RTEMS_VERSION}/bin/arm-rtems${RTEMS_VERSION}-objcopy
OBJCOPY="$OBJCOPY_FOR_TARGET"

START_ADDR=0x00104000
ENTRY_ADDR=0x00104040

for EXE_NAME in $*
do
 if [ ! -f $EXE_NAME ]; then
  echo "error: not found: $EXE_NAME"
  exit 1
 fi
 echo "Image: $EXE_NAME"
 ${OBJCOPY} -R -S --strip-debug -O binary "$EXE_NAME" "$EXE_NAME.bin" || exit 1
 cat "$EXE_NAME.bin" | gzip -9 >"$EXE_NAME.gz"
 mkimage \
   -A arm -O rtems -T kernel -a $START_ADDR -e $ENTRY_ADDR -n "RTEMS" \
   -d "$EXE_NAME.gz" "$EXE_NAME.img"
done

exit 0

We use simple commands - make variables expanded, paths simplified

arm-rtems7-objcopy  --output-target=binary -S rtems_can_test rtems_can_test.bin
cat rtems_can_test.bin | gzip -9 > rtems_can_test.bin.gz
mkimage -A arm -O rtems -T kernel -a 0x00104000 -e 0x00104000 -n "RTEMS" -d rtems_can_test.bin.gz rtems_can_test.img

Which reports

Image Name:   RTEMS
Created:      Sat Apr  4 01:33:24 2026
Image Type:   ARM RTEMS Kernel Image (gzip compressed)
Data Size:    251594 Bytes = 245.70 KiB = 0.24 MiB
Load Address: 00104000
Entry Point:  00104000
Compilation finished

The binary starts by exception table, reset vector in the sources

ldr     pc, .Lhandler_addr_reset

in the binary

  104000:       e59ff018        ldr     pc, [pc, #24] 

So you can set entry to be equivalent to the start address.

The strip of debug information (as Chris suggest) should be there probably.

Then our U-boot script which is loaded by U-boot over TFTP and controls booting looks like

echo Running autoscr

setenv tftp_path /zynq/rtems

setenv nfspath /srv/nfs/debian-armhf

setenv netstart 0x01000000
setenv bitstream_addr 0x04000000

setenv image_img ${tftp_path}/rtems_can_test.img
setenv bitstream_img ${tftp_path}/system-mz_apo-2x-xcan-4x-ctu.bit.bin

echo "MAC address is $ethaddr"

echo "About to load $image_img"
setenv image_tftp 'echo === Loading boot image; tftpboot ${netstart} ${tftpserverip}:${image_img};'
setenv bitstream_tftp 'tftpboot ${bitstream_addr} ${tftpserverip}:${bitstream_img}; setenv bitstream_size ${filesize}'
setenv boot_now 'bootm ${netstart}'

setenv bitstream_load 'echo === Loading fpga image; fpga load 0 ${bitstream_addr} ${bitstream_size}'

run image_tftp bitstream_tftp bitstream_load boot_now

I have even tested combined ITS image (kernel, device-tree, FPGA configuration stream) with RTEMS on the CAN test test system where we use ITS with Debian/Linux on Zynq. It is little faster, no need to connect multiple times. But loading by individual files is easier probably.

I use u-boot’s TFTP to boot RTEMS on a few projects. It works fine with an u-boot img file using the script I posted.

I do not use a Xilinx BOOT.BIN and I get RTEMS to load the bitfile rather than having Xilinx tools get involved. I store the bitfile on NFS or JFFS file systems.


I use the Flare Bootloader on Zynq devices these days and chain load u-boot from it when I need to use TFTP. This is the trace for an XIA Signal Processing System (SPS) on a Microzed with Flare in the SPI flash and the boot script on an SD card. The boot:

Flare Apache 2.0 Licensed FSBL
    Build ID: 69018c5d
 Reset Count: 20
  Reset Code: 0x80000000
   Boot Mode: 0x00000002
       Flash: S25FL128P_64K (16MiB)
Factory Mode: no        
 Boot Script: /flare-0 Length: 43
  Executable: /uboot-noqspi/u-boot.img (CRC32: 3c668db9)
       Loading: U-Boot Image: UBOOT
            To: 0x400000
          Size: 0x0007b0b3
    Compressed: 1
   Entry point: 0x00400000
Flare handing off to 0x00400000


U-Boot 2024.01-dirty (Mar 17 2025 - 09:50:08 +1100)

CPU:   Zynq 7z010
Silicon: v3.1
Model: Avnet MicroZed board
DRAM:  ECC disabled 1 GiB
Core:  19 devices, 14 uclasses, devicetree: board
Flash: 0 Bytes
NAND:  0 MiB
MMC:   mmc@e0100000: 0
Loading Environment from nowhere... OK
In:    serial@e0001000
Out:   serial@e0001000
Err:   serial@e0001000
Net:
ZYNQ GEM: e000b000, mdio bus e000b000, phyaddr 0, interface rgmii-id

Warning: ethernet@e000b000 (eth0) using random MAC address - ba:99:03:1c:9d:2f
eth0: ethernet@e000b000
Hit any key to stop autoboot:  0
## Error: "bootcmd_qspi" not defined
JTAG: Trying to boot script at 3000000
## Executing script at 03000000
Wrong image format for "source" command
JTAG: SCRIPT FAILED: continuing...
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
619 bytes read in 11 ms (54.7 KiB/s)
## Executing script at 03000000
No EFI system partition
No EFI system partition
Failed to persist EFI variables
No EFI system partition
Failed to persist EFI variables
No EFI system partition
Failed to persist EFI variables
No EFI system partition
Failed to persist EFI variables
No EFI system partition
Failed to persist EFI variables

  *** U-Boot Boot Menu ***

      RTEMS
      Linux
      mmc 0:1
      mmc 0:2
      U-Boot console


  Press UP/DOWN to move, ENTER to select, ESC to quit
BOOTP broadcast 1
DHCP client bound to address 10.10.5.70 (1 ms)
Using ethernet@e000b000 device
TFTP from server 10.10.5.2; our IP address is 10.10.5.70
Filename 'pixienet/rtems.img'.
Load address: 0x20000000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         ##########################
         3.2 MiB/s
done
Bytes transferred = 4184901 (3fdb45 hex)
## Booting kernel from Legacy Image at 20000000 ...
   Image Name:   "RTEMS"
   Image Type:   ARM RTEMS Kernel Image (gzip compressed)
   Data Size:    4184837 Bytes = 4 MiB
   Load Address: 00104000
   Entry Point:  00104040
   Verifying Checksum ... OK
   Uncompressing Kernel Image
## Transferring control to RTEMS (at address 00104040) ...

XIA Signal Processing System (SPS)

Hardware: start ......................... PASS
[ snip the reset of the application output]

And to see the boot.scr:

RTEMS Shell on stdin. Use 'help' to list commands.
xcon [/] # ls /dev
bpf       flashdev0 pfil      pty2      ttyS0     ttyWS1    zero
bpf0      fpga      pty0      pty3      ttyS1     ttyWS2
console   mmcsd0    pty1      rda       ttyWS0    ttyWS3
xcon [/] # fdisk /dev/mmcsd0 register
xcon [/] # ls /dev                   
bpf       flashdev0 mmcsd01   pty0      pty3      ttyS1     ttyWS2
bpf0      fpga      mmcsd02   pty1      rda       ttyWS0    ttyWS3
console   mmcsd0    pfil      pty2      ttyS0     ttyWS1    zero
xcon [/] # mkdir mnt
xcon [/] # mount -t dosfs /dev/mmcsd01 /mnt
mounted /dev/mmcsd01 -> /mnt
xcon [/] # ls /mnt
.Spotlight-V100 ._system.dtb    .fseventsd      boot.scr        zImage
.Trashes        ._zImage        boot.bin        system.dtb
xcon [/] # cat /mnt/boot.scr
'Vչhf2+GJXIA Boot script## This is a boot script for U-Boot
# mkimage -A arm -O RTEMS -T script -a 0 -e 0 -n "XIA Boot script" -d boot.cmd boot.scr

setenv -f ethaddr 72:44:fa:b0:29:da

setenv rtems 'setenv autoload no; dhcp; setenv serverip 10.10.5.2; tftpboot 0x20000000 pixienet/rtems.img; bootm; reset;'

setenv linux 'fatload mmc 0 0x10000000 zImage; fatload mmc 0 0x8000000 system.dtb; bootz 0x10000000 - 0x8000000; reset;'

setenv bootmenu_0 RTEMS=run rtems
setenv bootmenu_1 Linux=run linux
env delete -f bootmenu_2
env delete -f bootmenu_3
bootmenu 1

reset
exit

I hope this helps.

Thank you everyone. I now have a successful boot directly from image on the SD card. My main error was that I was clipping off the arm exception table by using address 0x00104040 instead of correct address of 0x00104000. As the first line of that table is the reset vector that jumps to 0x00104040, it can also double as an entry point.

Thanks also to Claude ai for helping come up to speed with u-boot syntax. But also lead to the address error as it as trying to key off the entry point address in the elf header.

I’ll follow up here with posting a guide on creating self boot images for anyone else attempting it.

Some questions I still have.

@kiwichris you mentioned a MR for a working ini file. I haven’t read the contributing section of the docs yet. And would this be to the RTEMS repo or the RSB repo?

device trees: while working with claude to make u-boot happy I came to believe a device tree would be required. Turns out it is not (at least for the ticker test case). Device trees are mentioned often in the rtems docs for the other arm bsps using u-boot, but not for the zynq bsp. It mentions a xilinx tool and a different first stage bootloader than the one I used. I have not yet installed any xilinx SDK or their IDE (I’m more the c side of team vs fpga work). I haven’t yet dove into figuring out how the zedboard bsp finds the UART pins or clock source. I’m assuming things will start to fall apart once we divert from what ever fabric firmware comes out of the Digilent box. I imagine it will be something like the IDE builds its own bsp, and I will need to find one of its auto generated source files and use it to build our own rtems zedboard bsp spinoff?

A quick start guide for deploying RTEMS to xilinx Zedboard

Host

WSL (Windows Subsystem for Linux) running Ubuntu 24.

RTEMS 6.2

Assumes zedboard has factory firmware load from Digilent

Using RSB to Build RTEMS

Dependencies:

  • build-essential
  • g++
  • gdb
  • unzip
  • pax
  • bison
  • flex
  • texinfo
  • python3-dev
  • python-is-python3
  • libncurses-dev
  • zlib1g-dev
  • ninja-build
  • pkg-config

Toolset

./sb-set-builder --prefix=$PREFIX 6/rtems-arm

Tools can be found in $PREFIX/bin. Consider adding to your PATH

BSP, Kernel, Test Cases

./sb-set-builder --prefix=$PREFIX --target=arm-rtems6 --with-rtems-bsp=arm/xilinx_zynq_zedboard --with-rtems-tests=yes 6/rtems-kernel

Test cases .exe can be found in $PREFIX/arm. I will be using the ticker.exe for the rest of the guide. It will under the var $TELF

U-Boot

repo: GitHub - Xilinx/u-boot-xlnx: The official Xilinx u-boot repository · GitHub
I
used the tag xilinx-v2025.2

Dependencies:

  • gcc-arm-linux-gnueabihf
  • device-tree-compiler
  • libssl-dev
  • python3
  • python3-dev
  • python3-setuptools
  • swig
  • bison
  • flex
  • bc

Building

export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
make distclean
make xilinx_zynq_virt_defconfig # I think this for qemu, but seems to work
export DEVICE_TREE="zynq-zed"
make -j$(nproc)

You will need u-boot.img and spl/boot.bin

you will also be calling tools/mkimage in the next step. might want to add it to your path

Creating RTEMS Image

using the tools in $PREFIX/bin and u-boot, as well as the desired .exe pointed to by $TELF. I will be using ticker.exe

First confirm the binary start point: arm-rtems6-objdump -d $TELF | less. In this example the magic number is 0x00104000 i.e. the start of the ARM exception table (entry 1 is the reset vector jumps you to address 0x00104040 _start)

Disassembly of section .start:

00104000 <bsp_section_start_begin>:
  104000:       e59ff018        ldr     pc, [pc, #24]   @ 104020 <bsp_section_start_begin+0x20>
  104004:       e59ff018        ldr     pc, [pc, #24]   @ 104024 <bsp_section_start_begin+0x24>
  104008:       e59ff018        ldr     pc, [pc, #24]   @ 104028 <bsp_section_start_begin+0x28>
  10400c:       e59ff018        ldr     pc, [pc, #24]   @ 10402c <bsp_section_start_begin+0x2c>
  104010:       e59ff018        ldr     pc, [pc, #24]   @ 104030 <bsp_section_start_begin+0x30>
  104014:       e7f000f0        udf     #0
  104018:       e59ff018        ldr     pc, [pc, #24]   @ 104038 <bsp_section_start_begin+0x38>
  10401c:       e59ff018        ldr     pc, [pc, #24]   @ 10403c <bsp_section_start_begin+0x3c>
  104020:       00104040        .word   0x00104040
  104024:       00108b18        .word   0x00108b18
  104028:       00108b2c        .word   0x00108b2c
  10402c:       00108b40        .word   0x00108b40
  104030:       00108b54        .word   0x00108b54
  104034:       00000000        .word   0x00000000
  104038:       00108a30        .word   0x00108a30
  10403c:       00108b68        .word   0x00108b68

00104040 <_start>:
  104040:       e3a00000        mov     r0, #0
  104044:       ee070fd5        mcr     15, 0, r0, cr7, cr5, {6}
  104048:       e59f121c        ldr     r1, [pc, #540]  @ 10426c <bsp_start_hook_0_done+0x44>
  10404c:       e59f021c        ldr     r0, [pc, #540]  @ 104270 <bsp_start_hook_0_done+0x48>
  104050:       e0813000        add     r3, r1, r0
  104054:       e10f4000        mrs     r4, CPSR
  104058:       e3a000d1        mov     r0, #209        @ 0xd1
  10405c:       e129f000        msr     CPSR_fc, r0

Alternately could use

arm-rtems6-readelf -S $TELF | grep "\.start"
  [ 1] .start            PROGBITS        00104000 001000 000558 00  AX  0   0  4

Now create the Image

arm-rtems6-objcopy --output-target=binary -S $TELF /tmp/ticker.exe.bin
cat /tmp/ticker.exe.bin | gzip -9 > /tmp/ticker.exe.bin.gz
mkimage -A arm -O rtems -T kernel -a 0x00104000 -e 0x00104000 -n "RTEMS" -d /tmp/ticker.exe.bin.gz /tmp/ticker.exe.img
Image Name:   RTEMS
Created:      Mon Apr  6 10:15:13 2026
Image Type:   ARM RTEMS Kernel Image (gzip compressed)
Data Size:    33981 Bytes = 33.18 KiB = 0.03 MiB
Load Address: 00104000
Entry Point:  00104000

Next compile the script that u-boot will use to boot RTEMS

cat > /tmp/boot.cmd << 'EOF'
fatload mmc 0:1 0x02000000 ticker.exe.img
bootm 0x02000000 - -
EOF

~/work/rtems/projects/zynq_demo/uboot/tools/mkimage \
  -A arm -O linux -T script -C none \
  -a 0 -e 0 -n "RTEMS Boot" \
  -d /tmp/boot.cmd /tmp/boot.scr

In /tmp you will now have ticker.exe.img and boot.scr

SD Card

I used the SD card included with the zedboard. I made a back up and cleaned the files off of it.

It shows up in u-boot as mmc 0:1

But i’m not sure how universal this will be. Some minor adjustments to the boot.scr above might be needed.

WSL Hint

To mount a sd card in wsl first take note of the drive windows assigns the card. In my case D:

# be sure your mount path exist
# mounting
sudo mount -t drvfs D: /mnt/d
# card now accessable under as /mnt/d
# to unmount
sync
sudo umount /mnt/d
# you should then also 'eject' from windows side 

Card layout

You will only need 4 files

  • boot.bin → the first stage bootloader (or rather what zedboard’s FSBL finds and runs)
    • copy in from u-boot-xlnx/spl/boot.bin
  • u-boot.img → the second stage bootloader
    • copy in from u-boot-xlnx/u-boot.img
  • boot.scr → u-boot script; built in the last step
  • ticker.exe.img → RTEMS image; built in the last step

Minicom

minicom can be used to view the board’s UART output. use the J14 header and 115200 baud rate. your device file may differ

minicom -D /dev/ttyACM0 -b 115200

WSL Hint

usb pass through is a bit difficult in wsl… you will need the usbipd tool and powershell

use usbipd list to find the device. mine came up as USB Serial Device (COM3) with bus id 2-3

first in powershell as admin

usbipd bind --busid=2-3

you should only have to do that once… and the state will show as ‘Shared’

next again in powershell but this time not as admin

usbipd attach --wsl --busid 2-3 --auto-attach

--auto-attach is optional and keeps the process running and re attaches every time the board power cycles

Expected Output

can only attach minicom when the board is on. can use the PS-RST button to get a clean run.

U-Boot 2021.01 (Apr 03 2026 - 16:53:23 -0400)

CPU:   Zynq 7z020
Silicon: v3.1
Model: Avnet ZedBoard board
DRAM:  ECC disabled 512 MiB
Flash: 0 Bytes
NAND:  0 MiB
MMC:   mmc@e0100000: 0
Loading Environment from FAT... *** Warning - bad CRC, using default environment

In:    serial@e0001000
Out:   serial@e0001000
Err:   serial@e0001000
Net:
ZYNQ GEM: e000b000, mdio bus e000b000, phyaddr 0, interface rgmii-id

Warning: ethernet@e000b000 (eth0) using random MAC address - e6:46:02:37:ca:65
eth0: ethernet@e000b000
Hit any key to stop autoboot:  0
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot.scr
182 bytes read in 17 ms (9.8 KiB/s)
## Executing script at 03000000
34045 bytes read in 22 ms (1.5 MiB/s)
20560 bytes read in 22 ms (912.1 KiB/s)
## Booting kernel from Legacy Image at 02000000 ...
   Image Name:   RTEMS
   Image Type:   ARM RTEMS Kernel Image (gzip compressed)
   Data Size:    33981 Bytes = 33.2 KiB
   Load Address: 00104000
   Entry Point:  00104000
   Verifying Checksum ... OK
   Uncompressing Kernel Image
## Transferring control to RTEMS (at address 00104000) ...


*** BEGIN OF TEST CLOCK TICK ***
*** TEST VERSION: 6.0.0.not-released
*** TEST STATE: EXPECTED_PASS
*** TEST BUILD: RTEMS_POSIX_API
*** TEST TOOLS: 13.3.0 20240521 (RTEMS 6, RSB 3814cb0e7f86cca2be403eac831f9bf571984659-modified, Newlib 1b3dcfd)
TA1  - rtems_clock_get_tod - 09:00:00   12/31/1988
TA2  - rtems_clock_get_tod - 09:00:00   12/31/1988
TA3  - rtems_clock_get_tod - 09:00:00   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:05   12/31/1988
TA2  - rtems_clock_get_tod - 09:00:10   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:10   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:15   12/31/1988
TA3  - rtems_clock_get_tod - 09:00:15   12/31/1988
TA2  - rtems_clock_get_tod - 09:00:20   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:20   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:25   12/31/1988
TA2  - rtems_clock_get_tod - 09:00:30   12/31/1988
TA1  - rtems_clock_get_tod - 09:00:30   12/31/1988
TA3  - rtems_clock_get_tod - 09:00:30   12/31/1988

*** END OF TEST CLOCK TICK ***


[ RTEMS shutdown ]

lastly see above posts on how u-boot can be used to load an image over the network (better for iterative dev) and hopeful following posts on how to update the bsp once the fpga image moves from factory image.

Please make the MR to RTEMS Tools repo.

The use of device trees in RTEMS is not consistent and not cleanly documented. Some BSPs need them to boot and some need them when doing specific things, for example networking. It is an area I would like see us improve to make the process for new users easier.

For the MR, I have a better ini file, but the u-boot i got working does not seem to use the uEvn.txt, but instead a boot.scr. which really is just the output of mkimage being called on uEvn.txt.

I don’t see a way to cleanly have the tool generate and copy over the boot.scr instead of uEvn.txt.

not sure if this is just me being new to u-boot, or how u-boot does things now.

The uboot repo and tag is in quick start i posted above.