How to build opendsss on its own, and how to wire it into openwifi-hw as a git submodule.
opendsss is pure behavioral SystemVerilog. There are no vendor primitives, no
.xci/coregen IP, no `define-driven configuration, and the cores use
0 block RAM and 0 DSP-macro IP of their own. That keeps the toolchain
requirements minimal: the only thing parse_board_name.tcl needs from a board is
its Xilinx part string.
See ARCHITECTURE.md for the module structure, and FARROW_DESIGN.md for the sub-sample timing recovery that the multicycle constraints below apply to.
- Xilinx Vivado / Vitis 2022.2 — the openwifi ecosystem pin. This is the version openwifi-hw builds with, and the default the wrapper scripts assume.
- A supported target part. The primary target is the ANTSDR E200
(Zynq-7020,
xc7z020clg400-1);parse_board_name.tclmaps the sameBOARD_NAMEset openwifi-hw understands (see Board support).
Because the RTL is portable behavioral SystemVerilog, it also synthesizes on
newer Vivado. The standalone wrapper honours a VIVADO_VERSION environment
override (e.g. VIVADO_VERSION=2023.2) for that purpose; 2022.2 remains the
supported/validated version.
Use this to regenerate a self-contained Vivado project and run an out-of-context (OOC) synth check without pulling in openwifi-hw. This is a synth-check / project-regen harness — it tops at the coexistence arbiter so a standalone synth reproduces the shipped LUT/DSP/WNS numbers; it is not the definition of the packaged IP (see As a submodule).
./create_vivado_proj.sh <XILINX_DIR> <TCL_FILENAME> [BOARD_NAME] [NUM_CLK_PER_US] [RUN_SYNTH]
| Arg | Meaning | Default |
|---|---|---|
XILINX_DIR |
Directory that contains Vivado/ (e.g. /opt/Xilinx, /tools/Xilinx) |
— (required) |
TCL_FILENAME |
opendsss_rx.tcl or opendsss_tx.tcl |
— (required) |
BOARD_NAME |
Board to resolve a part for | antsdr_e200 |
NUM_CLK_PER_US |
Accelerator clock in MHz (100 ⇒ 100 MHz / 10 ns) | 100 |
RUN_SYNTH |
1 ⇒ launch synthesis and report; 0 ⇒ create project only |
0 |
Environment override: VIVADO_VERSION selects the version subdir under
$XILINX_DIR/Vivado (default 2022.2).
Example — regenerate the RX project and run an in-project synth check:
./create_vivado_proj.sh /opt/Xilinx ./opendsss_rx.tcl antsdr_e200 100 1
The wrapper propagates Vivado's own exit status: a Tcl error (missing source,
unresolved part, or a RUN_SYNTH=1 synth that does not reach 100%) exits
non-zero, so it is safe to gate CI on.
What the project tcls do:
opendsss_rx.tcl— top moduledsss_rx_and_mux(RX PHY + OFDM/DSSS coexistence arbiter). Sources come fromverilog/dsss_rx_modules.list.opendsss_tx.tcl— top moduledsss_tx_and_mux(DSSS modulator + TX arbiter). Sources come fromverilog/dsss_tx_modules.list.
Both resolve the part via parse_board_name.tcl, tag every .sv as
SystemVerilog (see file-type tagging), add a
single create_clock on port clk, and leave synth_1 configured but not
launched unless RUN_SYNTH=1.
For a quick utilization + timing snapshot without a project:
vivado -mode batch -source ooc_synth_rx.tcl
vivado -mode batch -source ooc_synth_tx.tcl
ooc_synth_rx.tclreads the RX.svset, runssynth_design -top dsss_rx_and_mux -mode out_of_contextforxc7z020clg400-1at 100 MHz (clock portclk), and writesrun/ooc_rx_util.rpt/run/ooc_rx_timing.rptplus a WNS line.ooc_synth_tx.tcltops at the modulator alone (dsss_tx, clock portclock) and writes the corresponding TX reports.
Expected standalone timing: OOC (and any standalone project synth) is run without the sample-paced multicycle constraints that live in the openwifi-hw board XDC. The once-per-symbol Farrow / mu-track slow path is therefore expected to show negative slack standalone — that is not a defect of the core; it is closed in the full build by the multicycle XDC documented below. The TX modulator closes comfortably positive on its own. See FARROW_DESIGN.md.
opendsss is meant to be linked into openwifi-hw the same way openofdm is: a git
submodule under ip/.
The submodule path is ip/opendsss, registered in openwifi-hw's
.gitmodules alongside ip/openofdm_rx:
[submodule "ip/opendsss"]
path = ip/opendsss
url = <opendsss repo URL>
openwifi-hw packages two first-class IP cores from the submodule. Their packaging projects live in openwifi-hw (not in this repo):
| IP (VLNV) | Packaging tcl (in openwifi-hw) | Top wrapper | RTL from submodule |
|---|---|---|---|
user.org:user:opendsss_rx:1.0 |
ip/opendsss_rx/opendsss_rx.tcl |
opendsss_rx |
dsss_rx_fb + its RX-PHY submodules from ip/opendsss/verilog |
user.org:user:opendsss_tx:1.0 |
ip/opendsss_tx/opendsss_tx.tcl |
opendsss_tx |
dsss_tx (modulator) + dsss_crc/dsss_plcp_crc from ip/opendsss/verilog |
boards/ip_repo_gen.tcl lists opendsss_rx and opendsss_tx in its
ip_name_list and packages them via package_ip_complex.tcl (treated like
openofdm_rx — they don't receive the per-board board_def.v/clock_speed.v
generated-header copy).
Important IP-boundary note: the packaged IP tops at the PHY
(opendsss_rx wraps dsss_rx_fb; opendsss_tx wraps dsss_tx). The
coexistence arbiter/mux — dsss_rx_and_mux / dsss_tx_and_mux — is not
packaged into the opendsss IP. Those modules ship as source inside openwifi-hw's
rx_intf / tx_intf and are added by rx_intf.tcl / tx_intf.tcl. (The
standalone project tcls in this repo top at the arbiters only to reproduce the
whole-integration synth numbers.)
The BD instantiation and coexistence wiring live in openwifi-hw's
boards/post_script_common.tcl, which brings up opendsss_rx_0 and
opendsss_tx_0 as first-class BD cells, siblings of rx_intf_0 / tx_intf_0.
The whole block is guarded (a safe no-op if a cell/pin is absent), so it does
nothing on a non-DSSS board. In summary:
- RX:
opendsss_rx_0taps the shared 20 MSPS sample net exactly asopenofdm_rxdoes (sample_in/sample_in_strobe), takes anenableandresetn, and returns its decode bus torx_intf_0/dsss_phy_*. - TX:
opendsss_tx_0is fed fromtx_intf_0/dsss_tx_*(reset/length/go/ack_mode/sample_ready + the shared payload BRAM read port) and returns samples/address/status back intotx_intf. - Coexistence (OFDM priority): the arbiter taps
openofdm_rx'sdemod_is_ongoing(routed into the DSSS RX path so an in-progress OFDM reception blocks/preempts a DSSS grant) and gates the OFDM transmit start (ofdm_phy_tx_start_gated) so DSSS and OFDM never drive the DAC at once. No changes to openofdm are required — the arbiter only taps existing openofdm outputs and gates its start input.
The arbiter modules that rx_intf / tx_intf instantiate expose these port
groups (see the module headers in verilog/dsss_rx_and_mux.sv and
verilog/dsss_tx_and_mux.sv for exact widths):
dsss_rx_and_mux
- Clocking/control:
clk,rstn,dsss_enable. - Baseband in:
sample_i/sample_q/sample_valid(taprx_intf'srf_i0_to_acc/rf_q0_to_acc+ sample strobe). - OFDM path in (from
openofdm_rx): the header/rate/length/byte-bus/FCS signals plusofdm_early_active(=demod_is_ongoing). - Muxed result out (to
rx_intfdownstream): the same header/rate/length/ byte-bus/FCS bus, with OFDM taking priority on contention. - Status:
dsss_active(high while a DSSS frame owns the path).
dsss_tx_and_mux
- Clocking/control:
clk,rstn,is_dsss,is_dsss_ack,phy_tx_start. - OFDM baseband in (from
openofdm_tx):rf_i_from_acc/rf_q_from_acc/rf_iq_valid_from_acc. - Pacing/length:
tx_hold(FIFO backpressure),len_psdu. - Shared payload BRAM:
bram_rd_data(port-B data) +ofdm_bram_addr; the mux drivesbram_addr_muxedback to the BRAM. - Raw openofdm done/started:
tx_end_from_acc/tx_start_from_acc(kept raw, and re-muxed tomuxed_tx_end/muxed_tx_started). - Gain/fuzzer:
bb_gain_in/bb_gain1_in/bb_gain2_in→ muxed outputs. - Muxed sample stream out:
dsss_mux_rf_i/dsss_mux_rf_q/dsss_mux_rf_iq_valid. - Start gate out:
ofdm_phy_tx_start_gated. - Status:
dsss_busy.
The DSSS cores are SystemVerilog (.sv). Vivado can otherwise mis-detect .sv
as Verilog-2001 during packaging/synth, so every tcl that adds these files
tags them explicitly:
set_property file_type SystemVerilog [get_files -of_objects $sfs [list {*}$src_files]]This guard is applied consistently: in this repo's standalone tcls
(opendsss_rx.tcl / opendsss_tx.tcl), in openwifi-hw's packaging tcls
(ip/opendsss_rx/opendsss_rx.tcl / ip/opendsss_tx/opendsss_tx.tcl), and in
rx_intf.tcl / tx_intf.tcl for the arbiter sources. When integrating into any
flow of your own, tag the .sv sources the same way.
The DSSS timing-recovery slow path does not close at a true 1-cycle requirement
on a congested Zynq-7020, and it does not need to: the mu-track feedforward
loop and the Farrow interpolator update on strobes far slower than the clock, so
they are functionally multicycle. openwifi-hw's board constraints
(boards/antsdr_e200/src/system.xdc) declare that with set_multicycle_path.
The RX PHY runs at the 20 MSPS sample cadence on the 100 MHz accelerator clock (a strobe once every 5 clocks), and the mu-track feedforward registers update only once per DSSS symbol (every 20 despread pulses ⇒ ~100 clocks). The constraints below give those registers the extra clocks they actually have.
The paths key on the instance-path suffix (*u_dsss_rx/u_<sub>/...), so they
match the same registers regardless of where the PHY sits in the BD hierarchy.
Verify post-synth that each still matches at least one cell (no
CRITICAL WARNING 12-4739).
DSSS mu-track feedforward loop (acc/freq/mu) — the dominant slow path,
-setup 4 (path is ~3× longer than the sample-paced ones):
set_multicycle_path -setup 4 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_mu/acc_reg* || NAME =~ *u_dsss_rx/u_mu/freq_reg* || NAME =~ *u_dsss_rx/u_mu/mu_reg*}]
set_multicycle_path -hold 3 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_mu/acc_reg* || NAME =~ *u_dsss_rx/u_mu/freq_reg* || NAME =~ *u_dsss_rx/u_mu/mu_reg*}]DSSS mu-track per-sample argmax/power registers (maxv/maxb/pwr), updated
once per despread sample (1-in-5), -setup 2:
set_multicycle_path -setup 2 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_mu/maxv_reg* || NAME =~ *u_dsss_rx/u_mu/maxb_reg* || NAME =~ *u_dsss_rx/u_mu/pwr_reg*}]
set_multicycle_path -hold 1 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_mu/maxv_reg* || NAME =~ *u_dsss_rx/u_mu/maxb_reg* || NAME =~ *u_dsss_rx/u_mu/pwr_reg*}]Farrow interpolator output registers (out_i/out_q), read one cycle after they
latch and paced by the same 1-in-5 despread strobe, -setup 2:
set_multicycle_path -setup 2 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_farrow/out_i_reg* || NAME =~ *u_dsss_rx/u_farrow/out_q_reg*}]
set_multicycle_path -hold 1 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_farrow/out_i_reg* || NAME =~ *u_dsss_rx/u_farrow/out_q_reg*}]The despreader matched-filter accumulator and the demodulator's coded_bits
register are on the same 1-in-5 sample cadence and are relaxed the same way:
set_multicycle_path -setup 2 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_despreader/accum_i_reg* || NAME =~ *u_dsss_rx/u_despreader/accum_q_reg*}]
set_multicycle_path -hold 1 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_despreader/accum_i_reg* || NAME =~ *u_dsss_rx/u_despreader/accum_q_reg*}]
set_multicycle_path -setup 2 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_demodulator/*coded_bits*}]
set_multicycle_path -hold 1 -to [get_cells -hier -filter {NAME =~ *u_dsss_rx/u_demodulator/*coded_bits*}]These are genuine multicycle relaxations on strobe-paced registers, not
set_false_path— the bound stays real, just relaxed to the number of clocks the register actually has between updates. The same board XDC also carries a few congestion-only relaxations on adjacentopenofdm_rxandxpucells that the DSSS add nudged negative on the nearly-full die; those are openwifi-hw integration details rather than opendsss requirements. If you target a different board, port theu_dsss_rx/*constraints above into that board's constraint file.
parse_board_name.tcl (in both this repo and openwifi-hw) maps BOARD_NAME to a
part string. The Zynq-7020 (xc7z020clg400-1) SoC boards are the primary
opendsss target:
antsdr_e200(default; the hardware-validated target), plusantsdr,adrv9364z7020,sdrpi,e310v2,neptunesdr→xc7z020clg400-1.zed_fmcs2,zc702_fmcs2→xc7z020clg484-1zc706_fmcs2→xc7z045ffg900-2adrv9361z7035→xc7z035ifbg676-2Lzcu102_fmcs2→xczu9eg-ffvb1156-2-e
Because the cores use no vendor primitives, only the part string matters for a
standalone synth — there is no board-part / MIG dependency. Full-system BD
wiring, packaging, and the multicycle XDC live in openwifi-hw's board directory;
for any board other than antsdr_e200, see that board's tcl/XDC in openwifi-hw.
- ARCHITECTURE.md — module structure and datapath.
- FARROW_DESIGN.md — sub-sample timing recovery (the slow path the multicycle constraints cover).
- RATES_AND_FRAMING.md — supported rate, PLCP/framing scope.
- ../Readme.rst — project overview, layout, provenance, and license.