|
| 1 | +# wolfIP AMD/Xilinx bare-metal ports |
| 2 | + |
| 3 | +Bare-metal wolfIP ports for AMD/Xilinx PS-GEM SoCs, sharing one tree: |
| 4 | + |
| 5 | +- **ZCU102** - ZynqMP, Cortex-A53, AArch64, EL3 |
| 6 | +- **Versal Gen 1 / VMK180** - Cortex-A72, AArch64, EL3 |
| 7 | +- **Zynq-7000 / ZC702** - Cortex-A9, ARMv7-A, SVC |
| 8 | + |
| 9 | +All three are brought up on real hardware (DHCP, ICMP ping, UDP echo). |
| 10 | + |
| 11 | +## Layout |
| 12 | + |
| 13 | +Shared code lives once; each board's Makefile selects which components to |
| 14 | +compile (build-selected files, not `#ifdef` forks). |
| 15 | + |
| 16 | +``` |
| 17 | +common/ arch- and SoC-independent |
| 18 | + app.c app.h shared UDP-echo + DHCP demo (board hooks: board.c) |
| 19 | + gem_core.c gem.h shared Cadence GEM core (init, MDIO, polled TX, diag) |
| 20 | + gem_regs.h gem_port.h GEM register map / internal hook interface |
| 21 | + uart_util.c shared UART helpers (puts/puthex/putdec/putip4) |
| 22 | + entropy.c memuse-pattern RNG (counter via arch_counter64) |
| 23 | + wolfip_config.h shared wolfIP profile (board config.h includes it) |
| 24 | + gic.h uart.h mmu.h driver API headers |
| 25 | +
|
| 26 | +arch/aarch64/ cache.h timer.h mmu_aarch64.c startup_aarch64.S exception_aarch64.c |
| 27 | +arch/armv7/ cache.h timer.h mmu_armv7.c startup_armv7.S |
| 28 | +
|
| 29 | +ip/ per-IP-block drivers (build-selected) |
| 30 | + uart_cadence.c uart_pl011.c UART |
| 31 | + gic_gicv2.c gic_gicv3.c GIC |
| 32 | + gem_swq.c gem_rx_swq_poll.c gem_rx_poll.c RX delivery model (all boards poll) |
| 33 | + gem_rx_irq.c reference IRQ-driven RX (not built; see file) |
| 34 | + phy_dp83867.c phy_marvell.c PHY drivers |
| 35 | + phy_dispatch_dp83867.c phy_dispatch_multi.c PHY vendor dispatch |
| 36 | +
|
| 37 | +boards/<board>/ the build root for each board (keeps app.elf + JTAG in place) |
| 38 | + board.h board.c board_gem.c config.h Makefile target*.ld jtag/ [bootgen/] |
| 39 | +``` |
| 40 | + |
| 41 | +## Component selection per board |
| 42 | + |
| 43 | +| Component | ZCU102 | Versal | Zynq-7000 | |
| 44 | +|-----------|--------|--------|-----------| |
| 45 | +| arch | aarch64 | aarch64 | armv7 | |
| 46 | +| UART | cadence | pl011 | cadence | |
| 47 | +| GIC | gicv2 | gicv3 | gicv2 | |
| 48 | +| GEM RX | gem_rx_swq_poll + gem_swq | gem_rx_swq_poll + gem_swq | gem_rx_poll | |
| 49 | +| PHY | dp83867 | dp83867 | dp83867 + marvell (multi) | |
| 50 | +| GEM inst | GEM3 | GEM0 | GEM0 | |
| 51 | + |
| 52 | +## Build |
| 53 | + |
| 54 | +``` |
| 55 | +cd boards/zcu102 && make CROSS_COMPILE=aarch64-none-elf- |
| 56 | +cd boards/versal && make CROSS_COMPILE=aarch64-none-elf- |
| 57 | +cd boards/zynq7000 && make CROSS_COMPILE=arm-none-eabi- |
| 58 | +``` |
| 59 | + |
| 60 | +Output is `app.elf` in the board directory. See each board's `README.md` |
| 61 | +for the JTAG / BOOT.BIN flow and bring-up notes. |
| 62 | + |
| 63 | +## Throughput test (SPEED_TEST) |
| 64 | + |
| 65 | +The default build runs the UDP echo + DHCP demo. Building with |
| 66 | +`CFLAGS_EXTRA=-DSPEED_TEST` instead brings up a TCP throughput server on |
| 67 | +**port 9** (a discard/chargen-style sink + source, in the spirit of iperf but |
| 68 | +without iperf3's JSON control channel, which is impractical on bare metal). On |
| 69 | +each accepted connection the board sinks everything the host sends (RX) and, in |
| 70 | +the same window, sources chargen data whenever the socket is writable (TX); on |
| 71 | +close it prints the byte totals and an average rate over the UART: |
| 72 | + |
| 73 | +``` |
| 74 | +cd boards/zcu102 && make CROSS_COMPILE=aarch64-none-elf- CFLAGS_EXTRA=-DSPEED_TEST |
| 75 | +``` |
| 76 | + |
| 77 | +Measure from a host on the same subnet as the board (replace `<ip>` with the |
| 78 | +leased address printed at DHCP bind): |
| 79 | + |
| 80 | +``` |
| 81 | +# RX (host -> board): how fast the board sinks |
| 82 | +dd if=/dev/zero bs=1460 count=20000 | nc -q1 <ip> 9 |
| 83 | +
|
| 84 | +# TX (board -> host): how fast the board sources |
| 85 | +nc <ip> 9 </dev/null | pv -r >/dev/null |
| 86 | +``` |
| 87 | + |
| 88 | +The board's own `SPEED done ... RX/TX bytes (~B/s)` UART line is the |
| 89 | +authoritative figure (it times the connection with the hardware clock). Note |
| 90 | +the RX and TX counters cover the same connection window, so during the RX run |
| 91 | +the board is also back-sourcing; the printed RX B/s is the host->board goodput |
| 92 | +under that concurrent load. iperf3 host-to-host on the same link is a useful |
| 93 | +*link* reference, but the board is not an iperf3 endpoint. |
| 94 | + |
| 95 | +The `SPEED_TEST` build also widens the TCP window (`RXBUF_SIZE`/`TXBUF_SIZE` to |
| 96 | +`LINK_MTU * 6` in `config.h`) and trims the UDP socket count to keep the larger |
| 97 | +per-socket buffers inside the 256 KB OCM budget. |
| 98 | + |
| 99 | +### Results |
| 100 | + |
| 101 | +Single Cortex core, 1 Gbps RGMII link, MTU 1500, host on the same switch. |
| 102 | +RX is the board's UART `~B/s` line (host -> board); TX is host-measured |
| 103 | +(board -> host). Bytes x8 for Mbps. |
| 104 | + |
| 105 | +| Board (SoC, core) | Layout / boot | RX Mbps | TX Mbps | |
| 106 | +|------------------------------|-----------------|--------:|--------:| |
| 107 | +| VMK180 (Versal, A72 @ EL3) | DDR (JTAG) | ~300 | ~334 | |
| 108 | +| ZCU102 (ZynqMP, A53 @ EL3) | DDR (SD boot) | ~126 | ~194 | |
| 109 | +| ZC702 (Zynq-7000, A9 @ SVC) | OCM (JTAG) | ~22 | ~19 | |
| 110 | +| ZCU102 (ZynqMP, A53 @ EL3) | OCM (JTAG) | ~10 | ~9 | |
| 111 | + |
| 112 | +The single dominant factor is the **memory layout**: the OCM layout runs *all* |
| 113 | +code (and the rings) from Normal non-cacheable OCM, so every instruction fetch |
| 114 | +and frame copy is uncached. The DDR layout keeps code+data in cacheable DDR and |
| 115 | +maps only the GEM DMA region non-cacheable - ~13-30x faster, as the two ZCU102 |
| 116 | +rows show directly (same SoC/core, OCM ~10/9 vs DDR ~126/194 Mbps). The faster |
| 117 | +A72 (Versal) reaches ~300/334 on DDR. |
| 118 | + |
| 119 | +How each DDR number was loaded: Versal's PLM trains DDR from a boot PDI, so the |
| 120 | +DDR app loads cleanly over JTAG. On ZynqMP, JTAG writes into DDR after a bare |
| 121 | +`psu_init` are unreliable (the load goes through the A53 with a cache flush and |
| 122 | +either errors or lands corrupt - DDR itself is fine, a direct DAP memtest passes), |
| 123 | +so the ZCU102 DDR figure is from an **SD boot**: `FSBL_ELF=.../zynqmp_fsbl.elf |
| 124 | +make bootbin` produces a DDR-layout `BOOT.BIN` that the FSBL trains DDR for and |
| 125 | +DMA-loads (no JTAG memory writes). Copy it to the SD card's FAT boot partition |
| 126 | +and set SW6 = SD. The same applies to ZC702 (its OCM-only port has no DDR layout |
| 127 | +yet; a DDR profile is future work). |
| 128 | + |
| 129 | +What it took to get here: |
| 130 | + |
| 131 | +1. **NC-map the DMA rings in the DDR layout (correctness, not just speed).** |
| 132 | + The DDR layout had mapped the GEM BD rings cacheable with per-BD |
| 133 | + `cache_clean`. Because the 8-byte BDs share 64-byte cache lines, cleaning one |
| 134 | + BD wrote stale neighbours back over MAC-set OWN bits and wedged the RX ring |
| 135 | + under sustained (TCP-rate) load - the UDP-only profile never had two BDs live |
| 136 | + in a line at once. The DMA region is now Normal-NC in both layouts, with |
| 137 | + `.dma_buffers` in its own 2 MB block so `.text` stays cacheable. |
| 138 | +2. **Main-loop poll cadence.** The original loop called `wolfIP_poll()` then |
| 139 | + `delay_ms(1)`, capping the stack at ~1 poll/ms (~12 Mbps) and feeding wolfIP |
| 140 | + a `tick++` counter that only approximated real milliseconds. It now |
| 141 | + busy-polls with a real-millisecond clock from the hardware timer |
| 142 | + (`timer_now()/timer_freq()`), which also de-skews every DHCP/TCP/ARP timeout. |
| 143 | +3. **Drain RX fully, bounded TX per event.** Reading one chunk per READABLE |
| 144 | + left the advertised TCP window stuck (~2 KB) and deadlocked; the SPEED server |
| 145 | + now drains the rx buffer each event and does a bounded tx fill. |
| 146 | +4. **Word-wise `memcpy`/`memset`.** Frame-staging copies are 8 bytes at a time |
| 147 | + (bytewise tail), which matters for the non-cacheable DMA buffers. |
| 148 | + |
| 149 | +Notes / remaining levers: ZCU102 uses the same poll-driven RX as the other two |
| 150 | +boards - its original IRQ-driven RX storms the CPU under sustained RX load. |
| 151 | +A DDR/BOOT.BIN profile for the OCM boards (cached code) and draining more than |
| 152 | +one frame per poll are the next levers. |
0 commit comments