TFTP Network Boot
Boot the Linux kernel over Ethernet using TFTP instead of the SD card, with automatic fallback to the on-SD image if the network path fails.
Note
Netboot is opt-in. The default build mode is sd-only, which never
attempts DHCP or TFTP at all, so a board on a network with no TFTP server
pays none of the timeouts described below. Everything on this page applies
to boards built with -m fallback or -m tftp-only; see
Step 2 for the full mode table.
How It Works
On power-up, the boot ROM loads BOOT.BIN (FSBL + PMU firmware + ATF +
U-Boot) from the SD card, exactly as in a normal SD boot. From there,
U-Boot fetches only the kernel FIT image (image.ub) over TFTP
instead of reading it from the SD card, then boots it with bootm.
The SD card stays in the board and still holds a known-good
image.ub, so if the TFTP fetch fails, U-Boot falls back to booting
that on-SD image automatically. Nothing about the first boot stage
changes: only where the kernel FIT comes from.
The kernel FIT is fetched PXE-first. U-Boot first tries to download
a PXE (“pxelinux”) config from the TFTP server —
pxelinux.cfg/01-<MAC> (the board’s MAC, dash-separated and
lowercased, with the 01- ARP-hardware-type prefix) if a
board-specific file exists, otherwise pxelinux.cfg/default — parses
its KERNEL line, and boots the FIT that line names. Only if no PXE
config is served does U-Boot fall back to fetching image.ub
directly by name. The PXE config lets you change a board’s boot
behavior server-side — pointing it at a different FIT, for example —
without reflashing U-Boot or editing the board’s U-Boot
environment. Either path still relies on serverip (the TFTP
server address) being set, explicitly or via DHCP.
On boards built tftp-only for diskless (no-SD) operation, U-Boot
also fetches the PL bitstream over TFTP and programs it with
fpga load before booting the kernel, so the PL can be updated
server-side without reflashing. It probes four filenames, most- to
least-specific, preferring the Vivado .bit at each level:
system.bit.<mac> (e.g. system.bit.fc-c2-3d-5a-9a-08)
system.bin.<mac>
system.bit
system.bin
The MAC is dash-separated and lowercased. (U-Boot’s tftpboot treats
a : in a filename as a hostIP:file separator, so the env uses
setexpr gsub to rewrite ${ethaddr}’s colons to dashes before the
fetch.) A miss costs one immediate TFTP “not found” reply rather than a
timeout, so the extra probes are effectively free — provided the server
is reachable. If nothing answers at all, each probe instead waits its
full request timeout; see Troubleshooting.
fpga load accepts either a Vivado .bit or a bootgen-produced
raw .bin — see bitstream formats
below. This step is mandatory in tftp-only mode:
if the bitstream fetch or fpga load fails, netboot aborts before the
kernel boots, so a net kernel never runs over an unprogrammed or stale
PL. A fallback or sd-only build does not fetch a bitstream in
U-Boot — its PL is programmed later, after Linux boots, by
startup-app-init running fpgautil on the SD card’s system.bit
(unchanged from a normal SD boot). BOOT.BIN itself embeds an
FSBL-programmed bitstream in every mode; the tftp-only fpga load
re-programs the PL over it.
Why the PL must be programmed before the drivers load: the
axi_memory_map and axi_stream_dma kernel modules bind to AXI
endpoints that only exist once the PL is configured, so loading them
against an unprogrammed PL produces cryptic DMA/AXI errors (or an AXI
bus hang). To prevent that, startup-app-init gates the insmod
step on the FPGA manager: it reads /sys/class/fpga_manager/fpga0/state
and loads the drivers only when the state is operating. If the PL is
not programmed, it logs an error, skips the driver load, and lets Linux
continue booting rather than halting — so the board still comes up with
networking and a shell (a minimal recovery environment) instead of
stopping. The only path that deliberately halts before Linux is a failed
bitstream fetch in tftp-only mode (the netboot && chain aborts the
boot); a fallback or sd-only board always reaches Linux.
Warning
Because BOOT.BIN embeds a bitstream that the FSBL programs at
power-on, the FPGA manager reports operating on every boot — even
when no system.bit is present on the SD card and none is fetched
over the network. This guarantees a programmed PL for the driver-load
guard above, but it also means a fallback or sd-only board that
is missing its runtime system.bit will bind the DMA drivers against
the bitstream
frozen into BOOT.BIN at Yocto build time, which may be stale
relative to the firmware you intend to run. An operating state
therefore confirms only that a bitstream is loaded, not that it is the
correct one; use axiversiondump (printed near the end of
startup-app-init) to confirm the running firmware version and build
timestamp.
The active U-Boot network stack is lwIP (U-Boot 2026.01,
CONFIG_NET_LWIP); a bound DHCP lease prints a line starting with
DHCP client bound to address. The netboot hooks are added to
U-Boot’s CFG_EXTRA_ENV_SETTINGS macro, which is shared across boards
built on this platform.
Prerequisites
The board has been imaged and booted at least once (see SD Card Imaging in this how-to section), or has had a bitstream loaded via Remote Bitstream Update.
A development host on the same network as the board, with
dnsmasqandcurlinstalled. The provisioning script in Step 1 usessudoto write the server configuration and launch the daemon.Serial console access for observing boot messages:
cu --line /dev/ttyUSB1 --speed 115200 --parity=none
Note
The host IP, network interface, board IP, and serial device shown
below (e.g. 10.0.0.1, eth2, /dev/ttyUSB1) are this lab’s
values — substitute your own. The netboot path does not depend on any
particular subnet; the board takes its IP from whatever DHCP server is
present on your network, and only serverip needs to be set
explicitly to point at your TFTP host.
Steps
Set up the host TFTP server using the provisioning script, scripts/provision_tftp_host.sh, provided in the platform repository. The
-b BOARDargument is required and must match a directory name underaxi-soc-ultra-plus-core/hardware(RealDigitalRfSoC4x2shown here as an example):scripts/provision_tftp_host.sh -b RealDigitalRfSoC4x2
The script is idempotent: it writes the TFTP server configuration (
/etc/dnsmasq.d/tftp-lab.conf), stages the latest builtimage.ubfor that board into the TFTP root (/tftpboot), stages a PXE config at/tftpboot/pxelinux.cfg/default, and launches a standalonednsmasqinstance serving/tftpboot. Re-running it against an already-provisioned host is a no-op — it does not re-prompt forsudoand does not start a second daemon. Do not hand-derive the TFTP server configuration yourself.The staged
/tftpboot/pxelinux.cfg/defaultis minimal — it simply names the FIT to boot:LABEL Linux KERNEL image.ub
To give one board different boot behavior than the rest, add a MAC-specific override file beside it, named for that board’s MAC address as
pxelinux.cfg/01-aa-bb-cc-dd-ee-ff(the01-prefix plus the MAC dash-separated and lowercased). U-Boot prefers the MAC-specific file overdefaultwhen both are present, so you can repoint a single board — at a different FIT, say — without touching the shareddefaultconfig or reflashing that board’s U-Boot.For a tftp-only (diskless) board, also stage the PL bitstream so U-Boot can fetch and
fpga loadit (see How It Works).Add
-Bto stage the bitstream, and-M <mac>(repeatable) for per-board copies:scripts/provision_tftp_host.sh -b RealDigitalRfSoC4x2 -B -M fc:c2:3d:5a:9a:08
-Bcopieslinux/system.bitacross unconverted. That needs no Vitis environment, and it is the first nameloadpl_netprobes. Use-F bininstead to convert withbootgen(bootgen -arch zynqmp -image <bif> -process_bitstream bin); that requires sourcing your Vitis/Vivadosettings64.shfirst to putbootgenonPATH, and the script exits with a clear message if it is not there.-Fimplies-B.The MAC in
-Mmay be given with colons or dashes; it is stored dash-separated and lowercased (e.g.system.bit.fc-c2-3d-5a-9a-08) to match what the board’sloadpl_netrequests (it rewrites${ethaddr}’s colons to dashes withsetexpr).fallback-mode servers do not need any of this — omit-B/-F/-Mand onlyimage.uband the PXE config are staged.sd-onlyboards need no TFTP server at all, so this whole step is unnecessary for them.Note
The formats are staged exclusively: whichever you pick, the other’s names are removed, because a
.bitand a.bincannot both be useful at once. See bitstream formats for why, and for the substantial load-time difference between them.The server is TFTP-only (
dnsmasqruns with DNS and DHCP disabled), so it is safe to run alongside an existing site DHCP server on the same segment — the board still gets its lease from that DHCP server, and this host only answers TFTP requests.If the script cannot auto-detect a built
image.ubfor the board (No image.ub found for board ...), point it at the file explicitly with-f:scripts/provision_tftp_host.sh -b RealDigitalRfSoC4x2 -f /path/to/linux/image.ub
Before involving the board, confirm the host is serving the FIT by fetching it back over TFTP from the host itself:
curl -sf -o /tmp/verify_image.ub tftp://10.0.0.1/image.ub cmp /tmp/verify_image.ub /tftpboot/image.ub
A clean
curlexit and a matchingcmpprove the TFTP path is good end-to-end without needing the board. To stop the server, kill the PID it recorded:sudo kill "$(cat /run/dnsmasq-tftp-lab.pid)"
Choose the board’s boot mode at build time. The boot mode is baked into U-Boot — and therefore into
BOOT.BIN— when the Yocto image is built, via the-mflag toBuildYoctoProject.sh(see First SoC Bring-up for the full build invocation):-mvalueBoot behavior
sd-only(default)Never attempts netboot: no DHCP, no TFTP, and none of the ~85-110 s of timeouts a
fallbackboard pays on a network with no reachable TFTP server.run netbootis still defined and available by hand at theZynqMP>prompt for recovery.fallbackTries netboot first; on any failure boots the known-good
image.ubfrom the SD card.tftp-onlyTries netboot only; on failure prints
TFTP-only build: not falling back to SDand halts at theZynqMP>prompt. The SD image is never consulted.sd-onlyis the default, so it is applied even when-mis omitted, and a default build therefore does not netboot. The three modes produce byte-distinctBOOT.BINimages. To read a board’s mode straight out of the artifact — no board required — grep thebootcmdthat was baked into it:strings -a BOOT.BIN | grep -a '^bootcmd='
bootcmd=echo SD-only build: skipping netboot; run sdboot <- sd-only bootcmd=run netboot; run sdboot <- fallback bootcmd=run netboot; echo TFTP-only build: not falling back to SD <- tftp-only
That works on a freshly built image or on the SD card’s copy at
/boot/BOOT.BIN, and unlike a checksum it does not go stale between rebuilds.Note
strings -a BOOT.BIN | grep -aE '^(netboot|loadpl_net|loadpl_skip)='is a weaker check:loadpl_skipis selected by bothfallbackandsd-only, so that grep separatestftp-onlyfrom the other two but cannot tell those two apart. Only thebootcmd=line identifies a mode uniquely.Runtime behavior also distinguishes the modes: a
fallbackboard SD-boots after its TFTP attempts fail, atftp-onlyboard halts, and ansd-onlyboard printsSD-only build: skipping netbootand shows noDHCP client boundor TFTP lines at all. The login-banner hostname is not a mode indicator: it comes from the target name viahostname:pn-base-filesand is identical in all three modes (see Verification below).The mode also decides where the PL bitstream comes from. A
tftp-onlybuild additionally requires the bitstream to be staged on the TFTP server (Step 1,-B/-F/-M) and will halt rather than boot if it is missing. It also requires/boot/system.bitto be absent on the board:startup-app-initre-programs the PL from that file whenever it exists, overriding whatever U-Boot just fetched. Afallbackorsd-onlybuild ignores any staged bitstream and programs the PL from the SD card’ssystem.bitafter Linux boots, exactly as a normal SD boot does.To get the resulting
BOOT.BINonto the board, see SD Card Imaging (for a fresh SD card) or Remote Bitstream Update (for updating a board that is already imaged) in this how-to section.Warning
To switch a board between any two of the three modes, it is sufficient to replace only
BOOT.BINon the SD card’s FAT boot partition. On boards imaged by this platform’s tooling (1 GiB FAT32 boot partition), do that with a plain Linuxcpto the mounted/bootpartition. Never use U-Boot’s ``fatwrite`` command and never use a raw ``mmc write`` to do this: U-Boot 2026.01’sfatwriteis broken on that FAT32 boot partition (it corrupts the FSInfo free-count and fails every write with a bogus “no space left” error), and a rawmmc writerisks bricking the boot partition entirely.Run netboot manually from the
ZynqMP>prompt. To reach the prompt, press any key on the serial console during the autoboot countdown to interrupt it, then step through the fetch by hand.These commands mirror the built-in
netbootenvironment command (installed viaCFG_EXTRA_ENV_SETTINGS, see How It Works), which runsdhcp— skipped if a staticipaddris already set — then triespxe get/pxe bootand, only if no PXE config is served, falls back totftpboot 0x10000000 image.ubthenbootm 0x10000000;run netbootperforms the whole fetch-and-boot. Doing it by hand lets you setserveripexplicitly (netbootitself does not) and watch each stage.On a
fallbackortftp-onlyboard, boot time reachesnetbootthrough U-Boot’sbootcmd—run netboot; <mode-action>— which runsnetbootand then the mode-specific action from Step 2 (SD boot forfallback, halt fortftp-only). The mode action runs whenevernetbootreturns to U-Boot at all: a successful boot hands control to the kernel and never comes back, so simply reaching the mode action is the failure signal. The fallback therefore does not depend onnetbootreporting a nonzero exit code — some boot methods (notablypxe boot) return 0 even when no kernel booted.An
sd-onlyboard’sbootcmdis not of that form at all: it omitsrun netbootentirely, which is the whole point of the mode. Thenetboot,loadpl_net, andloadpl_skipenvironment variables are still defined there, though, so this manual sequence is exactly how you exercise netboot on such a board without rebuilding it.dhcp setenv serverip 10.0.0.1 pxe get pxe boot
pxe getdownloads thepxelinux.cfgfile (MAC-specific first, thendefault) fromserverip, andpxe bootloads and boots the FIT itsKERNELline names. This build also provides distro boot, sorun bootcmd_pxeis a one-line equivalent — but it runs its owndhcpand takesserveripfrom the DHCP response, so prefer settingserveripexplicitly and runningpxe get/pxe bootwhen your DHCP server does not hand out a TFTPnext-server.Mixed addressing — board IP from DHCP, TFTP server set by hand. When your DHCP server assigns the board’s IP but does not advertise a usable TFTP
next-server(or advertises the wrong one), setserveripyourself and let DHCP handle only the board address, then run the built-innetboot:setenv serverip 10.0.0.1 saveenv # optional: persist across reboots run netboot
The shipped
netbootpreserves a non-empty ``serverip`` across its own internal ``dhcp`` call, and clearstftpserverip(whichtftpbootandpxewould otherwise prefer overserverip), so your TFTP-server choice stays authoritative. This is required because U-Boot’s lwIPdhcpalways overwritesserveripwith the DHCP server’s own address and may settftpserveripfrom the DHCP next-server field. To hand TFTP addressing back to DHCP, clear it again withsetenv serverip(andsaveenvif you had persisted it).Warning
saveenvcosts more than it looks. The stored environment takes precedence over the one compiled intoBOOT.BINpermanently, and reflashingBOOT.BINdoes not update it, because U-Boot keeps the environment in a separate QSPI sector at0x1E00000that the flash scripts never write. A later build with a changedbootcmd,netboot, orloadpl_netis then silently ignored: the board keeps booting the old way after an apparently successful reflash. Prefer leaving addressing volatile. To undo asaveenv, erase the sector at the U-Boot prompt:sf probe 0 0 0 sf erase 0x1E00000 0x80000
See Program QSPI Flash Memory for the full explanation, including why the
*** Warning - bad CRC, using default environmentmessage on a freshly flashed board is both expected and the safer state.To fetch the FIT directly instead — the fallback path
netboottakes when no PXE config is served — skip thepxecommands and fetchimage.ubby name:dhcp setenv serverip 10.0.0.1 tftpboot 0x10000000 image.ub bootm 0x10000000
dhcpacquires a lease from your network’s DHCP server (a bound lease printsDHCP client bound to address). Setserveripexplicitly to your TFTP host rather than relying on a DHCPnext-serveroption. Use the load address0x10000000exactly as shown — this is the address this platform’s boot flow is built around, not a generic default.On a
tftp-onlybuild,netbootfirst runs itsloadpl_netstep to program the PL from the TFTP-served bitstream before fetching the kernel. To reproduce that by hand, fetch andfpga loadthe bitstream (0x10000000is reused —fpga loadconsumes the buffer before the kernel is fetched to the same address):dhcp setenv serverip 10.0.0.1 tftpboot 0x10000000 system.bit fpga load 0 0x10000000 ${filesize} tftpboot 0x10000000 image.ub bootm 0x10000000Substitute
system.binforsystem.bitif that is what you staged —fpga loadhandles both identically.If your network has no DHCP server, set a static IP instead (keep it volatile — do not
saveenv— so a plainresetrestores the DHCP path):setenv ipaddr 10.0.0.50 setenv serverip 10.0.0.1 setenv gatewayip 10.0.0.1 setenv netmask 255.255.255.0 run netboot
Verification
A bound DHCP lease is the first sign networking is up:
DHCP client bound to address 10.0.0.xxx (123 ms)
A successful TFTP fetch reports the size of the FIT image (roughly 112 MiB for this platform’s image):
Bytes transferred = 116835243
On a tftp-only build, the bitstream fetch and fpga load run
first — a successful load prints a Filename line naming whichever of
the four probed names hit, its own Bytes transferred line, and no
error from fpga load. A .bit transfers 222 bytes more than the
equivalent .bin (the Vivado header); both program the same PL
configuration. fpga load prints nothing at all on success, so the only
sign it ran is the next command appearing — after roughly 16 s for a
.bit, or a fraction of a second for a .bin. A long silent pause
directly after the bitstream’s Bytes transferred line is therefore
expected, not a hang. Once Linux is up, startup-app-init confirms the
PL is programmed before it loads the DMA drivers:
/sys/class/fpga_manager/fpga0/state: operating
If the PL is not operating, startup-app-init prints an
ERROR: PL not programmed line and skips the driver load instead of
failing later with cryptic DMA errors.
Reaching a login prompt confirms the board booted:
SimpleRfSoc4x2Example login:
The banner hostname comes from the project name of the built image
(SimpleRfSoc4x2Example here, set via hostname:pn-base-files),
not from the hardware directory name RealDigitalRfSoC4x2 used
in Step 1. It is the same for all three boot modes, so it does not tell
you which mode’s BOOT.BIN is running – distinguish the modes with the
strings -a BOOT.BIN | grep -a '^bootcmd=' check from Step 2, or by the
runtime behavior (a fallback build SD-boots after its TFTP attempts
fail; a tftp-only build halts; an sd-only build prints SD-only
build: skipping netboot and never touches the network). Finally, confirm
the board is reachable over the network:
ping -c 4 10.0.0.xxx
Troubleshooting
Symptom |
Cause |
Fix |
|---|---|---|
No |
TFTP fetch failed; a |
Confirm the host is serving the FIT with the host-side |
|
No |
Confirm |
|
No TFTP server is answering. |
Expected when TFTP is unreachable, and not a hang. ICMP
|
|
Expected behavior: a |
Bring the TFTP host back up and run |
An |
SD boot itself failed, so |
Confirm both files are present on the FAT boot partition (they are
staged by |
|
The PL bitstream is not staged on the server under any of the
probed names; the mandatory |
Stage it in Step 1 with |
Board loads a stale bitstream even after re-running
|
A per-MAC name outranks the generic one, and a run without |
Re-run with |
|
|
Remove |
|
|
On a |
No |
The board is not getting a DHCP lease on this network |
Use the static-IP override shown in Step 3 |
|
U-Boot 2026.01’s |
Do not use |
TFTP fetches fail after |
lwIP |
Set |
A PXE config is served and |
The PXE label’s |
Handled by design: |
How long the SD fallback takes depends entirely on whether the TFTP
server answers. A single tftpboot gives up after roughly 6 seconds,
but netboot is PXE-first: pxe get tries 13 pxelinux.cfg names
ahead of the direct FIT fetch, so a fallback board on a network with
no reachable TFTP server pays that timeout 14 times over — about
85 seconds with the daemon stopped, and about 110 seconds if packets are
silently dropped. When the server is reachable and merely missing a
file, every attempt is refused immediately and the fallback is effectively
instant. Allow up to ~2 minutes before concluding a board has hung, and
see the Request timeout entry in Troubleshooting above.
That full 14-attempt cost needs a reachable DHCP server as well as an
unreachable TFTP one. netboot starts with dhcp in an && chain, so
on a network with no DHCP server at all it short-circuits at the DHCP
timeout and never reaches the TFTP attempts. An sd-only board pays
neither: its bootcmd contains no run netboot, so there is no DHCP
attempt and no TFTP attempt on the boot path at all.
Note
sd-only removes netboot from the success path, not from every
possible path. sdboot is run distro_bootcmd, which walks
boot_targets in order; the mmc targets come first and a healthy SD
card short-circuits there, but a board whose SD boot fails still falls
through to distro_bootcmd’s trailing pxe and dhcp targets and
can pay the usual timeouts there. That tail is identical to a
fallback build’s and is unchanged by this mode. Read the list off an
artifact with strings -a BOOT.BIN | grep -a '^boot_targets='.
Notes
The full JTAG-based recovery procedure for a board that becomes unresponsive is not covered here; it is an emergency recovery path documented alongside this platform’s bring-up tooling.
The
tftp-onlybitstream step is deliberately mode-gated and all-or-nothing: intftp-onlyit is mandatory (halt on failure), and infallbackandsd-onlyit is skipped entirely so the SDfpgautilload stays authoritative and there is no double-program. It does not fetch a network bitstream to override an inserted SD in either of those modes.Note
A “best-effort” variant — where U-Boot programs a network bitstream if one is served but continues (rather than halting) if none is — would let a net bitstream override an inserted SD. That is out of scope here; the current design keeps each mode a coherent stack (full-network in
tftp-only, SD-owned PL infallback, and no network on the boot path at all insd-only). If such a mode is added later, it must preserve netboot’s&&failure chain — a fetch orfpga loadfailure must still be able to abort the boot — rather than relaxing the chain to;. Weakening it to;would run the kernel-fetch stages even after a faileddhcp(paying both timeouts on a dead network) and, because the env string is shared, would also disable thetftp-onlyguarantee that a net kernel never boots over an unprogrammed or stale PL.The per-MAC bitstream filename is dash-separated (
system.bit.fc-c2-3d-5a-9a-08), like thepxelinux.cfgMAC form but without the01-prefix. U-Boot’stftpbootparses the first:in a filename as ahostIP:fileseparator, so a colon-form name (${ethaddr}verbatim) is silently mis-parsed and never fetched;loadpl_nettherefore rewrites the colons to dashes withsetexpr gsub(requiresCONFIG_CMD_SETEXPR+CONFIG_REGEX, both on in the ZynqMP defconfig) before the fetch.BOOT.BINembeds the XSA bitstream by default. This follows from meta-xilinx’sxilinx-bootbinrecipe: on thezynqmp-usermachine (which does not set thefpga-overlayMACHINE_FEATURE),BIF_BITSTREAM_ATTRdefaults tobitstream, so the FSBL programs the PL fromdownload-zynqmp-user.bitat power-on and the FPGA manager readsoperatingbefore Linux starts. To build aBOOT.BINthat contains no bitstream — for example, to exercise thestartup-app-initdriver-load guard on a board with nosystem.bitand no network bitstream — setBIF_BITSTREAM_ATTR = ""inbuild/conf/local.conf(or the machine.conf) and rebuildxilinx-bootbin. With no embedded bitstream the FPGA manager stays out ofoperatinguntil something (the SDfpgautilload, or thetftp-onlyfpga load) programs the PL, and the guard then skips the driver load as intended.
Bitstream formats: .bit and .bin
fpga load on this platform accepts a Vivado .bit and a
bootgen-produced raw .bin, and both program the same PL design. They
are not, however, the same bytes. Beyond the .bit’s 222-byte Vivado
header (34,437,578 B versus 34,437,356 B for this design), bootgen
also byte-reverses every 32-bit word — the sync word is AA 99 55 66 in
the .bit and 66 55 99 AA in the .bin, and the bus-width-detect
pattern 00 00 00 BB / 11 22 00 44 is reversed the same way.
Stripping the header off a .bit therefore does not produce the
.bin: 2,363,968 of 34,437,356 bytes differ, a figure that looks small
only because 0x00000000 and 0xFFFFFFFF words are invariant under a
word swap. An unconverted .bit loads because the PCAP auto-detects bus
width and endianness from that pattern ahead of the sync word — not
because the two payloads agree.
The reason is in U-Boot’s ZynqMP driver (drivers/fpga/zynqmppl.c).
zynqmp_load() calls its format validator only on ancient PMU
firmware:
if (zynqmp_firmware_version() <= PMUFW_V1_0) {
...
if (zynqmp_validate_bitstream(desc, buf, bsize, bsize, &swap))
return FPGA_FAIL;
...
} else {
bstype = 0; /* modern PMUFW: no validation at all */
}
On any current PMUFW the else branch is taken: U-Boot performs no
sync-word scan, no byte-swap, and no format check, and hands the buffer
verbatim to the PMU via PM_FPGA_LOAD. The PMU’s PCAP loader skips the
.bit header itself.
Important
The two formats do not cost the same to load. Measured on this design’s 34 MB bitstream (RFSoC 4x2, PMUFW from Vivado 2026.1):
Format |
|
Total netboot |
|---|---|---|
|
~0.2 s |
~40 s |
|
~16.6 s |
~60 s |
The TFTP transfer is identical either way (~17.6 MiB/s for both), so the
entire difference falls inside fpga load — consistent with the PMU
performing the word swap in software. Because loadpl_net prefers
.bit, a board served one pays this on every boot. Prefer
-F bin where boot time matters and bootgen is available.
Warning
None of this holds on PMUFW ≤ v1.0. There the validator runs,
check_data() locates the sync word at a nonzero offset, and the
load fails with Bitstream is not validated yet (diff ...). A raw
.bin is the only format that works across both, which is why
provision_tftp_host.sh -F bin still converts.
Staging both formats is not a way to cover both PMUFW generations.
loadpl_net’s if/elif chain selects on fetch success, and the
fpga load result for whichever name it fetched is final. On
PMUFW ≤ v1.0 a co-present .bit would be fetched first, fail to load,
and halt the boot with the .bin sitting there untried.
loadpl_net probes .bit before .bin at each specificity level,
so a leftover system.bit outranks a system.bin.
provision_tftp_host.sh removes the unselected format’s names — the
generic one and one per -M MAC — so re-running it cannot leave a stale
file of the other format behind. Without -M it cannot know the board’s
MAC and so cannot clean a per-MAC leftover; it warns about any it finds,
since a per-MAC name outranks the generic one.