Field Guide · algorithm

Also known as: TSBK block interleaver, 98-dibit interleaver, P25 data-block interleaver

The P25 TSBK interleaver is the 98-dibit block interleaving permutation from TIA-102.BAAA-A Annex A that P25 applies around the trellis code protecting a TSBK or other data block.1 Its only job is to spread the dibits out: a contiguous fade or click on the channel corrupts a run of adjacent on-air dibits, and reordering those dibits before decode scatters that run into isolated single-dibit errors spaced far apart — exactly the error pattern the Viterbi trellis decoder is good at correcting.2

channel order (as received) ← burst → deinterleave → coding order 98-entry fixed permutation · adjacent errors become spaced-out singles the Viterbi decoder mops up
The deinterleaver reverses a fixed 98-entry permutation, so a run of adjacent corrupted channel dibits lands as widely separated single errors in coding order — the pattern the trellis Viterbi decoder corrects best.

How it works

The interleaver is a pure permutation of positions — no arithmetic, just a lookup table. GopherTrunk stores two 98-entry tables that are inverses of each other: tsbkInterleavePerm for the encoder (channel[i] = coding[perm[i]]) and tsbkDeinterleavePerm for the decoder (coding[i] = channel[perm[i]]). A build-time test asserts they invert. The permutation is a column/row transpose of the 98-dibit block; laid out as consecutive dibits it reads:

// TIA-102.BAAA-A Annex A, from internal/radio/p25/phase1/interleaver.go
// Encoder side: channel[i] = coding[tsbkInterleavePerm[i]].
var tsbkInterleavePerm = [98]int{
    0, 1, 8, 9, 16, 17, 24, 25, 32, 33, 40, 41, 48, 49, 56, 57, 64, 65, 72, 73, 80, 81, 88, 89, 96, 97,
    2, 3, 10, 11, 18, 19, 26, 27, 34, 35, 42, 43, 50, 51, 58, 59, 66, 67, 74, 75, 82, 83, 90, 91,
    4, 5, 12, 13, 20, 21, 28, 29, 36, 37, 44, 45, 52, 53, 60, 61, 68, 69, 76, 77, 84, 85, 92, 93,
    6, 7, 14, 15, 22, 23, 30, 31, 38, 39, 46, 47, 54, 55, 62, 63, 70, 71, 78, 79, 86, 87, 94, 95,
}

The stride of 8 between the first row’s entries (0, 8, 16, …) is the interleaver’s depth: two dibits that were adjacent in coding order end up separated by four positions on air, and vice versa, which is what converts a physical burst into scattered singles.

In practice

The interleaver only makes sense as one half of a pair with the trellis code, and order matters. On the encode side the information dibits are trellis-encoded to 98 channel dibits first, then interleaved for transmission; on receive GopherTrunk reverses that — DeinterleaveTSBK restores coding order, then DecodeTrellis runs Viterbi. Deinterleaving after the trellis decode, or skipping it, would leave the burst intact and the Viterbi decoder facing a dense cluster of adjacent errors it cannot resolve. The permutation is cross-verified in the source against three independent P25 implementations (kchmck/p25.rs, OP25, DSDPlus), because a single transposed index silently corrupts every block while synthetic round-trips still pass.

Relevance to SDR

internal/radio/p25/phase1/interleaver.go implements InterleaveTSBK / DeinterleaveTSBK, the deinterleave step that precedes the trellis Viterbi decoder on GopherTrunk’s control-channel path. Together they are what lets a scanner pull a channel grant TSBK opcode out of a marginal signal — the interleaver earns its keep precisely on the fading, bursty channels where losing a control block means missing a call. The same burst-breaking idea recurs throughout digital radio wherever a short block code must survive channel bursts.

Sources

  1. Burst error-correcting code — Wikipedia, on why interleaving lets short codes survive burst errors. 

  2. Interleaving — Wikipedia, on reordering symbols to spread out contiguous errors. 

See also