Skip to content

Commit 1102b81

Browse files
committed
zynq7000: bare-metal wolfIP port for Zynq-7000 (Cortex-A9, ARMv7-A)
ZC702 bring-up: A9 in SVC mode, GIC-390, short-descriptor MMU, MPCore Global Timer (the A9 has no ARMv7 generic timer), UART1 console, Cadence GEM0 with a Marvell 88E1518 PHY driver (the ZC702 PHY, not DP83867), GEM clock/RGMII RX-clock via SLCR, poll-driven RX with the GEM IRQ masked, and non-cacheable OCM for DMA-coherent descriptors. FSBL-based xsdb JTAG loader. HW-tested on a ZC702: DHCP + ping + UDP echo.
1 parent 10c4ecd commit 1102b81

24 files changed

Lines changed: 3140 additions & 0 deletions

src/port/zynq7000/.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/zynq7000/Makefile

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Xilinx Zynq-7000 (Cortex-A9, ARMv7-A 32-bit) wolfIP bare-metal port
2+
#
3+
# Build: make CROSS_COMPILE=arm-none-eabi-
4+
#
5+
# Toolchain: ARM GNU arm-none-eabi-gcc (tested with 13.2).
6+
#
7+
# UNTESTED ON HARDWARE -- structural scaffold mirroring src/port/zcu102/.
8+
9+
CROSS_COMPILE ?= arm-none-eabi-
10+
CC := $(CROSS_COMPILE)gcc
11+
OBJCOPY := $(CROSS_COMPILE)objcopy
12+
SIZE := $(CROSS_COMPILE)size
13+
14+
ROOT := ../../..
15+
16+
# Cortex-A9, ARMv7-A 32-bit, no NEON in cert paths.
17+
CFLAGS := -mcpu=cortex-a9 -marm
18+
CFLAGS += -Os -ffreestanding -fno-builtin -fno-common
19+
CFLAGS += -fdata-sections -ffunction-sections
20+
CFLAGS += -g -Wall -Wextra -Werror -Wno-unused-parameter
21+
CFLAGS += -std=gnu99
22+
CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src -I$(ROOT)/src/port
23+
CFLAGS += -DZYNQ7000 -DXILINX_ARMV7
24+
CFLAGS += $(CFLAGS_EXTRA)
25+
26+
ASFLAGS := -mcpu=cortex-a9 -marm
27+
28+
LDSCRIPT := target.ld
29+
LDFLAGS := -nostdlib -nostartfiles -T $(LDSCRIPT) -Wl,-gc-sections
30+
# Override newlib's memset/memcpy with bytewise variants in main.c
31+
# (the same "fast memset uses an instruction the bare-metal setup
32+
# does not tolerate" pattern we hit on the AArch64 port).
33+
LDFLAGS += -Wl,--wrap=memset -Wl,--wrap=memcpy
34+
35+
LOCAL_C := main.c uart.c mmu.c gic.c gem.c phy_dp83867.c phy_marvell.c entropy.c
36+
LOCAL_S := startup.S
37+
LOCAL_OBJS := $(LOCAL_C:.c=.o) $(LOCAL_S:.S=.o)
38+
39+
WOLFIP_OBJ := wolfip.o
40+
OBJS := $(LOCAL_OBJS) $(WOLFIP_OBJ)
41+
42+
all: app.elf
43+
@echo "Built: app.elf"
44+
@$(SIZE) app.elf
45+
46+
app.elf: $(OBJS) $(LDSCRIPT)
47+
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) \
48+
-Wl,--start-group -lc -lgcc -Wl,--end-group -o $@
49+
50+
$(WOLFIP_OBJ): $(ROOT)/src/wolfip.c
51+
$(CC) $(CFLAGS) -Wno-zero-length-bounds -Wno-type-limits -c $< -o $@
52+
53+
%.o: %.c
54+
$(CC) $(CFLAGS) -c $< -o $@
55+
56+
%.o: %.S
57+
$(CC) $(ASFLAGS) -c $< -o $@
58+
59+
clean:
60+
rm -f $(OBJS) app.elf BOOT.BIN
61+
62+
.PHONY: all clean help
63+
64+
help:
65+
@echo "Zynq-7000 wolfIP build (scaffold, untested):"
66+
@echo " make - build app.elf"
67+
@echo " make clean - remove artifacts"
68+
@echo ""
69+
@echo "Override CROSS_COMPILE if your toolchain prefix differs."

