A UDP/IPv4/Ethernet offload engine in VHDL, targeting a Xilinx FPGA behind the XXV Ethernet (10G/25G) MAC.
UOE_explainer.mp4
-
Unpacking Ethernet, IPv4 and UDP headers
✅ Filtering based on destination MAC address ❌️ Receive broadcast or multicast UDP ✅ Checks the IPv4 header: version, fragmentation, protocol, checksum, destination IP ❌️ Check the UDP checksum ✅ Drops frames the MAC flagged as bad, whole ❌️ Filter by UDP port (your logic routes by udp_rx_dst_port)✅ Reports the source MAC, IP and port, and the destination port ❌️ Handle backpressure: udp_rxhas notready✅ Frames up to 16 KB, so jumbo frames (MTU 9000) ❌️ Reassemble fragmented IP packets -
Building Ethernet, IPv4 and UDP headers
✅ Destination MAC, IP and port chosen per packet, even back to back ❌️ Compute the UDP checksum (always sent as 0) ✅ IPv4 header checksum and a per-packet ID ❌️ Fragment a payload larger than the MTU ✅ Pads short payloads to Ethernet's 60-byte minimum ❌️ Reply automatically to whoever sent a packet ✅ Jumbo frames, measured at 9.71 Gbps on a 10G link -
ARP
✅ Answers ARP requests for its own IP ❌️ Send ARP requests, so it cannot find a peer's MAC by itself ✅ Replies go out ahead of data, never mid-packet
Measured on hardware over a 10G link in loopback: 9.71 Gbps sustained with jumbo frames (8958-byte payloads, 97.5 % of the theoretical maximum), 9.44 Gbps at 4096 bytes (95.4 %) and 8.59 Gbps at 2048 bytes (87.6 %).
| group | direction | notes |
|---|---|---|
clk, rstn |
in | one clock for everything; active-low reset, stretched inside to at least 64 clocks |
mac_rx_* |
in | from the MAC. Carries tuser as the MAC's per-frame error flag |
mac_tx_* |
out | to the MAC, with tready |
udp_rx_* |
out | payload delivered to your logic. No tready. tuser high with tlast marks a packet to discard |
udp_tx_* |
in | payload to transmit, with tready |
udp_rx_src_mac/src_ip/src_port/dst_port |
out | who the packet came from, and which port it was for |
conf_mac, conf_ip, conf_port |
in | this node's own address |
conf_tx_dst_mac/dst_ip/dst_port |
in | where transmitted packets go |
-
Package the UOE as a Vivado IP, for your FPGA part:
./scripts/package_uoe.sh --part xczu9eg-ffvb1156-2-e
--partis the FPGA part of the project the IP is for: Vivado shows it under Project Settings > General > Project device, and the example is the ZCU102's. Any part Vivado accepts works.The IP lands in
ip/uoe(--outto change that). Addip/to your project's IP repositories (Settings > IP > Repository) and it is in the catalog as "UDP Offload Engine". -
Or add it to a block design, wired to an XXV 10G MAC. With the project closed in Vivado (this packages the IP itself):
./scripts/add_uoe.sh --project my_project.xpr --ip 192.168.1.128 \ --dclk zynq_ultra_ps_e_0/pl_clk0 --resetn zynq_ultra_ps_e_0/pl_resetn0 --sfp 0--project: the Vivado project (.xpr) whose block design gets the UOE.--ip: the board's own IPv4 address on the network (the UOE'sconf_ip).--dclk: a free-running clock already in the block design, e.g. 100 MHz from the PS. It clocks the MAC's transceiver control and its register port.--resetn: an active-low reset for that clock; it also holds the MAC in reset.--sfp: ZCU102 only: which SFP+ cage (0..3) the cable is in. Sets the transceiver and writes the pin constraints.
--dclkand--resetnname a pin as<block>/<pin>, as the block design shows it. Useful optional ones:--mac(the board's MAC, default 00:0A:35:00:00:01),--port(the UDP port it sends from, default 1234) and--axi(an AXI master, e.g.zynq_ultra_ps_e_0/M_AXI_HPM0_FPD, to reach the MAC's registers). On other boards use--gt_quad/--gt_laneinstead of--sfp. Then connect your logic toudp_rx/udp_txandconf_tx_dst_*. The options, and what is left for you, are in Docs/integration.md.
| Folder | What it holds |
|---|---|
rtl/ |
the hardware design -- one VHDL file per block |
testbenches/ |
VHDL testbenches and the reusable AXIS components they are built from (README) |
reference_model/ |
an independent Python model of the same protocols; writes the test vectors and compares the results |
scripts/ |
build and run helpers, and the Vivado scripts that package the UOE and add it to a block design |
host_test/ |
host-side bring-up test for the real board (README) |
Docs/ |
integration, and the logo and video used here |
- Written by hand: the hardware design, everything in
rtl/, by Yuval Peleg and Aylon Feraru. - Written by AI (Claude), under our direction: the
testbenches (
testbenches/), the Python reference model (reference_model/), the scripts (scripts/), the host-side test tool (host_test/) and most of the documentation. - Made with Claude Design: the explainer video and the logo.
Hope you find this repo useful 🙏 We've tried to make it as readable and accessible as possible.
— Yuval Peleg
MIT, see LICENSE.


0 comments
log in to comment.