2016-04-29 19:24:48 +00:00
|
|
|
#!/usr/bin/python2
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
mod_license = """
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2011-2016 Sylvain Munaut <tnt@246tNt.com>
|
|
|
|
* Copyright (C) 2016 sysmocom s.f.m.c. GmbH
|
|
|
|
*
|
|
|
|
* All Rights Reserved
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys, os, math
|
2016-11-01 15:19:28 +00:00
|
|
|
from functools import reduce
|
2016-11-10 10:10:42 +00:00
|
|
|
import conv_codes_gsm
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
class ConvolutionalCode(object):
|
|
|
|
|
2016-09-07 14:51:25 +00:00
|
|
|
def __init__(self, block_len, polys, name,
|
2016-09-08 15:06:07 +00:00
|
|
|
description = None, puncture = [], term_type = None):
|
2016-04-29 13:18:35 +00:00
|
|
|
# Save simple params
|
|
|
|
self.block_len = block_len
|
|
|
|
self.k = 1
|
|
|
|
self.puncture = puncture
|
|
|
|
self.rate_inv = len(polys)
|
2016-09-08 15:06:07 +00:00
|
|
|
self.term_type = term_type
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
# Infos
|
|
|
|
self.name = name
|
|
|
|
self.description = description
|
|
|
|
|
2016-09-08 13:30:36 +00:00
|
|
|
# Handle polynomials (and check for recursion)
|
2016-04-29 13:18:35 +00:00
|
|
|
self.polys = [(1, 1) if x[0] == x[1] else x for x in polys]
|
|
|
|
|
|
|
|
# Determine the polynomial degree
|
|
|
|
for (x, y) in polys:
|
|
|
|
self.k = max(self.k, int(math.floor(math.log(max(x, y), 2))))
|
|
|
|
self.k = self.k + 1
|
|
|
|
|
|
|
|
self.poly_divider = 1
|
|
|
|
rp = [x[1] for x in self.polys if x[1] != 1]
|
|
|
|
if rp:
|
|
|
|
if not all([x == rp[0] for x in rp]):
|
2016-09-08 13:30:36 +00:00
|
|
|
raise ValueError("Bad polynomials: "
|
|
|
|
"Can't have multiple different divider polynomials!")
|
2016-09-07 14:51:25 +00:00
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
if not all([x[0] == 1 for x in polys if x[1] == 1]):
|
2016-09-08 13:30:36 +00:00
|
|
|
raise ValueError("Bad polynomials: "
|
2016-09-07 14:51:25 +00:00
|
|
|
"Can't have a '1' divider with a non '1' dividend "
|
|
|
|
"in a recursive code")
|
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
self.poly_divider = rp[0]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def recursive(self):
|
|
|
|
return self.poly_divider != 1
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _state_mask(self):
|
|
|
|
return (1 << (self.k - 1)) - 1
|
|
|
|
|
|
|
|
def next_state(self, state, bit):
|
|
|
|
nb = combine(
|
|
|
|
(state << 1) | bit,
|
|
|
|
self.poly_divider,
|
|
|
|
self.k,
|
|
|
|
)
|
|
|
|
return ((state << 1) | nb) & self._state_mask
|
|
|
|
|
|
|
|
def next_term_state(self, state):
|
|
|
|
return (state << 1) & self._state_mask
|
|
|
|
|
|
|
|
def next_output(self, state, bit, ns = None):
|
|
|
|
# Next state bit
|
|
|
|
if ns is None:
|
|
|
|
ns = self.next_state(state, bit)
|
|
|
|
|
|
|
|
src = (ns & 1) | (state << 1)
|
|
|
|
|
2016-09-08 13:30:36 +00:00
|
|
|
# Scan polynomials
|
2016-04-29 13:18:35 +00:00
|
|
|
rv = []
|
|
|
|
for p_n, p_d in self.polys:
|
|
|
|
if self.recursive and p_d == 1:
|
2016-09-07 14:51:25 +00:00
|
|
|
# No choice ... (systematic output in recursive case)
|
|
|
|
o = bit
|
2016-04-29 13:18:35 +00:00
|
|
|
else:
|
|
|
|
o = combine(src, p_n, self.k)
|
|
|
|
rv.append(o)
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def next_term_output(self, state, ns = None):
|
|
|
|
# Next state bit
|
|
|
|
if ns is None:
|
|
|
|
ns = self.next_term_state(state)
|
|
|
|
|
|
|
|
src = (ns & 1) | (state << 1)
|
|
|
|
|
2016-09-08 13:30:36 +00:00
|
|
|
# Scan polynomials
|
2016-04-29 13:18:35 +00:00
|
|
|
rv = []
|
|
|
|
for p_n, p_d in self.polys:
|
|
|
|
if self.recursive and p_d == 1:
|
|
|
|
# Systematic output are replaced when in 'termination' mode
|
|
|
|
o = combine(src, self.poly_divider, self.k)
|
|
|
|
else:
|
|
|
|
o = combine(src, p_n, self.k)
|
|
|
|
rv.append(o)
|
|
|
|
|
|
|
|
return rv
|
|
|
|
|
|
|
|
def next(self, state, bit):
|
|
|
|
ns = self.next_state(state, bit)
|
|
|
|
nb = self.next_output(state, bit, ns = ns)
|
|
|
|
return ns, nb
|
|
|
|
|
|
|
|
def next_term(self, state):
|
|
|
|
ns = self.next_term_state(state)
|
|
|
|
nb = self.next_term_output(state, ns = ns)
|
|
|
|
return ns, nb
|
|
|
|
|
2016-09-07 14:51:25 +00:00
|
|
|
def _print_term(self, fi, num_states, pack = False):
|
2016-09-07 15:34:53 +00:00
|
|
|
items = []
|
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
for state in range(num_states):
|
2016-09-07 14:51:25 +00:00
|
|
|
if pack:
|
|
|
|
x = pack(self.next_term_output(state))
|
|
|
|
else:
|
|
|
|
x = self.next_term_state(state)
|
|
|
|
|
2016-09-07 15:34:53 +00:00
|
|
|
items.append(x)
|
|
|
|
|
|
|
|
# Up to 12 numbers should be placed per line
|
|
|
|
print_formatted(items, "%3d, ", 12, fi)
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
def _print_x(self, fi, num_states, pack = False):
|
2016-09-07 15:34:53 +00:00
|
|
|
items = []
|
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
for state in range(num_states):
|
2016-09-07 14:51:25 +00:00
|
|
|
if pack:
|
|
|
|
x0 = pack(self.next_output(state, 0))
|
|
|
|
x1 = pack(self.next_output(state, 1))
|
|
|
|
else:
|
|
|
|
x0 = self.next_state(state, 0)
|
|
|
|
x1 = self.next_state(state, 1)
|
|
|
|
|
2016-09-07 15:34:53 +00:00
|
|
|
items.append((x0, x1))
|
|
|
|
|
|
|
|
# Up to 4 blocks should be placed per line
|
|
|
|
print_formatted(items, "{ %2d, %2d }, ", 4, fi)
|
|
|
|
|
|
|
|
def _print_puncture(self, fi):
|
|
|
|
# Up to 12 numbers should be placed per line
|
|
|
|
print_formatted(self.puncture, "%3d, ", 12, fi)
|
2016-04-29 13:18:35 +00:00
|
|
|
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
def print_state_and_output(self, fi):
|
2016-09-07 14:51:25 +00:00
|
|
|
pack = lambda n: \
|
|
|
|
sum([x << (self.rate_inv - i - 1) for i, x in enumerate(n)])
|
2016-04-29 13:18:35 +00:00
|
|
|
num_states = 1 << (self.k - 1)
|
2016-09-07 14:51:25 +00:00
|
|
|
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("static const uint8_t %s_state[][2] = {\n" % self.name)
|
2016-04-29 13:18:35 +00:00
|
|
|
self._print_x(fi, num_states)
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("};\n\n")
|
|
|
|
|
|
|
|
fi.write("static const uint8_t %s_output[][2] = {\n" % self.name)
|
2016-04-29 13:18:35 +00:00
|
|
|
self._print_x(fi, num_states, pack)
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("};\n\n")
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
if self.recursive:
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("static const uint8_t %s_term_state[] = {\n" % self.name)
|
2016-04-29 13:18:35 +00:00
|
|
|
self._print_term(fi, num_states)
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("};\n\n")
|
2016-09-07 14:51:25 +00:00
|
|
|
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("static const uint8_t %s_term_output[] = {\n" % self.name)
|
2016-04-29 13:18:35 +00:00
|
|
|
self._print_term(fi, num_states, pack)
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("};\n\n")
|
2016-04-29 13:18:35 +00:00
|
|
|
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
def gen_tables(self, pref, fi, shared_tables = None):
|
|
|
|
# Do not print shared tables
|
|
|
|
if shared_tables is None:
|
|
|
|
self.print_state_and_output(fi)
|
|
|
|
table_pref = self.name
|
|
|
|
else:
|
|
|
|
table_pref = shared_tables
|
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
if len(self.puncture):
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("static const int %s_puncture[] = {\n" % self.name)
|
2016-09-07 15:34:53 +00:00
|
|
|
self._print_puncture(fi)
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("};\n\n")
|
2016-04-29 13:18:35 +00:00
|
|
|
|
2016-09-07 14:51:25 +00:00
|
|
|
# Write description as a multi-line comment
|
|
|
|
if self.description is not None:
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("/**\n")
|
2016-09-07 14:51:25 +00:00
|
|
|
for line in self.description:
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write(" * %s\n" % line)
|
|
|
|
fi.write(" */\n")
|
2016-09-07 14:51:25 +00:00
|
|
|
|
|
|
|
# Print a final convolutional code definition
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("const struct osmo_conv_code %s_%s = {\n" % (pref, self.name))
|
|
|
|
fi.write("\t.N = %d,\n" % self.rate_inv)
|
|
|
|
fi.write("\t.K = %d,\n" % self.k)
|
|
|
|
fi.write("\t.len = %d,\n" % self.block_len)
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
fi.write("\t.next_output = %s_output,\n" % table_pref)
|
|
|
|
fi.write("\t.next_state = %s_state,\n" % table_pref)
|
2016-10-26 19:19:37 +00:00
|
|
|
|
2016-09-08 15:06:07 +00:00
|
|
|
if self.term_type is not None:
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("\t.term = %s,\n" % self.term_type)
|
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
if self.recursive:
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
fi.write("\t.next_term_output = %s_term_output,\n" % table_pref)
|
|
|
|
fi.write("\t.next_term_state = %s_term_state,\n" % table_pref)
|
2016-10-26 19:19:37 +00:00
|
|
|
|
2016-04-29 13:18:35 +00:00
|
|
|
if len(self.puncture):
|
2016-10-26 19:19:37 +00:00
|
|
|
fi.write("\t.puncture = %s_puncture,\n" % self.name)
|
|
|
|
fi.write("};\n\n")
|
2016-04-29 13:18:35 +00:00
|
|
|
|
|
|
|
poly = lambda *args: sum([(1 << x) for x in args])
|
|
|
|
|
|
|
|
def combine(src, sel, nb):
|
|
|
|
x = src & sel
|
|
|
|
fn_xor = lambda x, y: x ^ y
|
|
|
|
return reduce(fn_xor, [(x >> n) & 1 for n in range(nb)])
|
|
|
|
|
2016-09-07 15:34:53 +00:00
|
|
|
def print_formatted(items, format, count, fi):
|
|
|
|
counter = 0
|
|
|
|
|
|
|
|
# Print initial indent
|
|
|
|
fi.write("\t")
|
|
|
|
|
|
|
|
for item in items:
|
|
|
|
if counter > 0 and counter % count == 0:
|
|
|
|
fi.write("\n\t")
|
|
|
|
|
|
|
|
fi.write(format % item)
|
|
|
|
counter += 1
|
|
|
|
|
|
|
|
fi.write("\n")
|
|
|
|
|
2016-11-10 10:10:42 +00:00
|
|
|
def print_shared(fi, shared_polys):
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
for (name, polys) in shared_polys.items():
|
|
|
|
# HACK
|
|
|
|
code = ConvolutionalCode(0, polys, name = name)
|
|
|
|
code.print_state_and_output(fi)
|
|
|
|
|
2016-11-10 10:10:42 +00:00
|
|
|
def generate_codes(codes, path, prefix):
|
utils/conv_gen.py: generate a single file
Instead of generating every convolutional code into a separate
file (such as conv_xcch_gen.c, conv_cs3_gen.c), it is better to
have a single file, containing all definitions, because as many
convolutional codes we add, as many entries we will have to add
into 'src/gsm/Makefile.am'. This approach increases readability
of the Makefile.am, and also makes us able to share some data
between some convolutional code definitions.
For example: xCCH, RACH, SCH, TCH/F, both CS2 and CS3 may use
the same *_state[][2] and *_output[][2] arrays within a single
file. This optimization is currently WIP.
Change-Id: Ib4e4ee5fdde38429e68e3b2fa50ec03a18f59daa
2016-09-07 15:18:10 +00:00
|
|
|
# Open a new file for writing
|
2016-11-10 10:10:42 +00:00
|
|
|
f = open(os.path.join(path, prefix + "_conv.c"), 'w')
|
2016-10-26 19:19:37 +00:00
|
|
|
f.write(mod_license + "\n")
|
|
|
|
f.write("#include <stdint.h>\n")
|
|
|
|
f.write("#include <osmocom/core/conv.h>\n\n")
|
2016-11-10 10:10:42 +00:00
|
|
|
|
|
|
|
# Print shared tables first
|
|
|
|
if hasattr(codes, "shared_polys"):
|
|
|
|
print_shared(f, codes.shared_polys)
|
2016-04-29 13:18:35 +00:00
|
|
|
|
utils/conv_gen.py: generate a single file
Instead of generating every convolutional code into a separate
file (such as conv_xcch_gen.c, conv_cs3_gen.c), it is better to
have a single file, containing all definitions, because as many
convolutional codes we add, as many entries we will have to add
into 'src/gsm/Makefile.am'. This approach increases readability
of the Makefile.am, and also makes us able to share some data
between some convolutional code definitions.
For example: xCCH, RACH, SCH, TCH/F, both CS2 and CS3 may use
the same *_state[][2] and *_output[][2] arrays within a single
file. This optimization is currently WIP.
Change-Id: Ib4e4ee5fdde38429e68e3b2fa50ec03a18f59daa
2016-09-07 15:18:10 +00:00
|
|
|
# Generate the tables one by one
|
2016-11-10 10:10:42 +00:00
|
|
|
for code in codes.conv_codes:
|
2016-10-26 19:19:37 +00:00
|
|
|
sys.stderr.write("Generate '%s' definition\n" % code.name)
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
|
|
|
|
# Check whether shared polynomials are used
|
|
|
|
shared = None
|
2016-11-10 10:10:42 +00:00
|
|
|
if hasattr(codes, "shared_polys"):
|
|
|
|
for (name, polys) in codes.shared_polys.items():
|
|
|
|
if code.polys == polys:
|
|
|
|
shared = name
|
|
|
|
break
|
utils/conv_gen.py: use shared tables if possible
This change introduces the memory usage optimization, mentioned
in d2d9760c08f35a231d32f0ebeb73b2927e5573b3. The aim is to make
code generator able to detect, whether the same tables are used
by several convolutional code definitions, and prevent one from
writing these tables multiple times.
For now, the detection process isn't fully automatic, so all
shared polynomials should be placed inside the 'shared_polys'
dictionary, for example:
shared_polys = {
"xcch" : [
( G0, 1 ),
( G1, 1 ),
],
"mcs" : [
( G4, 1 ),
( G7, 1 ),
( G5, 1 ),
],
}
Change-Id: I84760f5cdfdaece376b801d2e6cb2954ee875a3b
2016-10-28 17:00:57 +00:00
|
|
|
|
|
|
|
code.gen_tables(prefix, f, shared_tables = shared)
|
utils/conv_gen.py: generate a single file
Instead of generating every convolutional code into a separate
file (such as conv_xcch_gen.c, conv_cs3_gen.c), it is better to
have a single file, containing all definitions, because as many
convolutional codes we add, as many entries we will have to add
into 'src/gsm/Makefile.am'. This approach increases readability
of the Makefile.am, and also makes us able to share some data
between some convolutional code definitions.
For example: xCCH, RACH, SCH, TCH/F, both CS2 and CS3 may use
the same *_state[][2] and *_output[][2] arrays within a single
file. This optimization is currently WIP.
Change-Id: Ib4e4ee5fdde38429e68e3b2fa50ec03a18f59daa
2016-09-07 15:18:10 +00:00
|
|
|
|
2016-11-10 10:10:42 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
path = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
|
|
|
|
|
|
|
|
sys.stderr.write("Generating convolutional codes...\n")
|
|
|
|
|
|
|
|
# Generate GSM specific codes
|
|
|
|
generate_codes(conv_codes_gsm, path, "gsm0503")
|
|
|
|
|
2016-10-26 19:19:37 +00:00
|
|
|
sys.stderr.write("Generation complete.\n")
|