src/port/zynq7000/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# wolfIP port: Xilinx Zynq-7000 (Cortex-A9, ARMv7-A 32-bit)
2+
3+
**STATUS: brought up on a ZC702.** DHCP, ICMP ping and the UDP echo demo all work on real hardware (Cortex-A9, Marvell 88E1518 PHY). See "Hardware bring-up notes" below for the Zynq-7000-specific differences that mattered.
4+
5+
## What this port is
6+
7+
Bare-metal wolfIP port for the Xilinx Zynq-7000 family (Z-7020 etc., e.g. ZC702 / ZedBoard / MicroZed dev boards). Cortex-A9 in SVC mode, GCC bare-metal, no Xilinx Standalone BSP, no FreeRTOS. Targets the same deterministic UDP/IPv4 profile as the ZCU102 port.
8+
9+
## What differs from ZCU102
10+
11+
| Subsystem | ZCU102 (ZynqMP) | Zynq-7000 | Where it lives |
12+
|-----------|-----------------|-----------|----------------|
13+
| Architecture | ARMv8-A AArch64 | ARMv7-A 32-bit | toolchain prefix |
14+
| CPU core | Cortex-A53 | Cortex-A9 | `Makefile` (-mcpu) |
15+
| Bootloader handoff | FSBL -> EL3 | FSBL -> SVC | `startup.S` |
16+
| Toolchain | `aarch64-none-elf-gcc` | `arm-none-eabi-gcc` | `Makefile` |
17+
| Exception model | EL3 vectors | ARMv7 exception modes | `startup.S` rewritten |
18+
| MMU | 4-level long descriptor | 1-level short descriptor | `mmu.c` rewritten |
19+
| Cache ops | DC CVAC / DC IVAC | MCR p15 c7 (DCCMVAC/DCIMVAC) | `gem.c` |
20+
| Generic timer | `mrs cntpct_el0` | `mrrc p15, 0, ..., c14` | `timer.h`, `entropy.c` |
21+
| GIC | GIC-400 (GICv2) | GIC-390 (GICv2) | `gic.c` (same driver, different base) |
22+
| GIC base addrs | `0xF901xxxx` | `0xF8F0xxxx` | `board.h` |
23+
| UART | Cadence at 0xFF000000 | Cadence at 0xE0000000 | `board.h` (same driver) |
24+
| Clock + reset | CRL_APB at 0xFF5E0000 | SLCR at 0xF8000000 | `board.h` (gem.c clock helper needs rewrite) |
25+
| GEM count | 4 (GEM0-3) | 2 (GEM0-1) | `board.h` |
26+
| On-board RJ45 | GEM3 (INTID 95) | GEM0 (INTID 54) | `board.h` |
27+
| BD format | 8-byte (DMACR[30]=0) | 8-byte (no 64-bit option) | `gem.c` (unchanged) |
28+
29+
## Build
30+
31+
```
32+
cd src/port/zynq7000
33+
make CROSS_COMPILE=arm-none-eabi-
34+
```
35+
36+
Output: `app.elf`.
37+
38+
## JTAG boot (ZC702)
39+
40+
The ZC702 boots its onboard JTAG over the Digilent USB module; set SW10
41+
to the on-board (USB) JTAG position and SW16 to JTAG boot mode, then:
42+
43+
```
44+
XSDB=/opt/Xilinx/<ver>/Vitis/bin/xsdb \
45+
FSBL_ELF=/path/to/zynq_fsbl.elf \
46+
./jtag/boot.sh
47+
```
48+
49+
`jtag/boot.tcl` runs the prebuilt FSBL (ps7_init brings up DDR/MIO/clocks/
50+
UART), remaps all four OCM banks high (`SLCR.OCM_CFG`) so the app can load
51+
at `0xFFFC0000`, then loads `app.elf` and starts it in SVC mode. The
52+
console is on **UART1** (the ZC702 USB-UART), not UART0. After a run the
53+
A9 must be power-cycled to be JTAG-loadable again.
54+
55+
## Hardware bring-up notes (what was Zynq-7000-specific)
56+
57+
These are the things that differed from the AArch64 ports and had to be
58+
fixed for the ZC702 to reach DHCP/ping/echo:
59+
60+
- **No ARM generic timer.** The Cortex-A9 does not implement CNTPCT/CNTFRQ
61+
(CP15 c14); those encodings are UNDEFINED and trap. `timer.h` and
62+
`entropy.c` use the MPCore **Global Timer** at `0xF8F00200` (333 MHz)
63+
instead.
64+
- **Console is UART1.** The ZC702 routes the USB console to Cadence UART1
65+
(`0xE0001000`); `uart.c` trusts the FSBL's baud config rather than
66+
reprogramming the divisor (the UART_REF_CLK is not the ZynqMP value).
67+
- **Marvell 88E1518 PHY, not DP83867.** The ZC702 fits a Marvell PHY
68+
(OUI `0x0141`) at MDIO addr 7. `phy_marvell.c` handles its paged RGMII
69+
delay registers + autoneg; `gem.c` dispatches on the PHY ID.
70+
- **GEM clock via SLCR, write-protected.** `SLCR.GEM0_CLK_CTRL`
71+
(`0xF8000140`) has a different layout than ZynqMP's CRL_APB and is
72+
write-locked. `gem3_set_ref_clk` unlocks the SLCR (`0xDF0D`) and writes
73+
`0x00100801` for 125 MHz (1 Gbps). `SLCR.GEM0_RCLK_CTRL` (`0xF8000138`)
74+
must also be set to source the RGMII RX clock from the PHY, or the MAC
75+
receives nothing (matches Xilinx ps7_init).
76+
- **Poll-driven RX, GEM IRQ masked.** Unlike the Versal GICv3, the A9 GIC
77+
delivers the GEM SPI, and an enabled RX-complete interrupt storms the
78+
CPU. RX is polled from `eth_poll` and the GEM interrupt is left masked.
79+
- **Non-cacheable OCM for DMA.** The 8-byte GEM descriptors share 32-byte
80+
cache lines, so per-descriptor cache maintenance corrupts neighbours'
81+
OWN bits and stalls RX. The OCM section is mapped Normal non-cacheable
82+
(`mmu.c`) so the descriptor rings and buffers are DMA-coherent. (The
83+
PL310 L2 is also disabled as a belt-and-braces measure.) Note the A9
84+
L1 cache line is 32 bytes, not the 64 of the AArch64 cores.
85+
- `NWCFG_DWIDTH_64` (NWCFG bit 21) is never set: the A9 GEM AXI master
86+
path is 32-bit and the BDs stay 8 bytes.
87+
88+
## Files
89+
90+
Same layout as `src/port/zcu102/`, plus `phy_marvell.c` / `phy_marvell.h`
91+
(the ZC702 PHY) and `jtag/` (FSBL-based JTAG loader). See the ZCU102
92+
README for the shared per-file responsibilities.

