Lab 5.7 — BPSK 8x symbol upsampler¶
Goal¶
Insert the explicit multi-rate bridge between the BPSK symbol mapper and the sample-rate RRC TX FIR.
The new block accepts one symbol-rate Q1.15 sample and emits the 8x zero-stuffed sample stream required by the pulse-shaping filter:
BPSK mapper -> 8x upsampler / zero-stuffer -> RRC TX FIR -> future DAC / RF chain
Executable HDL package¶
| File | Purpose |
|---|---|
blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v |
symbol-rate to sample-rate bridge with SPS = 8 |
blocks/block_05_fpga_hdl_flow/python/generate_bpsk_upsampler_8x_vectors.py |
generates deterministic input and expected-output vectors from the shared Block 11 package |
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.v |
self-checking Verilog testbench |
blocks/block_05_fpga_hdl_flow/tb/bpsk_upsampler_8x_input_vectors.txt |
generated Q1.15 symbol stream |
blocks/block_05_fpga_hdl_flow/tb/bpsk_upsampler_8x_expected_vectors.txt |
generated zero-stuffed sample stream |
Run from the repository root:
python blocks/block_05_fpga_hdl_flow/python/generate_bpsk_upsampler_8x_vectors.py
iverilog -g2012 \
-o blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.out \
blocks/block_05_fpga_hdl_flow/rtl/bpsk_upsampler_8x.v \
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.v
vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_upsampler_8x.out
Expected result:
PASS: bpsk_upsampler_8x test completed without errors
Why this block matters¶
The symbol mapper and the TX FIR do not naturally run at the same effective rate:
- mapper output: one complex symbol per symbol period;
- TX FIR input: one complex sample every FPGA clock in the sample-rate domain.
Without an explicit zero-stuffer, the mapper cannot be connected to the pulse-shaping filter in a deterministic RTL chain.
Interface contract¶
| Signal | Meaning |
|---|---|
in_valid |
upstream presents one Q1.15 complex symbol |
in_ready |
upsampler can accept the next symbol |
out_valid |
one sample-rate output sample is available |
out_i, out_q |
first sample is the symbol, next SPS-1 samples are zero |
The block keeps out_valid high for the full 8-sample expansion of every accepted symbol.
Shared inputs¶
The Python generator reuses:
| Shared file | Role |
|---|---|
tx_symbols_q15.txt |
exact symbol sequence from the shared BPSK package |
config.json |
provides samples_per_symbol = 8 |
What to expect¶
PASS: bpsk_upsampler_8x test completed without errors (281 symbols, 2248 samples)
2248 = 281 x 8. The block zero-stuffs: the symbol appears on phase 0 and the next 7 output samples are zero, with in_ready low for those 7 cycles. The RRC filter of Lab 5.6 turns the zero-stuffed impulses into the shaped waveform.
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_upsampler_8x
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.
- Make the counter one short:
if (phase == SPS - 2). The bench reports 786 errors. The first isout=(32767,0) expected=(0,0) idx=7(the next symbol arrives one sample early), and the last isout_valid=0 expected=1 idx=2247(the stream ends early). An off-by-one in a counter shows up as a periodic misalignment, not as a random error. - Why zero-stuffing and not sample-and-hold (repeating the symbol 8 times)? Describe what holding does to the spectrum before the RRC filter.
- With zero-stuffing, the average power per output sample drops by a factor of 8. The 65 TX taps in
rtl/bpsk_rrc_tx_fir_taps.memsum to about 2.85 (close to the square root of 8), and the largest tap is 0.387. Explain where that scaling comes from and how much headroom it leaves below Q1.15 full scale.
Answers (checked against the RTL and the bench)
- The observed output is in the exercise: a counter off by one gives a periodic misalignment.
- Holding a symbol for 8 samples convolves the impulse train with an 8-sample rectangle: the spectrum is multiplied by a sinc, which droops across the passband and only partly suppresses the images. The RRC filter after it would then shape a rectangle-filtered pulse, so the TX pulse is no longer root-raised-cosine, the RX matched filter no longer matches it, and inter-symbol interference appears. Zero-stuffing keeps the images at full height but leaves the pulse shape entirely to the RRC filter, which removes them.
- The taps have unit energy (
Σ h² = 1.000), so each symbol carries the same energy as one full-scale sample; their sum, 2.85, is≈ √8, the DC gain that restores the factor of 8 lost to zero-stuffing. Each output sample is a sum over one polyphase branch, at mostΣ|h[p + 8m]| = 0.55of the symbol amplitude: about 5.2 dB of headroom below Q1.15 full scale (8.2 dB above the single largest tap, 0.387).
Lab 5.7b: the upsampler and the TX filter as one polyphase filter¶
The zeros this block inserts are not free downstream: bpsk_rrc_tx_fir multiplies them. For output
phase p (0..7) only the taps p, p+8, ..., p+64 meet a non-zero input, so
y[8n + p] = sum over m of h[p + 8m] * s[n - m], m = 0..8
rtl/bpsk_rrc_tx_polyphase.v computes exactly that: 9 multipliers per channel whose coefficients
change with the phase, a registered adder tree, the same rounding and saturation. It has the ports
of the pair (symbol input with in_ready, sample output) and is checked clock for clock against
bpsk_upsampler_8x followed by bpsk_rrc_tx_fir:
iverilog -g2012 -o /tmp/tx_pp.vvp \
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_tx_polyphase.v \
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_rrc_tx_polyphase_equivalence.v
vvp /tmp/tx_pp.vvp
PASS: bpsk_rrc_tx_polyphase equals bpsk_upsampler_8x + bpsk_rrc_tx_fir clock for clock (600 symbols, 4800 samples, 0 saturated)
Vivado 2021.1 on xc7z020clg400-2 at 100 MHz (report):
| Implementation | LUT | FF | DSP48E1 | WNS |
|---|---|---|---|---|
bpsk_upsampler_8x + bpsk_rrc_tx_fir |
169 | 2303 | 96 | +3.038 ns (filter) |
bpsk_rrc_tx_polyphase |
404 | 541 | 14 | +3.233 ns |
Same output, a seventh of the DSP slices and a quarter of the flip-flops; the extra LUTs are the coefficient multiplexers. The hardware TX path keeps the original pair, so nothing on the board changes.
- The design has 18 multiplies (9 per channel) but uses 14 DSP slices. Synthesize it
(
python tools/generate_block5_bpsk_vivado_reports.py --top bpsk_rrc_tx_polyphasewrites the reports) and read the "DSP: Preliminary Mapping Report" in the synthesis log: most slices get only a 2-3 bit B input. Where did the rest of each coefficient go, and what does that cost in LUTs? - Why can the RX matched filter in Lab 5.8 not use the same trick, and which receive-side structure would (think of the decimation after the matched filter)?
Report checklist¶
- Explain why the mapper-to-FIR boundary is a multi-rate interface.
- Show
in_readyandout_validtiming for at least two symbols. - State that the expected output is one symbol sample followed by seven zeros.
- Show how this block feeds
bpsk_rrc_tx_fir.vin the next integration step.
Engineering conclusion template¶
The BPSK 8x upsampler converts one symbol-rate Q1.15 sample into eight sample-rate outputs.
It is the explicit timing bridge between the symbol mapper and the RRC TX FIR.
With this block in place, the TX HDL path now has a deterministic sample-rate input stream for pulse shaping.