LDPC
The Problem
A satellite sends 1024 bits of telemetry to Earth. The channel is noisy — individual bits can flip with some probability. We want the receiver to correct as many flipped bits as possible, without retransmission.
Reed-Solomon (covered in Reed-Solomon) corrects byte errors and works best against short bursts. LDPC codes work at the bit level and are designed for channels where errors are spread randomly across the frame. They get closer to the theoretical Shannon limit — meaning they can correct more errors for the same amount of redundancy.
The Idea
Instead of building a polynomial over a Galois field (like RS), LDPC uses a large, sparse parity-check matrix . Each row of defines one parity-check equation: the XOR of a small subset of codeword bits must equal zero. If the codeword is valid, every equation is satisfied. If bits are flipped, some equations fail, and the pattern of failures reveals which bits are wrong.
"Low-density" means most entries in are zero — each check involves only a few bits, and each bit participates in only a few checks. This sparsity is what makes decoding tractable: instead of solving a huge linear system, the decoder passes messages between checks and bits along the edges of a sparse graph.
Parity-Check Matrix
A codeword of bits is valid if and only if:
For a code with information bits and transmitted bits, has rows and columns (plus some extra columns for punctured bits — more on that later). Each row is one check equation. Each column corresponds to one bit.
Example: A Tiny Code
Consider a toy code with 4 info bits and 3 checks:
Row 0 says: . If the received bits satisfy all three equations, the codeword is valid. If not, the decoder uses the failed checks to figure out which bits flipped.
AR4JA: The CCSDS LDPC Code
CCSDS 131.0-B-5 specifies a family of Accumulate-Repeat-4-Jagged-Accumulate (AR4JA) codes for telemetry. They come in three rates:
| Rate | Parity | Punctured | ||
|---|---|---|---|---|
| 1/2 | 1024 | 2048 | 1024 | 512 |
| 2/3 | 1024 | 1536 | 512 | 256 |
| 4/5 | 1024 | 1280 | 256 | 128 |
The variants use the same structure with larger matrices.
The code is systematic: the first bits of the codeword are the original data, unchanged. Only parity bits are appended.
Block Structure
The matrix is not stored as a dense array of bits. Instead it is defined as a grid of sub-matrix blocks, where is the submatrix size (e.g. 512 for rate 1/2 with ).
For rate 1/2, the base matrix has 3 block-rows and 5 block-columns. Each block is one of three types:
- Zero block (): an all-zeros matrix — this check doesn't connect to any bit in this column.
- Identity block (): the identity matrix — check connects to bit in this column.
- Permutation block (): an matrix defined by the permutation — check connects to bit .
The base matrices for the three rates are:
Rate 1/2 (3 x 5 blocks):
| Col 0 | Col 1 | Col 2 | Col 3 | Col 4 | |
|---|---|---|---|---|---|
| Row 0 | |||||
| Row 1 | |||||
| Row 2 |
Each uses a different permutation. The + denotes XOR
(mod-2 addition of matrices), so means one matrix
where row has 1s at both column (identity) and column
(permutation).
The last column (column 4 for rate 1/2) corresponds to punctured bits: they are part of the code's internal structure but are not transmitted. The transmitted codeword comes from columns 0--3.
The Permutation
Each permutation maps row position (in ) to a column position within the same block. It is defined by two lookup tables, and , from the CCSDS standard:
The formula splits into 4 quadrants. rotates which quadrant the output lands in; offsets within the quadrant. Together they produce a bijection (every output appears exactly once), which is essential for to have full rank.
For and : , . So:
The CCSDS standard defines 26 permutations (). How many are used depends on the rate: rate 1/2 uses , rate 2/3 uses , rate 4/5 uses .
Encoding
Encoding computes the parity bits from the information bits. CCSDS AR4JA codes use a precomputed compact generator matrix such that:
The codeword is then , with info bits followed by parity bits.
Circulant Structure
The matrix has dimensions . Storing it directly would require bits for rate 1/2. Instead, is decomposed into circulant blocks of size , where is the circulant size ().
A circulant block is fully defined by its first row — every subsequent row is the previous row rotated right by one position. So we only store one row per block:
For rate 1/2: rows of u64 each u64 values total (1024 bytes to represent a million-bit matrix).
Encoding Algorithm
The encoder processes info bits in groups aligned to the circulant structure:
- For each rotation offset :
- For each compact row :
- The info bit at position : if it is 1, XOR the compact generator row into the parity accumulator.
- Left-rotate each parity block by 1 bit (simulating the circulant shift for the next offset).
- For each compact row :
After rotations, every info bit has contributed its generator row at the correct rotation, and the parity accumulator holds the final parity bits.
Syndrome Check
To verify a received codeword, we check . But the transmitted codeword has bits, while has columns (the extra are the punctured bits). The punctured bits are not transmitted, so they must be recovered first.
Recovering Punctured Bits
For all AR4JA rates, block-row 2 of the punctured column (the last column) is a pure identity block. This means check equation connects to punctured bit via identity, plus some transmitted bits via other columns. So:
We compute this for every , giving us all punctured bits.
Verifying Rows 0 and 1
With the punctured bits recovered, we check rows 0 and 1 of . For each check position, XOR all connected codeword bits (from the transmitted columns) and all connected punctured bits (from the recovered column). If every check evaluates to zero, the codeword is valid.
Row 2 is not checked separately — it was used to define the punctured bits, so it passes by construction. Row 3 of the base matrix is all zeros and contributes no checks.
Decoding: Belief Propagation
Not yet implemented. The decoder will use layered min-sum belief propagation with fixed-point (i16) log-likelihood ratios.
The idea: each bit starts with a "soft" confidence value from the channel (positive = probably 0, negative = probably 1). The decoder iteratively passes messages between check nodes and variable nodes:
- Variable -> Check: each bit tells each of its checks "here's my current belief, excluding what you told me last time."
- Check -> Variable: each check combines messages from all its other bits and sends back "based on everyone else, I think you should be..."
After enough iterations (typically 20--50), the bits converge to their corrected values. The min-sum variant approximates the optimal sum-product algorithm using only additions and comparisons — no multiplications or transcendental functions — making it suitable for embedded and FPGA implementations.
Why These Numbers
Why three rates? Different missions have different channel conditions. A deep-space probe with a weak signal uses rate 1/2 (sends 2 bits for every 1 bit of data — maximum protection). A low-Earth-orbit satellite with a strong link uses rate 4/5 (sends only 1.25 bits per data bit — maximum throughput).
Why is a column punctured? The AR4JA code's internal structure needs an extra set of "accumulator seed" bits to make the encoder work. These bits are determined by the code constraints and don't carry independent information, so transmitting them would waste bandwidth. The decoder recovers them from the parity checks.
Why for rate 1/2? The base matrix has columns. The transmitted codeword has bits. For : .
Why circulant size ? The permutation splits each -row block into 4 quadrants. The circulant structure of the generator aligns with these quadrants, so .
CCSDS Parameters
| Parameter | Value |
|---|---|
| Standard | CCSDS 131.0-B-5 |
| Code family | AR4JA (Accumulate-Repeat-4-Jagged-Accumulate) |
| Rates | 1/2, 2/3, 4/5 |
| Info lengths | or |
| Base matrix rows | 3 (plus 1 all-zero row) |
| Base matrix cols | 5 (r1/2), 7 (r2/3), 11 (r4/5) |
| Block types | (zero), (identity), (permutation) |
| Permutations | with / lookup tables |
| Encoding | Compact generator with circulant rotation |
| Decoding | Layered min-sum belief propagation |
End-to-End Example
We encode 1024 bits using the rate 1/2 code (), corrupt some bits, and verify detection.
1. Encoding
The 1024 info bits (128 bytes) are the input. For this example,
we use the test vector info[i] = i for :
Info (128 bytes):
[ 0x00, 0x01, 0x02, 0x03, ..., 0x7E, 0x7F ]
The encoder computes 1024 parity bits (128 bytes) using the compact generator matrix. The first few parity bytes are:
Parity (128 bytes):
[ 0xEE, 0xA9, 0xAA, 0xAF, 0x98, 0xD9, 0x16, 0xCE, ... ]
The codeword is systematic — info followed by parity:
Codeword (256 bytes = 2048 bits):
[ 0x00, 0x01, ..., 0x7F, 0xEE, 0xA9, 0xAA, 0xAF, ... ]
|---- 128 info bytes ---|---- 128 parity bytes ------|
2. Syndrome Check (Valid)
The syndrome checker first recovers the 512 punctured bits from row 2 of the matrix, then verifies rows 0 and 1. For this valid codeword, all check equations evaluate to zero.
3. Corruption
Flip bit 0 of the codeword (the MSB of the first byte):
Received:
[ 0x80, 0x01, 0x02, ..., 0x7F, 0xEE, 0xA9, ... ]
^^^^
was 0x00, now 0x80
4. Syndrome Check (Invalid)
The recovered punctured bits are now different (because the transmitted bits changed), and at least one check in rows 0 or 1 evaluates to 1. The syndrome check returns false — the codeword is corrupted.
5. Correction (Future)
A belief propagation decoder would take the received bits with soft channel information, run iterative message passing, and converge on the corrected codeword. This is not yet implemented.
Comparison with Reed-Solomon
| Reed-Solomon | LDPC (AR4JA) | |
|---|---|---|
| Error model | Byte errors | Bit errors |
| Arithmetic | GF() field ops | Binary XOR only |
| Matrix | Dense (syndrome) | Sparse (parity-check) |
| Decoding | Algebraic (BM + Forney) | Iterative (belief prop.) |
| Best for | Burst errors | Random bit errors |
| Performance | ~2 dB from Shannon | ~0.5 dB from Shannon |
| Complexity | Low (255 symbols) | Higher (thousands of bits) |
In the CCSDS stack, both are used: RS protects the Transfer Frame layer, while LDPC provides the physical-layer FEC. They can be concatenated for extra robustness.