Skip to content

Commit 30c911f

Browse files
committed
Add ZCU102 (UltraScale+ A53 EL3) bare-metal wolfIP port with GEM3 + DP83867 + UDP echo
1 parent fe6f1d5 commit 30c911f

27 files changed

Lines changed: 3563 additions & 0 deletions

src/port/zcu102/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.o
2+
*.elf
3+
*.bin
4+
BOOT.BIN

src/port/zcu102/Makefile

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Xilinx ZCU102 (UltraScale+ MPSoC, Cortex-A53) wolfIP bare-metal port
2+
#
3+
# Build: make CROSS_COMPILE=aarch64-none-elf-
4+
# Bootbin: FSBL_ELF=/path/to/fsbl.elf make bootbin
5+
#
6+
# Toolchain: ARM GNU aarch64-none-elf-gcc (tested with 14.3.rel1).
7+
8+
CROSS_COMPILE ?= aarch64-none-elf-
9+
CC := $(CROSS_COMPILE)gcc
10+
OBJCOPY := $(CROSS_COMPILE)objcopy
11+
SIZE := $(CROSS_COMPILE)size
12+
13+
ROOT := ../../..
14+
15+
# Cortex-A53, AArch64, EL3 single-EL bare-metal. No SIMD/FP in the
16+
# wolfIP/driver paths - keep -mgeneral-regs-only to catch any
17+
# accidental FP use and make the ABI deterministic for cert.
18+
CFLAGS := -mcpu=cortex-a53 -mgeneral-regs-only
19+
CFLAGS += -Os -ffreestanding -fno-builtin -fno-common
20+
CFLAGS += -fdata-sections -ffunction-sections
21+
CFLAGS += -g -Wall -Wextra -Werror -Wno-unused-parameter
22+
CFLAGS += -std=gnu99
23+
CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src -I$(ROOT)/src/port
24+
CFLAGS += -DZCU102 -DXILINX_AARCH64
25+
26+
ASFLAGS := -mcpu=cortex-a53
27+
28+
LDSCRIPT := target.ld
29+
LDFLAGS := -nostdlib -nostartfiles -T $(LDSCRIPT) -Wl,-gc-sections
30+
# Replace newlib's aarch64 memset/memcpy (which use 'dc zva' and hang
31+
# on this Cortex-A53 setup) with our bytewise versions in main.c.
32+
LDFLAGS += -Wl,--wrap=memset -Wl,--wrap=memcpy
33+
34+
LOCAL_C := main.c uart.c mmu.c gic.c gem.c phy_dp83867.c
35+
LOCAL_S := startup.S
36+
LOCAL_OBJS := $(LOCAL_C:.c=.o) $(LOCAL_S:.S=.o)
37+
38+
# Compile wolfIP core into our directory (don't reuse the upstream .o,
39+
# which may have been built for a different ABI).
40+
WOLFIP_OBJ := wolfip.o
41+
OBJS := $(LOCAL_OBJS) $(WOLFIP_OBJ)
42+
43+
all: app.elf
44+
@echo "Built: app.elf"
45+
@$(SIZE) app.elf
46+
47+
app.elf: $(OBJS) $(LDSCRIPT)
48+
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) \
49+
-Wl,--start-group -lc -lgcc -Wl,--end-group -o $@
50+
51+
# wolfIP core: -Wno-zero-length-bounds is needed when MAX_TCPSOCKETS=0
52+
# because MAX_TIMERS expands to 0 and the timer heap declares a
53+
# zero-length array. The actual [0] accesses are runtime-guarded by
54+
# heap->size > 0, so the warning is a false positive in this profile.
55+
$(WOLFIP_OBJ): $(ROOT)/src/wolfip.c
56+
$(CC) $(CFLAGS) -Wno-zero-length-bounds -Wno-type-limits -c $< -o $@
57+
58+
%.o: %.c
59+
$(CC) $(CFLAGS) -c $< -o $@
60+
61+
%.o: %.S
62+
$(CC) $(ASFLAGS) -c $< -o $@
63+
64+
# Build a bootable BOOT.BIN. Requires FSBL_ELF env var pointing to a
65+
# pre-built ZCU102 FSBL (built in Vitis or PetaLinux). bootgen itself
66+
# is part of Vitis or available standalone.
67+
bootbin: app.elf
68+
@if [ -z "$$FSBL_ELF" ]; then \
69+
echo "ERROR: FSBL_ELF must point to a prebuilt ZCU102 FSBL ELF."; \
70+
exit 1; \
71+
fi
72+
FSBL_ELF=$$FSBL_ELF APP_ELF=$$PWD/app.elf bootgen/build_bootbin.sh
73+
74+
clean:
75+
rm -f $(OBJS) app.elf BOOT.BIN
76+
77+
.PHONY: all clean bootbin help
78+
79+
help:
80+
@echo "ZCU102 wolfIP build:"
81+
@echo " make - build app.elf"
82+
@echo " FSBL_ELF=... make bootbin - build BOOT.BIN"
83+
@echo " make clean - remove artifacts"
84+
@echo ""
85+
@echo "Override CROSS_COMPILE if your toolchain prefix differs."

