Decodium 4.0 “Core Shannon” · GPL-3.0

fastldpc: why your FT8 decodes what it decodes — and how we gained 1.3 dB

A from-scratch decoder for the LDPC(174,91) code shared by FT2, FT8 and FT4 — and a sign bug in Decodium's own min-sum branch that was silently costing decodes in all three modes.

+1.3 dBover plain min-sum
~16×faster chain
0false decodes from min-sum+CRC

When a weak station goes undecoded, the last word doesn't belong to the antenna or the DSP: it belongs to the error-correction decoder. FT2, FT8 and FT4 all share the same LDPC(174,91) code with a CRC-14, and the decoder handling it has stayed essentially unchanged for years. Today we present fastldpc, a decoder written from scratch for that code, integrated into Decodium 4.0 “Core Shannon” and released under GPL-3.0.

Let's say upfront what fastldpc is not: it is not a new algorithm, nor a theoretical discovery. LDPC codes are Robert Gallager's (1962), normalized min-sum is Chen and Fossorier's, ordered statistics decoding is Fossorier and Lin's. What fastldpc does is make practical what existed on paper but cost too much — and in weak-signal decoding, what is practical decides what gets decoded.

The results, in two numbers

On the bench (AWGN, 20,000 codewords per point, details below): +1.3 dB of sensitivity over plain min-sum and +0.35 dB over the classic WSJT-X-style OSD-2 compromise, at equal false-decode rates — with a chain roughly 16 times faster. The two numbers are linked: the gain comes from using a search order (OSD-3, tens of thousands of candidates per word) that nobody uses because it's too expensive. It became usable because the cost per candidate collapsed.

The key insight: the CRC-14 is linear

The OSD decoder generates candidates and checks them against the CRC-14. Normally, checking a candidate means rebuilding all 174 bits and re-running the LFSR — expensive, and done per candidate. But the CRC-14's LFSR starts from zero and has no final XOR, so its syndrome is a linear function of the 91 information bits: each bit-flip's contribution is precomputed once per word, and from then on testing a candidate costs a single XOR on a 16-bit integer.

The practical consequence flips the architecture: the most selective filter (the CRC accepts 1 candidate in 16,384) becomes the cheapest one, so it goes first, and everything else is paid only on the very few candidates that pass it. Measured: going from OSD-0 to OSD-2 used to cost 13×; now it costs 3%. At that point, using “unreasonable” search orders and spans becomes the obvious choice.

Around this insight sit the other optimizations: the min-sum hand-written in AVX2 intrinsics (compilers refuse to vectorize it — a measured 30× difference), branchless Gaussian elimination on 256-bit rows, and a normalized soft-distance “gate” that keeps false decodes in check — because trying 21,000 candidates without a strict gate would mean accepting ghosts. Words closed by min-sum+CRC alone never produced a single false decode at any measured point.

A bug found along the way (affecting FT8 and FT4 too)

Benchmarking fastldpc head-to-head against the existing decoder exposed an anomaly: the existing decoder's BP closed zero words. The cause: the min-sum branch of FtxLdpc.cpp — a branch added in Decodium's C++ port, which exists neither in WSJT-X's original Fortran nor in ft8_lib, both unaffected — was missing a sign negation in the check→variable message (the exact branch computes 2*platanh(-tmn); the min-sum branch omitted the minus). Every parity equation pushed bits the wrong way, BP never converged, and every decode was silently coming from the OSD alone. Fixed with a single negation (release 1.0.590): at 1 dB on 1,000 words, decodes go from 713 to 834. Within Decodium the branch is shared with FT8 and FT4, so the fix benefits all three modes.

Verify it yourselves: how to reproduce every number

This is the most important section of this post. No number above requires an act of faith:

git clone https://github.com/iu8lmc/Decodium-4.0-Core-Shannon
cd Decodium-4.0-Core-Shannon/Detector/fastldpc
make test     # CRC, bit-for-bit equivalence with the reference, the three presets
make sweep    # the full FER/false/µs table from the README
make gate     # the false-decode gate calibration

