Skip to content

Latest commit

 

History

History
303 lines (234 loc) · 14.3 KB

File metadata and controls

303 lines (234 loc) · 14.3 KB

Integration

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.


Prerequisites

  • 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.tcl maps the same BOARD_NAME set 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.


Standalone build (opendsss on its own)

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

./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 module dsss_rx_and_mux (RX PHY + OFDM/DSSS coexistence arbiter). Sources come from verilog/dsss_rx_modules.list.
  • opendsss_tx.tcl — top module dsss_tx_and_mux (DSSS modulator + TX arbiter). Sources come from verilog/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.

Out-of-context synth (ooc_synth_rx.tcl / ooc_synth_tx.tcl)

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.tcl reads the RX .sv set, runs synth_design -top dsss_rx_and_mux -mode out_of_context for xc7z020clg400-1 at 100 MHz (clock port clk), and writes run/ooc_rx_util.rpt / run/ooc_rx_timing.rpt plus a WNS line.
  • ooc_synth_tx.tcl tops at the modulator alone (dsss_tx, clock port clock) 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.


As a submodule of openwifi-hw

opendsss is meant to be linked into openwifi-hw the same way openofdm is: a git submodule under ip/.

1. Add the submodule

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>

2. What gets packaged

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

3. Block-design wiring

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_0 taps the shared 20 MSPS sample net exactly as openofdm_rx does (sample_in / sample_in_strobe), takes an enable and resetn, and returns its decode bus to rx_intf_0/dsss_phy_*.
  • TX: opendsss_tx_0 is fed from tx_intf_0/dsss_tx_* (reset/length/go/ack_mode/sample_ready + the shared payload BRAM read port) and returns samples/address/status back into tx_intf.
  • Coexistence (OFDM priority): the arbiter taps openofdm_rx's demod_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.

4. Arbiter top ports exposed

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 (tap rx_intf's rf_i0_to_acc / rf_q0_to_acc + sample strobe).
  • OFDM path in (from openofdm_rx): the header/rate/length/byte-bus/FCS signals plus ofdm_early_active (= demod_is_ongoing).
  • Muxed result out (to rx_intf downstream): 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 drives bram_addr_muxed back to the BRAM.
  • Raw openofdm done/started: tx_end_from_acc / tx_start_from_acc (kept raw, and re-muxed to muxed_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.

SystemVerilog file-type tagging

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.

Required multicycle XDC (Farrow / mu slow path)

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 adjacent openofdm_rx and xpu cells 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 the u_dsss_rx/* constraints above into that board's constraint file.


Board support

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), plus antsdr, adrv9364z7020, sdrpi, e310v2, neptunesdrxc7z020clg400-1.
  • zed_fmcs2, zc702_fmcs2xc7z020clg484-1
  • zc706_fmcs2xc7z045ffg900-2
  • adrv9361z7035xc7z035ifbg676-2L
  • zcu102_fmcs2xczu9eg-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.


See also