src/port/zcu102/README.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# wolfIP port: Xilinx ZCU102 (UltraScale+ MPSoC)
2+
3+
Bare-metal wolfIP port for the AMD/Xilinx Zynq UltraScale+ MPSoC, demoed
4+
on the ZCU102 dev board. Targets a single Cortex-A53 core (APU 0) at
5+
EL3, GCC bare-metal, no Xilinx Standalone BSP, no FreeRTOS, no wolfBoot.
6+
7+
This first milestone is aimed at a deterministic UDP-only profile
8+
suitable for DO-178C DAL-C qualification. The application opens a
9+
UDP echo socket on port 7 and runs a DHCP client to acquire a lease.
10+
11+
## What this port covers
12+
13+
- PS-GEM3 (on-board RJ45) at 1 Gbps via the TI DP83867IR PHY (RGMII).
14+
- Polled RX + polled TX (GIC-400 SPI 63 latches correctly, but the
15+
Cortex-A53 IRQ exception is not delivered in our EL3 single-EL setup;
16+
`eth_poll()` drives `gem_isr()` directly from the main loop).
17+
- Clean-room Cadence GEM driver - no XEmacPs, no Xilinx Standalone BSP,
18+
no `xparameters.h`. All register base addresses live in `board.h`.
19+
- MMU at EL3 with a static page table: DDR Normal WB, a Normal-NC DMA
20+
carve-out for GEM BDs/buffers, peripherals Device-nGnRnE, and an
21+
OCM (0xFFFC0000+) Normal-WB executable block.
22+
- PS-UART0 polled console (USB-UART on the ZCU102 board, channel 0).
23+
- DHCP client and a UDP echo demo (port 7); ICMP echo reply works
24+
through the wolfIP core.
25+
26+
## What is explicitly NOT in this port yet
27+
28+
- Software VLAN (Daniele has a separate wolfIP-core PR in flight).
29+
- uC/OS-II socket port (planned follow-up; trivially adapts an existing
30+
`bsd_socket.c`).
31+
- Additional GEM instances (GEM0/1/2). Driver is single-instance.
32+
- Versal Gen 1, Zynq-7000.
33+
- wolfBoot integration. Stock Xilinx FSBL hands control directly to
34+
`app.elf`.
35+
- TLS / wolfSSL.
36+
37+
## Hardware
38+
39+
- AMD/Xilinx ZCU102 evaluation board (XCZU9EG-2FFVB1156). Rev 1.0 or
40+
1.1 are both fine.
41+
- USB-UART via the on-board FTDI FT4232 (host sees four `/dev/ttyUSB*`
42+
channels; UART0 is the standard one, typically `/dev/ttyUSB0` or the
43+
channel labelled "MIO" depending on board / udev).
44+
- Ethernet via the on-board RJ45 (PS-GEM3 -> DP83867 PHY @ MDIO 0x0C).
45+
46+
## Build
47+
48+
Toolchain: ARM GNU `aarch64-none-elf-gcc`. The default is on `$PATH`;
49+
override with `CROSS_COMPILE=...-` if needed.
50+
51+
```
52+
cd src/port/zcu102
53+
make CROSS_COMPILE=aarch64-none-elf-
54+
```
55+
56+
Output: `app.elf`. Section sizes are printed at the end of the build.
57+
58+
## Build BOOT.BIN
59+
60+
You need a pre-built ZCU102 FSBL ELF. The simplest way to obtain one
61+
is the Vitis "zynqmp_fsbl" template (single-click build), or PetaLinux
62+
`petalinux-build -c bootloader`. We deliberately do NOT vendor FSBL
63+
sources here; FSBL is a Xilinx-provided component and stock works.
64+
65+
Source Vitis first (so `bootgen` is on `$PATH`), then:
66+
67+
```
68+
FSBL_ELF=/path/to/zynqmp_fsbl.elf make bootbin
69+
```
70+
71+
Output: `BOOT.BIN` in the port directory.
72+
73+
## Boot
74+
75+
### SD card boot
76+
77+
1. Format a microSD as FAT32.
78+
2. Copy `BOOT.BIN` to the root of the SD card.
79+
3. Set ZCU102 boot mode DIP SW6 to SD (positions 1-4 = ON, OFF, OFF, OFF).
80+
4. Insert the card and power-cycle the board.
81+
82+
### JTAG boot (Vitis xsct)
83+
84+
```
85+
xsct
86+
% connect
87+
% targets -set -filter {name =~ "PSU"}
88+
% rst -system
89+
% loadhw -hw /path/to/your-design.xsa
90+
% targets -set -filter {name =~ "Cortex-A53 #0"}
91+
% dow /path/to/wolfip/src/port/zcu102/app.elf
92+
% con
93+
```
94+
95+
If you do not have an XSA from your own design, the stock ZCU102 base
96+
design from Vitis is fine - we only depend on the PS configuration
97+
(DDR controller, MIO pinmuxing, IOPLL clocks) which is identical
98+
across base designs.
99+
100+
### JTAG iteration (no SD swap)
101+
102+
This port ships a self-contained xsdb loader under `jtag/` that
103+
power-cycles the board (via remote Pi GPIO, optional), forces JTAG
104+
boot mode, runs `psu_init`, loads `app.elf` into OCM, and releases
105+
A53-0 at the OCM entry. The whole app + BSS + page tables + DMA
106+
buffers fit in the 256 KB OCM, so DDR-via-JTAG flakiness is avoided.
107+
108+
```
109+
./jtag/boot.sh # one-shot
110+
./jtag/boot_iter.sh # build + power-cycle + load loop
111+
```
112+
113+
See `jtag/boot.tcl` for the actual xsdb sequence.
114+
115+
## Expected UART output
116+
117+
```
118+
=== wolfIP ZCU102 (UltraScale+ A53-0 EL3) ===
119+
MMU on, caches on. Bringing up GIC-400...
120+
Initializing wolfIP stack...
121+
Bringing up GEM3 (RGMII, DP83867)...
122+
GEM3: PHY at MDIO addr=0x0000000C
123+
DP83867: ID1=0x00002000 ID2=0x0000A231
124+
DP83867 link: 1000 Mbps FD
125+
link UP, PHY=0x0000000C
126+
Starting DHCP client...
127+
DHCP bound:
128+
IP: 192.168.1.50
129+
Mask: 255.255.255.0
130+
GW: 192.168.1.1
131+
Opening UDP echo socket on port 7
132+
Ready. Try: nc -u <leased-ip> 7
133+
```
134+
135+
## Verification
136+
137+
From a host on the same subnet as the board:
138+
139+
```
140+
$ ping -c 3 192.168.1.50
141+
$ echo "hello wolfip" | nc -u -w1 192.168.1.50 7
142+
hello wolfip
143+
```
144+
145+
UART capture via the `uart-monitor` skill (add a board entry pointing
146+
at `/dev/ttyUSB0` and 115200 8N1).
147+
148+
## Files
149+
150+
| File | Purpose |
151+
|---------------------|---------|
152+
| `Makefile` | Build app.elf and BOOT.BIN |
153+
| `target.ld` | aarch64 EL3 linker script - separate RX/RW segments, 2 MB DMA region |
154+
| `startup.S` | EL3 vectors, BSS clear, MMU/main bring-up, IRQ trampoline |
155+
| `board.h` | PS register base addresses, GIC SPI IDs |
156+
| `mmu.c` / `.h` | EL3 page tables (T0SZ=32, 1 GB L1 + 2 MB L2 for DDR + DMA carve-out) |
157+
| `gic.c` / `.h` | GIC-400 (GICv2) minimal driver |
158+
| `uart.c` / `.h` | PS-UART0 polled console |
159+
| `gem.c` / `.h` | Cadence GEM driver (PS-GEM3): BDs, polled-RX/TX, MDIO, cache maintenance |
160+
| `phy_dp83867.c` / `.h` | TI DP83867IR init + RGMII skew + AN + RX_CTRL strap quirk |
161+
| `main.c` | wolfIP init, DHCP client, UDP echo on port 7, memset/memcpy wrappers |
162+
| `config.h` | wolfIP build profile (UDP-only intent) |
163+
| `bootgen/boot.bif` | bootgen template (substitutes `${FSBL_ELF}` and `${APP_ELF}`) |
164+
| `bootgen/build_bootbin.sh` | renders the bif and invokes bootgen |
165+
| `jtag/boot.sh` / `.tcl` | xsdb loader for OCM-only JTAG iteration |
166+
167+
## Notes for cert / DAL-C
168+
169+
- No Xilinx Standalone BSP linked in. `aarch64-none-elf-gcc` newlib
170+
provides `memcpy`/`memset` only.
171+
- No dynamic allocation. All buffers static in BSS or `.dma_buffers`.
172+
- No floating point (`-mgeneral-regs-only`).
173+
- The MAC address is hard-coded in `board.h`. Replace with a
174+
per-board value (e.g., read from EEPROM or PS_VERSION fuses) for
175+
production; we keep static for repeatability in the lab.
176+
- The wolfIP core currently sizes its timer heap as
177+
`MAX_TIMERS = MAX_TCPSOCKETS * 3`. This port sets `MAX_TCPSOCKETS=2`
178+
in `config.h` so DHCP / ARP can schedule timers; the application
179+
does not open any TCP sockets. A core wolfIP follow-up should
180+
decouple the timer count from TCP so the TCP code can be fully
181+
excluded from a DAL-C build.
182+
- The wolfIP core triggers two false-positive GCC warnings
183+
(`-Wzero-length-bounds`, `-Wtype-limits`) when `MAX_TCPSOCKETS`
184+
reaches its lower bound. We suppress them on the wolfip.c compile
185+
only; the diagnostics on this port's source remain at `-Wall -Wextra
186+
-Werror`.
187+
- newlib's aarch64 `memset`/`memcpy` use `dc zva`, which hangs on this
188+
Cortex-A53 setup even with `SCTLR_EL3.DZE=1`. We override both with
189+
bytewise versions in `main.c` via `-Wl,--wrap`.
190+
191+
## Known issues
192+
193+
- The A53 IRQ exception is not delivered (GIC latches the SPI/SGI and
194+
`GICC_IAR` ack works when polled, but the IRQ vector never fires).
195+
Worked around by polling `gem_isr()` from `eth_poll()` in the main
196+
loop. Real root cause is open.
197+
- `MAX_TCPSOCKETS=2` is the minimum for the current wolfIP core - see
198+
the timer-heap note above.

src/port/zcu102/board.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/* board.h
2+
*
3+
* Copyright (C) 2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfIP TCP/IP stack.
6+
*
7+
* wolfIP is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfIP is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*
21+
* Xilinx UltraScale+ MPSoC PS register base addresses, GIC SPI IDs,
22+
* and clock parents for the ZCU102 board. All values are derived from
23+
* the ZynqMP TRM (UG1085) and the ZCU102 board user guide (UG1182).
24+
* No Xilinx BSP header (xparameters.h) is required.
25+
*/
26+
#ifndef ZCU102_BOARD_H
27+
#define ZCU102_BOARD_H
28+
29+
#include <stdint.h>
30+
31+
/* ---------------------------------------------------------------------
32+
* Memory map (ZynqMP PS)
33+
* ------------------------------------------------------------------- */
34+
#define DDR_BASE 0x00000000UL
35+
#define DDR_SIZE 0x80000000UL /* 2 GB lower bank */
36+
37+
#define OCM_BASE 0xFFFC0000UL
38+
#define OCM_SIZE 0x00040000UL /* 256 KB */
39+
40+
/* ---------------------------------------------------------------------
41+
* PS peripherals
42+
* ------------------------------------------------------------------- */
43+
#define UART0_BASE 0xFF000000UL
44+
#define UART1_BASE 0xFF010000UL
45+
46+
#define GEM0_BASE 0xFF0B0000UL
47+
#define GEM1_BASE 0xFF0C0000UL
48+
#define GEM2_BASE 0xFF0D0000UL
49+
#define GEM3_BASE 0xFF0E0000UL
50+
51+
#define CRL_APB_BASE 0xFF5E0000UL
52+
#define IOU_SLCR_BASE 0xFF180000UL
53+
54+
/* GIC-400 distributor and CPU interface (per ZynqMP TRM). */
55+
#define GICD_BASE 0xF9010000UL
56+
#define GICC_BASE 0xF9020000UL
57+
58+
/* ---------------------------------------------------------------------
59+
* GIC SPI numbers (ZynqMP TRM Table 13-1)
60+
* ------------------------------------------------------------------- */
61+
#define IRQ_GEM0 57 /* SPI 57 */
62+
#define IRQ_GEM1 59 /* SPI 59 */
63+
#define IRQ_GEM2 61 /* SPI 61 */
64+
#define IRQ_GEM3 63 /* SPI 63 - on-board ZCU102 RJ45 */
65+
66+
/* ---------------------------------------------------------------------
67+
* CRL_APB clock and reset registers
68+
* ------------------------------------------------------------------- */
69+
#define CRL_APB_GEM3_REF_CTRL (CRL_APB_BASE + 0x5C)
70+
#define CRL_APB_RST_LPD_IOU0 (CRL_APB_BASE + 0x230) /* GEM3 reset bit 3 */
71+
72+
/* ---------------------------------------------------------------------
73+
* PS UART0 (Cadence) - on-board USB-UART on ZCU102 via U104 FT4232
74+
* ------------------------------------------------------------------- */
75+
#define UART_BAUD 115200
76+
77+
/* Default A53-0 EL3 stack location (set in startup.S, mirrored here for
78+
* any C code that needs to know). */
79+
#define A53_STACK_TOP 0x00100000UL /* 1 MB - 8 B */
80+
81+
/* MAC address for eth0. Locally-administered, even first octet:
82+
* 02:00:5A:11:22:33. Customer can replace via build-time -D. */
83+
#ifndef WOLFIP_MAC_ADDR
84+
#define WOLFIP_MAC_0 0x02
85+
#define WOLFIP_MAC_1 0x00
86+
#define WOLFIP_MAC_2 0x5A
87+
#define WOLFIP_MAC_3 0x11
88+
#define WOLFIP_MAC_4 0x22
89+
#define WOLFIP_MAC_5 0x33
90+
#endif
91+
92+
#endif /* ZCU102_BOARD_H */

src/port/zcu102/bootgen/boot.bif

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// ZCU102 BOOT.BIN definition for wolfIP bare-metal app.
2+
//
3+
// Variables expanded by build_bootbin.sh:
4+
// ${FSBL_ELF} - path to a pre-built ZynqMP FSBL (A53-0, EL3, NS)
5+
// ${APP_ELF} - path to the wolfIP app ELF (this directory's app.elf)
6+
//
7+
// bootgen consumes this file with:
8+
// bootgen -arch zynqmp -image boot.bif -w on -o BOOT.BIN
9+
10+
the_ROM_image:
11+
{
12+
[bootloader, destination_cpu=a53-0] ${FSBL_ELF}
13+
[destination_cpu=a53-0] ${APP_ELF}
14+
}

0 commit comments

Comments
 (0)