Architecture Overview¶
This page explains how openwifi is put together: the split between Linux, the driver, and the FPGA. Read it before you start modifying code. For where each part lives in the source tree, see The Repositories. The driver internals are in The Linux Driver, and the FPGA cores in FPGA IP Cores.

openwifi's full composition: software modules (top) and FPGA modules (bottom). The module names in this diagram match the source file names (xpu, openofdm_tx/rx, tx_intf, rx_intf, side_ch), which is the key to navigating both the code and this wiki.
The big picture¶
openwifi is a SoftMAC Wi-Fi design. The word "soft" refers to where the upper MAC lives: management, association, and higher-layer logic run in software (Linux mac80211), exactly as they do for a commercial SoftMAC chip. What makes openwifi unusual is that the PHY and the timing-critical low MAC live in FPGA fabric that you can read, modify, and rebuild.
Layered from top to bottom:
- Linux user space:
hostapd,wpa_supplicant,iw,dhclient,tcpdump, plus openwifi's ownsdrctltool and helper scripts. The two Wi-Fi daemons are stock builds that reach the driver only through nl80211 andmac80211, never directly (see hostapd and wpa_supplicant). - Linux kernel: cfg80211 / mac80211: the generic Linux wireless stack. Handles the upper MAC and calls into the driver through a fixed API.
- openwifi driver (
driver/sdr.cand friends): a SoftMAC driver that implements the mac80211 API and translates it into FPGA register writes and DMA transfers. - FPGA design (the openwifi-hw repo): OFDM transmitter and receiver, the CSMA/CA low MAC, and DMA interfaces to the processor.
- AD9361 RF front end: the analog radio (70 MHz–6 GHz), connected to the FPGA over the Analog Devices RF interface and controlled in real time over an FPGA-driven SPI link.
Because it registers a normal Linux network interface (sdr0), every tool that works with a commercial card works here too, which is the core idea behind openwifi.
xpu) and the PHY. The processor reaches every FPGA core over the AXI bus. The AD9361 RF front end is the external analog radio.How the driver talks to Linux: the mac80211 API¶
The Linux mac80211 subsystem defines a set of callbacks (ieee80211_ops) that every SoftMAC driver implements. That shared contract is why one kernel can drive Wi-Fi chips from dozens of vendors. openwifi's sdr.c implements the relevant subset: tx to send a frame, start / stop when the NIC goes up or down, config on a channel change, get_tsf / set_tsf for the hardware timer, testmode_cmd for sdrctl, and around a dozen more. The full callback table is on the driver page.
When Linux invokes one of these, sdr.c does the work by driving the FPGA. It uses per-block helper "sub-drivers" (tx_intf_api, rx_intf_api, openofdm_tx_api, openofdm_rx_api, and xpu_api), each of which wraps register access to one FPGA module. These are compiled as separate kernel modules (tx_intf.ko, rx_intf.ko, …) that sdr.ko binds to at load time, which is why wgd.sh inserts all of them.
openwifi is a Linux platform driver (not PCI or USB): it binds to a device-tree node with compatible = "sdr,sdr", and the device tree is what tells Linux the AXI addresses and interrupts of every FPGA block, which is why porting a board is largely a device-tree exercise. Separately, the AD9361 RF chip is driven by the standard Analog Devices IIO driver rather than by openwifi: the driver finds it on the SPI bus at probe time and calls into it (ad9361_set_tx_atten, ad9361_do_calib_run), which is why some patches to the ADI kernel are needed (see Software Development Workflow).
For the probe sequence, board auto-detection, the TX rings and RX cyclic buffer, the received-packet metadata format, and the register category encoding, see The Linux Driver.
The FPGA modules¶
The FPGA design decomposes into modules whose names match their source files (in openwifi-hw/ip/). These five names cover most of the register documentation:
openofdm_tx: the OFDM transmitter. Turns a MAC frame into baseband IQ samples (PHY header, pilots, scrambling, modulation). Based on original openwifi work.openofdm_rx: the OFDM receiver. Detects the preamble, synchronizes, estimates the channel, equalizes, and decodes (including a Xilinx Viterbi decoder). Derived from the openofdm project (originally by jhshi, with openwifi's improvements on thedot11zynqbranch).tx_intf: the transmit interface: DMA from the processor into per-queue FIFOs, the TX BRAM thatopenofdm_txreads the frame out of, the DAC feed that carries the modulated IQ back out, per-packet PHY configuration, and the four hardware TX queues.rx_intf: the receive interface: unpacks the ADC samples into IQ streams foropenofdm_rx, takes the decoded packets and side-channel data back, attaches metadata (TSF timestamp, RSSI, length, MCS, FCS status), and DMAs them up to the processor.xpu: the "eXtensible Processing Unit," which holds the real-time low MAC: the CSMA/CA state machine, NAV, DIFS/SIFS/EIFS timing, the TSF timer, hardware ACK generation and reception, retransmission, RTS/CTS, packet filtering, and the time-slicing gates for the TX queues. Anything that has to happen within microseconds is implemented inxpu.
There's also a side_ch (side channel) module used for research features (CSI and IQ capture), described on the Research Features page.
The processor reaches these modules over the ARM AXI bus. Each module exposes a bank of registers (slv_regN in the Verilog), whose addresses are defined in driver/hw_def.h. This AXI coupling is what gives openwifi very low processor↔PHY latency, and also what makes the design fairly platform-specific.
For a core-by-core walkthrough, see the dedicated FPGA IP Cores page: the submodules inside xpu (the CSMA/CA state machine, TSF timer, hardware SPI to the AD9361), the OFDM transmit and receive chains, and how a register write travels from sdrctl all the way to a slv_regN.
openwifi's FPGA design is built on top of the Analog Devices HDL reference design (vendored as the adi-hdl submodule of openwifi-hw): ADI provides the AD9361 interfacing IP, DMA engines, and board plumbing, and openwifi inserts its own cores into that design. This is why porting to a new board is framed as "diff openwifi against the matching ADI reference design."
Packet flow at a glance¶
The transmit lane runs left to right from Linux out to the antenna, and the receive lane runs back. Note where the two interface cores sit: tx_intf and rx_intf are the cores that touch the AD9361 converters, and the OFDM cores hang off them rather than sitting between them and the radio.
openwifi_tx() DMAs a frame into one of tx_intf's four TX queues, openofdm_tx reads the bytes out and hands modulated IQ back, and tx_intf drives the DAC. The xpu core releases the packet when CSMA/CA allows, then raises openwifi_tx_interrupt with the result. Indigo is receive: rx_intf takes the IQ from the ADC and passes it to openofdm_rx, which decodes and hands the bytes back, then rx_intf attaches TSF/RSSI/MCS/FCS metadata and DMAs the frame up to openwifi_rx_interrupt.The receive path, step by step¶
- A signal arrives at the AD9361 and is delivered to the FPGA as ADC samples.
rx_intfunpacks them into per-antenna IQ streams and feeds them to the demodulator. openofdm_rxdetects, synchronizes, and decodes it, and hands the bytes back torx_intf. Whether the FCS/CRC passes or fails, the packet is offered up if the current frame-filtering rules allow it (in monitor mode, everything is allowed, even bad-CRC frames and control frames like ACKs).rx_intfwrites the packet plus metadata into a DMA buffer and raises an interrupt.- The driver's
openwifi_rx_interrupt()runs: it pulls the raw buffer, parses out the inserted metadata (TSF timestamp, raw RSSI that it corrects to dBm per band/channel, length, MCS, FCS-valid flag), and hands the packet and its metadata to Linux viaieee80211_rx_irqsafe().
The exact 16-byte metadata layout is on the driver page, including the detail that the FCS-OK bit is carried in the last byte of the frame rather than in the header.
The transmit path, step by step¶
- Linux
mac80211callsopenwifi_tx()with a frame to send. - The driver reads what it needs from the 802.11 header and mac80211 metadata: length and MCS, unicast vs broadcast, whether an ACK is required and the maximum number of retransmissions the FPGA may attempt, which TX queue / time slice to use, whether RTS/CTS or CTS-to-self protection applies, and whether the driver should insert a sequence number.
- It picks one of four TX rings (by Linux priority, or by destination MAC when time slicing is active) and writes the frame into a buffer descriptor.
- It writes the per-packet FPGA configuration (so the FPGA generates the right PHY header, etc.) and fires a DMA transfer into one of the four FPGA TX queues. The packet may not go out immediately: the FPGA sends it when the channel and the CSMA state machine allow.
- When it is released,
openofdm_txreads the frame out of the TX BRAM insidetx_intfand hands the modulated IQ back, andtx_intfdrives it into the DAC. - When the FPGA finishes sending, it raises an interrupt.
openwifi_tx_interrupt()reads back the result (success or failure, meaning whether an ACK was received, and how many retransmissions happened) and reports it to Linux viaieee80211_tx_status_irqsafe().
The ring sizes, the index cross-checking, and the queue-mapping hook are covered on The Linux Driver.
The TSF timestamp¶
The 64-bit TSF (Timing Synchronization Function) timer is defined by the 802.11 standard and implemented in the FPGA. When a packet's PHY header is received, the FPGA samples the TSF value and attaches it to the packet's DMA buffer. The driver forwards it to Linux, which is why you see a consistent TSF timestamp in Wireshark/tcpdump. That same TSF value is the key that lets you line up side-channel data (CSI, IQ) with specific packets, since they share one time base. (See this discussion for the matching recipe.)
RF and baseband: the frequency/clock design¶
openwifi drives the AD9361 in FDD mode with identical TX and RX frequencies, and controls the AD9361 TX chain in real time over an FPGA SPI link (openwifi-hw/ip/xpu/src/spi.v). The TX local oscillator (or an RF switch) is turned on just before a transmit packet and off just after it, with two consequences:
- No LO leakage during receive, so the receiver does not interfere with itself, which enables full-duplex self-reception (the basis of the CSI radar and loopback features).
- Fast TX/RX turnaround (~0.6 µs), which is what makes the tight SIFS and hardware ACK timing achievable (SIFS is 10 µs in 2.4 GHz and 16 µs in 5 GHz).
The AD9361↔FPGA IQ rate is 40 Msps, decimated/interpolated inside the FPGA to the 20 Msps the Wi-Fi baseband uses. The FPGA baseband clock is derived from the AD9361 clock, so RF and baseband never drift relative to each other. This design (replacing the older "offset tuning" approach) is what gives openwifi its good EVM, spectral mask conformance, sensitivity, and RSSI accuracy.

The FPGA baseband clock is generated from the AD9361 sample clock, so the two never drift. The exact clock frequency per board is the NUM_CLK_PER_US parameter discussed in Supported Boards.
The configuration points of this RF/digital chain are spread across the AD9361 registers, the driver's .c files, and the FPGA .v modules:

What openwifi implements of 802.11a/g/n¶
openwifi implements 802.11a/g (legacy OFDM) and a single-stream 20 MHz subset of 802.11n (Wi-Fi 4). Which 11n improvements it does and doesn't have sets its performance envelope. 802.11n added five PHY improvements on top of 802.11a/g's 54 Mbps ceiling:
| 802.11n improvement | Effect | openwifi? |
|---|---|---|
| More subcarriers (48 → 52 data) | 54 → 58.5 Mbps | ✅ yes |
| Higher FEC rate (3/4 → 5/6) | 58.5 → 65 Mbps | ✅ yes |
| Short guard interval (800 → 400 ns) | 65 → 72.2 Mbps | ✅ yes |
| MIMO (up to 4 spatial streams) | 72.2 → 288.9 Mbps | ❌ no |
| 40 MHz bandwidth (108 data subcarriers) | 288.9 → 600 Mbps | ❌ no |
So the open-source release reaches a theoretical 72.2 Mbps single-stream, not the full-11n 600 Mbps (which requires 4×4 MIMO + 40 MHz).


On the MAC side, 802.11n added frame aggregation. There are two flavors: A-MSDU (efficient, but one bit error invalidates the whole aggregate) and A-MPDU (per-subframe headers, so a single error only costs one retransmission, which is the more widely adopted choice).

openwifi supports A-MPDU aggregation experimentally (./wgd.sh 1, which sets test_mode bit 0). A-MSDU is not supported. Background and the full derivation are in the 802.11n app note. For how to enable and verify these features in practice, and where Wi-Fi 6 stands, see Wi-Fi 4 & Wi-Fi 6 Features.
Where the source lives¶
| Component | Location |
|---|---|
| Driver (main) | openwifi/driver/sdr.c, sdr.h |
| Per-block driver APIs | openwifi/driver/{tx_intf,rx_intf,openofdm_tx,openofdm_rx,xpu}/ |
| Side channel (separate module) | openwifi/driver/side_ch/ |
| Register addresses | openwifi/driver/hw_def.h |
| sdrctl ↔ driver glue | openwifi/driver/sdrctl_intf.c |
| sysfs interface | openwifi/driver/sysfs_intf.c |
sdrctl tool source |
openwifi/user_space/sdrctl_src/ |
| Helper scripts & demos | openwifi/user_space/ |
| FPGA IP cores | openwifi-hw/ip/{openofdm_tx,openofdm_rx,tx_intf,rx_intf,xpu,side_ch}/ |
| Board-level FPGA projects | openwifi-hw/boards/<board_name>/ |
One convention to note: a driver file and its FPGA counterpart usually share a name (xpu.c ↔ xpu.v), and each FPGA register is slv_regN in the .v file. The register tables on the sdrctl page always point back to these.
Two communication channels between driver and user space¶
sdrctl: annl80211testmode command, routed through the standardnl80211 → cfg80211 → mac80211path and handled byopenwifi_testmode_cmd()insdrctl_intf.c. Best for issuing commands and reading/writing registers.- sysfs: driver variables exposed as virtual files (via
sysfs_intf.c). Best for statistics and for scripts. On the ZCU102 these files live under/sys/devices/platform/fpga-axi@0/fpga-axi@0:sdr, on other boards under/sys/devices/soc0/fpga-axi@0/fpga-axi@0:sdr.
Both are described in detail on The Linux Driver, including how the register category is packed into the upper 16 bits of the address and which categories never reach the FPGA.