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
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
-
Burst error-correcting code — Wikipedia, on why interleaving lets short codes survive burst errors. ↩
-
Interleaving — Wikipedia, on reordering symbols to spread out contiguous errors. ↩