opendsss ships its test vectors in the repository so the PHY can be re-verified without any external capture or golden bitstream. Every vector is produced by a deterministic Python generator (fixed random seed), so regenerating them is reproducible and the committed reference outputs are stable.
This document covers what the vectors are, how to run the testbenches, and how to regenerate everything. See ARCHITECTURE.md for the module breakdown and RATES_AND_FRAMING.md for the PLCP/framing details the vectors encode.
| Path | Contents |
|---|---|
scripts/dsss_lib.py |
shared bit-exact Python model (CRC engines + frame builder) |
scripts/gen_stim_*.py |
per-module equivalence stimulus generators |
scripts/gen_iq_frame.py |
full-chain 1 Mbps DSSS I/Q frame generator (RX self-test) |
scripts/gen_tx_golden.py |
TX modulator golden-oracle generator |
tb/ |
SystemVerilog testbenches (.sv) and VHDL golden testbenches (.vhd) |
golden_vhdl/ |
the original bladeRF-wiphy VHDL sources used as the equivalence golden |
verilog/ |
the SystemVerilog PHY sources + the Makefile sim runner |
vectors/ |
committed stimulus, expected, and reference-output files |
vectors/tx_golden/ |
committed TX golden oracle (bits, symbols, I/Q, meta) |
scripts/dsss_lib.py is the single source of truth for
all frame-level vectors. It contains:
plcp_crc16()/plcp_endian()— the bit-serial PLCP CRC-16, transcribed verbatim from the VHDL (wlan_dsss_plcp_crc.vhd).crc32_hw()— the byte-parallel payload FCS CRC-32, transcribed fromwlan_crc.vhd.build_descrambled_frame()— assembles the descrambled bit stream the framer must see:[preamble ones][SFD 0xF3A0][PLCP: SIGNAL,SERVICE,LENGTH,CRC-16][payload][FCS-32], all fields LSB-first in time.
Because the CRC models are transcribed from the same VHDL the RTL was ported from,
a frame these functions build decodes crc_correct in the real framer — which is how
the models are ultimately validated. Both gen_iq_frame.py and gen_tx_golden.py
import dsss_lib, so the RX stimulus and the TX golden are guaranteed consistent.
opendsss is a SystemVerilog port of the bladeRF-wiphy VHDL. Each leaf module is checked against its VHDL ancestor by running both through identical stimulus and diffing the dumped outputs. The VHDL golden runs under GHDL; the SystemVerilog port runs under Vivado xsim.
| Module | SV source | VHDL golden | Stimulus | Reference outputs |
|---|---|---|---|---|
p_norm |
verilog/dsss_p_norm.sv |
golden_vhdl/wlan_dsss_p_norm.vhd |
vectors/stim_p_norm.txt |
vectors/out_{vhdl,sv}_p_norm.txt |
despreader |
verilog/dsss_despreader.sv |
golden_vhdl/wlan_dsss_despreader.vhd |
vectors/stim_despreader.txt |
vectors/out_{vhdl,sv}_despreader.txt |
peak_finder |
verilog/dsss_peak_finder.sv |
golden_vhdl/wlan_dsss_peak_finder.vhd |
vectors/stim_peak_finder.txt |
vectors/out_{vhdl,sv}_peak_finder.txt |
demodulator |
verilog/dsss_demodulator.sv |
golden_vhdl/wlan_dsss_demodulator.vhd |
vectors/stim_demodulator.txt |
vectors/out_{vhdl,sv}_demodulator.txt |
controller |
verilog/dsss_controller.sv |
golden_vhdl/wlan_dsss_rx_controller.vhd |
vectors/stim_controller.txt |
vectors/out_{vhdl,sv}_controller.txt |
plcp_crc |
verilog/dsss_plcp_crc.sv |
golden_vhdl/wlan_dsss_plcp_crc.vhd |
vectors/stim_plcp_crc.txt |
vectors/out_{vhdl,sv}_plcp_crc.txt |
crc (FCS-32) |
verilog/dsss_crc.sv |
golden_vhdl/wlan_crc.vhd |
vectors/stim_crc.txt |
vectors/out_{vhdl,sv}_crc.txt |
The datapath and CRC leaves above are checked bit-exact: the harness diffs
out_vhdl_<mod>.txt against out_sv_<mod>.txt (after skipping a warm-up region so
pipeline fill is not compared) and both must match cycle-for-cycle. The stimuli use
fixed seeds and deliberately drive full-range I/Q, valid bubbles, and counter wraps
to exercise the truncating-resize, hold, and mod-20 finger-sweep paths.
The framer (verilog/dsss_framer.sv, golden golden_vhdl/wlan_dsss_rx_framer.vhd,
stimulus vectors/stim_framer.txt) is verified by its decoded result rather than a
raw cycle dump: vectors/framer_expected.txt records the payload bytes and
n_data_bytes the framer must recover from the crafted bit stream.
scripts/gen_iq_frame.py is a minimal 802.11b 1 Mbps
long-preamble DSSS transmitter that produces a complete I/Q waveform to drive the
whole RX chain (p_norm → despreader → peak_finder → demodulator → framer) end to
end. It writes:
vectors/stim_dsss_rx.txt— the I/Q stimulus, onei q validtriple per line.vectors/dsss_rx_expected.txt— thepayload_bytesline the decode must reproduce.
The default chain testbench tb/tb_chain_fb.sv reads
../vectors/stim_dsss_rx.txt and drives dsss_rx_fb — the integrated Farrow-timing
receiver (see FARROW_DESIGN.md). It takes +stim= / +out=
plusargs (defaulted by the Makefile) and writes a machine-parseable result file
(framed / length / datarate / crc / done / nbytes / bytes); a pass is crc 1
(crc_correct at framer_done) with the recovered bytes equal to
dsss_rx_expected.txt. This is what make sim runs out of the box.
The testbenches tb/tb_dsss_rx.sv and
tb/tb_chain_param.sv drive the plain dsss_rx core
instead of dsss_rx_fb. That module lives in verilog/dsss_rx.sv, which is not
in the default dsss_rx_modules.list compile order; add it to the list to build
those two testbenches. The same +stim= / +out= result format feeds the impairment
sweeps generated by scripts/gen_iq_impaired.py.
scripts/gen_tx_golden.py emits the bit-exact
reference the dsss_tx modulator must reproduce for a 1 Mbps DBPSK long-preamble
frame (128 SYNC ones), staged so each transform can be checked independently. Files
land in vectors/tx_golden/:
| File | Stage | Contents |
|---|---|---|
tx_bits_D.txt |
A | descrambled frame bits D(n) |
tx_bits_C.txt |
B | scrambled/coded bits C(n) = D(n) ^ C(n-4) ^ C(n-7) |
tx_symbols.txt |
C | DBPSK signs, +1 / -1 per symbol |
tx_iq.txt |
D | modulator-output oracle — frame-body i q per line, 20 beats/symbol (two 40 MSPS samples packed per beat = 40 samples/symbol on air) |
tx_stim_rx.txt |
E | i q 1 with lead-in/out zero pad — drives dsss_rx for a TX→RX round-trip |
tx_payload_hex.txt |
— | payload bytes as hex, for $readmemh in a TB |
tx_golden_meta.json |
— | params + derived values (pkt_len, length_us, FCS, counts) |
dsss_tx_pulse_lut.svh |
D | SystemVerilog localparam Barker pulse LUT for the 0-DSP sign-select spreader |
gen_tx_golden.py self-verifies the frame in Python before writing (symbol→C demod,
C→D descramble, all-ones preamble, SFD 0xF3A0, PLCP CRC-16, LENGTH, payload, FCS).
The dsss_tx RTL output must match tx_iq.txt sample-for-sample, and feeding
tx_stim_rx.txt back through dsss_rx must still decode crc_correct == 1.
Note the deliberate preamble difference:
gen_iq_frame.pyuses a 64-one preamble (a compact RX self-test), whilegen_tx_golden.pyuses the spec 128-one long preamble. Both decode end to end.
From the repository root, generate a 16-octet frame and run it through the RX:
# 1. generate the vector (writes vectors/stim_dsss_rx.txt + vectors/dsss_rx_expected.txt)
PAYLOAD_N=16 python3 scripts/gen_iq_frame.py
# 2. run the default chain testbench (tb_chain_fb) under Vivado xsim
cd verilog
make simPAYLOAD_N selects the number of data octets (default 16); the seed is fixed so the
payload is reproducible. The generator prints the payload it wrote; make sim
(default TB=tb_chain_fb, STIM=../vectors/stim_dsss_rx.txt) writes
tb_chain_fb_out.txt with the parsed length / datarate, the recovered bytes,
and crc. Confirm the result shows crc 1 and that the recovered bytes match the
payload_bytes line in vectors/dsss_rx_expected.txt.
Each generator is standalone and deterministic; run with scripts/ on the path (which
happens automatically when invoked as python3 scripts/<name>.py from the repo root):
# per-module equivalence stimuli
for m in p_norm despreader peak_finder demodulator controller plcp_crc crc framer; do
python3 scripts/gen_stim_$m.py
done
# full-chain RX I/Q frame + expected payload
python3 scripts/gen_iq_frame.py
# TX modulator golden oracle (vectors/tx_golden/)
python3 scripts/gen_tx_golden.pyOptional knobs (environment variables): PAYLOAD_N on the frame generators,
and N_PREAMBLE_ONES / PAYLOAD_N / SEED / N_PAD on gen_tx_golden.py.
Regenerating does not change results unless you change a knob — the outputs are
byte-stable across runs.
The committed sim runner is the Vivado-xsim Makefile in
verilog/Makefile (the DSSS cores are behavioral
SystemVerilog and are validated under the openwifi xsim toolchain). Source your
Vivado settings64.sh first, then from verilog/:
| Command | Effect |
|---|---|
make rx |
compile-check the RX PHY core (dsss_rx_modules.list, top dsss_rx_fb) |
make tx |
compile-check the TX PHY core (dsss_tx_modules.list, top dsss_tx) |
make sim TB=<tb> |
compile + elaborate + run a testbench from ../tb |
make clean |
remove xsim work products |
make sim defaults TB=tb_chain_fb, STIM=../vectors/stim_dsss_rx.txt, and writes
<tb>_out.txt, so it runs out of the box. tb_chain_fb drives the integrated
dsss_rx_fb core, which is in the default compile list. tb_dsss_rx and
tb_chain_param drive the plain dsss_rx core and require adding dsss_rx.sv to
dsss_rx_modules.list first (see above). Examples:
make sim # default TB=tb_chain_fb: full-chain RX (Farrow) self-test -> tb_chain_fb_out.txt
make sim TB=tb_chain_fb # same, stated explicitlyThe verilog/ module lists (dsss_rx_modules.list, dsss_tx_modules.list) give the
leaf-first compile order and name the top modules.
The bit-exact VHDL-vs-SystemVerilog equivalence check (Section 1) needs both a VHDL
simulator (GHDL, for golden_vhdl/*.vhd + tb/tb_<mod>.vhd) and xsim (for
verilog/dsss_<mod>.sv + tb/tb_<mod>.sv). That two-simulator driver is part of the
internal openwifi-hw build workspace rather than the IP tree; the committed
vectors/out_vhdl_<mod>.txt and vectors/out_sv_<mod>.txt are its frozen reference
outputs. To reproduce a check, run the VHDL golden testbench under GHDL and the
SystemVerilog testbench under xsim against the same vectors/stim_<mod>.txt, then diff
the two dumps (after skipping the warm-up region). The alignment of the committed
out_vhdl_* / out_sv_* pairs is the proof the port is faithful.