FPGA Development¶
This page covers building and modifying the FPGA design in the openwifi-hw repository.
The design is built on top of the Analog Devices HDL reference designs (see the FPGA overview for how that fits together and where the IP Cores reference picks up). For anything that isn't openwifi-specific, the ADI wiki is often the fastest source of answers.
Prerequisites¶
First set up the shared host toolchain on the Environment Setup page (Vivado 2022.2 with Vitis, Ubuntu packages such as libtinfo5, and the XILINX_DIR and BOARD_NAME environment variables). FPGA builds also need the evaluation license of the Xilinx Viterbi Decoder installed into Vivado. This evaluation license is why a running board's decoder halts after about 2 hours (see Troubleshooting).
Set export XILINX_DIR=/opt/Xilinx and export BOARD_NAME=<your board> before building. If the software and FPGA repos disagree on the Vivado version, match the one the repo README states at the time you build (see Environment Setup).
Building the bitstream¶
Run these from the openwifi-hw repo root unless noted.
-
Prepare the ADI HDL library (once ever):
./prepare_adi_lib.sh $XILINX_DIR -
Prepare ADI board-specific IP (once per board):
./prepare_adi_board_ip.sh $XILINX_DIR $BOARD_NAME # You can stop it once it prints "Building <project> project [..." (<project> depends on the board) -
Pull in openofdm_rx (once, and again whenever openofdm is updated):
./get_ip_openofdm_rx.sh -
Generate the IP repo and top-level project (takes a while):
cd boards/$BOARD_NAME/ ../create_ip_repo.sh $XILINX_DIRExpect this to take tens of minutes. When it finishes, the bitstream and the
.xsahardware handoff file sit under the board project directory, ready for thesdk_update.shstep below to collect.If Vitis HLS errors with
'2xxxxxxxxx' is an invalid argument. Please specify an integer value, see Troubleshooting, which applies the fix from Xilinx article 76960. -
Stash the outputs where the software build can find them:
cd boards ./sdk_update.sh $BOARD_NAME $OPENWIFI_HW_IMG_DIRThis copies the FPGA image (
.xsa,.ltx) and git info into$OPENWIFI_HW_IMG_DIRso the openwifi (software) build can pick it up (see below).
GUI iteration
The create_ip_repo.sh step above already runs Vivado for you. To iterate by hand, open the project in the Vivado GUI and generate the bitstream:
source ../openwifi.tcl
# then in the GUI: Generate Bitstream
# then: File → Export → Export Hardware → Include bitstream → Finish
Prebuilt outputs for each board live in the openwifi-hw-img repo under boards/$BOARD_NAME/sdk/ (bitstream, ILA .ltx, init files) if you'd rather not synthesize.
Updating the FPGA image on a running board¶
If you only want to swap the FPGA bitstream (just built, or taken from openwifi-hw-img) without a full rebuild:
cd openwifi/user_space
./boot_bin_gen.sh $XILINX_DIR $BOARD_NAME $OPENWIFI_HW_IMG_DIR/boards/$BOARD_NAME/sdk/system_top.xsa
scp ./system_top.bit.bin root@192.168.10.122:openwifi/
Once system_top.bit.bin is in the board's openwifi/ directory, wgd.sh loads it before loading the driver (see Reloading driver and FPGA without rebooting).
Modifying an IP core¶
IP core projects live in ip/<ip_name>/ (for example xpu, tx_intf, rx_intf, openofdm_tx, openofdm_rx, side_ch). To open one as its own Vivado project:
cd ip/<ip_name>
../create_vivado_proj.sh $XILINX_DIR <ip_name>.tcl
Make your changes there, then re-integrate into the board design by re-running ../create_ip_repo.sh $XILINX_DIR from the board directory. If a complex change breaks create_ip_repo.sh, read create_ip_repo.sh / ip_repo_gen.tcl and adjust them (for example to include newly added files).
Simulating an IP core¶
Most cores ship a top-level testbench (*_tb.v), which is the fastest way to develop without hardware. As a quick start, using openofdm_rx as the example:
- Create the IP's Vivado project (as above):
./create_vivado_proj.sh $XILINX_DIR openofdm_rx.tcl. - In Vivado: Sources → Simulation Sources → sim_1 → dot11_tb.
- SIMULATION → Run Simulation → Run Behavioral Simulation, then Run All (F3). The first run is slow because sub-IP cores compile once. Later runs are fast.
- After editing design files, use Relaunch Simulation.
The FPGA Simulation and Testbenches page covers this in full. It describes the IQ input format, the test vectors, and which signals and dumped files to inspect. It also covers batch simulation and the transmitter and block-level unit tests.
Conditional compilation with Verilog macros¶
create_vivado_proj.sh accepts extra arguments that become `define macros in <ip_name>_pre_def.v, letting you enable or disable code blocks (ILA debug cores, feature variants). The argument order:
| Position | Meaning | Example |
|---|---|---|
| First | BOARD_NAME |
zc706_fmcs2 |
| Second | NUM_CLK_PER_US |
100 for 100 MHz |
| Third through seventh | Your own macro names, which become `define IP_NAME_<NAME> |
ENABLE_DBG |
For openofdm_rx, the third argument instead selects the simulation SAMPLE_FILE, changeable later in the pre_def file.
When building the top-level project, pass the same macros to create_ip_repo.sh so the IP is compiled identically:
./create_ip_repo.sh $XILINX_DIR \
xpu ENABLE_DBG tx_intf ENABLE_DBG rx_intf ENABLE_DBG \
openofdm_tx ENABLE_DBG openofdm_rx ENABLE_DBG side_ch ENABLE_DBG
(That example turns on the ILA debug cores in every core. Only xpu, tx_intf, rx_intf, openofdm_tx, openofdm_rx, and side_ch accept macros here.)
Pair these FPGA macros with the driver's conditional-compile arguments (see Software Development Workflow) to build matched driver+FPGA variants, then package them with drv_and_fpga_package_gen.sh.
Changing the baseband clock¶
The default baseband clock is 100 MHz, set by NUM_CLK_PER_US at the top of boards/openwifi.tcl in the openwifi-hw repo. Available options depend on the board: 240/100 MHz on ZCU102, 100/200 MHz on ZC706 and ADRV9361-Z7035, and 100 MHz elsewhere. Change the value and re-run openwifi.tcl to regenerate the project.
Adapting the design for 10 MHz or 2 MHz channels¶
The openwifi README lists 10 MHz for 802.11p-style experiments and 2 MHz for sub-GHz 802.11ah-style experiments. The released bitstream and driver implement 20 MHz OFDM. There is no runtime bandwidth setting or ready-made narrow-channel image. The steps below identify the changes needed for a matched experimental build. They are derived from the current source and guidance from the maintainer. Neither target has been verified as a working release.
| Target channel | OFDM sample rate | AD9361 sample rate with the existing 2:1 FPGA decimator and interpolator | OFDM symbol with guard interval | Legacy preamble and SIGNAL |
|---|---|---|---|---|
| 10 MHz | 10 Msps | 20 Msps | 8 µs | 40 µs |
| 2 MHz | 2 Msps | 4 Msps | 40 µs | 200 µs |
The table assumes the same 64-point OFDM waveform scaled in time. It does not turn the design into a complete 802.11p or 802.11ah implementation.
- Trace the active FPGA sample pacing. In the current
rx_iq_intf.v,RX_IQ_RATE_ADAPTATION_BYPASSforwardsbw20_iq_validas the receiver sample strobe. During normal RF reception,rx_intf.vgets that valid signal fromadc_intf.v. TheNUM_CLK_PER_SAMPLEcounter path inrx_iq_intf.vis inactive. ChangingSAMPLING_RATE_MHZinip/board_def.vor widening those five-bit counters alone leaves the active RX sample timing unchanged. Verify the sample-valid rate through the active RX path and review other FPGA logic that assumes 20 Msps. KeepNUM_CLK_PER_USat the board-supported FPGA clock. It sets FPGA processing speed and microsecond timers, not RF channel width. - Keep the RF and FPGA sample rates matched. The RX path in
adc_intf.vkeeps every second AD9361 sample. The TX path indac_intf.vinserts a zero between baseband samples. With those paths unchanged, use the AD9361 rates in the table. Check their valid signals and FIFO behavior in simulation after changing clocks. A direct one-to-one RF and baseband rate instead requires changing both paths and their handshakes, as the maintainer describes in issue #155. - Prepare a matching AD9361 filter and initialization script. Copy
rf_init_11n.shand replace its 40,000,000 Hz input and output sampling-frequency values with 20,000,000 Hz for a 10 MHz channel or 4,000,000 Hz for a 2 MHz channel. Design a new.ftrfile for that rate and occupied spectrum, using the shipped filter file as a format example. Set both RF bandwidth values in the script to match the new filter design. The shipped.ftrfile and RF bandwidth values are for the 20 MHz waveform.wgd.shcallsrf_init_11n.shbefore loading the modules, so make it call your variant. The IIO sysfs values should read back at the intended rates after the script runs. - Update and rebuild the driver. In
sdr.c,priv->rf_bw = 40000000selects the 40 MHz RX and TX interface configuration. The probe code accepts only 20 MHz and 40 MHz values, and rejects other values. Extend that mode selection and its interface settings for the new AD9361 rate. The calls that would set the AD9361 clock chain and RF bandwidth frompriv->rf_bware disabled under#if 0, so the current probe path leaves the script's RF settings in place. Check those settings after loading the modules. The driver also reportsRATE_INFO_BW_20for received packets. For 10 MHz operation exposed through Linux, add the appropriate 10 MHz channel-width capability and report the actual RX width. The bundlednl80211.hlists 10 MHz but does not enable it in the openwifi driver. The kernel and regulatory channel list must also permit the selected frequency. For 2 MHz, Linux has no equivalent 2 MHz OFDM channel width in this driver, so treat the Linux interface as an experimental control path and verify the actual width at RF. - Rework MAC timing and rate reporting. The table gives the new symbol and legacy preamble durations. The ACK, CTS, timeout, and duration calculations in
sdr.c,xpu.c, andtx_intf.ccontain values calibrated for 20 MHz. Update them for the target waveform, and check the Linux rate table so it does not advertise 20 MHz data rates. The maintainer also warns that RSSI and CCA calibration changes with the RF and FPGA configuration. - Build and measure the matched image. Rebuild the FPGA as described above, rebuild the driver as described in Software Development Workflow, and load both on each test board. Check the AD9361 sampling-frequency and RF-bandwidth sysfs values after
wgd.sh, measure the transmitted spectrum and packet duration, then test receive and ACK timing between two boards configured identically. Use a shielded setup or an attenuated cable link for the first tests. Changing theiwfrequency only changes the center frequency and does not demonstrate a narrow channel.
After loading the matched FPGA and driver on a board, read back the RF settings from the AD9361 IIO device:
for device in /sys/bus/iio/devices/iio:device*; do
if [ -f "$device/in_voltage_rf_bandwidth" ]; then
grep -H . "$device"/{in,out}_voltage_{sampling_frequency,rf_bandwidth}
fi
done
For 10 MHz vehicular use, a working narrow OFDM waveform still needs the appropriate 802.11p channel, MAC timing, and regulatory behavior. For 2 MHz sub-GHz use, scaling legacy OFDM does not implement the 802.11ah S1G PHY or its MAC. The 802.11p discussion reports that a 10 MHz FPGA and RF modification did not establish a working ad-hoc link, so verify the complete link before treating either target as supported.
High-Level Synthesis (HLS) modules¶
Two receiver modules, channel estimation (ch_gain_cal) and equalization (equalizer), are also available as C++ that Vitis HLS turns into Verilog, which can speed up algorithm development.
To build with the HLS receiver: follow the bitstream build up to before generating ip_repo, then switch openofdm_rx to the HLS branch:
cd ip/openofdm_rx
git checkout dot11zynq_hls
Continue the build. Before generating the bitstream, select openofdm_rx under IP Status and click Upgrade Selected.
To modify the HLS code:
- Run
./get_ip_openofdm_rx.shand check outdot11zynq_hls. - In Vitis HLS, create a project importing the source files (except
*_test.cpp) from thech_gain_calorequalizerfolder. Choose that module as top level and its*_test.cppas testbench, and select the FPGA part for your board. - Run C-sim and co-sim. When they pass, Export RTL produces a ZIP whose
hdl/verilogfolder replaces the corresponding folder underopenwifi-hw/ip/openofdm_rx/hls/.../hdl/verilog/. - Update
openofdm_rx.tclto include the new files (example). - If you changed the top-level function arguments, wire them up in
dot11.v. - Resume the normal build from "generate ip_repo."
For background, see the FCCM 2023 poster.
Migrating to a new Vivado or ADI release¶
Two approaches:
- Vivado auto-upgrade. Create the design in the current Vivado version, then open it in the target version and let Vivado upgrade it. Export the upgraded project as a
.tcland diff it against the originalopenwifi.tclto see what changed. openwifi's own commits onopenwifi.tclshow how past migrations were handled. -
Start fresh from the new ADI reference design, then add openwifi IP. Export the openwifi IP hierarchy from the current design with
write_bd_tcl. Thensourceit into the new ADI reference design and instantiate it:write_bd_tcl -hier_blks [get_bd_cells /hier_mig] ./mig_hierarchy.tcl source ./mig_hierarchy.tcl create_hier_cell_hier_mig / my_new_hierarchy
The primary reference is Xilinx UG994 (Designing IP Subsystems Using IP Integrator).
Porting to a new board¶
openwifi's baseline is tag 2022_R2 of the ADI HDL reference designs (the adi-hdl submodule pin). To port, diff openwifi against the matching ADI reference design, then replicate those changes on your target board.
- Open the ADI reference design for your platform (for example
hdl/projects/fmcomms2/zc706) and the corresponding openwifi board design (openwifi-hw/boards/zc706_fmcs2) side by side. - Use Open Block Design and compare both the diagram and the Address Editor. That's where openwifi's additions show up.
- The addresses and interrupts of every FPGA block hooked to the ARM bus must be reflected in the board's device tree,
openwifi/kernel_boot/boards/<board_name>/devicetree.dts. Linux parsesdevicetree.dtbat boot to discover these blocks. (openwifi obtains a.dtsby runningdtcon the ADI image's.dtb, then edits it to match the added or modified blocks.) The device-tree half of a port is covered step by step in Porting the device tree to a new board. - Study the image-build scripts (see Software Development Workflow) to understand how
devicetree.dtb,BOOT.BIN, and the kernel come together into a bootable SD image.
Debugging on hardware¶
Use the Xilinx ILA (Integrated Logic Analyzer) to watch internal FPGA signals in real time. It is the clearest way to understand the low-MAC and interface state machines in xpu, tx_intf, and rx_intf. Enable the debug macros (see conditional compilation above) to insert ILA cores. The prebuilt .ltx in openwifi-hw-img matches the shipped bitstreams. Background and an example are in openwifi-hw issue #39. See also the GPIO and LED map. It routes signals such as tx_bb_is_ongoing, tx_rf_is_ongoing, fcs_ok, and demod_is_ongoing to board LEDs and PMOD pins, so you can probe them with a scope or logic analyzer.