Repository navigation
Demod 2400x - #330
Demod 2400x#330CharlesBaud wants to merge 25 commits into
Conversation
Pass the preamble threshold to locate_preamble(); a metric below it is stored as 0, tested exactly as n < thres * d, so the divide is skipped. The peak search gives the same results (a neighbour below the threshold is below the peak either way). NEON/AVX2 skip the divide when all 8 lanes are below (96% of vectors on noise). thres = 0 gives the old metric. aarch64 NEON 1.64 -> 1.22 ms, ARMv7 NEON 2.66 -> 1.34 ms per buffer; dump1090 demod ~1574 -> ~1495 ms on the test file, identical frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The x86_avx2 flavour is built with -ffast-math, so the compiler may replace _mm256_div_ps / sqrt with reciprocal estimates. The truncated result can then be one too small, which the one-sided check missed (locate_preamble failed verification on real Intel hardware). Use two-sided checks in the AVX2 divide and sqrt and in the scalar demod_isqrt, as the ARMv7 code already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AVX2 is now fastest for locate_preamble too (it was last in the old, pre-vectorisation timings), so production x86 builds use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bestPhase() picks the frame position by bit quality, which is flat across about 3 x 12 MHz samples on a clean pulse, with ties going to the earliest. Used as the timestamp, that gave a 0..2 sample bias depending on the sub-sample phase and a bimodal +-1 sample jitter in noise. After a frame is accepted, fineTiming() correlates its data chips (from the corrected message) with the upsampled signal at pos-2..pos+2 and timestamps the best offset. On synthetic frames at known 12 MHz ticks, all 5 phases, the timestamp is now exact (stock: 0..2 ticks bias, sigma 0.41-0.48 ticks). Decoded messages are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upsampler: each of the 5 phases is a least-squares fractional-delay fit (delay 3.1 + 0.2p input samples, flat over 0..1.0 MHz), rounded with a common offset so every phase sums to exactly 112. Against the windowed sinc it matches delay and gain across phases (group delay error <= 0.046 samples to 0.8 MHz) with lower noise gain (0.94 vs 1.04). DATA_THRES 80 -> 65: more messages at no CPU cost and no extra tracks. 60 s Pi 4 RTL-SDR capture (--fix): 83910 -> 84366 usable messages. Weak synthetic frames (+-100 kHz offset) decoded out of 2000: noise 300: 1205 -> 1405, noise 350: 454 -> 585. Timestamps stay exact. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
demodulate2400x() never called adaptive_update(), so --adaptive-range and --adaptive-burst got no noise floor or loud-burst input. When either is on, compute 2.4 MHz magnitudes (stock magnitude_sc16q11 kernel, stock scale) and feed them as demod_2400.c does: gaps between frames, then each frame with its message, carrying a frame that runs into the overlap to the next buffer. Signal power per sample is capped at full scale, as the stock magnitude kernels clamp, so message power can't exceed 0 dBFS (was up to +1.1 dBFS on clipped frames). 60 s Pi 4 capture, fake gain table on --ifile: same adaptive decisions as stock (range+burst: same 4 gain steps), noise floor -34.1 vs -34.5 dBFS; per-message RSSI vs stock: mean +0.01 dB, sigma 0.20 dB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shows in receiver.json (and so on the map) and --help, to tell this build from the packaged dump1090-fa 11.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The upsampler phases are mirror images (phase 4-c is phase c reversed, phase 2 symmetric), so NEON and AVX2 form u[k] = x[k] + x[7-k] and v[k] = x[k] - x[7-k] and compute each phase pair from shared sums: half the multiplies. Exact integer arithmetic, so the output is identical to the direct form; both fall back to it if the table isn't mirrored. Generic keeps the direct form as the reference. Rounding shift 5 -> UPSAMPLE_SHIFT 4 in all implementations, one more bit for weak signals. UPSAMPLE_ABS_SUM (largest sum of |taps|, 212) and a _Static_assert keep the power I^2 + Q^2 below 2^31 (max 1.47e9). starch-benchmark: upsample12 also runs on full-range data (uniform -2048..2047 and per-phase worst-case windows, magnitude up to 38371 of 38376), and the generic reference is checked in 64-bit arithmetic. Pi 5: upsample12 NEON 2.48 -> 1.54 ms per call; 60 s capture demod 5.9 -> 4.8 s, 84369 messages (84366 before). Validated on aarch64, ARMv7 and x86 (QEMU). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decodeShortAdbsData() (5 calls per preamble candidate) becomes a wrapper around a new starch kernel, decode_bits(p, nbytes, msg, quality): generic (the old loop), NEON (vld2 splits the chips, vcgt + weighted sum packs a byte, vabd/vpadal for quality and power) and AVX2 (one 256-bit load per byte as 32-bit (a, b) lanes, cmpgt + lane reverse + movemask). Quality goes out through a pointer so starch-benchmark can verify it. Benchmark: 7 bytes on ordinary data and 2048 bytes on full-range data. 7 bytes: Pi 5 NEON 26 ns vs 82 ns generic; ARMv7 NEON 65 vs 102 ns. 60 s capture demod 4.8 -> 4.3 s; raw output with timestamps identical. Validated on aarch64, ARMv7 and x86 (QEMU); x86 wisdom provisional. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the branch's demodulator does, and stock vs DEMOD_2400X results on a 60 s Pi 4 RTL-SDR capture: messages overall, by signal strength and by distance, MLAT timing on synthetic frames, and CPU on Pi 4 and Pi 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same least-squares design (gen/ls_coeffs.py 1.0 128) at gain 128 instead of 112: less rounding error, group delay error between phases 0.046 -> 0.028 samples. Largest tap 126; UPSAMPLE_ABS_SUM 244 keeps the worst-case power at 1.95e9 < 2^31 with UPSAMPLE_SHIFT 4 (checked by the static assert and the full-range benchmark). 60 s capture: 84389 usable messages (84369 at 112); timestamps exact. Validated on aarch64, ARMv7 and x86 (QEMU). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README.md: short DEMOD_2400X section linking to: * README-demod2400x.md: design outline and results (figures from the current build) * NOTES-demod2400x.md: development notes, decisions with measurements, what was tried and dropped, testing and gotchas * AI-DISCLOSURE.md: use of generative AI on this branch * sim/: upsampler coefficient generator (ls_coeffs.py), synthetic test signal generator (gen_timing.c, needs liquid-dsp) and timestamp checker Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same build as the GitHub workflow: gcc 14 and clang 19 on AArch64 build without warnings and pass make test, with byte-identical decoder output; ARMv7 and x86 cross-builds (gcc, clang) pass starch-benchmark -V. Notes that clang turns every AVX2 divide/sqrt into a reciprocal estimate, which QEMU can't reproduce, so the newest AVX2 kernels still need an Intel run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NOTES-demod2400x.md gets a Test data section: the SDRplay SC16 recording used for early development, the 60 s RTL-SDR recording from the Pi 4 / FlightAware Pro Stick Plus, synthetic frames from sim/, and the live receiver. Results and AI disclosure say which data each comparison used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s input Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Will create branch from dev but want to work on coherent demodulation if magnitude demod fails. |
|
This looks really interesting! |
|
Following up on this: the work now lives on a branch based on Branch: https://github.com/CharlesBaud/dump1090/tree/demod-2400x-dev What it is: an alternative 2.4 MHz demodulator. The I/Q is upsampled to Switching it off: Results (60 s Pi 4 RTL-SDR capture,
The gain is mostly weak, distant aircraft. MLAT timestamps have no bias across Docs: README-demod2400x.md (design and results), NOTES-demod2400x.md (a Generative AI: much of the code and text was written with Claude Code to my You and others are very welcome to try this. I have tested on PI5 PI4 PI3. I did not want to create a pull request yet. |
PR checklist
dev(notmaster)