Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 73 additions & 30 deletions util/design/sparse-fsm-encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
import argparse
import logging as log
import math
import os
import platform
import random
import subprocess
import sys

from lib.common import get_hd, hd_histogram, wrapped_docstring
Expand Down Expand Up @@ -55,6 +58,30 @@
"""


def get_python_version() -> str:
"""Returns the current Python version as a string."""
return platform.python_version()


def get_git_commit_hash(short: bool = False) -> str | None:
"""Returns the current commit hash of the repo containing the CWD, if it exists."""
cmd = ['git', 'rev-parse']
if short:
cmd.append('--short')
cmd.append('HEAD')
try:
return subprocess.check_output(
cmd,
stderr=subprocess.DEVNULL,
text=True,
cwd=os.getcwd()
).strip()
except subprocess.CalledProcessError as e:
# Not a git repo, or some other git error.
log.debug("Error getting git commit hash: %s", str(e))
return None


class EncodingGenerator:

def __init__(self, min_hd: int, num_states: int, encoding_len: int,
Expand Down Expand Up @@ -102,7 +129,7 @@ def generate(self) -> None:
num_restarts = 0

rand = random.Random()
rand.seed(self.seed)
rand.seed(self.seed, version=2)
rnd = rand.getrandbits(self.encoding_len)

while len(self.encodings) < self.num_states:
Expand Down Expand Up @@ -136,22 +163,38 @@ def generate(self) -> None:
def _print_comment(self):
if self.language == "c":
comment = " *"
print(C_INSTRUCTIONS)
print(C_INSTRUCTIONS, file=sys.stderr)
print("/*")
elif self.language == "sv":
comment = "//"
print(SV_INSTRUCTIONS)
print(SV_INSTRUCTIONS, file=sys.stderr)
elif self.language == "rust":
comment = "///"
print(RUST_INSTRUCTIONS)
print(RUST_INSTRUCTIONS, file=sys.stderr)
print("///```text")
else:
raise ValueError(f"Unsupported language: {self.lanugage}")

# Retrieve the current git commit and Python version for reproducibility.
commit = get_git_commit_hash(short=True)
version = get_python_version()

print(f"{comment} Encoding generated", end="")
if commit:
print(f" at commit {commit}", end="")
if version:
print(f" using Python {version}", end="")
print(
f"{comment} Encoding generated with:\n"
f"{comment} $ ./util/design/sparse-fsm-encode.py -d {self.min_hd} -m {self.num_states} -n {self.encoding_len} \\\n" # noqa: E501
f"{comment} -s {self.seed} --language={self.language}\n"
" with:\n"
f"{comment} $ ./util/design/sparse-fsm-encode.py"
f" --language={self.language}"
+ (" --avoid-zero" if self.avoid_zero else "") +
f" \\\n"
f"{comment} "
f" --seed {self.seed}"
f" --distance {self.min_hd}"
f" --states {self.num_states}"
f" --bits {self.encoding_len}\n"
f"{comment}\n"
f"{comment} Hamming distance histogram:\n"
f"{comment}")
Expand Down Expand Up @@ -211,12 +254,12 @@ def _print_c(self):
print("} my_state_t;")

def _print_rust(self):
print("#[derive(Clone,Copy,Eq,PartialEq,Ord,ParitalOrd,Hash,Debug)]\n"
print("#[derive(Clone, Copy, Eq, PartialEq, Ord, ParitalOrd, Hash, Debug)]\n"
"#[repr(transparent)]\n"
f"struct MyState(u{self.encoding_len});\n"
"\n"
"impl MyState {")
fmt_str = " const MY_STATE{0:}: MyState {1:}= MyState(0x{3:0" + str(
fmt_str = " const MY_STATE{0:}: MyState {1:}= MyState(0x{3:0" + str(
math.ceil(self.encoding_len / 4)) + "x});"
for j, k in enumerate(self.encodings):
pad = " " * (len(str(self.num_states)) - len(str(j)))
Expand All @@ -241,26 +284,26 @@ def main():
description=wrapped_docstring(),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'-d',
'-d', '--distance',
type=int,
default=5,
metavar='<minimum HD>',
help='Minimum Hamming distance between encoded states.')
parser.add_argument('-m',
parser.add_argument('-m', '--states',
type=int,
default=7,
metavar='<#states>',
help='Number of states to encode.')
parser.add_argument('-n',
parser.add_argument('-n', '--bits',
type=int,
default=10,
metavar='<#nbits>',
help='Encoding length [bit].')
parser.add_argument('-s',
parser.add_argument('-s', '--seed',
type=int,
metavar='<seed>',
help='Custom seed for RNG.')
parser.add_argument('--language',
parser.add_argument('-l', '--language',
choices=['sv', 'c', 'rust'],
default='sv',
help='Choose the language of the generated enum.')
Expand All @@ -272,47 +315,47 @@ def main():
args = parser.parse_args()

if args.language in ['c', 'rust']:
if args.n not in [8, 16, 32]:
if args.bits not in [8, 16, 32]:
log.error("When using C or Rust, widths must be a power-of-two "
f"at least a byte (8 bits) wide. You chose {args.n}.")
f"at least a byte (8 bits) wide. You chose {args.bits}.")
sys.exit(1)

if args.m < 2:
if args.states < 2:
log.error('Number of states (m) must be at least 2.')
sys.exit(1)

if args.m > 2**args.n:
if args.states > 2**args.bits:
log.error(
f'Statespace 2^{args.n} not large enough to accommodate {args.m} states.'
f'Statespace 2^{args.bits} not large enough to accommodate {args.states} states.'
)
sys.exit(1)

if (args.d >= args.n) and not (args.d == args.n and args.m == 2):
if (args.distance >= args.bits) and not (args.distance == args.bits and args.states == 2):
log.error(
f'State is only {args.n} bits wide, which is not enough to fulfill a '
f'minimum Hamming distance constraint of {args.d}.')
f'State is only {args.bits} bits wide, which is not enough to fulfill a '
f'minimum Hamming distance constraint of {args.distance}.')
sys.exit(1)

if args.d <= 0:
if args.distance <= 0:
log.error('Hamming distance must be > 0.')
sys.exit(1)

if args.d < 3:
if args.distance < 3:
log.warning(
'A value of 4-5 is recommended for the minimum Hamming distance '
'constraint. At a minimum, this should be set to 3.')

# If no seed has been provided, we choose a seed and print it
# into the generated output later on such that this run can be
# reproduced.
if args.s is None:
if args.seed is None:
random.seed()
args.s = random.getrandbits(32)
args.seed = random.getrandbits(32)

generator = EncodingGenerator(min_hd=args.d,
num_states=args.m,
encoding_len=args.n,
seed=args.s,
generator = EncodingGenerator(min_hd=args.distance,
num_states=args.states,
encoding_len=args.bits,
seed=args.seed,
language=args.language,
avoid_zero=args.avoid_zero)
generator.generate()
Expand Down