Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/port/versal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.o
*.elf
*.bin
BOOT.BIN
.layout_stamp
108 changes: 108 additions & 0 deletions src/port/versal/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Xilinx Versal Gen 1 (VMK180, Cortex-A72) wolfIP bare-metal port
#
# Build: make CROSS_COMPILE=aarch64-none-elf-
#
# Toolchain: ARM GNU aarch64-none-elf-gcc (tested with 14.3.rel1).
#
# Brought up on a VMK180 (Cortex-A72): DHCP, ICMP ping and UDP echo work.

CROSS_COMPILE ?= aarch64-none-elf-
CC := $(CROSS_COMPILE)gcc
OBJCOPY := $(CROSS_COMPILE)objcopy
SIZE := $(CROSS_COMPILE)size

ROOT := ../../..

# Cortex-A72, AArch64, EL3 single-EL bare-metal. No SIMD/FP in the
# wolfIP/driver paths - keep -mgeneral-regs-only to catch any
# accidental FP use and make the ABI deterministic for cert.
CFLAGS := -mcpu=cortex-a72 -mgeneral-regs-only
CFLAGS += -Os -ffreestanding -fno-builtin -fno-common
CFLAGS += -fdata-sections -ffunction-sections
CFLAGS += -g -Wall -Wextra -Werror -Wno-unused-parameter
CFLAGS += -std=gnu99
CFLAGS += -I. -I$(ROOT) -I$(ROOT)/src -I$(ROOT)/src/port
CFLAGS += -DVERSAL -DXILINX_AARCH64
# Append extra defines for investigation builds, e.g.:
# make CFLAGS_EXTRA="-DDEBUG_GIC -DDEBUG_GEM -DDEBUG_PHY"
CFLAGS += $(CFLAGS_EXTRA)

ASFLAGS := -mcpu=cortex-a72

# Layout selector. Default ocm keeps the OCM-only layout that the JTAG
# iteration scripts depend on (everything in OCM @ 0xFFFC0000). Pass
# LAYOUT=ddr to relink for DDR @ 0x10000000 -- which is what wolfBoot
# uses (WOLFBOOT_LOAD_ADDRESS in zynqmp.config also applies to Versal
# when adapted).
LAYOUT ?= ocm
ifeq ($(LAYOUT),ddr)
LDSCRIPT := target_ddr.ld
CFLAGS += -DVERSAL_LAYOUT_DDR
else ifeq ($(LAYOUT),ocm)
LDSCRIPT := target.ld
CFLAGS += -DVERSAL_LAYOUT_OCM
else
$(error LAYOUT must be 'ocm' or 'ddr')
endif

LDFLAGS := -nostdlib -nostartfiles -T $(LDSCRIPT) -Wl,-gc-sections
# Replace newlib's aarch64 memset/memcpy (which use 'dc zva' and may
# hang on a similar Cortex-A72 setup; the safe pattern is to override
# them as we did on ZCU102).
LDFLAGS += -Wl,--wrap=memset -Wl,--wrap=memcpy

LOCAL_C := main.c uart.c mmu.c gic.c gem.c phy_dp83867.c entropy.c
LOCAL_S := startup.S
LOCAL_OBJS := $(LOCAL_C:.c=.o) $(LOCAL_S:.S=.o)

WOLFIP_OBJ := wolfip.o
OBJS := $(LOCAL_OBJS) $(WOLFIP_OBJ)

# Keep 'all' the default goal even though the layout-stamp rules below
# are defined before it.
.DEFAULT_GOAL := all

# A change in LAYOUT must force a full rebuild: OCM objects link against
# 0xFFFC0000 and a DDR build against 0x10000000, so reusing stale objects
# across a layout switch silently produces a wrong image. The stamp
# records the last LAYOUT; its mtime only bumps when LAYOUT actually
# changes, so same-layout incremental builds are unaffected.
LAYOUT_STAMP := .layout_stamp
.PHONY: FORCE
FORCE:
$(LAYOUT_STAMP): FORCE
@if [ "`cat $@ 2>/dev/null`" != "$(LAYOUT)" ]; then \
echo "LAYOUT -> $(LAYOUT) (was `cat $@ 2>/dev/null`); forcing rebuild"; \
echo "$(LAYOUT)" > $@; \
fi
$(OBJS): $(LAYOUT_STAMP)

all: app.elf
@echo "Built: app.elf"
@$(SIZE) app.elf

