Skip to content

The FPGA IP Cores

openwifi's custom FPGA IP cores live in openwifi-hw/ip/. This page traces the signal path through those cores and explains what each one does and how it connects to the driver. The register map is documented on the sdrctl & Runtime Control page. For the build, simulation, and porting workflow, see FPGA Development.

The signal chain

The six cores form a transmit chain and a receive chain that meet at the AD9361 RF front end. The xpu real-time MAC coordinates both chains, and side_ch taps the receiver for research capture.

tx_intf and rx_intf each sit on both sides of an OFDM core rather than in a straight line between the processor and the converters. tx_intf receives frame bytes over DMA, passes them to openofdm_tx, then drives the DAC with the returned modulated IQ. rx_intf passes ADC IQ to openofdm_rx, then DMAs the decoded bytes to Linux.

xpu · real-time MAC CSMA/CA · TSF timer · ACK gen/rx · CCA · packet filter · 4× TX-queue gating queue gating · ACK into TX BRAM ADC IQ for CCA / RSSI tx start / done demod status DMA DMA Linux tx_intf TX BRAM · DAC feed DAC AD9361 RF ADC rx_intf ADC unpack · metadata Linux frame bytes IQ IQ bytes · fcs_ok openofdm_tx FEC · mod · IFFT openofdm_rx sync · eq · Viterbi CSI · equalizer · raw IQ taps side_ch · CSI / IQ capture per-packet CSI, equalizer, raw IQ DMA → Linux
The openwifi FPGA signal chain. Solid boxes are openwifi IP cores (teal = transmit, indigo = receive), and dashed pills are the external AD9361 RF front end. The row through the middle is the path to and from the radio, with each OFDM core hanging off its interface core below it. The xpu real-time MAC orchestrates timing and channel access, while side_ch taps the receiver to stream CSI/IQ to the host.

Every core is an AXI4-Lite slave for control (register bank named *_s_axi.v) and, where it moves sample data, an AXI-Stream master/slave for DMA. A driver file and its FPGA core usually share a name (xpu.c ↔ xpu.v), and every register the driver writes (hw_def.h) has a matching slv_regN in the core's _s_axi.v.

Core Role Register bank size Testbenches
xpu Real-time MAC (CSMA/CA, ACK, TSF, filtering, queue gating) 64 AXI-Lite regs unit: mv_avg, fifo_sample_delay
openofdm_tx 802.11 OFDM transmitter 32 regs full transmitter: dot11_tx_tb + test_vec/
openofdm_rx 802.11 OFDM receiver 32 regs (in the openofdm submodule) full receiver: dot11_tb
tx_intf DAC-side interface + TX BRAM + CSI fuzzer 32 regs none
rx_intf ADC-side interface + RX DMA 32 regs unit: adc_intf
side_ch CSI / raw-IQ capture side channel 32 regs none

xpu, the real-time MAC

xpu (sometimes read as "transceiver" or "eXtensible processing unit") is the central core of openwifi. Its register file xpu_s_axi.v (48 KB) is twice the size of any other core's register bank. By handling microsecond-scale operations in FPGA fabric, it lets openwifi meet 802.11 timing that a software MAC cannot.

What lives inside (ip/xpu/src/, 21 Verilog files):

  • csma_ca.v: the CSMA/CA (DCF) state machine itself. Its inputs are the NAV, DIFS, and EIFS enable flags, the contention-window exponent, and the SIFS, slot, DIFS, and backoff timing parameters. It also takes the MAC-address match and TX-status feedback, and uses them all to arbitrate channel access exactly per the 802.11 distributed coordination function. This is the hardware DCF, offloaded from mac80211.
  • tx_control.v: sequences packet transmission (the largest logic file at 30 KB).
  • tsf_timer.v: the 64-bit TSF counter that timestamps received packets and drives timing-critical MAC operations. Readable via xpu registers 58 and 59, loadable via registers 2 and 3.
  • pkt_filter_ctl.v: packet address and type filtering (the FPGA side of openwifi_configure_filter(), which monitor mode opens fully).
  • phy_rx_parse.v: parses PHY-header fields coming out of the receiver.
  • rssi.v, iq_rssi_to_db.v, cca.v, dc_rm.v, mv_avg*.v: clear channel assessment (carrier sensing) and RSSI measurement (moving-average power, DC removal).
  • time_slice_gen.v: generates the gating for the four hardware TX queues (slice_en[0:3]), the mechanism behind MAC-address time slicing.
  • spi.v: an SPI master that controls the AD9361 TX chain in real time. It turns the TX LO or RF switch on just before a packet and off just after (see Architecture).
  • cw_exp.v, tx_on_detection.v, edge_to_flip.v, fifo_sample_delay.v, n_sym_len14_pkt.v: contention-window exponent, TX-onset detection, and assorted timing and FIFO helpers.