src/port/zynq7000/board.h

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 Zynq-7000 (Cortex-A9, ARMv7-A 32-bit) PS register base
22+
* addresses and GIC interrupt IDs. Derived from the Zynq-7000 TRM
23+
* (UG585). No Xilinx Standalone BSP header is required.
24+
*
25+
* UNTESTED ON HARDWARE -- code-only scaffold while the lab board is
26+
* unavailable. Mirrors src/port/zcu102/ structurally. Key differences:
27+
* - Cortex-A9 (not A53), ARMv7-A 32-bit (not AArch64)
28+
* - SLCR replaces ZynqMP's CRL_APB
29+
* - GIC-390 (GICv2) inside the SCU at different base addresses
30+
* - Cadence UART (same IP as ZynqMP; different base address)
31+
* - Cadence GEM (older revision; 32-bit BD format default)
32+
* - 2 GEMs (GEM0 / GEM1); on-board RJ45 is typically GEM0
33+
*/
34+
#ifndef ZYNQ7000_BOARD_H
35+
#define ZYNQ7000_BOARD_H
36+
37+
#include <stdint.h>
38+
39+
/* ---------------------------------------------------------------------
40+
* Memory map (Zynq-7000 PS)
41+
* ------------------------------------------------------------------- */
42+
#define DDR_BASE 0x00000000UL
43+
#define DDR_SIZE 0x40000000UL /* 1 GB typical, e.g. ZC702 */
44+
45+
/* OCM is mappable to 0x00000000 (low) or 0xFFFC0000 (high). Most
46+
* bare-metal apps use the high mapping; FSBL configures the OCM
47+
* address filter via SLCR.OCM_CFG. We assume the high mapping. */
48+
#define OCM_BASE 0xFFFC0000UL
49+
#define OCM_SIZE 0x00040000UL /* 256 KB */
50+
51+
/* ---------------------------------------------------------------------
52+
* PS peripherals
53+
* ------------------------------------------------------------------- */
54+
#define UART0_BASE 0xE0000000UL /* Cadence */
55+
#define UART1_BASE 0xE0001000UL
56+
57+
#define GEM0_BASE 0xE000B000UL /* on-board RJ45 typical */
58+
#define GEM1_BASE 0xE000C000UL
59+
60+
#define SLCR_BASE 0xF8000000UL /* clock + reset */
61+
62+
/* GIC-390 (ARMv7 GICv2 compatible). Distributor + CPU IF are in the
63+
* SCU (Snoop Control Unit) memory region on Zynq-7000. */
64+
#define GICD_BASE 0xF8F01000UL
65+
#define GICC_BASE 0xF8F00100UL
66+
67+
/* ---------------------------------------------------------------------
68+
* GIC interrupt IDs (raw GIC INTIDs, not GIC_SPI offsets).
69+
* Per Zynq-7000 TRM Table 7-3:
70+
* GEM0: INTID 54
71+
* GEM1: INTID 77
72+
* ------------------------------------------------------------------- */
73+
#define IRQ_GEM0 54
74+
#define IRQ_GEM1 77
75+
76+
/* ---------------------------------------------------------------------
77+
* SLCR clock and reset registers
78+
* ------------------------------------------------------------------- */
79+
#define SLCR_LOCK (SLCR_BASE + 0x004)
80+
#define SLCR_UNLOCK (SLCR_BASE + 0x008)
81+
#define SLCR_GEM0_CLK_CTRL (SLCR_BASE + 0x140)
82+
#define SLCR_GEM0_RCLK_CTRL (SLCR_BASE + 0x138) /* RGMII RX clock src */
83+
#define SLCR_GEM1_CLK_CTRL (SLCR_BASE + 0x144)
84+
#define SLCR_GEM_RST_CTRL (SLCR_BASE + 0x214)
85+
86+
#define SLCR_UNLOCK_KEY 0xDF0D /* per TRM */
87+
88+
/* ---------------------------------------------------------------------
89+
* Cadence UART0 baud
90+
* ------------------------------------------------------------------- */
91+
#define UART_BAUD 115200
92+
93+
/* MAC address for eth0. Locally-administered, even first octet. */
94+
#ifndef WOLFIP_MAC_0
95+
#define WOLFIP_MAC_0 0x02
96+
#endif
97+
#ifndef WOLFIP_MAC_1
98+
#define WOLFIP_MAC_1 0x00
99+
#endif
100+
#ifndef WOLFIP_MAC_2
101+
#define WOLFIP_MAC_2 0x5A
102+
#endif
103+
#ifndef WOLFIP_MAC_3
104+
#define WOLFIP_MAC_3 0x11
105+
#endif
106+
#ifndef WOLFIP_MAC_4
107+
#define WOLFIP_MAC_4 0x22
108+
#endif
109+
#ifndef WOLFIP_MAC_5
110+
#define WOLFIP_MAC_5 0x33
111+
#endif
112+
113+
#endif /* ZYNQ7000_BOARD_H */

