Also known as: P25 1/2-rate trellis, 4-state trellis code, TIA-102 Annex A trellis
The P25 trellis code is the 4-state ½-rate convolutional code defined by TIA-102.BAAA-A Annex A that protects the TSBK and other P25 data-block channels.1 It is deliberately not the textbook (7,5)-octal convolutional code: instead of a generator polynomial, it is table-driven — the encoder’s state is the most recent input dibit, and each transition emits a two-dibit output pair looked up from a fixed 16-entry constellation table. Decoding is the Viterbi algorithm, which finds the most-likely input sequence through the trellis.2
How it works
The encoder holds a state in {0,1,2,3} equal to the previous input dibit. For each new input
dibit d, the next state is d, an index is read from states[cur][d], and the two output
dibits (hi, lo) come from pairs[idx]. Because one input dibit produces two output dibits,
the code is rate ½. For a TSBK, 48 information dibits (12 bytes) encode to 98 channel dibits:
the 48 data transitions plus one finisher transition (input dibit 0) that flushes the
machine back to state 0, giving the decoder a known terminal state. The two spec-locked tables
are the whole code — a 4×4 state/index table and the 16-entry output constellation:
// TIA-102.BAAA-A Annex A Table A.1, from internal/radio/p25/phase1/trellis.go
var trellisStates = [4][4]int{ // [cur][next] → constellation index
{0, 15, 12, 3}, {4, 11, 8, 7}, {13, 2, 1, 14}, {9, 6, 5, 10},
}
var trellisPairs = [16][2]uint8{ // index → (hi, lo) output dibit
{0b00, 0b10}, {0b10, 0b10}, {0b01, 0b11}, {0b11, 0b11},
{0b11, 0b10}, {0b01, 0b10}, {0b10, 0b11}, {0b00, 0b11},
{0b11, 0b01}, {0b01, 0b01}, {0b10, 0b00}, {0b00, 0b00},
{0b00, 0b01}, {0b10, 0b01}, {0b01, 0b00}, {0b11, 0b00},
}
The decoder runs a hard-decision Viterbi over the 98 channel dibits: at each stage it computes, for all four possible successor states, the path metric as the accumulated dibit-distance between the expected output pair and the received pair (distance 0/1/2 by how many of the two bits differ), keeps the survivor into each state, and back-traces from the flushed terminal state 0. The surviving path’s total metric doubles as a confidence figure — zero means a clean channel; positive values count corrected dibit errors.
Variants and what is implemented
P25 defines this ½-rate trellis for control and short data blocks, and a separate
¾-rate trellis (TIA-102.BAAA-A Annex A) for the confirmed packet-data
channel, which packs more information per transition at lower coding gain. GopherTrunk
implements only the ½-rate code; the ¾-rate PDU variant is not present, so confirmed-data PDU
payloads are not trellis-decoded. GopherTrunk also adds a true soft-decision decoder
(DecodeP25TrellisSoftC) that takes complex differential samples and uses a magnitude-weighted
correlation branch metric, so an ambiguous near-zero symbol contributes little and a strong one
dominates — information a hard slicer throws away.
Relevance to SDR
The same tables back two paths: internal/radio/p25/phase1/trellis.go for Phase 1 TSBKs, and
the shared internal/radio/framing/p25_trellis.go primitives that P25 Phase 2
reuses for MAC PDU channel coding. On air the trellis code is applied after the
block interleaver, so on receive GopherTrunk deinterleaves
the 98 channel dibits first, then Viterbi-decodes them back to the 48 information dibits — the
combination is what lets a scanner recover a control-channel grant through a burst of channel
errors instead of dropping the block.
Sources
-
Convolutional code — Wikipedia, on the convolutional-coding family the P25 trellis belongs to. ↩
-
Viterbi algorithm — Wikipedia, on the maximum-likelihood trellis decoder used here. ↩