xpu connects to both the ADC path (ddc_i/q, mute_adc_out_to_bb) and the demodulator (demod_is_ongoing, pkt_header_valid, fcs_ok, pkt_rate, pkt_len). This allows it to implement hardware ACK generation and reception, retransmission, and CCA. It is addressed by the driver as register space xpu (category 6) and its git build revision is readable at register 63.


openofdm_tx, the OFDM transmitter

openofdm_tx turns a MAC frame into baseband IQ samples. It reads bytes from a 64-bit-wide, 1024-deep TX BRAM shared with tx_intf and xpu. The 802.11 transmit chain applies scrambling, convolutional encoding, puncturing and interleaving, modulation mapping, pilot and preamble insertion, and an IFFT.

Notable source (ip/openofdm_tx/src/, 28 files):

  • dot11_tx.v: the 802.11 TX datapath FSM.
  • The IFFT pipeline: ifftmain.v, ifftstage.v, butterfly.v, hwbfly.v, and partial-product multipliers (bimpy.v, longbimpy.v).
  • convenc.v + punc_interlv_lut.v: convolutional encoder and the punctured-interleave lookup ROMs. punc_interlv_lut.v (128 KB) is the largest single file in the whole IP tree. It holds the FEC puncturing and interleaving patterns for every 802.11 MCS.
  • Preamble ROMs: l_stf_rom.v and l_ltf_rom.v (legacy short and long training fields), and ht_stf_rom.v and ht_ltf_rom.v (802.11n HT training fields).
  • modulation.v, crc32_tx.v, bitreverse.v, dpram.v, axi_fifo_bram.v: the modulation mapper, frame CRC, and buffering.
FRAME AND BIT DOMAIN FREQUENCY-DOMAIN IQ TIME-DOMAIN IQ TX BRAM 64-bit words 10-bit read address Frame bit source PLCP · service · PSDU FCS · tail · pad Scrambler + FCS 7-bit LFSR CRC-32 appended Convolutional code 1 bit → 2 coded bits BRAM FIFO decouples FSMs Puncture + interleave rate-selected LUT symbol bit RAM 64-point IFFT frequency bins → time samples 64-bin assembly data + four pilots DC + guard bins = 0 Constellation mapper BPSK · QPSK 16-QAM · 64-QAM Symbol + CP FIFOs all 64 IFFT samples last 16 or 8 copied for CP CP / symbol mux 16 + 64 legacy 8 + 64 short GI Preamble mux L-STF/L-LTF ROMs HT-STF/HT-LTF ROMs IQ output 16-bit I + 16-bit Q valid / ready backpressure training-field ROMs result_iq_ready advances every output source. Deasserting it holds the current sample and stalls forwarding.
The implemented dot11_tx path. Three state machines collect and encode frame bits, generate OFDM symbols, and forward packet samples. IQ first exists after constellation mapping. It is frequency-domain IQ before the IFFT and time-domain IQ after the IFFT. CP is the cyclic prefix.

Addressed as register space tx (category 5). Scrambler seeds are at regs 1/2 (default 127).


openofdm_rx, the OFDM receiver

The receive counterpart. It detects the preamble, synchronizes, estimates the channel, equalizes, and Viterbi-decodes. It then hands the parsed bytes (byte_in, fcs_ok, pkt_rate, pkt_len) up to xpu and rx_intf. It is the core that most affects receiver sensitivity, documented at around −92 dBm at MCS0 and −73 dBm at MCS7 on FMCOMMS2 at 2.4 GHz.

Unlike the other five cores, openofdm_rx is a git submodule. It lives in the separate openofdm repo, on branch dot11zynq (or dot11zynq_hls for the HLS variant). A fresh openwifi-hw clone has an empty ip/openofdm_rx/ until you run ./get_ip_openofdm_rx.sh. Its simulation entry point is the dot11_tb testbench (dot11_inst → ofdm_decoder_inst → viterbi_inst). The testbench hierarchy also contains the Xilinx Viterbi decoder, whose evaluation license halts a running board's receiver after about 2 hours (see Troubleshooting).

TIME-DOMAIN IQ AND SYNCHRONIZATION FREQUENCY-DOMAIN IQ BITS AND BYTES Sample input signed 16-bit I signed 16-bit Q + strobe Short synchronization 16-sample autocorrelation power plateau detection Long synchronization sample RAM + LTS correlation FFT-window selection Frequency correction 64-sample delayed product complex sample rotation 64-point FFT 64 corrected samples → 64 subcarriers The same strobed IQ stream drives short and long synchronization. Post-FFT rotation phase correction per bin frequency-dependent adjustment Channel estimate average two LTS symbols optional subcarrier smoothing Equalization pilot common-phase correction channel-corrected constellation Side-channel and debug access 32-bit CSI + valid · 32-bit equalizer IQ + valid Demodulation BPSK to 64-QAM hard or soft bits Deinterleave rate-selected order puncture erasures Viterbi decoder AXI-stream Xilinx IP coded bits → data bits Descramble reverse data LFSR bit stream Bytes + status 8-bit byte + strobe length · rate · CRC/FCS sample_in_strobe marks valid IQ samples. Each later stage emits its own strobe or AXI-stream valid signal.
The receive path at openofdm commit 2bb3ad1. The top-level dot11 core keeps IQ packed as {I[15:0], Q[15:0]}. IQ is time-domain before sync_long's FFT and frequency-domain after it. The equalizer's CSI and corrected constellation outputs are available outside the decoder, while payload access is an 8-bit byte stream.