app.elf: $(OBJS) $(LDSCRIPT)
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) \
-Wl,--start-group -lc -lgcc -Wl,--end-group -o $@

$(WOLFIP_OBJ): $(ROOT)/src/wolfip.c
$(CC) $(CFLAGS) -Wno-zero-length-bounds -Wno-type-limits -c $< -o $@

%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@

%.o: %.S
$(CC) $(ASFLAGS) -c $< -o $@

clean:
rm -f $(OBJS) app.elf BOOT.BIN $(LAYOUT_STAMP)

.PHONY: all clean help

help:
@echo "Versal Gen 1 wolfIP build (scaffold, untested):"
@echo " make - build app.elf (OCM layout)"
@echo " make LAYOUT=ddr - DDR layout for wolfBoot"
@echo " make clean - remove artifacts"
@echo ""
@echo "Override CROSS_COMPILE if your toolchain prefix differs."
78 changes: 78 additions & 0 deletions src/port/versal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# wolfIP port: Xilinx Versal Gen 1 (VMK180)

**STATUS: brought up on a VMK180.** DHCP, ICMP ping and the UDP echo demo all work on real hardware (Cortex-A72 EL3, GEM0 + DP83867). See "Hardware bring-up notes" below for the Versal-specific differences.

Comment thread
dgarske marked this conversation as resolved.
## What this port is

Bare-metal wolfIP port for the AMD/Xilinx Versal ACAP Gen 1, demoed on the VMK180 dev board. Cortex-A72 APU 0 at EL3, GCC bare-metal, no Xilinx Standalone BSP, no FreeRTOS. Targets the same deterministic UDP/IPv4 profile as the ZCU102 port for DO-178C DAL-C qualification.

## What differs from ZCU102

| Subsystem | ZCU102 | Versal Gen 1 | Where it lives |
|-----------|--------|--------------|----------------|
| APU core | Cortex-A53 | Cortex-A72 | `Makefile` (-mcpu) |
| Bootloader handoff | FSBL -> EL3 | PLM -> BL31 -> EL3 (or EL2) | `startup.S` |
| GIC | GIC-400 (GICv2) | GIC-600 (GICv3) | `gic.c` rewritten for GICv3 system regs + GICR |
| UART | Cadence | ARM PL011 | `uart.c` rewritten |
| GEM count | 4 (GEM0-3) | 2 (GEM0-1) | `board.h` |
| On-board RJ45 | GEM3 (INTID 95) | GEM0 (INTID 88) | `board.h` |
| GEM IP | Cadence GEM3 | Cadence GEM3 | `gem.c` unchanged (just base addr / INTID) |
| PHY | DP83867 RGMII | DP83867 RGMII (VMK180) | `phy_dp83867.c` unchanged |
| MMU | EL3 ARMv8 | EL3 ARMv8 | `mmu.c` unchanged |
| RNG | memuse entropy | memuse entropy | `entropy.c` unchanged |

The reused 90% (`gem.c`, `phy_dp83867.c`, `mmu.c`, `entropy.c`, `main.c`, `target.ld`, `target_ddr.ld`) is identical to the ZCU102 port; only `board.h`, `uart.c`, `gic.c`, and the startup/Makefile breadcrumbs are Versal-specific.

## Build

```
cd src/port/versal
make CROSS_COMPILE=aarch64-none-elf- # OCM layout (default)
make CROSS_COMPILE=aarch64-none-elf- LAYOUT=ddr # DDR layout for wolfBoot
```

Output: `app.elf`. Size info is printed at the end of the build.

## JTAG boot (VMK180)

The VMK180 must be in **JTAG boot mode** (SW1 mode pins = 0000) and
power-cycled so the BootROM does not auto-boot Linux from SD/QSPI -- a
booted Linux owns GEM0 and runtime-suspends its clock, which stalls the
bare-metal driver. Then:

```
XSDB=/opt/Xilinx/<ver>/Vitis/bin/xsdb \
BOOT_PDI=/path/to/vmk180_boot.pdi \
./jtag/boot.sh
```

`jtag/boot.tcl` does `rst -system`, programs the boot PDI through the PMC
(the PLM brings up DDR/clocks/MIO and de-isolates the A72), then resets
A72 #0 (`-skip-activate-subsystem`, which lands at EL3) and loads
`app.elf`. The PS console is on FT4232 **interface 1**
(`VERSAL_VMK180_UART1`).

