Lab 5.8 - BPSK RX matched filter and bit recovery¶
Goal¶
Turn the deterministic Block 11 synthetic capture into a reproducible receive-side RTL chain:
corrected capture -> RRC matched filter -> fixed-phase symbol timing -> hard decision -> recovered bits
This is the first executable BER-oriented RX anchor in the HDL route.
Executable HDL package¶
| File | Purpose |
|---|---|
blocks/block_05_fpga_hdl_flow/rtl/bpsk_rrc_rx_fir.v |
RX-side wrapper around the shared 65-tap RRC FIR |
blocks/block_05_fpga_hdl_flow/rtl/bpsk_symbol_timing_sampler.v |
selects one symbol sample every 8 samples after the known start offset |
blocks/block_05_fpga_hdl_flow/rtl/bpsk_hard_decision.v |
maps the sampled symbol sign to bit 0/1 |
blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rx_bit_recovery_vectors.py |
generates corrected-capture vectors and expected recovered bits |
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.v |
self-checking RX recovery testbench |
Run from the repository root:
python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_rx_bit_recovery_vectors.py
iverilog -g2012 \
-o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.out \
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/tb/tb_bpsk_rx_bit_recovery.v
vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rx_bit_recovery.out
Expected result:
PASS: bpsk_rx_bit_recovery completed without errors
Shared inputs from Block 11¶
| Shared file | Role |
|---|---|
end_to_end_bpsk_reference_v1.ci16 |
deterministic synthetic captured burst |
sample_plan.json |
known matched-filter sampling start and samples_per_symbol |
tx_bits.txt |
expected bit sequence for BER comparison |
rrc_taps_q15.txt |
matched-filter coefficients |
The Python generator applies the same CFO/phase correction used by the MATLAB reference, appends the FIR flush tail, and emits a deterministic Q1.15 input stream for the RTL testbench.
Datapath¶
flowchart LR
CAP[Corrected Q1.15 capture] --> MF[RRC matched filter]
MF --> TS[Known-phase timing sampler]
TS --> HD[Hard decision]
HD --> BITS[Recovered bits]
BITS --> CMP[Compare with tx_bits.txt]
Why this stage matters¶
The TX chain is no longer enough once the course moves toward BER. A BER-oriented modem route needs:
- deterministic matched filtering;
- reproducible symbol selection;
- explicit bit decisions;
- bit-for-bit comparison against the known source frame.
This lab provides all four in a single executable HDL check.
Fixed assumptions¶
This first RX recovery lab intentionally uses known reference values rather than a blind synchronizer:
- CFO and phase are corrected offline before the RTL testbench starts;
- symbol timing uses the known
matched_filter_sample_start; - the threshold is fixed at zero for BPSK hard decisions.
That makes the chain simple enough to verify before later blocks introduce timing and carrier recovery loops.
Report checklist¶
- [ ] State that the RX matched filter reuses the same
rrc_taps_q15.txtas the TX FIR. - [ ] Show the configured start offset from
sample_plan.json. - [ ] Explain why this lab uses fixed timing instead of a synchronizer.
- [ ] Include the recovered-bit pass log and the total/payload error counts.
- [ ] State what comes next: framed burst control or true synchronization blocks.
Engineering conclusion template¶
The RX HDL chain reused the shared RRC coefficients and recovered the deterministic BPSK frame without bit errors.
Symbol selection used the known matched-filter start offset and one sample every eight clocks.
This provides the first executable HDL BER anchor before moving to framed Zynq TX/RX integration.
Timing-recovery extension (Lab 5.8b) — what comes next¶
The fixed-phase decimator above assumes exactly 8 samples per symbol. On a real AD9361 sample path that assumption can be off by a fraction of a percent, so the sampling instant drifts across a 281-symbol burst and BER floors at ~40 % even in a coherent loopback (no carrier offset). The course therefore adds a drop-in Gardner symbol timing-recovery loop that tracks the drift:
- a decrementing modulo-1 NCO at 2 strobes/symbol, a linear interpolator
(
mu ≈ nco<<2), a sign-Gardner timing-error detectore = sgn(y_mid)·sgn(y_on[k]−y_on[k−1])(amplitude-independent), and a PI loop filter with power-of-two gains (k1 = 1/256,k2 = 1/4096) — no multipliers in the loop.
Reference models and RTL, all bit-exact with each other:
| Artifact | Path |
|---|---|
| Float + fixed-point Python models (+ demo) | python/bpsk_timing_recovery_model.py |
| MATLAB float + fixed-point models | matlab/bpsk_timing_recovery_model.m |
| Simulink build script (HDL-Coder MATLAB Function block) | simulink/bpsk_timing_recovery_build_simulink.m |
| Synthesizable RTL | rtl/bpsk_symbol_timing_recovery.v |
| Vector generator | python/generate_bpsk_timing_recovery_vectors.py |
| Bit-exact HDL check | tb/tb_bpsk_symbol_timing_recovery.v |
| Full-chain BER check (TR vs fixed-phase) | tb/tb_bpsk_zynq_ber_timing_recovery.v |
bpsk_rx_bit_recovery_chain selects between the two via parameter TIMING_RECOVERY
(0 = this lab's fixed-phase sampler, 1 = the Gardner loop); the runtime AD9361
bridge sets it to 1. Running python/bpsk_timing_recovery_model.py prints the
float / fixed-point / fixed-phase BER table on a drifted burst — both
timing-recovery models reach BER 0 where the fixed-phase decimator does not.