Skip to content

Latest commit

 

History

History
207 lines (164 loc) · 11.1 KB

File metadata and controls

207 lines (164 loc) · 11.1 KB

Test vectors and simulation

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.

Layout

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)

The shared model: dsss_lib.py

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 from wlan_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.

Three kinds of vector

1. Per-module equivalence stimuli

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.

2. Full-chain I/Q frame (RX self-test)

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, one i q valid triple per line.
  • vectors/dsss_rx_expected.txt — the payload_bytes line 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.

3. TX modulator golden oracle

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.py uses a 64-one preamble (a compact RX self-test), while gen_tx_golden.py uses the spec 128-one long preamble. Both decode end to end.

Runnable example: full-chain RX

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 sim

PAYLOAD_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.

Regenerating all vectors

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.py

Optional 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.

Running the simulation

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 explicitly

The verilog/ module lists (dsss_rx_modules.list, dsss_tx_modules.list) give the leaf-first compile order and name the top modules.

Per-module equivalence (GHDL vs xsim)

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.