You need a C++17 compiler and an x86-64 CPU; with AVX2 you get the stated timings, without AVX2 everything still works (just slower). verify.cpp compares the optimized implementation against the reference word by word: zero differences over 21,492 words. If you find a number that doesn't add up, opening an issue is the best contribution you can make.

The stated limitation, and the ask

All measurements are on an AWGN channel with ideal LLRs. The real 4-GFSK demodulator won't produce them that way, and the roadmap says so explicitly: the gate and the anti-impulse clip need re-calibration on real LLRs. This is why we're looking for testers: operators willing to run the fastldpc build in real conditions — crowded bands, QRM, static crashes — and report results, good or bad. It's the step that decides how much of the lab gain makes it on the air, and we can't do it alone.

Transparency

fastldpc was developed with AI-assisted tooling. The design decisions, constraints, measurements, discarded experiments and verification are the author's, and every result is reproducible with the benchmarks included in the repo. The code is GPL-3.0, like WSJT-X and ft8_lib, from which the code tables originate — kept bit-identical for full compatibility with any other station.

The LDPC(174,91) and the CRC-14 belong to the FT8 protocol by Steve Franke K9AN and Joe Taylor K1JT; to them, as always, the greatest debt.

73 de Martino IU8LMC


Versione italiana
Decodium 4.0 “Core Shannon” · GPL-3.0

fastldpc: perché il vostro FT8 decodifica quello che decodifica — e come abbiamo guadagnato 1,3 dB

Un decodificatore scritto da zero per il codice LDPC(174,91) condiviso da FT2, FT8 e FT4 — e un errore di segno nel ramo min-sum di Decodium che costava decodifiche in tutti e tre i modi.

+1,3 dBrispetto al solo min-sum
~16×catena più veloce
0false decodifiche da min-sum+CRC

Quando una stazione debole non viene decodificata, l'ultima parola non ce l'ha l'antenna e nemmeno il DSP: ce l'ha il decodificatore del codice a correzione d'errore. FT2, FT8 e FT4 usano tutti lo stesso codice LDPC(174,91) con CRC-14, e il decoder che lo gestisce è rimasto sostanzialmente lo stesso per anni. Oggi presentiamo fastldpc, un decodificatore scritto da zero per quel codice, integrato in Decodium 4.0 “Core Shannon” e rilasciato in GPL-3.0.

Diciamo subito cosa fastldpc non è: non è un nuovo algoritmo e non è una scoperta teorica. I codici LDPC sono di Robert Gallager (1962), il min-sum normalizzato di Chen e Fossorier, l'ordered statistics decoding di Fossorier e Lin. Quello che fastldpc fa è rendere praticabile ciò che sulla carta esisteva già ma costava troppo — e nella decodifica dei segnali deboli, ciò che è praticabile decide ciò che si decodifica.

I risultati, in due numeri

Al banco (AWGN, 20.000 parole di codice per punto, dettagli sotto): +1,3 dB di sensibilità rispetto al solo min-sum e +0,35 dB rispetto al compromesso classico OSD-2 in stile WSJT-X, a parità di false decodifiche — con una catena circa 16 volte più veloce. I due numeri sono legati: il guadagno viene dall'usare un ordine di ricerca (OSD-3, decine di migliaia di candidati per parola) che nessuno usa perché troppo costoso. È diventato usabile perché il costo per candidato è crollato.

L'idea chiave: la CRC-14 è lineare

Il decoder OSD genera candidati e li verifica con la CRC-14. Normalmente verificare un candidato significa ricostruire i 174 bit e ripercorrere l'LFSR: costoso, e va fatto per ogni candidato. Ma l'LFSR della CRC-14 parte da zero e non ha XOR finale, quindi la sua sindrome è una funzione lineare dei 91 bit d'informazione: il contributo del flip di ogni bit si precalcola una volta per parola, e da lì in poi testare un candidato costa un solo XOR su un intero a 16 bit.

