Skip to content

Commit 581d263

Browse files
committed
ci: build + QEMU boot-smoke for the AMD/Xilinx ports
Add .github/workflows/amd.yml: a full build matrix (zcu102/versal {ocm,ddr} x {default,SPEED_TEST} + zynq7000 {default,SPEED_TEST}, ~10 legs, -Werror) and a QEMU boot-smoke job that boots app.elf under the matching Xilinx QEMU machine (xlnx-zcu102 / xlnx-versal-virt / xilinx-zynq-a9) and asserts it reaches the 'Ready' banner. zcu102 gates; versal and zynq7000 are informational until their QEMU device models are confirmed. The official ARM GNU toolchains (aarch64-none-elf + arm-none-eabi, 14.3.rel1) are downloaded once and cached; path filters + a concurrency group keep runs cheap. QEMU smoke logic lives in tools/scripts/amd/qemu-smoke.sh.
1 parent 46cce85 commit 581d263

2 files changed

Lines changed: 278 additions & 0 deletions

File tree

.github/workflows/amd.yml

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
name: AMD/Xilinx ports
2+
3+
# Build (and QEMU boot-smoke) the bare-metal wolfIP ports under src/port/amd/:
4+
# ZCU102 (A53), Versal VMK180 (A72) and Zynq-7000 ZC702 (A9). app.elf builds
5+
# from repo sources only - no Vitis/FSBL/bootgen/hardware. (BOOT.BIN needs an
6+
# FSBL + bootgen and is out of scope here.)
7+
8+
on:
9+
push:
10+
paths:
11+
- 'src/port/amd/**'
12+
- 'src/wolfip.c'
13+
- 'wolfip.h'
14+
- 'tools/scripts/amd/**'
15+
- '.github/workflows/amd.yml'
16+
pull_request:
17+
paths:
18+
- 'src/port/amd/**'
19+
- 'src/wolfip.c'
20+
- 'wolfip.h'
21+
- 'tools/scripts/amd/**'
22+
- '.github/workflows/amd.yml'
23+
24+
# Cancel superseded runs on the same ref (runner optimization).
25+
concurrency:
26+
group: amd-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
env:
30+
ARM_TC_VER: 14.3.rel1
31+
TC_ROOT: /home/runner/toolchains
32+
33+
jobs:
34+
# --------------------------------------------------------------------------
35+
# Prime the toolchain cache once so the matrix legs don't each re-download
36+
# ~150 MB. Pin the official ARM GNU Toolchain bundle that ships BOTH cross
37+
# compilers (aarch64-none-elf for ZCU102/Versal, arm-none-eabi for Zynq-7000).
38+
# --------------------------------------------------------------------------
39+
toolchains:
40+
runs-on: ubuntu-latest
41+
timeout-minutes: 15
42+
steps:
43+
- name: Cache ARM GNU toolchains
44+
id: tc-cache
45+
uses: actions/cache@v4
46+
with:
47+
path: ${{ env.TC_ROOT }}
48+
key: arm-gnu-${{ env.ARM_TC_VER }}-x86_64
49+
50+
- name: Download + extract toolchains
51+
if: steps.tc-cache.outputs.cache-hit != 'true'
52+
run: |
53+
set -euxo pipefail
54+
mkdir -p "$TC_ROOT"
55+
base="https://developer.arm.com/-/media/Files/downloads/gnu/${ARM_TC_VER}/binrel"
56+
for t in aarch64-none-elf arm-none-eabi; do
57+
f="arm-gnu-toolchain-${ARM_TC_VER}-x86_64-${t}.tar.xz"
58+
curl -fSL --retry 3 --retry-delay 5 -o "/tmp/$f" "$base/$f"
59+
tar -xf "/tmp/$f" -C "$TC_ROOT"
60+
done
61+
ls -d "$TC_ROOT"/*/
62+
63+
# --------------------------------------------------------------------------
64+
# Full build matrix: per board x layout x default/SPEED_TEST (~10 legs).
65+
# -Werror is already in each board's CFLAGS, so a clean compile is the gate.
66+
# --------------------------------------------------------------------------
67+
build:
68+
needs: toolchains
69+
runs-on: ubuntu-latest
70+
timeout-minutes: 10
71+
strategy:
72+
fail-fast: false
73+
matrix:
74+
include:
75+
- { board: zcu102, cross: aarch64-none-elf-, layout: ocm, speed: "" }
76+
- { board: zcu102, cross: aarch64-none-elf-, layout: ocm, speed: "-DSPEED_TEST" }
77+
- { board: zcu102, cross: aarch64-none-elf-, layout: ddr, speed: "" }
78+
- { board: zcu102, cross: aarch64-none-elf-, layout: ddr, speed: "-DSPEED_TEST" }
79+
- { board: versal, cross: aarch64-none-elf-, layout: ocm, speed: "" }
80+
- { board: versal, cross: aarch64-none-elf-, layout: ocm, speed: "-DSPEED_TEST" }
81+
- { board: versal, cross: aarch64-none-elf-, layout: ddr, speed: "" }
82+
- { board: versal, cross: aarch64-none-elf-, layout: ddr, speed: "-DSPEED_TEST" }
83+
- { board: zynq7000, cross: arm-none-eabi-, layout: ocm, speed: "" }
84+
- { board: zynq7000, cross: arm-none-eabi-, layout: ocm, speed: "-DSPEED_TEST" }
85+
steps:
86+
- uses: actions/checkout@v4
87+
88+
- name: Restore toolchains
89+
uses: actions/cache/restore@v4
90+
with:
91+
path: ${{ env.TC_ROOT }}
92+
key: arm-gnu-${{ env.ARM_TC_VER }}-x86_64
93+
fail-on-cache-miss: true
94+
95+
- name: Add toolchains to PATH
96+
run: |
97+
echo "$TC_ROOT/arm-gnu-toolchain-${ARM_TC_VER}-x86_64-aarch64-none-elf/bin" >> "$GITHUB_PATH"
98+
echo "$TC_ROOT/arm-gnu-toolchain-${ARM_TC_VER}-x86_64-arm-none-eabi/bin" >> "$GITHUB_PATH"
99+
100+
- name: Build ${{ matrix.board }} (${{ matrix.layout }}${{ matrix.speed && ' SPEED' || '' }})
101+
run: |
102+
set -euxo pipefail
103+
${{ matrix.cross }}gcc --version | head -1
104+
args="CROSS_COMPILE=${{ matrix.cross }}"
105+
# zynq7000 is OCM-only (no LAYOUT switch / no target_ddr.ld).
106+
if [ "${{ matrix.board }}" != "zynq7000" ]; then
107+
args="$args LAYOUT=${{ matrix.layout }}"
108+
fi
109+
if [ -n "${{ matrix.speed }}" ]; then
110+
args="$args CFLAGS_EXTRA=${{ matrix.speed }}"
111+
fi
112+
make -C "src/port/amd/boards/${{ matrix.board }}" $args
113+
${{ matrix.cross }}size "src/port/amd/boards/${{ matrix.board }}/app.elf"
114+
115+
- name: Upload app.elf
116+
uses: actions/upload-artifact@v4
117+
with:
118+
name: amd-${{ matrix.board }}-${{ matrix.layout }}-${{ matrix.speed != '' && 'speed' || 'default' }}
119+
path: src/port/amd/boards/${{ matrix.board }}/app.elf
120+
if-no-files-found: error
121+
122+
# --------------------------------------------------------------------------
123+
# QEMU boot smoke: build the OCM/default app per board and confirm it boots
124+
# to "Ready" under the matching Xilinx QEMU machine. zcu102 gates; versal and
125+
# zynq7000 are informational (continue-on-error) until their QEMU device
126+
# models are confirmed - the machine/UART/load details may need iteration.
127+
# --------------------------------------------------------------------------
128+
qemu:
129+
needs: toolchains
130+
runs-on: ubuntu-latest
131+
timeout-minutes: 15
132+
strategy:
133+
fail-fast: false
134+
matrix:
135+
include:
136+
- { board: zcu102, cross: aarch64-none-elf-, gate: true }
137+
- { board: versal, cross: aarch64-none-elf-, gate: false }
138+
- { board: zynq7000, cross: arm-none-eabi-, gate: false }
139+
continue-on-error: ${{ !matrix.gate }}
140+
steps:
141+
- uses: actions/checkout@v4
142+
143+
- name: Restore toolchains
144+
uses: actions/cache/restore@v4
145+
with:
146+
path: ${{ env.TC_ROOT }}
147+
key: arm-gnu-${{ env.ARM_TC_VER }}-x86_64
148+
fail-on-cache-miss: true
149+
150+
- name: Add toolchains to PATH
151+
run: |
152+
echo "$TC_ROOT/arm-gnu-toolchain-${ARM_TC_VER}-x86_64-aarch64-none-elf/bin" >> "$GITHUB_PATH"
153+
echo "$TC_ROOT/arm-gnu-toolchain-${ARM_TC_VER}-x86_64-arm-none-eabi/bin" >> "$GITHUB_PATH"
154+
155+
- name: Install QEMU
156+
run: |
157+
sudo apt-get update
158+
sudo apt-get install -y --no-install-recommends \
159+
qemu-system-arm qemu-system-aarch64
160+
161+
- name: Build ${{ matrix.board }} (OCM, default)
162+
run: |
163+
set -euxo pipefail
164+
make -C "src/port/amd/boards/${{ matrix.board }}" CROSS_COMPILE=${{ matrix.cross }}
165+
166+
- name: QEMU boot smoke
167+
run: |
168+
chmod +x tools/scripts/amd/qemu-smoke.sh
169+
UART_LOG="uart-${{ matrix.board }}.log" \
170+
tools/scripts/amd/qemu-smoke.sh "${{ matrix.board }}"
171+
172+
- name: Upload UART log
173+
if: always()
174+
uses: actions/upload-artifact@v4
175+
with:
176+
name: qemu-uart-${{ matrix.board }}
177+
path: uart-${{ matrix.board }}.log
178+
if-no-files-found: warn

tools/scripts/amd/qemu-smoke.sh

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env bash
2+
#
3+
# QEMU boot smoke test for a wolfIP AMD/Xilinx bare-metal port.
4+
#
5+
# Boots boards/<board>/app.elf under the matching mainline Xilinx QEMU machine,
6+
# captures the UART console, and asserts the app reaches its "Ready" banner -
7+
# i.e. startup (EL3/SVC), MMU, GIC, UART, the GEM bring-up and the wolfIP main
8+
# loop all execute under emulation. The PHY autoneg/link waits time out
9+
# gracefully under QEMU (no real link), and DHCP then falls back to a static IP,
10+
# so reaching "Ready" is a robust gate that does not depend on QEMU's GEM PHY
11+
# reporting link. (Full DHCP/echo over QEMU user-net is a separate, best-effort
12+
# concern and is intentionally not wired here.)
13+
#
14+
# Usage: qemu-smoke.sh <zcu102|versal|zynq7000> [app.elf]
15+
# Env: QEMU_TIMEOUT (seconds, default 120), UART_LOG (default uart-<board>.log)
16+
#
17+
set -u
18+
19+
BOARD="${1:?usage: qemu-smoke.sh <zcu102|versal|zynq7000> [app.elf]}"
20+
ELF="${2:-src/port/amd/boards/$BOARD/app.elf}"
21+
TIMEOUT="${QEMU_TIMEOUT:-120}"
22+
LOG="${UART_LOG:-uart-$BOARD.log}"
23+
24+
if [ ! -f "$ELF" ]; then
25+
echo "ERROR: app.elf not found: $ELF (build it first)" >&2
26+
exit 2
27+
fi
28+
29+
# Per-board QEMU machine + console UART routing. The console UART differs per
30+
# board (zcu102 = PS-UART0 = serial0; versal = PL011 = serial0; zynq7000 =
31+
# UART1 = serial1, so serial0 is routed to null).
32+
case "$BOARD" in
33+
zcu102)
34+
QEMU=qemu-system-aarch64
35+
MACHINE="xlnx-zcu102,secure=on"
36+
SERIAL=(-serial "mon:stdio")
37+
;;
38+
versal)
39+
QEMU=qemu-system-aarch64
40+
MACHINE="xlnx-versal-virt"
41+
SERIAL=(-serial "mon:stdio")
42+
;;
43+
zynq7000)
44+
QEMU=qemu-system-arm
45+
MACHINE="xilinx-zynq-a9"
46+
SERIAL=(-serial null -serial "mon:stdio")
47+
;;
48+
*)
49+
echo "ERROR: unknown board '$BOARD'" >&2
50+
exit 2
51+
;;
52+
esac
53+
54+
if ! command -v "$QEMU" >/dev/null 2>&1; then
55+
echo "ERROR: $QEMU not found (install qemu-system-arm / qemu-system-aarch64)" >&2
56+
exit 2
57+
fi
58+
59+
echo "=== QEMU smoke: $BOARD ($QEMU -M $MACHINE), elf=$ELF, timeout=${TIMEOUT}s ==="
60+
: > "$LOG"
61+
62+
# Bare-metal load: -device loader sets PC to the ELF entry at the machine's
63+
# reset EL (EL3 on the AArch64 machines, SVC on zynq-a9). No netdev is attached:
64+
# the gate does not need networking, and an unconsumed -netdev would error out.
65+
"$QEMU" -M "$MACHINE" -nographic -no-reboot \
66+
"${SERIAL[@]}" \
67+
-device "loader,file=$ELF,cpu-num=0" \
68+
>>"$LOG" 2>&1 &
69+
QPID=$!
70+
71+
ok=0
72+
fault=0
73+
deadline=$((SECONDS + TIMEOUT))
74+
while kill -0 "$QPID" 2>/dev/null; do
75+
if grep -qa "Ready" "$LOG"; then ok=1; break; fi
76+
# Hard-fault / abort markers from the exception vectors or QEMU itself.
77+
if grep -qaiE "synchronous exception|unhandled|abort|panic" "$LOG"; then
78+
fault=1; break
79+
fi
80+
if [ "$SECONDS" -ge "$deadline" ]; then break; fi
81+
sleep 2
82+
done
83+
84+
kill "$QPID" 2>/dev/null
85+
wait "$QPID" 2>/dev/null || true
86+
87+
echo "----- captured UART ($LOG) -----"
88+
cat "$LOG"
89+
echo "--------------------------------"
90+
91+
if [ "$ok" -eq 1 ]; then
92+
echo "PASS: $BOARD reached 'Ready' under QEMU"
93+
exit 0
94+
fi
95+
if [ "$fault" -eq 1 ]; then
96+
echo "FAIL: $BOARD hit a fault/abort marker before 'Ready'" >&2
97+
exit 1
98+
fi
99+
echo "FAIL: $BOARD did not reach 'Ready' within ${TIMEOUT}s" >&2
100+
exit 1

0 commit comments

Comments
 (0)