Addressed as register space rx (category 4). Its signal_watchdog submodule powers the openofdm_rx watchdog counters, and the driver reads its build revision at register 31. See FPGA Development → HLS for building the channel-estimation and equalizer stages from C++ via Vitis HLS.


tx_intf, the transmit DAC interface

Sits between the OFDM transmitter and the AD9361 DAC. It owns the 64-bit-wide, 1024-deep TX BRAM that openofdm_tx reads from. It packages transmit I/Q for the DAC through ADI's axi_ad9361_dac_dma and util_ad9361_dac_upack blocks. It also streams frame data in from the driver over AXI-Stream DMA, and raises the TX-done interrupt and LEDs (tx_itrpt_led, tx_end_led).

This core also holds two pieces used in research:

  • csi_fuzzer.v: injects a controlled artificial channel response into the transmitter, the hardware behind the CSI fuzzer privacy feature (tx_intf register 5).
  • ht_sig_crc_calc.v: computes the CRC for the 802.11n HT-SIG field.

Other files in this core are tx_bit_intf.v (the raw-bit, PHY-level TX interface and the largest file in this core), dac_intf.v, tx_iq_intf.v (which holds the 512-sample arbitrary-IQ FIFO), and tx_status_fifo.v. Addressed as register space tx_intf (category 3). See the tx_intf register table.


rx_intf, the receive ADC interface

rx_intf unpacks raw ADC samples from the AD9361 through ADI's axi_ad9361_adc_dma and util_ad9361_adc_pack, converting them into per-antenna I/Q streams. It adds FCS and sequence-number bookkeeping to received frames (byte_to_word_fcs_sn_insert.v) and drives status LEDs (fcs_ok_led). The core then DMAs packets and their metadata to the processor.

The driver's openwifi_rx_interrupt() parses the 16-byte metadata header that rx_intf prepends to each received packet. The header holds the TSF timestamp, rssi_half_db, AGC status, length, and rate index, and the FCS-OK bit sits at the end of the frame (see The Linux Driver). It also exposes 8 debug trigger_out signals and supports the FPGA-internal loopback path (rx_intf register 3 selects "IQ from tx_intf" instead of "IQ from the ADC") used by self-loopback testing. Addressed as register space rx_intf (category 2), with source in ip/rx_intf/src/ (11 files) and an adc_intf testbench.


side_ch, the CSI and IQ capture side channel

side_ch is openwifi's research capture core. It buffers data from the receiver's I/Q datapath and the OFDM demodulator, then streams it over a DMA channel separate from the normal packet path. With side_ch_ctl, you can forward per-packet CSI, equalizer output, frequency offset, raw IQ, AGC gain, and RSSI to a PC.

Its inputs, read directly from side_ch.v and side_ch_control.v, show what it can reach:

  • TX-side taps (openofdm_tx_iq0/iq1, tx_intf_iq0/iq1)
  • raw ADC-rate I/Q (sample0_in, sample1_in)
  • demodulator status (demod_is_ongoing, long/short_preamble_detected, ht_unsupport, pkt_rate, pkt_len)
  • csi/csi_valid and equalizer/equalizer_valid, the per-subcarrier channel estimate and equalizer coefficients from the OFDM receiver

Every capture carries a timestamp from the shared 64-bit TSF and an RSSI tag, so you can match it to a packet.

side_ch_control.v (36 KB) is the capture and trigger FSM that implements the 32 trigger conditions. A MAX_NUM_DMA_SYMBOL parameter sizes the internal FIFO at 8192 normally. The SIDE_CH_LESS_BRAM macro halves it to 4096 on small FPGAs, so Zynq-7020 boards have a lower capture-length limit.

Unlike the other cores, side_ch is outside the main sdr.ko driver. You load its kernel module side_ch.ko (built by openwifi/driver/side_ch/make_driver.sh) and run side_ch_ctl on demand. See Research Features for the full workflow, and side_ch_ctl and the Side Channel for the command grammar and register map.


How a register write reaches a core

When you run sdrctl dev sdr0 set reg xpu 11 16, the value travels an nl80211 testmode message → openwifi_testmode_cmd() in the driver → the per-core driver API (xpu_api->reg_write) → an AXI-Lite write to slv_reg11 in xpu_s_axi.v. The register category number is fixed across the whole stack. The table of categories is on The Linux Driver.