La conseguenza pratica ribalta l'architettura: il filtro più selettivo (la CRC accetta 1 candidato su 16.384) diventa anche il più economico, quindi va applicato per primo, e tutto il resto si paga solo sui pochissimi candidati che lo superano. Misurato: prima, passare da OSD-0 a OSD-2 costava 13 volte tanto; ora costa il 3%. A quel punto usare ordini e ampiezze di ricerca “insensati” diventa la scelta ovvia.

Attorno a questa idea ci sono le altre ottimizzazioni: il min-sum riscritto a mano in AVX2 (i compilatori si rifiutano di vettorizzarlo: 30× di differenza misurata), l'eliminazione di Gauss senza salti condizionati su righe da 256 bit, e un “gate” sulla distanza soft normalizzata che tiene a bada le false decodifiche — perché provare 21.000 candidati senza un cancello severo significherebbe accettare fantasmi. Le parole chiuse dal solo min-sum+CRC non hanno mai prodotto una falsa decodifica in nessun punto di misura.

Un bug trovato per strada (e che riguarda anche FT8 e FT4)

Misurando fastldpc testa a testa contro il decoder esistente è emersa un'anomalia: il BP del decoder esistente non chiudeva nessuna parola. La causa: nel ramo min-sum di FtxLdpc.cpp — un ramo aggiunto nel port C++ di Decodium, che non esiste nel Fortran originale di WSJT-X né in ft8_lib, entrambi non interessati — mancava una negazione di segno nel messaggio check→variabile (il ramo esatto calcola 2*platanh(-tmn); quello min-sum ometteva il meno). Ogni equazione di parità spingeva i bit nella direzione sbagliata, il BP non convergeva mai, e tutte le decodifiche venivano in realtà dal solo OSD. Corretto con una negazione (release 1.0.590): a 1 dB su 1.000 parole, le decodifiche passano da 713 a 834. Dentro Decodium il ramo è condiviso con FT8 e FT4, quindi il fix beneficia tutti e tre i modi.

Verificate voi: come riprodurre ogni numero

Questa è la sezione più importante del post. Nessun numero qui sopra richiede un atto di fede:

git clone https://github.com/iu8lmc/Decodium-4.0-Core-Shannon
cd Decodium-4.0-Core-Shannon/Detector/fastldpc
make test     # CRC, equivalenza bit-per-bit col riferimento, i tre preset
make sweep    # la tabella completa FER/false/µs del README
make gate     # la taratura del gate anti-false-decode

Serve un compilatore C++17 e una CPU x86-64; con AVX2 si ottengono i tempi dichiarati, senza AVX2 tutto funziona comunque (solo più lento). verify.cpp confronta l'implementazione ottimizzata con quella di riferimento parola per parola: zero differenze su 21.492 parole. Se trovate un numero che non torna, aprire una issue è il miglior contributo possibile.

Il limite dichiarato, e la richiesta alla comunità

Tutte le misure sono su canale AWGN con LLR ideali. Il demodulatore 4-GFSK reale non li produce così, e la roadmap lo dice esplicitamente: la taratura del gate e del clip anti-interferenza va rifatta sugli LLR veri. Per questo cerchiamo tester: operatori disposti a usare la versione con fastldpc in condizioni reali — bande affollate, QRM, static crash — e a riportare risultati, positivi o negativi. È il passaggio che decide quanto del guadagno di laboratorio arriva in aria, e non possiamo farlo da soli.

Trasparenza

fastldpc è stato sviluppato con l'assistenza di strumenti AI. Le scelte di progetto, i vincoli, le misure, gli esperimenti scartati e le verifiche sono dell'autore, e ogni risultato è riproducibile con i banchi inclusi nel repo. Il codice è GPL-3.0, come WSJT-X e ft8_lib da cui provengono le tabelle del codice — che restano invariate al bit, per compatibilità totale con qualunque altra stazione.

Il LDPC(174,91) e la CRC-14 appartengono al protocollo FT8 di Steve Franke K9AN e Joe Taylor K1JT; a loro, come sempre, il debito più grande.

73 de Martino IU8LMC