Architecture Overview¶
This page explains how openwifi divides work 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.

The openwifi software modules (top) and FPGA modules (bottom). The module names in this diagram match the source file names (xpu, openofdm_tx, openofdm_rx, tx_intf, rx_intf, side_ch). Knowing these names makes both the code and this wiki easier to navigate.
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), as they do for a commercial SoftMAC chip. openwifi differs in 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 and mac80211): the generic Linux wireless stack. It handles the upper MAC and calls into the driver through a fixed API.
- openwifi driver (
driver/sdr.cand related files): 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 to 6 GHz). It connects to the FPGA over the Analog Devices RF interface, and the FPGA controls it in real time over an SPI link.
Because the driver registers a normal Linux network interface (sdr0), every tool that works with a commercial card also works with 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 through the mac80211 API¶
The Linux mac80211 subsystem defines a set of callbacks (ieee80211_ops) that every SoftMAC driver implements. This shared contract allows one kernel to drive Wi-Fi chips from dozens of vendors. openwifi's sdr.c implements the subset it needs. For example, tx sends a frame, start and stop run when the NIC goes up or down, and config handles a channel change. get_tsf and set_tsf access the hardware timer, and testmode_cmd serves sdrctl. Around a dozen more callbacks cover the rest. 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. The build creates a separate kernel module for each helper (tx_intf.ko, rx_intf.ko, …). sdr.ko binds to these modules at load time, so wgd.sh inserts all of them.
openwifi is a Linux platform driver, not a PCI or USB driver. It binds to a device-tree node with compatible = "sdr,sdr". The device tree gives Linux the AXI addresses and interrupts of every FPGA block, so porting a board is largely a device-tree exercise. The AD9361 RF chip is driven separately, by the standard Analog Devices IIO driver. The openwifi driver finds it on the SPI bus at probe time and calls into it (ad9361_set_tx_atten, ad9361_do_calib_run). The ADI kernel needs some patches for these calls to work (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. It handles DMA from the processor into per-queue FIFOs and holds the TX BRAM thatopenofdm_txreads the frame from. It also feeds the modulated IQ out to the DAC, applies the per-packet PHY configuration, and hosts the four hardware TX queues.rx_intf: the receive interface. It unpacks the ADC samples into IQ streams foropenofdm_rxand takes the decoded packets and side-channel data back. It then attaches metadata (TSF timestamp, RSSI, length, MCS, FCS status) and DMAs the packets up to the processor.xpu: the "eXtensible Processing Unit," which holds the real-time low MAC. Its timing logic covers the CSMA/CA state machine, NAV, DIFS, SIFS, EIFS, and the TSF timer. It also handles 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.
The side_ch (side channel) module serves the research features (CSI and IQ capture) and is 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 gives openwifi very low latency between the processor and the PHY. It also makes the design fairly platform specific.
The FPGA IP Cores page covers the submodules inside xpu (the CSMA/CA state machine, TSF timer, hardware SPI to the AD9361) and the OFDM transmit and receive chains. It also traces a register write from sdrctl 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 interface IP, DMA engines, and board-level infrastructure, while openwifi inserts its own cores into that design. When porting to a new board, start by comparing openwifi with 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. Notice that tx_intf and rx_intf touch the AD9361 converters. The OFDM cores connect to those interface cores 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, and 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. The packet is passed up whether its FCS (CRC) passes or fails, as long as the current frame-filtering rules allow it. In monitor mode, everything is allowed, including 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()pulls the raw buffer and parses out the inserted metadata. This includes the TSF timestamp, the raw RSSI (which it corrects to dBm per band and channel), the length, the MCS, and the FCS-valid flag. It then hands the packet and its metadata to Linux viaieee80211_rx_irqsafe().
The driver page has the exact 16-byte metadata layout. It also notes that the FCS-OK bit is carried in the last byte of the frame, not 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 the mac80211 metadata:
- length and MCS
- unicast or 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
- 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 that the FPGA generates the right PHY header, and starts a DMA transfer into one of the four FPGA TX queues. The packet may not go out immediately, since the FPGA sends it only 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, meaning whether an ACK was received and how many retransmissions happened. It reports the result 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, so Wireshark and tcpdump show a consistent TSF timestamp. The same TSF value lets you match side-channel data (CSI, IQ) to specific packets, since they share one time base. See this discussion for the matching recipe.
RF and baseband: frequency and 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.
- No LO leakage during receive. Turning off the TX chain keeps its local oscillator from leaking into the receiver. For CSI radar and loopback, openwifi instead unmutes the receiver during its own transmission.
- Fast TX-to-RX turnaround (about 0.6 µs) helps meet the tight SIFS and hardware ACK timing. SIFS is 10 µs in 2.4 GHz and 16 µs in 5 GHz.
The IQ rate between the AD9361 and the FPGA is 40 Msps. The FPGA decimates and interpolates it to the 20 Msps that 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 replaced the older "offset tuning" approach. It 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 and 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 |
The open-source release therefore reaches a theoretical 72.2 Mbps single-stream. Full 11n reaches 600 Mbps, but only with 4×4 MIMO and 40 MHz channels.


On the MAC side, 802.11n added frame aggregation in two forms. A-MSDU is efficient, but one bit error invalidates the whole aggregate. A-MPDU, the more widely adopted form, keeps a header per subframe, so a single error costs only one retransmission.

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>/ |
A driver file and its FPGA counterpart usually share a name (xpu.c and 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 and 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.
The Linux Driver describes both in detail. It also shows how the register category is packed into the upper 16 bits of the address, and which categories never reach the FPGA.