Lab 5.10 — Zynq-ready BPSK BER top-level¶
Goal¶
Wrap the deterministic framed BPSK chain in a control-oriented top-level that looks much closer to the future Zynq integration path:
start -> frame bit source -> framed TX chain -> external TX sample boundary
-> external RX sample boundary -> RX recovery -> BER counter -> done
This lab keeps the BPSK frame source and BER checker inside the FPGA-facing logic while exposing the sample-domain TX/RX boundary for later AD9363, FIFO or DMA wiring.
Executable HDL package¶
| File | Purpose |
|---|---|
blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bit_source.v |
deterministic ROM-backed frame source with start/busy/done semantics |
blocks/block_05_fpga_hdl_flow/rtl/bpsk_ber_counter.v |
compares recovered bits against the same frame ROM and counts total/payload BER |
blocks/block_05_fpga_hdl_flow/rtl/bpsk_zynq_ber_top.v |
top-level wrapper around source, TX chain, RX chain and BER checker |
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.v |
self-checking top-level loopback testbench |
Run from the repository root:
python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_framed_loopback_vectors.py
python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rrc_tx_fir_vectors.py
iverilog -g2012 \
-o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.out \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_mapper.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_tx_fir.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_rx_fir.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_timing_sampler.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_hard_decision.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_framed_tx_chain.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_rx_bit_recovery_chain.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bit_source.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_ber_counter.v \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_zynq_ber_top.v \
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.v
vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_top.out
Expected result:
PASS: bpsk_zynq_ber_top completed without errors
Top-level contract¶
The new top-level is intentionally small but hardware-oriented:
| Signal group | Role |
|---|---|
start, busy, done |
one-shot frame launch and completion status |
frame_bit_count, preamble_count, start_offset |
deterministic control inputs derived from the shared Block 11 package |
tx_valid, tx_i, tx_q |
TX sample stream to a future DAC, FIFO or AD9363 TX path |
rx_valid, rx_i, rx_q |
RX sample stream from a future ADC, FIFO or AD9363 RX path |
received_bits, total_errors, payload_errors |
compact BER result interface |
lock_inverted |
1 when the counter locked on the inverted preamble (180 degrees) and flipped the payload back; held after done until the next start |
Why this stage matters¶
The earlier loopback testbench proved that the DSP chain works. This lab adds the system-level structure needed before a board design can use it:
- a deterministic frame source rather than a direct testbench stimulus loop;
- a BER counter that can later be memory-mapped or exported to software;
- a clean sample-domain seam where AD9363 or AXI/DMA can attach;
- explicit top-level completion semantics for automated hardware testing.
Shared frame contract¶
The top-level reuses:
| Shared artifact | Role |
|---|---|
blocks/block_05_fpga_hdl_flow/rtl/bpsk_frame_bits.mem |
deterministic frame bits for both the source and BER checker |
blocks/block_05_fpga_hdl_flow/tb/bpsk_framed_loopback_meta.txt |
start offset, bit count, preamble count and flush assumptions |
This keeps the source bits, BER reference and timing metadata synchronized across the standalone loopback and the Zynq-ready top-level.
Testbench strategy¶
The self-checking testbench connects:
top-level TX sample outputs -> 24-sample delay, I sign inverted -> top-level RX sample inputs
The sign inversion is deliberate: it makes every run go through the inverted-lock path of the BER counter.
Then it verifies:
startlaunches exactly one frame;busybecomes active during the run and clears at the end;doneasserts only after the full burst finishes;- the BER counters report zero total and payload errors;
lock_inverted = 1: the counter locked on the inverted preamble that the inverted loopback produces.
First hardware-facing interpretation¶
This top-level is still synthetic, but it is now structurally aligned with the future board path:
frame source / control -> TX samples -> AD9363 TX -> RF path -> AD9363 RX -> RX samples -> BER counters
That is the correct place to attach AXI-Lite registers, DMA, BRAM control or board-specific clock/reset logic in the next step.
What to expect¶
PASS: bpsk_zynq_ber_top completed without errors (281 bits, payload errors 0)
Unlike Labs 5.8 and 5.9, the top-level contains bpsk_ber_counter, which finds the frame by correlating the received bits with the known preamble instead of trusting a fixed offset.
The course runner generates the vectors, compiles, simulates and turns any FAIL line or non-zero simulator exit into an error:
python tools/run_block5_hdl_smoke.py --test tb_bpsk_zynq_ber_top
In Vivado 2021.1 this top places and routes on xc7z020clg400-2 at 100 MHz with +1.436 ns of
slack, using 676 LUTs, 3085 flip-flops and 192 of the 220 DSP48E1 slices (87 %)
(report): the TX pulse-shaping filter and the RX matched filter take 96 each
(Lab 5.6). Anything else that needs multipliers on the same device, such as a carrier loop or a
second channel, would not fit. The worst path is a fan-out, not arithmetic: the per-frame RX
restart (rst || frame_start) driving 2560 reset pins of the matched filter, 3 logic levels and
88 % routing.
Exercises¶
Each exercise below is a deliberate one-line RTL mutation. Make it, run the bench, read the messages, then restore the file (git checkout -- <file>). The quoted outputs were observed with Icarus Verilog 12.0.
- Apply the same polarity flip as in Labs 5.8 and 5.9 (
out_bit <= (in_i >= 0);inbpsk_hard_decision.v). Those two benches fail with 281 / 256 errors. This one fails differently:ERROR: lock_inverted=0 on the sign-inverted loopback, expected 1. Find the reason inbpsk_ber_counter.v: it counts preamble matches against both the true pattern (m_noninv) and the inverted one (m_inv), and on an inverted lock it flips the payload. This is 180-degree ambiguity resolution, and a real receiver needs it because a BPSK carrier loop can lock at 0 or 180 degrees. What would the error counters alone have shown? - The bench inverts the loopback on purpose. Why, and what should the PS do with
CONTROL_STATUS[3](Lab 5.11) on hardware? - What is the probability that 8 random bits match an 8-bit preamble exactly? Relate it to
LOCK_PREAMBLE_BITSandLOCK_ERR_TOL. - Bring back the frame search this counter used before issue #65: in
bpsk_ber_counter.vreplace the condition((LOCK_ERR_TOL > 0) || (lock_preamble_limit_reg == WIN[INDEX_W-1:0]))by(LOCK_ERR_TOL > 0), so that the exact lock goes through the sequential matcher, and runpython tools/run_block5_hdl_smoke.py --test tb_bpsk_lock_leading_bits. The bench puts 0 to 12 extra bits in front of the frame. Observed:FAIL: tb_bpsk_lock_leading_bits - 27 of 52 runs failed, for exampleFAIL 4 zero leading bits, inverted 0: received 281, total errors 140. The frame is received in full and half of its bits are wrong. Why does the counter lock at a wrong position instead of failing to lock, and why do leading zeros hurt this particular preamble (00000110...)?
Answers (checked against the RTL and the bench)
- The flip cancels the inversion of the loopback, so the counter now locks on the true preamble, counts zero total and payload errors, and drops
lock_invertedto 0. The error counters alone would show a clean run: they cannot see a polarity bug, because the counter normalises the payload either way. Only the flag shows it. Before issue #64 the polarity stayed in the internalinvert_bitsregister and this mutation passed silently. - A straight loopback only ever exercises the non-inverted lock, so the inverted branch (
m_inv,invert_bits, the payload flip) would never run in the regression. The inverted loopback runs it every time, and thelock_inverted = 1check makes the expected polarity part of the contract. On hardware, readCONTROL_STATUS[3]with every BER result and log it (the Block 11 gpreg bridge has the same information ingp_status[6:5]). On a link with a fixed phase, such as a digital or in-fabric loopback, it must not change, and a change points to a sign error in the chain. Behind a carrier loop either value is legitimate, and the logged values show how often the loop lands on each lock point. The QPSK top has the same output as a 2-bitrotation_index(k·90 degrees). - An exact 8-bit match by random bits has probability
1/256 ≈ 0.4 %, doubled to about 0.8 % because either polarity is accepted. WithLOCK_ERR_TOL = tthe window accepts up totmismatches:P = 2 · Σ_{k≤t} C(8,k) / 256, already 7 % fort = 1. Longer lock windows and a small tolerance keep false locks rare; the course default is 8 bits withLOCK_ERR_TOL = 0. - The sequential matcher compares each new bit with the next expected preamble bit. On a mismatch it does not go back: it takes the current bit as a new first preamble bit and carries on. This is naive substring search without the failure function of Knuth-Morris-Pratt. The preamble starts with five zeros, so extra zeros in front are accepted as its beginning. The real
1then arrives where the matcher expects a0, and the true start is already behind it: the matcher has consumed those bits and never looks at them again. It goes on matching from a later bit and finally finds a position where the next bits happen to pass all its checks. From there it counts the frame at the wrong alignment, so about half of the bits disagree with the ROM (140 of 281). The sliding correlator compares the last full window of bits at every new bit, so it cannot skip the true start. On the board the same bug showed up only at some sampler start offsets, where the sampler emits extra bits before the frame (silicon report). In the usual flow the offset is swept and the best one kept, which hid it.
Report checklist¶
- State the meaning of
start,busyanddone. - Show how
frame_bit_count,preamble_countandstart_offsetare supplied. - Show the exposed TX and RX sample boundaries.
- Include the final BER counter values.
- State the next integration step: AXI-Lite registers, DMA hookup or AD9363 connection.
Engineering conclusion template¶
The deterministic BPSK modem chain now has a Zynq-ready top-level with a one-shot start interface, explicit completion status and BER counters.
The frame source and BER checker reuse the same ROM-backed bit sequence, so the test remains deterministic.
This is the correct integration anchor before connecting the design to AXI control, DMA or the AD9363 sample path.