src/port/zynq7000/config.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/* config.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+
* wolfIP configuration for Xilinx ZCU102 (UltraScale+ MPSoC, A53-0 EL3
22+
* bare-metal). UDP-only profile aimed at deterministic DAL-C use.
23+
*/
24+
#ifndef WOLF_CONFIG_H
25+
#define WOLF_CONFIG_H
26+
27+
#ifndef CONFIG_IPFILTER
28+
#define CONFIG_IPFILTER 0
29+
#endif
30+
31+
#define ETHERNET
32+
#define LINK_MTU 1536
33+
34+
/* UDP-only profile in intent: the application does not call
35+
* wolfIP_sock_socket() with IPSTACK_SOCK_STREAM. MAX_TCPSOCKETS is set
36+
* to a small non-zero value only because core wolfIP currently sizes
37+
* its timer heap via MAX_TIMERS = MAX_TCPSOCKETS * 3, and DHCP / ARP
38+
* aging need timers. With MAX_TCPSOCKETS=0 the timer-heap insert path
39+
* is permanently full and DHCP cannot schedule its retransmit timer.
40+
* A core wolfIP follow-up should decouple MAX_TIMERS from
41+
* MAX_TCPSOCKETS so DAL-C builds can truly opt TCP code out at
42+
* compile time. */
43+
#define MAX_TCPSOCKETS 2
44+
#define MAX_UDPSOCKETS 4
45+
#define MAX_ICMPSOCKETS 1
46+
#define RXBUF_SIZE (LINK_MTU * 4)
47+
#define TXBUF_SIZE (LINK_MTU * 4)
48+
49+
#define MAX_NEIGHBORS 16
50+
51+
#ifndef WOLFIP_MAX_INTERFACES
52+
#define WOLFIP_MAX_INTERFACES 1
53+
#endif
54+
55+
#ifndef WOLFIP_ENABLE_FORWARDING
56+
#define WOLFIP_ENABLE_FORWARDING 0
57+
#endif
58+
59+
#ifndef WOLFIP_ENABLE_LOOPBACK
60+
#define WOLFIP_ENABLE_LOOPBACK 0
61+
#endif
62+
63+
#ifndef WOLFIP_ENABLE_DHCP
64+
#define WOLFIP_ENABLE_DHCP 1
65+
#endif
66+
67+
/* Static IP fallback (used if DHCP is disabled or times out). */
68+
#define WOLFIP_IP "192.168.1.100"
69+
#define WOLFIP_NETMASK "255.255.255.0"
70+
#define WOLFIP_GW "192.168.1.1"
71+
#define WOLFIP_STATIC_DNS_IP "8.8.8.8"
72+
73+
#if WOLFIP_ENABLE_DHCP
74+
#define DHCP
75+
#define DHCP_DISCOVER_RETRIES 2
76+
#define DHCP_REQUEST_RETRIES 2
77+
#endif
78+
79+
/* Hardware debug: define for verbose GEM / MDIO / DHCP logging. */
80+
/* #define DEBUG_HW */
81+
82+
#endif /* WOLF_CONFIG_H */

0 commit comments

Comments
 (0)