SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
viterbi.h
Go to the documentation of this file.
1 // This file is part of LeanSDR Copyright (C) 2016-2018 <pabr@pabr.org>.
2 // See the toplevel README for more information.
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 
17 #ifndef LEANSDR_VITERBI_H
18 #define LEANSDR_VITERBI_H
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 // This is a generic implementation of Viterbi with explicit
25 // representation of the trellis. There is special support for
26 // convolutional coding, but the code can handle other schemes.
27 // TBD This is very inefficient. For a specific trellis all loops
28 // can be be unrolled.
29 
30 namespace leansdr
31 {
32 
33 // TS is an integer type for a least NSTATES+1 states.
34 // NSTATES is the number of states (e.g. 2^(K-1)).
35 // TUS is an integer type for uncoded symbols (branch identifiers).
36 // NUS is the number of uncoded symbols.
37 // TCS is an integer type for coded symbols (branch labels).
38 // NCS is the number of coded symbols.
39 // TP is a type for representing paths.
40 // TPM, TBM are unsigned integer types for path/branch metrics.
41 // TPM is at least as wide as TBM.
42 
43 template <typename TS, int NSTATES, typename TUS, int NUS, int NCS>
44 struct trellis
45 {
46  static const int NOSTATE = NSTATES + 1;
47 
48  struct state
49  {
50  struct branch
51  {
52  TS pred; // Predecessor state or NOSTATE
53  TUS us; // Uncoded symbol
54  } branches[NCS]; // Incoming branches indexed by coded symbol
55  } states[NSTATES];
56 
58  {
59  for (TS s = 0; s < NSTATES; ++s)
60  for (int cs = 0; cs < NCS; ++cs)
61  states[s].branches[cs].pred = NOSTATE;
62  }
63 
64  // TBD Polynomial width should be a template parameter ?
65  void init_convolutional(const uint16_t G[])
66  {
67  if (NCS & (NCS - 1))
68  {
69  fprintf(stderr, "NCS must be a power of 2\n");
70  }
71  // Derive number of polynomials from NCS.
72  int nG = log2i(NCS);
73 
74  for (TS s = 0; s < NSTATES; ++s)
75  {
76  for (TUS us = 0; us < NUS; ++us)
77  {
78  // Run the convolutional encoder from state s with input us
79  uint64_t shiftreg = s; // TBD type
80  // Reverse bits
81  TUS us_rev = 0;
82  for (int b = 1; b < NUS; b *= 2)
83  if (us & b)
84  us_rev |= (NUS / 2 / b);
85  shiftreg |= us_rev * NSTATES;
86  uint32_t cs = 0; // TBD type
87  for (int g = 0; g < nG; ++g)
88  cs = (cs << 1) | parity(shiftreg & G[g]);
89  shiftreg /= NUS; // Shift bits for 1 uncoded symbol
90  // [us] at state [s] emits [cs] and leads to state [shiftreg].
91  typename state::branch *b = &states[shiftreg].branches[cs];
92  if (b->pred != NOSTATE)
93  {
94  fprintf(stderr, "Invalid convolutional code\n");
95  }
96  b->pred = s;
97  b->us = us;
98  }
99  }
100  }
101 
102  void dump()
103  {
104  for (int s = 0; s < NSTATES; ++s)
105  {
106  fprintf(stderr, "State %02x:", s);
107  for (int cs = 0; cs < NCS; ++cs)
108  {
109  typename state::branch *b = &states[s].branches[cs];
110  if (b->pred == NOSTATE)
111  fprintf(stderr, " - ");
112  else
113  fprintf(stderr, " %02x+%x", b->pred, b->us);
114  }
115  fprintf(stderr, "\n");
116  }
117  }
118 };
119 
120 // Interface that hides the templated internals.
121 template <typename TUS,
122  typename TCS,
123  typename TBM,
124  typename TPM>
126 {
127  virtual TUS update(TBM *costs, TPM *quality = NULL) = 0;
128  virtual TUS update(TCS s, TBM cost, TPM *quality = NULL) = 0;
129 };
130 
131 template <typename TS, int NSTATES,
132  typename TUS, int NUS,
133  typename TCS, int NCS,
134  typename TBM, typename TPM,
135  typename TP>
136 struct viterbi_dec : viterbi_dec_interface<TUS, TCS, TBM, TPM>
137 {
138 
140 
141  struct state
142  {
143  TPM cost; // Metric of best path leading to this state
144  TP path; // Best path leading to this state
145  };
146  typedef state statebank[NSTATES];
147  state statebanks[2][NSTATES];
148  statebank *states, *newstates; // Alternate between banks
149 
151  {
152  states = &statebanks[0];
153  newstates = &statebanks[1];
154  for (TS s = 0; s < NSTATES; ++s)
155  (*states)[s].cost = 0;
156  // Determine max value that can fit in TPM
157  max_tpm = (TPM)0 - 1;
158  if (max_tpm < 0)
159  {
160  // TPM is signed
161  for (max_tpm = 0; max_tpm * 2 + 1 > max_tpm; max_tpm = max_tpm * 2 + 1)
162  ;
163  }
164  }
165 
166  // Update with full metric
167 
168  TUS update(TBM costs[NCS], TPM *quality = NULL)
169  {
170  TPM best_tpm = max_tpm, best2_tpm = max_tpm;
171  TS best_state = 0;
172  // Update all states
173  for (int s = 0; s < NSTATES; ++s)
174  {
175  TPM best_m = max_tpm;
177  // Select best branch
178  for (int cs = 0; cs < NCS; ++cs)
179  {
181  &trell->states[s].branches[cs];
182  if (b->pred == trell->NOSTATE)
183  continue;
184  TPM m = (*states)[b->pred].cost + costs[cs];
185  if (m <= best_m)
186  { // <= guarantees one match
187  best_m = m;
188  best_b = b;
189  }
190  }
191  (*newstates)[s].path = (*states)[best_b->pred].path;
192  (*newstates)[s].path.append(best_b->us);
193  (*newstates)[s].cost = best_m;
194  // Select best and second-best states
195  if (best_m < best_tpm)
196  {
197  best_state = s;
198  best2_tpm = best_tpm;
199  best_tpm = best_m;
200  }
201  else if (best_m < best2_tpm)
202  best2_tpm = best_m;
203  }
204  // Swap banks
205  {
206  statebank *tmp = states;
207  states = newstates;
208  newstates = tmp;
209  }
210  // Prevent overflow of path metrics
211  for (TS s = 0; s < NSTATES; ++s)
212  (*states)[s].cost -= best_tpm;
213 #if 0
214  // Observe that the min-max range remains bounded
215  fprintf(stderr,"-%2d = [", best_tpm);
216  for ( TS s=0; s<NSTATES; ++s ) fprintf(stderr," %d", (*states)[s].cost);
217  fprintf(stderr," ]\n");
218 #endif
219  // Return difference between best and second-best as quality metric.
220  if (quality)
221  *quality = best2_tpm - best_tpm;
222  // Return uncoded symbol of best path
223  return (*states)[best_state].path.read();
224  }
225 
226  // Update with partial metrics.
227  // The costs provided must be negative.
228  // The other symbols will be assigned a cost of 0.
229 
230  TUS update(int nm, TCS cs[], TBM costs[], TPM *quality = NULL)
231  {
232  TPM best_tpm = max_tpm, best2_tpm = max_tpm;
233  TS best_state = 0;
234  // Update all states
235  for (int s = 0; s < NSTATES; ++s)
236  {
237  // Select best branch among those for with metrics are provided
238  TPM best_m = max_tpm;
240  for (int im = 0; im < nm; ++im)
241  {
243  &trell->states[s].branches[cs[im]];
244  if (b->pred == trell->NOSTATE)
245  continue;
246  TPM m = (*states)[b->pred].cost + costs[im];
247  if (m <= best_m)
248  { // <= guarantees one match
249  best_m = m;
250  best_b = b;
251  }
252  }
253  if (nm != NCS)
254  {
255  // Also scan the other branches.
256  // We actually rescan the branches with metrics.
257  // This works because costs are negative.
258  for (int cs = 0; cs < NCS; ++cs)
259  {
261  &trell->states[s].branches[cs];
262  if (b->pred == trell->NOSTATE)
263  continue;
264  TPM m = (*states)[b->pred].cost;
265  if (m <= best_m)
266  {
267  best_m = m;
268  best_b = b;
269  }
270  }
271  }
272  (*newstates)[s].path = (*states)[best_b->pred].path;
273  (*newstates)[s].path.append(best_b->us);
274  (*newstates)[s].cost = best_m;
275  // Select best states
276  if (best_m < best_tpm)
277  {
278  best_state = s;
279  best2_tpm = best_tpm;
280  best_tpm = best_m;
281  }
282  else if (best_m < best2_tpm)
283  best2_tpm = best_m;
284  }
285  // Swap banks
286  {
287  statebank *tmp = states;
288  states = newstates;
289  newstates = tmp;
290  }
291  // Prevent overflow of path metrics
292  for (TS s = 0; s < NSTATES; ++s)
293  (*states)[s].cost -= best_tpm;
294 #if 0
295  // Observe that the min-max range remains bounded
296  fprintf(stderr,"-%2d = [", best_tpm);
297  for ( TS s=0; s<NSTATES; ++s ) fprintf(stderr," %d", (*states)[s].cost);
298  fprintf(stderr," ]\n");
299 #endif
300  // Return difference between best and second-best as quality metric.
301  if (quality)
302  *quality = best2_tpm - best_tpm;
303  // Return uncoded symbol of best path
304  return (*states)[best_state].path.read();
305  }
306 
307  // Update with single-symbol metric.
308  // cost must be negative.
309 
310  TUS update(TCS cs, TBM cost, TPM *quality = NULL)
311  {
312  return update(1, &cs, &cost, quality);
313  }
314 
315  void dump()
316  {
317  fprintf(stderr, "[");
318  for (TS s = 0; s < NSTATES; ++s)
319  if (states[s].cost)
320  fprintf(stderr, " %02x:%d", s, states[s].cost);
321  fprintf(stderr, "\n");
322  }
323 
324  private:
325  TPM max_tpm;
326 };
327 
328 // Paths (sequences of uncoded symbols) represented as bitstreams.
329 // NBITS is the number of bits per symbol.
330 // DEPTH is the number of symbols stored in the path.
331 // T is an unsigned integer type wider than NBITS*DEPTH.
332 
333 template <typename T, typename TUS, int NBITS, int DEPTH>
334 struct bitpath
335 {
336  T val;
337  bitpath() : val(0) {}
338  void append(TUS us) { val = (val << NBITS) | us; }
339  TUS read() { return (val >> (DEPTH - 1) * NBITS) & ((1 << NBITS) - 1); }
340 };
341 
342 } // namespace leansdr
343 
344 #endif // LEANSDR_VITERBI_H
static const int NOSTATE
Definition: viterbi.h:46
int log2i(uint64_t x)
Definition: math.cpp:48
struct leansdr::trellis::state::branch branches[NCS]
viterbi_dec(trellis< TS, NSTATES, TUS, NUS, NCS > *_trellis)
Definition: viterbi.h:150
unsigned int uint32_t
Definition: rtptypes_win.h:46
struct leansdr::trellis::state states[NSTATES]
unsigned short uint16_t
Definition: rtptypes_win.h:44
TUS update(TCS cs, TBM cost, TPM *quality=NULL)
Definition: viterbi.h:310
trellis< TS, NSTATES, TUS, NUS, NCS > * trell
Definition: viterbi.h:139
TUS update(int nm, TCS cs[], TBM costs[], TPM *quality=NULL)
Definition: viterbi.h:230
void init_convolutional(const uint16_t G[])
Definition: viterbi.h:65
statebank * states
Definition: viterbi.h:148
unsigned char parity(uint8_t x)
Definition: math.cpp:27
TUS update(TBM costs[NCS], TPM *quality=NULL)
Definition: viterbi.h:168
void append(TUS us)
Definition: viterbi.h:338
unsigned __int64 uint64_t
Definition: rtptypes_win.h:48