Fix OFDM RX pilot interpolation and delay estimation axis - #5
Conversation
The OFDM receivers place their four pilots at raw FFT bins [1, 14, 38, 51]. In signed baseband frequency those are [+1, +14, -26, -13], which is not monotonic, and two receiver stages depend on a monotonic frequency axis: - demodulate() interpolates the per-symbol pilot channel estimate over the raw index axis, so it interpolates across the Nyquist guard band and blends the +14 pilot with the -26 pilot. About a quarter of the data subcarriers get a channel estimate built from opposite band edges. - estimate_delay() fits the pilot phase slope over the same axis, so for a fractional timing residual tau it returns roughly -0.52 * tau: wrong sign and about half the magnitude. Measured with the pipeline's own modulator/demodulator (deterministic, seeded, noiseless unless stated): estimate_delay at a true +0.5-sample delay: -0.272 -> +0.509 QPSK BER at a 0.5-sample residual: 6.6e-2 -> ~0 QPSK BER at 2.0 samples, with the fine-sync loop of the existing code path: 0.229 -> 0.000 Ideal timing and flat channel (AWGN only): unchanged _setup_carriers() now also derives signed-frequency coordinate tables (carrier_freqs, pilot_freqs, pilot_freq_order); the channel interpolation and the delay polyfit use them, and the equalizer keeps its raw-bin indexing. The change is receiver-side only: modulate() output is byte-identical to before for both QPSK and 16QAM, so transmitters and existing captures are unaffected. sdr_video_commv3.py keeps its virtual edge pilots, placed at the signed-frequency band edges. tests/test_pilot_geometry.py exercises the actual pipeline classes and fails on the previous code (estimator -0.271, BER floor 0.0661) while passing with this change, in under a second, with no hardware and no torch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9dc5bf5 to
2cbf311
Compare
|
I re-audited my own numbers here before sending anything else, and five statements in the description above need correcting. Sorry for the noise — I would rather flag them myself than leave them for you to trip over. The code change and the fix's effect are unchanged; these are all claims about it. I have also pushed one small commit update (details at the end). 1. "The effect is invisible in clean loopback tests: with integer-only timing and CP-circular channels the raw-index axis happens to be a valid coordinate." This is wrong, and it is the sentence most likely to be tested. The raw-index axis is a valid coordinate only at exactly zero offset, not at integer offsets. Measured on the pre-fix code with integer, CP-circular delays: BER 0.042 / 0.229 / 0.573 at offsets of 1 / 2 / 3 samples. Two separate mechanisms break it there: 12 of the 48 data carriers (raw bins 52..63) lie beyond the last raw-index pilot and get clamped, and 2. The estimator coefficient is −0.54, not −0.52. For this pilot set the closed form is exactly −830/1538 = −0.5397, so τ = +0.5 gives −0.271 — which is what the table and the shipped test actually print. The "−0.52·τ" in the problem statement was a rounding of an earlier estimate and disagrees with my own numbers. 3. Table row 1 says −0.272; the correct value is −0.271 (−0.2715 to four places). The prose and the test output both say −0.271. 4. "About a quarter of the data subcarriers" understates it. In signed frequency, 23 of the 48 data carriers — essentially half — get a channel estimate that is either interpolated across the guard band or clamped beyond the outermost pilot. 5. The 16QAM remedy claim is only half true. I wrote that moving pilots to the On the OTFS note at the end of the description. I said the OTFS path "has the analogous raw-index interpolation pattern". The pattern is there, but I have since found it is unreachable: Two things I should have mentioned in the original description:
Commit update. I force-pushed one amendment: removed two dead 🤖 Generated with Claude Code |
Hi Prof. Liu — I've been studying this repository closely; having simulation and real SDR workflows in one place is rare and it has been a great learning resource. While reading the OFDM receiver I found a coordinate-axis issue that I think is worth fixing, and I was able to demonstrate and fix it entirely in simulation using the pipeline's own classes.
Problem
The OFDM receivers place their four pilots at raw FFT bins
[1, 14, 38, 51]. In signed baseband frequency those are[+1, +14, -26, -13]— not monotonic. Two receiver stages assume a monotonic frequency axis:demodulate()interpolates the per-symbol pilot channel estimate over the raw index axis, so it interpolates across the Nyquist guard band, blending the+14pilot with the-26pilot. About a quarter of the data subcarriers end up with a channel estimate built from opposite band edges.estimate_delay()fits the pilot phase slope over the same axis. For a fractional timing residual τ it returns roughly −0.52·τ — wrong sign and about half the magnitude, so the fine-time sync loop is steered by a meaningless estimate.The effect is invisible in clean loopback tests: with integer-only timing and CP-circular channels the raw-index axis happens to be a valid coordinate. It shows up as soon as there is a fractional residual, which is the normal situation with independent TX and RX clocks.
Measured (pipeline's own modulator/demodulator, seeded, noiseless unless noted)
estimate_delayat a true +0.5-sample delayChanges
sdradi/sdr_video_commv2.py,sdr_video_commv2_lab.py,sdr_video_commv3.py:_setup_carriers()now also derives signed-frequency coordinate tables (carrier_freqs,pilot_freqs,pilot_freq_order); the channel interpolation and the delay polyfit use them, while the equalizer keeps its raw-bin indexing.sdr_video_commv3.pykeeps its virtual edge pilots, placed at the signed-frequency band edges.modulate()output is byte-identical to the current code for both QPSK and 16QAM, so transmitters and existing captures are unaffected.tests/test_pilot_geometry.py: three regression tests that exercise the actual pipeline classes. They fail on the current code (estimator −0.271, BER floor 0.0661) and pass with this change, in under a second, with no hardware and no torch required.How to reproduce
One deliberate scope note: 16QAM keeps a small residual BER floor at half-sample residuals, because the shipped pilot positions do not bracket the band edges. Moving pilots to the
(-21, -7, 7, 21)plan already declared inOFDMConfigwould remove that, but it changes the transmitted signal, so I left it out of a receiver-only change. The OTFS path in the same files has the analogous raw-index interpolation pattern — also untouched here; happy to look at either as a follow-up if you'd find it useful.A fuller simulation study behind this (impairment sweeps, CSVs and figures, ~100k bits per point) is on a separate branch so it doesn't add noise here: https://github.com/egeboy35/AIsensing/tree/research/pilot-geometry
🤖 Generated with Claude Code