## Hardware bring-up notes (what was Versal-specific)

- **GEM RX is poll-driven.** The GICv3 CPU interface did not deliver the
GEM SPI in this EL3 bring-up, so `eth_poll` polls `gem_isr` from the
main loop to drain the RX ring (the IRQ path stays registered but
dormant).
- **GEM clock is owned by the PLM.** The CRL block is PMC/PLM-protected;
a direct APU write to `CRL.GEM0_REF_CTRL` (`0xFF5E0118`, not the ZynqMP
`+0x50`) stalls the bus. The PLM already configures the GEM clock, so
`gem.c` does not touch it. The correct Versal offsets are documented in
`board.h` for reference.
- **Two DP83867 PHYs.** The VMK180 presents more than one PHY on the MDIO
bus; `gem.c` scans all 32 addresses and prefers the one reporting copper
link (the on-board RJ45 PHY answered at addr 1). Make sure the cable is
in the **PS-GEM** RJ45, not the System Controller jack.
- `SCR_EL3` IRQ/FIQ/EA routing is carried over from the ZCU102 fix and is
harmless on the A72.

## Files

Identical layout to `src/port/zcu102/` plus `jtag/` (PDI-based JTAG
loader). See that port's README for per-file responsibilities. The
differences listed in the table above are the only substantive
Versal-specific code.
113 changes: 113 additions & 0 deletions src/port/versal/board.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* board.h
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfIP TCP/IP stack.
*
* wolfIP is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfIP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*
* Xilinx Versal Gen 1 (VCxxxx / VMK180 board) PS register base
* addresses and GIC SPI IDs. Values are derived from the Versal ACAP
* Technical Reference Manual (AM011), the VMK180 board user guide,
* and the published `versal.dtsi` device tree. No Xilinx BSP header
* (xparameters.h) or xilstandalone code is referenced.
*
* Brought up on a VMK180 (Cortex-A72). The structure mirrors
* src/port/zcu102/. Key differences from ZynqMP are:
* - Cortex-A72 (not A53), with PLM handoff at EL2
* - GICv3 distributor + redistributor (no GICv2 legacy GICC)
* - ARM PL011 UART (not Cadence)
* - 2 GEMs (GEM0/GEM1) instead of 4; on-board RJ45 is GEM0 on VMK180
*/
#ifndef VERSAL_BOARD_H
#define VERSAL_BOARD_H

#include <stdint.h>

/* ---------------------------------------------------------------------
* Memory map (Versal PS)
* ------------------------------------------------------------------- */
#define DDR_BASE 0x00000000UL
#define DDR_SIZE 0x80000000UL /* 2 GB lower bank */

/* OCM on Versal lives at 0xFFFC0000 (256 KB). Same as ZynqMP. */
#define OCM_BASE 0xFFFC0000UL
#define OCM_SIZE 0x00040000UL

/* ---------------------------------------------------------------------
* PS peripherals
* ------------------------------------------------------------------- */
#define UART0_BASE 0xFF000000UL /* PL011 */
#define UART1_BASE 0xFF010000UL /* PL011 */

#define GEM0_BASE 0xFF0C0000UL /* on-board GEM (VMK180) */
#define GEM1_BASE 0xFF0D0000UL

#define CRL_APB_BASE 0xFF5E0000UL /* LPD clock & reset */
#define IOU_SLCR_BASE 0xFF180000UL

/* GICv3: distributor + redistributor */
#define GICD_BASE 0xF9000000UL
#define GICR_BASE 0xF9080000UL /* per-CPU redistributors */

/* ---------------------------------------------------------------------
* GIC SPI numbers as GIC INTIDs (ARM GIC numbering: SPI N -> INTID 32+N).
* Versal versal.dtsi:
* GEM0: GIC_SPI 56 -> INTID 88
* GEM1: GIC_SPI 58 -> INTID 90
* ------------------------------------------------------------------- */
#define IRQ_GEM0 (32 + 56) /* GIC_SPI 56 -> INTID 88,
* on-board VMK180 RJ45 */
#define IRQ_GEM1 (32 + 58) /* GIC_SPI 58 -> INTID 90 */

