blob: 27ffe62533f1d8f6494c7307b499bad96cf48c1b [file] [log] [blame]
Oliver Smitha47d37c2019-12-11 08:46:41 +01001#!/usr/bin/env python3
Harald Welteeea18a62016-04-29 15:18:35 +02002
3mod_license = """
4/*
5 * Copyright (C) 2011-2016 Sylvain Munaut <tnt@246tNt.com>
6 * Copyright (C) 2016 sysmocom s.f.m.c. GmbH
7 *
8 * All Rights Reserved
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
Harald Welteeea18a62016-04-29 15:18:35 +020019 */
20"""
21
Vadim Yanitskiy2c717942017-01-13 02:23:01 +070022import sys, os, math, argparse
Vadim Yanitskiyf9c2c562016-11-01 22:19:28 +070023from functools import reduce
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +070024import conv_codes_gsm
Harald Welteeea18a62016-04-29 15:18:35 +020025
26class ConvolutionalCode(object):
27
Vadim Yanitskiye31cf802016-09-07 21:51:25 +070028 def __init__(self, block_len, polys, name,
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +070029 description = None, puncture = [], term_type = None,
30 vec_in = None, vec_out = None):
Harald Welteeea18a62016-04-29 15:18:35 +020031 # Save simple params
32 self.block_len = block_len
33 self.k = 1
34 self.puncture = puncture
35 self.rate_inv = len(polys)
Vadim Yanitskiya6b52162016-09-08 22:06:07 +070036 self.term_type = term_type
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +070037 self.vec_in = vec_in
38 self.vec_out = vec_out
Harald Welteeea18a62016-04-29 15:18:35 +020039
40 # Infos
41 self.name = name
42 self.description = description
43
Vadim Yanitskiy84fc2ce2016-09-08 20:30:36 +070044 # Handle polynomials (and check for recursion)
Harald Welteeea18a62016-04-29 15:18:35 +020045 self.polys = [(1, 1) if x[0] == x[1] else x for x in polys]
46
47 # Determine the polynomial degree
48 for (x, y) in polys:
49 self.k = max(self.k, int(math.floor(math.log(max(x, y), 2))))
50 self.k = self.k + 1
51
52 self.poly_divider = 1
53 rp = [x[1] for x in self.polys if x[1] != 1]
54 if rp:
55 if not all([x == rp[0] for x in rp]):
Vadim Yanitskiy84fc2ce2016-09-08 20:30:36 +070056 raise ValueError("Bad polynomials: "
57 "Can't have multiple different divider polynomials!")
Vadim Yanitskiye31cf802016-09-07 21:51:25 +070058
Harald Welteeea18a62016-04-29 15:18:35 +020059 if not all([x[0] == 1 for x in polys if x[1] == 1]):
Vadim Yanitskiy84fc2ce2016-09-08 20:30:36 +070060 raise ValueError("Bad polynomials: "
Vadim Yanitskiye31cf802016-09-07 21:51:25 +070061 "Can't have a '1' divider with a non '1' dividend "
62 "in a recursive code")
63
Harald Welteeea18a62016-04-29 15:18:35 +020064 self.poly_divider = rp[0]
65
66 @property
67 def recursive(self):
68 return self.poly_divider != 1
69
70 @property
71 def _state_mask(self):
72 return (1 << (self.k - 1)) - 1
73
74 def next_state(self, state, bit):
75 nb = combine(
76 (state << 1) | bit,
77 self.poly_divider,
78 self.k,
79 )
80 return ((state << 1) | nb) & self._state_mask
81
82 def next_term_state(self, state):
83 return (state << 1) & self._state_mask
84
85 def next_output(self, state, bit, ns = None):
86 # Next state bit
87 if ns is None:
88 ns = self.next_state(state, bit)
89
90 src = (ns & 1) | (state << 1)
91
Vadim Yanitskiy84fc2ce2016-09-08 20:30:36 +070092 # Scan polynomials
Harald Welteeea18a62016-04-29 15:18:35 +020093 rv = []
94 for p_n, p_d in self.polys:
95 if self.recursive and p_d == 1:
Vadim Yanitskiye31cf802016-09-07 21:51:25 +070096 # No choice ... (systematic output in recursive case)
97 o = bit
Harald Welteeea18a62016-04-29 15:18:35 +020098 else:
99 o = combine(src, p_n, self.k)
100 rv.append(o)
101
102 return rv
103
104 def next_term_output(self, state, ns = None):
105 # Next state bit
106 if ns is None:
107 ns = self.next_term_state(state)
108
109 src = (ns & 1) | (state << 1)
110
Vadim Yanitskiy84fc2ce2016-09-08 20:30:36 +0700111 # Scan polynomials
Harald Welteeea18a62016-04-29 15:18:35 +0200112 rv = []
113 for p_n, p_d in self.polys:
114 if self.recursive and p_d == 1:
115 # Systematic output are replaced when in 'termination' mode
116 o = combine(src, self.poly_divider, self.k)
117 else:
118 o = combine(src, p_n, self.k)
119 rv.append(o)
120
121 return rv
122
123 def next(self, state, bit):
124 ns = self.next_state(state, bit)
125 nb = self.next_output(state, bit, ns = ns)
126 return ns, nb
127
128 def next_term(self, state):
129 ns = self.next_term_state(state)
130 nb = self.next_term_output(state, ns = ns)
131 return ns, nb
132
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700133 def _print_term(self, fi, num_states, pack = False):
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700134 items = []
135
Harald Welteeea18a62016-04-29 15:18:35 +0200136 for state in range(num_states):
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700137 if pack:
138 x = pack(self.next_term_output(state))
139 else:
140 x = self.next_term_state(state)
141
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700142 items.append(x)
143
144 # Up to 12 numbers should be placed per line
145 print_formatted(items, "%3d, ", 12, fi)
Harald Welteeea18a62016-04-29 15:18:35 +0200146
147 def _print_x(self, fi, num_states, pack = False):
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700148 items = []
149
Harald Welteeea18a62016-04-29 15:18:35 +0200150 for state in range(num_states):
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700151 if pack:
152 x0 = pack(self.next_output(state, 0))
153 x1 = pack(self.next_output(state, 1))
154 else:
155 x0 = self.next_state(state, 0)
156 x1 = self.next_state(state, 1)
157
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700158 items.append((x0, x1))
159
160 # Up to 4 blocks should be placed per line
161 print_formatted(items, "{ %2d, %2d }, ", 4, fi)
162
163 def _print_puncture(self, fi):
164 # Up to 12 numbers should be placed per line
165 print_formatted(self.puncture, "%3d, ", 12, fi)
Harald Welteeea18a62016-04-29 15:18:35 +0200166
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700167 def print_description(self, fi, brief = False):
168 if brief is True:
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200169 fi.write("/*! structure describing %s.\n"
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700170 % self.description[0])
171 for line in self.description[1:]:
172 fi.write(" * %s\n" % line)
173 else:
174 fi.write("/**\n")
175 for line in self.description:
176 fi.write(" * %s\n" % line)
177
178 fi.write(" */\n")
179
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700180 def print_state_and_output(self, fi):
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700181 pack = lambda n: \
182 sum([x << (self.rate_inv - i - 1) for i, x in enumerate(n)])
Harald Welteeea18a62016-04-29 15:18:35 +0200183 num_states = 1 << (self.k - 1)
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700184
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700185 fi.write("static const uint8_t %s_state[][2] = {\n" % self.name)
Harald Welteeea18a62016-04-29 15:18:35 +0200186 self._print_x(fi, num_states)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700187 fi.write("};\n\n")
188
189 fi.write("static const uint8_t %s_output[][2] = {\n" % self.name)
Harald Welteeea18a62016-04-29 15:18:35 +0200190 self._print_x(fi, num_states, pack)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700191 fi.write("};\n\n")
Harald Welteeea18a62016-04-29 15:18:35 +0200192
193 if self.recursive:
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700194 fi.write("static const uint8_t %s_term_state[] = {\n" % self.name)
Harald Welteeea18a62016-04-29 15:18:35 +0200195 self._print_term(fi, num_states)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700196 fi.write("};\n\n")
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700197
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700198 fi.write("static const uint8_t %s_term_output[] = {\n" % self.name)
Harald Welteeea18a62016-04-29 15:18:35 +0200199 self._print_term(fi, num_states, pack)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700200 fi.write("};\n\n")
Harald Welteeea18a62016-04-29 15:18:35 +0200201
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700202 def gen_tables(self, pref, fi, shared_tables = None):
203 # Do not print shared tables
204 if shared_tables is None:
205 self.print_state_and_output(fi)
206 table_pref = self.name
207 else:
208 table_pref = shared_tables
209
Harald Welteeea18a62016-04-29 15:18:35 +0200210 if len(self.puncture):
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700211 fi.write("static const int %s_puncture[] = {\n" % self.name)
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700212 self._print_puncture(fi)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700213 fi.write("};\n\n")
Harald Welteeea18a62016-04-29 15:18:35 +0200214
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700215 # Write description as a multi-line comment
216 if self.description is not None:
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700217 self.print_description(fi)
Vadim Yanitskiye31cf802016-09-07 21:51:25 +0700218
219 # Print a final convolutional code definition
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700220 fi.write("const struct osmo_conv_code %s_%s = {\n" % (pref, self.name))
221 fi.write("\t.N = %d,\n" % self.rate_inv)
222 fi.write("\t.K = %d,\n" % self.k)
223 fi.write("\t.len = %d,\n" % self.block_len)
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700224 fi.write("\t.next_output = %s_output,\n" % table_pref)
225 fi.write("\t.next_state = %s_state,\n" % table_pref)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700226
Vadim Yanitskiya6b52162016-09-08 22:06:07 +0700227 if self.term_type is not None:
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700228 fi.write("\t.term = %s,\n" % self.term_type)
229
Harald Welteeea18a62016-04-29 15:18:35 +0200230 if self.recursive:
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700231 fi.write("\t.next_term_output = %s_term_output,\n" % table_pref)
232 fi.write("\t.next_term_state = %s_term_state,\n" % table_pref)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700233
Harald Welteeea18a62016-04-29 15:18:35 +0200234 if len(self.puncture):
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700235 fi.write("\t.puncture = %s_puncture,\n" % self.name)
236 fi.write("};\n\n")
Harald Welteeea18a62016-04-29 15:18:35 +0200237
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700238 def calc_out_len(self):
239 out_len = self.block_len * self.rate_inv
240
241 # By default CONV_TERM_FLUSH
242 if self.term_type is None:
243 out_len += self.rate_inv * (self.k - 1)
244
245 if len(self.puncture):
246 out_len -= len(self.puncture) - 1
247
248 return out_len
249
250 def gen_test_vector(self, fi, prefix):
251 code_name = "%s_%s" % (prefix, self.name)
252
253 fi.write("\t{\n")
254 fi.write("\t\t.name = \"%s\",\n" % code_name)
255 fi.write("\t\t.code = &%s,\n" % code_name)
256
257 fi.write("\t\t.in_len = %d,\n" % self.block_len)
258 fi.write("\t\t.out_len = %d,\n" % self.calc_out_len())
259
260 # Print pre computed vectors if preset
261 if self.vec_in is not None and self.vec_out is not None:
262 fi.write("\t\t.has_vec = 1,\n")
263 fi.write("\t\t.vec_in = {\n")
264 print_formatted(self.vec_in, "0x%02x, ", 8, fi, indent = "\t\t\t")
265 fi.write("\t\t},\n")
266 fi.write("\t\t.vec_out = {\n")
267 print_formatted(self.vec_out, "0x%02x, ", 8, fi, indent = "\t\t\t")
268 fi.write("\t\t},\n")
269 else:
270 fi.write("\t\t.has_vec = 0,\n")
271 fi.write("\t\t.vec_in = { },\n")
272 fi.write("\t\t.vec_out = { },\n")
273
274 fi.write("\t},\n")
275
Harald Welteeea18a62016-04-29 15:18:35 +0200276poly = lambda *args: sum([(1 << x) for x in args])
277
278def combine(src, sel, nb):
279 x = src & sel
280 fn_xor = lambda x, y: x ^ y
281 return reduce(fn_xor, [(x >> n) & 1 for n in range(nb)])
282
Vadim Yanitskiy6908fa72016-09-07 22:34:53 +0700283def print_formatted(items, format, count, fi):
284 counter = 0
285
286 # Print initial indent
287 fi.write("\t")
288
289 for item in items:
290 if counter > 0 and counter % count == 0:
291 fi.write("\n\t")
292
293 fi.write(format % item)
294 counter += 1
295
296 fi.write("\n")
297
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700298def print_shared(fi, shared_polys):
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700299 for (name, polys) in shared_polys.items():
300 # HACK
301 code = ConvolutionalCode(0, polys, name = name)
302 code.print_state_and_output(fi)
303
Neels Hofmeyrd1537e02017-03-13 14:14:17 +0100304def open_for_writing(parent_dir, base_name):
305 path = os.path.join(parent_dir, base_name)
306 if not os.path.isdir(parent_dir):
307 os.makedirs(parent_dir)
308 return open(path, 'w')
309
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700310def generate_codes(codes, path, prefix, name):
Vadim Yanitskiyd2d97602016-09-07 22:18:10 +0700311 # Open a new file for writing
Neels Hofmeyrd1537e02017-03-13 14:14:17 +0100312 f = open_for_writing(path, name)
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700313 f.write(mod_license + "\n")
314 f.write("#include <stdint.h>\n")
315 f.write("#include <osmocom/core/conv.h>\n\n")
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700316
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700317 sys.stderr.write("Generating convolutional codes...\n")
318
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700319 # Print shared tables first
320 if hasattr(codes, "shared_polys"):
321 print_shared(f, codes.shared_polys)
Harald Welteeea18a62016-04-29 15:18:35 +0200322
Vadim Yanitskiyd2d97602016-09-07 22:18:10 +0700323 # Generate the tables one by one
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700324 for code in codes.conv_codes:
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700325 sys.stderr.write("Generate '%s' definition\n" % code.name)
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700326
327 # Check whether shared polynomials are used
328 shared = None
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700329 if hasattr(codes, "shared_polys"):
330 for (name, polys) in codes.shared_polys.items():
331 if code.polys == polys:
332 shared = name
333 break
Vadim Yanitskiy6431add2016-10-29 00:00:57 +0700334
335 code.gen_tables(prefix, f, shared_tables = shared)
Vadim Yanitskiyd2d97602016-09-07 22:18:10 +0700336
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700337def generate_vectors(codes, path, prefix, name, inc = None):
338 # Open a new file for writing
Neels Hofmeyrd1537e02017-03-13 14:14:17 +0100339 f = open_for_writing(path, name)
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700340 f.write(mod_license + "\n")
341
342 # Print includes
343 if inc is not None:
344 for item in inc:
345 f.write("%s\n" % item)
346 f.write("#include <osmocom/core/conv.h>\n")
347 f.write("#include \"conv.h\"\n\n")
348
349 sys.stderr.write("Generating test vectors...\n")
350
351 vec_count = len(codes.conv_codes)
352 f.write("const int %s_vectors_len = %d;\n\n"
353 % (prefix, vec_count))
354
355 f.write("const struct conv_test_vector %s_vectors[%d] = {\n"
356 % (prefix, vec_count))
357
358 # Generate the vectors one by one
359 for code in codes.conv_codes:
360 sys.stderr.write("Generate '%s' test vector\n" % code.name)
361 code.gen_test_vector(f, prefix)
362
363 f.write("};\n")
364
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700365def generate_header(codes, path, prefix, name, description = None):
366 # Open a new file for writing
Neels Hofmeyrd1537e02017-03-13 14:14:17 +0100367 f = open_for_writing(path, name)
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700368
369 # Print license and includes
370 f.write(mod_license + "\n")
371 f.write("#pragma once\n\n")
372 f.write("#include <stdint.h>\n")
373 f.write("#include <osmocom/core/conv.h>\n\n")
374
375 # Print general file description if preset
376 if description is not None:
377 f.write("/*! \\file %s.h\n" % prefix)
378 f.write(" * %s\n" % description)
379 f.write(" */\n\n")
380
381 sys.stderr.write("Generating header file...\n")
382
383 # Generate declarations one by one
384 for code in codes.conv_codes:
385 sys.stderr.write("Generate '%s' declaration\n" % code.name)
386 code.print_description(f, True)
387 f.write("extern const struct osmo_conv_code %s_%s;\n\n"
388 % (prefix, code.name))
389
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700390def parse_argv():
391 parser = argparse.ArgumentParser()
392
393 # Positional arguments
394 parser.add_argument("action",
395 help = "what to generate",
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700396 choices = ["gen_codes", "gen_vectors", "gen_header"])
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700397 parser.add_argument("family",
398 help = "convolutional code family",
399 choices = ["gsm"])
400
401 # Optional arguments
402 parser.add_argument("-p", "--prefix",
403 help = "internal naming prefix")
404 parser.add_argument("-n", "--target-name",
405 help = "target name for generated file")
406 parser.add_argument("-P", "--target-path",
407 help = "target path for generated file")
408
409 return parser.parse_args()
410
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700411if __name__ == '__main__':
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700412 # Parse and verify arguments
413 argv = parse_argv()
414 path = argv.target_path or os.getcwd()
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700415 inc = None
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700416
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700417 # Determine convolutional code family
418 if argv.family == "gsm":
419 codes = conv_codes_gsm
420 prefix = argv.prefix or "gsm0503"
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700421 inc = [ "#include <osmocom/gsm/gsm0503.h>" ]
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700422
Vadim Yanitskiy2c717942017-01-13 02:23:01 +0700423 # What to generate?
424 if argv.action == "gen_codes":
425 name = argv.target_name or prefix + "_conv.c"
426 generate_codes(codes, path, prefix, name)
Vadim Yanitskiye9a90ee2017-01-13 03:43:58 +0700427 elif argv.action == "gen_vectors":
428 name = argv.target_name or prefix + "_test_vectors.c"
429 generate_vectors(codes, path, prefix, name, inc)
Vadim Yanitskiy804c4c72017-01-13 15:04:37 +0700430 elif argv.action == "gen_header":
431 name = argv.target_name or prefix + ".h"
432 generate_header(codes, path, prefix, name)
Vadim Yanitskiy15492bc2016-11-10 17:10:42 +0700433
Vadim Yanitskiy45ebc522016-10-27 02:19:37 +0700434 sys.stderr.write("Generation complete.\n")