blob: 963f45c12b00ef663bac718dc4ab8961666911bb [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r"""Script for generating sparse FSM encodings that fulfill a minimum
Hamming distance requirement.
"""
import argparse
import logging
import random
import sys
MAX_DRAWS = 10000
MAX_RESTARTS = 10000
USAGE = '''
./sparse-fsm-encode -d <minimum HD> -m <#states> -n <#bits>
'''
def main():
logging.basicConfig(level=logging.INFO,
format="%(asctime)s - %(message)s",
datefmt="%Y-%m-%d %H:%M")
parser = argparse.ArgumentParser(
prog="sparse-fsm-encode",
formatter_class=argparse.RawDescriptionHelpFormatter,
usage=USAGE)
parser.add_argument('-d',
type=int,
default=2,
help='Minimum Hamming distance between encoded states.')
parser.add_argument('-m',
type=int,
default=12,
help='Number of states.')
parser.add_argument('-n',
type=int,
default=8,
help='Encoding length [bit].')
args = parser.parse_args()
if args.m > 2**args.n:
logging.error(
'Statespace 2^%d not large enough to accommodate %d states.' %
(args.n, args.m))
sys.exit(1)
random.seed()
# This is a heuristic that opportunistically draws random
# state encodings and check whether they fulfill the minimum
# Hamming distance constraint.
# Other solutions that use a brute-force approach would be
# possible as well (see e.g. https://math.stackexchange.com/
# questions/891528/generating-a-binary-code-with-maximized-hamming-distance).
# However, due to the sparse nature of the state space, this
# probabilistic heuristic works pretty well for most practical
# cases, and it scales favorably to large N.
num_draws = 0
num_restarts = 0
encodings = [random.getrandbits(args.n)]
while len(encodings) < args.m:
# if we iterate for too long, start over.
if num_draws >= MAX_DRAWS:
num_draws = 0
num_restarts += 1
encodings = [random.getrandbits(args.n)]
# if we restarted for too many times, abort.
if num_restarts >= MAX_RESTARTS:
logging.error(
'Did not find a solution after restarting {} times. This is '
'an indicator that not many (or even no) solutions exist for '
'the current parameterization. Rerun the script and/or adjust '
'the D/M/N parameters. E.g. make the state space more sparse by '
'increasing N, or lower the minimum Hamming distance threshold D.'
.format(num_restarts))
sys.exit(1)
num_draws += 1
# draw a candidate and check whether it fulfills the minimum
# distance requirement with respect to other encodings.
c = random.getrandbits(args.n)
for k in encodings:
if bin(c ^ k).count('1') < args.d:
break
else:
encodings.append(c)
# build Hamming distance histogram
minimum = args.n
maximum = 0
hist = [0] * (args.n + 1)
for i, j in enumerate(encodings):
if i < len(encodings) - 1:
for k in encodings[i + 1:]:
dist = bin(j ^ k).count('1')
hist[dist] += 1
minimum = min(dist, minimum)
maximum = max(dist, maximum)
print("// Encoding generated with ./sparse-fsm-encode -d {} -m {} -n {}".format(args.d, args.m, args.n))
print("// Hamming distance histogram:")
print("// ")
for i, j in enumerate(hist):
hist_bar = "// {}: ".format(i)
for k in range(len(str(args.m)) - len(str(i))):
hist_bar += " "
for k in range(j * 20 // max(hist)):
hist_bar += "|"
hist_bar += " ({:.2f}%)".format(100.0 * j / sum(hist)) if j else "--"
print(hist_bar)
print("// ")
print("// Minimum Hamming distance: {}".format(minimum))
print("// Maximum Hamming distance: {}".format(maximum))
print("//")
print('typedef enum logic [{}:0]'.format(args.n - 1) + ' {')
fmt_str = " State{0:} {1:}= {2:}'b{3:0" + str(args.n) + "b}"
for j, k in enumerate(encodings):
pad = ""
for i in range(len(str(args.m)) - len(str(j))):
pad += " "
comma = "," if j < len(encodings) - 1 else ""
print(fmt_str.format(j, pad, args.n, k) + comma)
print('} state_e;')
if __name__ == "__main__":
main()