/* ---------------------------------------------------------------------
* CRL clock and reset registers (LPD). Versal's CRL register map is NOT
* the same as ZynqMP: the GEM clock/reset offsets differ. Verified
* against the Versal PSM firmware crl.h (Vitis embeddedsw):
* CRL.GEM0_REF_CTRL = CRL + 0x118 (CLKACT bit 25, DIVISOR0 [13:8],
* SRCSEL [2:0])
* CRL.RST_GEM0 = CRL + 0x308 (RESET bit 0)
* ------------------------------------------------------------------- */
#define CRL_APB_GEM0_REF_CTRL (CRL_APB_BASE + 0x118) /* Versal CRL.GEM0_REF_CTRL */
#define CRL_GEM0_RST (CRL_APB_BASE + 0x308) /* Versal CRL.RST_GEM0 */
#define CRL_GEM0_REF_CTRL_CLKACT (1u << 25)
#define CRL_RST_GEM0_RESET (1u << 0)

/* ---------------------------------------------------------------------
* PL011 UART0 - on-board USB-UART on VMK180
* ------------------------------------------------------------------- */
#define UART_BAUD 115200

/* MAC address for eth0. Locally-administered, even first octet. */
#ifndef WOLFIP_MAC_0
#define WOLFIP_MAC_0 0x02
#endif
#ifndef WOLFIP_MAC_1
#define WOLFIP_MAC_1 0x00
#endif
#ifndef WOLFIP_MAC_2
#define WOLFIP_MAC_2 0x5A
#endif
#ifndef WOLFIP_MAC_3
#define WOLFIP_MAC_3 0x11
#endif
#ifndef WOLFIP_MAC_4
#define WOLFIP_MAC_4 0x22
#endif
#ifndef WOLFIP_MAC_5
#define WOLFIP_MAC_5 0x33
#endif

#endif /* VERSAL_BOARD_H */
82 changes: 82 additions & 0 deletions src/port/versal/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* config.h
*
* Copyright (C) 2026 wolfSSL Inc.
*
* This file is part of wolfIP TCP/IP stack.
*
* wolfIP is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfIP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*
* wolfIP configuration for Xilinx Versal Gen 1 / VMK180 (Cortex-A72
* EL3 bare-metal). UDP-only profile aimed at deterministic DAL-C use.
*/
#ifndef WOLF_CONFIG_H
#define WOLF_CONFIG_H

#ifndef CONFIG_IPFILTER
#define CONFIG_IPFILTER 0
#endif

#define ETHERNET
#define LINK_MTU 1536

/* UDP-only profile in intent: the application does not call
* wolfIP_sock_socket() with IPSTACK_SOCK_STREAM. MAX_TCPSOCKETS is set
* to a small non-zero value only because core wolfIP currently sizes
* its timer heap via MAX_TIMERS = MAX_TCPSOCKETS * 3, and DHCP / ARP
* aging need timers. With MAX_TCPSOCKETS=0 the timer-heap insert path
* is permanently full and DHCP cannot schedule its retransmit timer.
* A core wolfIP follow-up should decouple MAX_TIMERS from
* MAX_TCPSOCKETS so DAL-C builds can truly opt TCP code out at
* compile time. */
#define MAX_TCPSOCKETS 2
#define MAX_UDPSOCKETS 4
#define MAX_ICMPSOCKETS 1
#define RXBUF_SIZE (LINK_MTU * 4)
#define TXBUF_SIZE (LINK_MTU * 4)

#define MAX_NEIGHBORS 16

#ifndef WOLFIP_MAX_INTERFACES
#define WOLFIP_MAX_INTERFACES 1
#endif

#ifndef WOLFIP_ENABLE_FORWARDING
#define WOLFIP_ENABLE_FORWARDING 0
#endif

#ifndef WOLFIP_ENABLE_LOOPBACK
#define WOLFIP_ENABLE_LOOPBACK 0
#endif

#ifndef WOLFIP_ENABLE_DHCP
#define WOLFIP_ENABLE_DHCP 1
#endif

/* Static IP fallback (used if DHCP is disabled or times out). */
#define WOLFIP_IP "192.168.1.100"
#define WOLFIP_NETMASK "255.255.255.0"
#define WOLFIP_GW "192.168.1.1"
#define WOLFIP_STATIC_DNS_IP "8.8.8.8"

#if WOLFIP_ENABLE_DHCP
#define DHCP
#define DHCP_DISCOVER_RETRIES 2
#define DHCP_REQUEST_RETRIES 2
#endif

/* Hardware debug: define for verbose GEM / MDIO / DHCP logging. */
/* #define DEBUG_HW */

#endif /* WOLF_CONFIG_H */
Loading
Loading