All docsStellar LinkRF bench · by Stellar Systems v0.1.0

Deployment

Building the Bitstream

Building the SatLink PL bitstream with Vivado: prerequisites, the one-command build, the guards and the timing gate, constraints per board, and debug builds.

The PL is built with Vivado for part xc7z020clg400-1. The flow lives in pl/top/: a block design generated by Tcl (axi_interconnect_map.tcl), the SystemVerilog top (satlink_zynq7020.sv), ADI's HDL library for axi_ad9361 and axi_dmac, and one constraint file per board. Full detail is in pl/top/BUILD.md.

Prerequisites#

RequirementDetail
Vivado2024.1 (2024.1.2 validated on hardware; 2023.2 also validated). pl/top/vivado-env.sh is the only place that names a version
MemorySynthesis peaks around 7 GB. Use a disk swap file, not zram, if the machine is short
TimeAbout 35 to 45 minutes for a full build on 16 cores with VIVADO_JOBS=4
Shell
. pl/top/vivado-env.sh          # sources Vivado and exports REQUIRED_VIVADO_VERSION
                                # override with VIVADO_VERSION=2023.2 or VIVADO_ROOT=<path>
export VIVADO_JOBS=4            # 2 on a 4-core / 8 GB machine

The one-command build#

Shell
cd pl/top
systemd-run --user --collect --unit=satlink-build bash build-noila.sh
journalctl --user -u satlink-build -f

build-noila.sh runs the whole flow for the fishball board: the fast guards, the block design, a forced re-synthesis, implementation, the timing gate, then the bitstream and the .xsa. Artefacts land in pl/top/build/.

Other scripts in pl/top/:

ScriptPurpose
build-noila.shFull production build, no debug core
build-ila.shFull build inserting an ILA on every net marked (* mark_debug = "true" *) (none today, so it produces a debug-free bitstream)
resume-ila.sh, build-from-synth.shResume from a checkpointed synthesis
check-timing.shThe timing gate, called by every build script
deploy-sd.shConvert the .bit to system_top.bin and copy it to the SD card (bitstream only)

Read the directory before launching anything long: the tool you need usually exists.

Step by step#

Shell
cd pl/top
make guards                                   # lints (fatal) — a few seconds
make area                                     # area ceilings per module — about 10 minutes
make synth XDC=constraints/zynqsdr_fishball.xdc
cd build && vivado -mode batch -nolog -nojournal \
    -source ../scripts/run_impl.tcl -tclargs satlink_zynq7020
vivado -mode batch -nolog -nojournal \
    -source ../scripts/run_bit.tcl -tclargs satlink_zynq7020 satlink_zynq7020

Call the implementation Tcl directly: make impl depends on synth. Set FORCE_RESYNTH=1 whenever the bitstream must provably match the sources (the build scripts do).

Constraints: one file per board#

BoardXDC=
fishball (development)constraints/zynqsdr_fishball.xdc
LibreSDR / zynqsdr rev5 (product)constraints/zynqsdr_rev5.xdc

Each board file has a <base>_timing.xdc companion, added automatically, holding the clock groups and the timing exceptions used only in implementation.

The guards#

Checks that can fail, run before the long flow:

GuardCatches
make guards → pl/tools/lint_inert_regs.pyCSR registers written and read back, but consumed by nothing
make guards → pl/tools/lint_phantom_bits.pyRegister bits software writes that no RTL decodes
make guards → ps/tools/lint_unused_profile_fields.pyProfile fields nothing reads (a known list with tickets and dates is allowed)
make areaPer-module LUT/BRAM/DSP ceilings (pl/top/scripts/area_budget.tsv); known breaches are listed with a ticket, new ones fail
run_synth.tclFails on RAM inferred as flip-flops (Synth 8-4767), except allow-listed RAMs
check-timing.shReads the routed timing report and refuses to write a bitstream with negative slack
pl/tools/read_cdc_report.pyClock-domain crossings without exceptions beyond the allowlist

Keep the synthesis critical-warning count at zero: a dismissed "harmless" critical warning once hid unconstrained clock crossings and disabled Vivado's synthesis cache.

Checking a change before a full build#

An out-of-context synthesis of the affected module takes minutes and shows area and timing surprises:

tcl
synth_design -mode out_of_context -top <module> -part xc7z020clg400-1
report_utilization
report_timing_summary

The DSP budget (220 DSP48E1) is the historical constraint of this design; the RRC filters fit because they fold the linear-phase symmetry into the DSP pre-adders. See pl/top/DSP.md.

Simulation#

The cocotb testbenches under pl/sim/ run with Verilator:

Shell
. .venv/bin/activate
cd pl && make -C sim -f $PWD/tools/verilator.mk TEST=<name> PL_ROOT=$PWD sim

A passing simulation does not guarantee synthesis: Vivado rejects constructs Verilator accepts (input logic under default_nettype none, data-dependent while loops).

Debug builds (ILA)#

scripts/run_ila.tcl runs between synthesis and implementation, probes every net carrying (* mark_debug = "true" *), and writes its constraints into a dedicated build/debug_nets.xdc. To capture:

  1. build with build-ila.sh and deploy with deploy-sd.sh, which copies the .ltx produced by that implementation;
  2. open the hardware manager, select the FPGA device (xc7z020_1, not arm_dap_0, which reports "0 ILAs found" without error), and load that .ltx;
  3. trigger on the event rather than immediately: an immediate trigger on a bursty signal can land in a gap and show nothing.

Pitfalls: a .ltx older than the .bit describes another circuit; debug constraints must never be saved into the active constraint set (a phantom ILA would then ride into every production build); an ILA can only trigger on a running clock and will not tell you when its clock is dead.

After the build#

Produce and deploy a release with the new bitstream (see Releases and Deployment), or, for a PL-only experiment, deploy-sd.sh. The .xsa's internal bitstream is named satlink_zynq7020.bit.

Stellar Link · v0.1.0

↑↓ to moveEnter to open