| #!/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() |