Lab 5.11 — AXI-Lite control wrapper for the BPSK BER top-level¶
Goal¶
Expose the deterministic BPSK BER top-level to a Zynq Processing System style control plane:
PS / software -> AXI-Lite registers -> framed BPSK BER core -> TX/RX sample seam
This is the control-path companion to Lab 5.10. The DSP datapath stays the same, but the start command, timing parameters and BER results now pass through a compact memory-mapped register interface.
Executable HDL package¶
| File | Purpose |
|---|---|
blocks/block_05_fpga_hdl_flow/rtl/bpsk_zynq_ber_axi_lite.v |
AXI-Lite wrapper around bpsk_zynq_ber_top.v |
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_axi_lite.v |
self-checking AXI-Lite 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_axi_lite.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/rtl/bpsk_zynq_ber_axi_lite.v \
blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_axi_lite.v
vvp blocks/block_05_fpga_hdl_flow/tb/tb_bpsk_zynq_ber_axi_lite.out
Expected result:
PASS: bpsk_zynq_ber_axi_lite completed without errors
Register map¶
| Address | Name | Access | Meaning |
|---|---|---|---|
0x00 |
CONTROL_STATUS |
W/RO |
bit 0: write 1 to launch one frame; bit 1: busy; bit 2: done sticky, clear by writing 1 to bit 2; bit 3 (read-only): lock_inverted, the last run locked on the inverted preamble (180 degrees, see Lab 5.10) |
0x04 |
FRAME_BIT_COUNT |
RW |
number of bits to transmit and compare |
0x08 |
PREAMBLE_COUNT |
RW |
number of preamble bits excluded from payload BER |
0x0C |
START_OFFSET |
RW |
deterministic matched-filter sample index for symbol decisions |
0x10 |
RECEIVED_BITS |
RO |
number of recovered bits in the last completed run |
0x14 |
TOTAL_ERRORS |
RO |
total bit errors in the last completed run |
0x18 |
PAYLOAD_ERRORS |
RO |
payload-only bit errors in the last completed run |
0x1C |
ID |
RO |
fixed identification word 0x4250534B |
Interface intent¶
This wrapper is designed for low-rate control, not bulk sample transport:
- AXI-Lite configures the frame launch and reads back BER counters;
- the sample stream remains on the explicit TX/RX seam from Lab 5.10;
- a future design can connect that seam to AD9363, DMA or a stream bridge.
Why this stage matters¶
The earlier labs proved that the modem logic works. This lab proves that the control plane is also integration-ready:
- software can program the deterministic frame configuration;
- software can trigger exactly one burst;
- software can poll
busyanddone; - software can read BER counters after the burst finishes.
That is the minimum register contract needed before handing the design to a real Zynq PS or no-OS software layer.
Testbench strategy¶
The self-checking testbench:
- reads the shared deterministic metadata;
- writes frame configuration registers over AXI-Lite;
- starts one burst through the control register;
- loops TX samples back into RX samples;
- polls the status register until the sticky
donebit is set; - verifies zero BER and then clears the sticky
doneflag.
Hardware-facing interpretation¶
The first hardware use is straightforward:
PS writes AXI-Lite registers -> one short deterministic burst launches -> AD9363 TX/RX path or external monitor observes the result
This is the correct point to connect no-OS software, Linux userspace register pokes, or a small driver.
First AD9363 handoff order¶
Use the existing repository tools as the bring-up path:
- Probe the remote IIO context with
python blocks/block_06_rf_frontend_and_ad9363/python/lab_6_3_probe_iio_context.py --uri ip:192.168.40.1. - Apply the conservative Block 6.3 RF policy: minimum TX power, low manual RX gain, AGC disabled, and a short burst only.
- Read the AXI-Lite
IDregister first, then programFRAME_BIT_COUNT,PREAMBLE_COUNT, andSTART_OFFSET. - Write
1toCONTROL_STATUS[0], pollbusy, then wait for stickydone. - Use the controlled Zynq RX path for BER counters and keep RTL-SDR as a spectrum-only observer during the first OTA check.
This keeps the discovery run small and deterministic before moving to longer bursts or quantitative BER measurement.
What to expect¶
PASS: bpsk_zynq_ber_axi_lite completed without errors
The bench acts as the PS: it reads the ID register, checks that control/status resets to zero, writes and reads back the frame bit count, preamble count and start offset, starts a run with bit 0, polls until it has seen both busy (bit 1) and done (bit 2), checks received_bits and zero total/payload errors, clears the sticky done by writing bit 2, and confirms that TX samples were produced.
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_axi_lite
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
doneimpossible to clear: inbpsk_zynq_ber_axi_lite.vreplaceif (wstrb_latched[0] && wdata_latched[2]) beginwithif (1'b0) begin. The bench reportsERROR: done sticky bit did not clear. On hardware, software pollingdonewould see every later run as already finished. - Swap the
busyanddonebits in the status read ({29'd0, core_busy, done_sticky, 1'b0}). The bench reportsERROR: busy bit was never observed during the AXI-Lite controlled run. The datapath is untouched and the design would synthesize, so only a check of the register map catches this kind of error. - Write the same sequence as a short C function using
devmem-style 32-bit reads and writes, with a poll timeout. Which register do you read to decide that the run finished, and what happens if you read it only once?
Answers (checked against the RTL and the bench)
- The observed output is in the exercise: a
donebit that cannot be cleared makes every later run look finished. - The observed output is in the exercise: a register-map error leaves the datapath intact and is caught only by a test of the register map.
-
A sketch (
rd32/wr32are 32-bit accesses atbase + offset):int run_frame(uintptr_t base, unsigned timeout_ms) { wr32(base + 0x00, 0x4); /* clear the sticky done bit */ wr32(base + 0x00, 0x1); /* launch one frame */ for (unsigned t = 0; t < timeout_ms; ++t) { if (rd32(base + 0x00) & 0x4) /* done */ return (int)rd32(base + 0x18); /* payload errors */ usleep(1000); } return -1; /* timed out */ }
Read CONTROL_STATUS (0x00) and wait for the sticky done bit (bit 2). A single read right after the launch almost always sees the run still busy, and without the timeout a stuck core hangs the software forever. Clearing done first is what makes the poll mean "this run", not "some earlier run".
Report checklist¶
- Include the register map.
- State which registers must be programmed before
start. - Show how
doneis cleared. - Show the
IDreadback as a bring-up sanity check. - State that AXI-Lite controls only the configuration path, not the sample stream.
Engineering conclusion template¶
The deterministic BPSK BER core is now exposed through an AXI-Lite control wrapper.
Software can program the frame size, preamble size and sampling offset, launch one burst and read back BER counters.
This is the minimal PS-facing contract needed before connecting the modem path to AD9363 or DMA-based hardware integration.