blob: 23aa8a9dd81284aa3be0d81cfed43ff52025a283 [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"""Generate RTL and documentation collateral from OTP memory
map definition file (hjson).
"""
import argparse
import datetime
import logging as log
import random
import re
from pathlib import Path
import hjson
from lib.common import wrapped_docstring
from lib.OtpMemImg import OtpMemImg
# Get the memory map definition.
MMAP_DEFINITION_FILE = 'hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson'
# Life cycle state and ECC poly definitions.
LC_STATE_DEFINITION_FILE = 'hw/ip/lc_ctrl/data/lc_ctrl_state.hjson'
# Default image file definition (can be overridden on the command line).
IMAGE_DEFINITION_FILE = 'hw/ip/otp_ctrl/data/otp_ctrl_img_dev.hjson'
# Default output path (can be overridden on the command line). Note that
# "BITWIDTH" will be replaced with the architecture's bitness.
MEMORY_MEM_FILE = 'otp-img.BITWIDTH.vmem'
def _override_seed(args, name, config):
'''Override the seed key in config with value specified in args'''
arg_seed = getattr(args, name)
# An override seed of 0 will not trigger the override, which is intended, as
# the OTP-generation Bazel rule sets the default seed values to 0.
if arg_seed:
log.warning('Commandline override of {} with {}.'.format(
name, arg_seed))
config['seed'] = arg_seed
# Otherwise, we either take it from the .hjson if present, or
# randomly generate a new seed if not.
else:
new_seed = random.getrandbits(64)
if config.setdefault('seed', new_seed) == new_seed:
log.warning('No {} specified, setting to {}.'.format(
name, new_seed))
def _permutation_string(data_perm):
'''Check permutation format and expand the ranges'''
if not isinstance(data_perm, str):
TypeError()
if not data_perm:
return ''
# Check the format first
pattern = r'^((?:\[[0-9]+:[0-9]+\])+(?:,\[[0-9]+:[0-9]+\])*)'
match = re.fullmatch(pattern, data_perm)
if match is None:
raise ValueError()
# Expand the ranges
expanded_perm = []
groups = match.groups()
for group in groups[0].split(','):
k1, k0 = [int(x) for x in group[1:-1].split(':')]
if k1 > k0:
expanded_perm = list(range(k0, k1 + 1)) + expanded_perm
else:
expanded_perm = list(range(k0, k1 - 1, -1)) + expanded_perm
return expanded_perm
# TODO: this can be removed when we have moved to Python 3.8
# in all regressions, since the extend action is only available
# from that version onward.
# This workaround solution has been taken verbatim from
# https://stackoverflow.com/questions/41152799/argparse-flatten-the-result-of-action-append
class ExtendAction(argparse.Action):
'''Extend action for the argument parser'''
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest) or []
items.extend(values)
setattr(namespace, self.dest, items)
def main():
log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s")
# Make sure the script can also be called from other dirs than
# just the project root by adapting the default paths accordingly.
proj_root = Path(__file__).parent.joinpath('../../')
lc_state_def_file = Path(proj_root).joinpath(LC_STATE_DEFINITION_FILE)
mmap_def_file = Path(proj_root).joinpath(MMAP_DEFINITION_FILE)
img_def_file = Path(proj_root).joinpath(IMAGE_DEFINITION_FILE)
parser = argparse.ArgumentParser(
prog="gen-otp-img",
description=wrapped_docstring(),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.register('action', 'extend', ExtendAction)
parser.add_argument('--quiet',
'-q',
action='store_true',
help='''Don't print out progress messages.''')
parser.add_argument('--seed',
type=int,
metavar='<seed>',
help="Custom seed used for randomization.")
parser.add_argument('--img-seed',
type=int,
metavar='<seed>',
help='''
Custom seed for RNG to compute randomized items in OTP image.
Can be used to override the seed value specified in the image
config Hjson.
''')
parser.add_argument('--lc-seed',
type=int,
metavar='<seed>',
help='''
Custom seed for RNG to compute randomized life cycle netlist constants.
Note that this seed must coincide with the seed used for generating
the LC state encoding (gen-lc-state-enc.py).
This value typically does not need to be specified as it is taken from
the LC state encoding definition Hjson.
''')
parser.add_argument('--otp-seed',
type=int,
metavar='<seed>',
help='''
Custom seed for RNG to compute randomized OTP netlist constants.
Note that this seed must coincide with the seed used for generating
the OTP memory map (gen-otp-mmap.py).
This value typically does not need to be specified as it is taken from
the OTP memory map definition Hjson.
''')
parser.add_argument('-o',
'--out',
type=str,
metavar='<path>',
default=MEMORY_MEM_FILE,
help='''
Custom output path for generated MEM file.
Defaults to {}
'''.format(MEMORY_MEM_FILE))
parser.add_argument('--lc-state-def',
type=Path,
metavar='<path>',
default=lc_state_def_file,
help='''
Life cycle state definition file in Hjson format.
''')
parser.add_argument('--mmap-def',
type=Path,
metavar='<path>',
default=mmap_def_file,
help='''
OTP memory map file in Hjson format.
''')
parser.add_argument('--img-cfg',
type=Path,
metavar='<path>',
default=img_def_file,
help='''
Image configuration file in Hjson format.
Defaults to {}
'''.format(img_def_file))
parser.add_argument('--add-cfg',
type=Path,
metavar='<path>',
action='extend',
nargs='+',
default=[],
help='''
Additional image configuration file in Hjson format.
This switch can be specified multiple times.
Image configuration files are parsed in the same
order as they are specified on the command line,
and partition item values that are specified multiple
times are overridden in that order.
Note that seed values in additional configuration files
are ignored.
''')
parser.add_argument('--data-perm',
type=_permutation_string,
metavar='<map>',
default='',
help='''
This is a post-processing option and allows permuting
the bit positions before writing the memfile.
The bit mapping needs to be supplied as a comma separated list
of bit slices, where the numbers refer to the bit positions in
the original data word before remapping, for example:
"[7:0],[16:8]".
The mapping must be bijective - otherwise this will generate
an error.
''')
args = parser.parse_args()
if args.quiet:
log.getLogger().setLevel(log.WARNING)
log.info('Loading LC state definition file {}'.format(args.lc_state_def))
with open(args.lc_state_def, 'r') as infile:
lc_state_cfg = hjson.load(infile)
log.info('Loading OTP memory map definition file {}'.format(args.mmap_def))
with open(args.mmap_def, 'r') as infile:
otp_mmap_cfg = hjson.load(infile)
log.info('Loading main image configuration file {}'.format(args.img_cfg))
with open(args.img_cfg, 'r') as infile:
img_cfg = hjson.load(infile)
# Set the initial random seed so that the generated image is
# deterministically randomized.
random.seed(args.seed)
# If specified, override the seeds.
_override_seed(args, 'lc_seed', lc_state_cfg)
_override_seed(args, 'otp_seed', otp_mmap_cfg)
_override_seed(args, 'img_seed', img_cfg)
try:
otp_mem_img = OtpMemImg(lc_state_cfg, otp_mmap_cfg, img_cfg,
args.data_perm)
for f in args.add_cfg:
log.info(
'Processing additional image configuration file {}'.format(f))
log.info('')
with open(f, 'r') as infile:
cfg = hjson.load(infile)
otp_mem_img.override_data(cfg)
log.info('')
except RuntimeError as err:
log.error(err)
exit(1)
# Print all defined args into header comment for reference
argstr = ''
for arg, argval in sorted(vars(args).items()):
if argval:
if not isinstance(argval, list):
argval = [argval]
for a in argval:
argname = '-'.join(arg.split('_'))
# Get absolute paths for all files specified.
a = a.resolve() if isinstance(a, Path) else a
argstr += ' \\\n// --' + argname + ' ' + str(a) + ''
dt = datetime.datetime.now(datetime.timezone.utc)
dtstr = dt.strftime("%a, %d %b %Y %H:%M:%S %Z")
memfile_header = '// Generated on {} with\n// $ gen-otp-img.py {}\n//\n'.format(
dtstr, argstr)
memfile_body, bitness = otp_mem_img.streamout_memfile()
# If the out argument does not contain "BITWIDTH", it will not be changed.
memfile_path = Path(args.out.replace('BITWIDTH', str(bitness)))
with open(memfile_path, 'w') as outfile:
outfile.write(memfile_header)
outfile.write(memfile_body)
if __name__ == "__main__":
main()