Skip to content

Commit 1656f93

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

28 files changed

Lines changed: 3801 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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
# Append extra defines for investigation builds, e.g.:
26+
# make CFLAGS_EXTRA="-DDEBUG_GIC -DDEBUG_GEM -DDEBUG_PHY"
27+
CFLAGS += $(CFLAGS_EXTRA)
28+
29+
ASFLAGS := -mcpu=cortex-a53
30+
31+
LDSCRIPT := target.ld
32+
LDFLAGS := -nostdlib -nostartfiles -T $(LDSCRIPT) -Wl,-gc-sections
33+
# Replace newlib's aarch64 memset/memcpy (which use 'dc zva' and hang
34+
# on this Cortex-A53 setup) with our bytewise versions in main.c.
35+
LDFLAGS += -Wl,--wrap=memset -Wl,--wrap=memcpy
36+
37+
LOCAL_C := main.c uart.c mmu.c gic.c gem.c phy_dp83867.c entropy.c
38+
LOCAL_S := startup.S
39+
LOCAL_OBJS := $(LOCAL_C:.c=.o) $(LOCAL_S:.S=.o)
40+
41+
# Compile wolfIP core into our directory (don't reuse the upstream .o,
42+
# which may have been built for a different ABI).
43+
WOLFIP_OBJ := wolfip.o
44+
OBJS := $(LOCAL_OBJS) $(WOLFIP_OBJ)
45+
46+
all: app.elf
47+
@echo "Built: app.elf"
48+
@$(SIZE) app.elf
49+
50+
app.elf: $(OBJS) $(LDSCRIPT)
51+
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) \
52+
-Wl,--start-group -lc -lgcc -Wl,--end-group -o $@
53+
54+
# wolfIP core: -Wno-zero-length-bounds is needed because wolfIP sizes
55+
# its timer heap as MAX_TIMERS = MAX_TCPSOCKETS * 3. With our minimum
56+
# of MAX_TCPSOCKETS=2 (forced by DHCP/ARP timer scheduling, see
57+
# README), the heap is 6 entries which is fine. The warning fires
58+
# anyway on the zero-length-array code path that wolfIP includes for
59+
# the MAX_TCPSOCKETS=0 profile we'd actually like to use; the [0]
60+
# accesses are runtime-guarded by heap->size > 0 so this is a false
61+
# positive. Drop the suppression once core decouples the timer count
62+
# from MAX_TCPSOCKETS.
63+
$(WOLFIP_OBJ): $(ROOT)/src/wolfip.c
64+
$(CC) $(CFLAGS) -Wno-zero-length-bounds -Wno-type-limits -c $< -o $@
65+
66+
%.o: %.c
67+
$(CC) $(CFLAGS) -c $< -o $@
68+
69+
%.o: %.S
70+
$(CC) $(ASFLAGS) -c $< -o $@
71+
72+
# Build a bootable BOOT.BIN. Requires FSBL_ELF env var pointing to a
73+
# pre-built ZCU102 FSBL (built in Vitis or PetaLinux). bootgen itself
74+
# is part of Vitis or available standalone.
75+
bootbin: app.elf
76+
@if [ -z "$$FSBL_ELF" ]; then \
77+
echo "ERROR: FSBL_ELF must point to a prebuilt ZCU102 FSBL ELF."; \
78+
exit 1; \
79+
fi
80+
FSBL_ELF=$$FSBL_ELF APP_ELF=$$PWD/app.elf bootgen/build_bootbin.sh
81+
82+
clean:
83+
rm -f $(OBJS) app.elf BOOT.BIN
84+
85+
.PHONY: all clean bootbin help
86+
87+
help:
88+
@echo "ZCU102 wolfIP build:"
89+
@echo " make - build app.elf"
90+
@echo " FSBL_ELF=... make bootbin - build BOOT.BIN"
91+
@echo " make clean - remove artifacts"
92+
@echo ""
93+
@echo "Override CROSS_COMPILE if your toolchain prefix differs."

src/port/zcu102/README.md

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

0 commit comments

Comments
 (0)