Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright lowRISC contributors. |
| 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | r"""Generate RTL and documentation collateral from OTP memory |
| 6 | map definition file (hjson). |
| 7 | """ |
| 8 | import argparse |
| 9 | import datetime |
| 10 | import logging as log |
| 11 | import random |
Michael Schaffner | 82c2181 | 2021-06-23 15:07:39 -0700 | [diff] [blame] | 12 | import re |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 13 | from pathlib import Path |
| 14 | |
| 15 | import hjson |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 16 | |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 17 | from lib.common import wrapped_docstring |
| 18 | from lib.OtpMemImg import OtpMemImg |
| 19 | |
| 20 | # Get the memory map definition. |
| 21 | MMAP_DEFINITION_FILE = 'hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson' |
| 22 | # Life cycle state and ECC poly definitions. |
| 23 | LC_STATE_DEFINITION_FILE = 'hw/ip/lc_ctrl/data/lc_ctrl_state.hjson' |
| 24 | # Default image file definition (can be overridden on the command line). |
| 25 | IMAGE_DEFINITION_FILE = 'hw/ip/otp_ctrl/data/otp_ctrl_img_dev.hjson' |
| 26 | # Default output path (can be overridden on the command line). |
Michael Schaffner | 7e0b00a | 2021-02-23 16:22:05 -0800 | [diff] [blame] | 27 | MEMORY_HEX_FILE = 'otp-img.vmem' |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 28 | |
| 29 | |
| 30 | def _override_seed(args, name, config): |
| 31 | '''Override the seed key in config with value specified in args''' |
| 32 | arg_seed = getattr(args, name) |
| 33 | if arg_seed: |
| 34 | log.warning('Commandline override of {} with {}.'.format( |
| 35 | name, arg_seed)) |
| 36 | config['seed'] = arg_seed |
| 37 | # Otherwise, we either take it from the .hjson if present, or |
| 38 | # randomly generate a new seed if not. |
| 39 | else: |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 40 | new_seed = random.getrandbits(64) |
| 41 | if config.setdefault('seed', new_seed) == new_seed: |
| 42 | log.warning('No {} specified, setting to {}.'.format( |
| 43 | name, new_seed)) |
| 44 | |
| 45 | |
Michael Schaffner | 82c2181 | 2021-06-23 15:07:39 -0700 | [diff] [blame] | 46 | def _permutation_string(data_perm): |
| 47 | '''Check permutation format and expand the ranges''' |
| 48 | |
| 49 | if not isinstance(data_perm, str): |
| 50 | TypeError() |
| 51 | |
| 52 | if not data_perm: |
| 53 | return '' |
| 54 | |
| 55 | # Check the format first |
| 56 | pattern = r'^((?:\[[0-9]+:[0-9]+\])+(?:,\[[0-9]+:[0-9]+\])*)' |
| 57 | match = re.fullmatch(pattern, data_perm) |
| 58 | if match is None: |
| 59 | raise ValueError() |
| 60 | # Expand the ranges |
| 61 | expanded_perm = [] |
| 62 | groups = match.groups() |
| 63 | for group in groups[0].split(','): |
| 64 | k1, k0 = [int(x) for x in group[1:-1].split(':')] |
| 65 | if k1 > k0: |
| 66 | expanded_perm = list(range(k0, k1 + 1)) + expanded_perm |
| 67 | else: |
| 68 | expanded_perm = list(range(k0, k1 - 1, -1)) + expanded_perm |
| 69 | |
| 70 | return expanded_perm |
| 71 | |
| 72 | |
Michael Schaffner | 5987660 | 2021-02-23 19:25:31 -0800 | [diff] [blame] | 73 | # TODO: this can be removed when we have moved to Python 3.8 |
| 74 | # in all regressions, since the extend action is only available |
| 75 | # from that version onward. |
| 76 | # This workaround solution has been taken verbatim from |
| 77 | # https://stackoverflow.com/questions/41152799/argparse-flatten-the-result-of-action-append |
| 78 | class ExtendAction(argparse.Action): |
| 79 | '''Extend action for the argument parser''' |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 80 | |
Michael Schaffner | 5987660 | 2021-02-23 19:25:31 -0800 | [diff] [blame] | 81 | def __call__(self, parser, namespace, values, option_string=None): |
| 82 | items = getattr(namespace, self.dest) or [] |
| 83 | items.extend(values) |
| 84 | setattr(namespace, self.dest, items) |
| 85 | |
| 86 | |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 87 | def main(): |
| 88 | log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") |
| 89 | |
| 90 | # Make sure the script can also be called from other dirs than |
| 91 | # just the project root by adapting the default paths accordingly. |
| 92 | proj_root = Path(__file__).parent.joinpath('../../') |
| 93 | lc_state_def_file = Path(proj_root).joinpath(LC_STATE_DEFINITION_FILE) |
| 94 | mmap_def_file = Path(proj_root).joinpath(MMAP_DEFINITION_FILE) |
| 95 | img_def_file = Path(proj_root).joinpath(IMAGE_DEFINITION_FILE) |
| 96 | hex_file = Path(MEMORY_HEX_FILE) |
| 97 | |
| 98 | parser = argparse.ArgumentParser( |
| 99 | prog="gen-otp-img", |
| 100 | description=wrapped_docstring(), |
| 101 | formatter_class=argparse.RawDescriptionHelpFormatter) |
Michael Schaffner | 5987660 | 2021-02-23 19:25:31 -0800 | [diff] [blame] | 102 | parser.register('action', 'extend', ExtendAction) |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 103 | parser.add_argument('--quiet', |
| 104 | '-q', |
| 105 | action='store_true', |
Rupert Swarbrick | 717b0c0 | 2021-04-09 15:57:43 +0100 | [diff] [blame] | 106 | help='''Don't print out progress messages.''') |
Srikrishna Iyer | 0913e13 | 2021-11-23 17:28:12 -0800 | [diff] [blame] | 107 | parser.add_argument('--seed', |
| 108 | type=int, |
| 109 | metavar='<seed>', |
| 110 | help="Custom seed used for randomization.") |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 111 | parser.add_argument('--img-seed', |
| 112 | type=int, |
| 113 | metavar='<seed>', |
| 114 | help=''' |
| 115 | Custom seed for RNG to compute randomized items in OTP image. |
| 116 | |
| 117 | Can be used to override the seed value specified in the image |
| 118 | config Hjson. |
| 119 | ''') |
| 120 | parser.add_argument('--lc-seed', |
| 121 | type=int, |
| 122 | metavar='<seed>', |
| 123 | help=''' |
| 124 | Custom seed for RNG to compute randomized life cycle netlist constants. |
| 125 | |
| 126 | Note that this seed must coincide with the seed used for generating |
| 127 | the LC state encoding (gen-lc-state-enc.py). |
| 128 | |
| 129 | This value typically does not need to be specified as it is taken from |
| 130 | the LC state encoding definition Hjson. |
| 131 | ''') |
| 132 | parser.add_argument('--otp-seed', |
| 133 | type=int, |
| 134 | metavar='<seed>', |
| 135 | help=''' |
| 136 | Custom seed for RNG to compute randomized OTP netlist constants. |
| 137 | |
| 138 | Note that this seed must coincide with the seed used for generating |
| 139 | the OTP memory map (gen-otp-mmap.py). |
| 140 | |
| 141 | This value typically does not need to be specified as it is taken from |
| 142 | the OTP memory map definition Hjson. |
| 143 | ''') |
| 144 | parser.add_argument('-o', |
| 145 | '--out', |
| 146 | type=Path, |
| 147 | metavar='<path>', |
| 148 | default=hex_file, |
| 149 | help=''' |
| 150 | Custom output path for generated hex file. |
| 151 | Defaults to {} |
| 152 | '''.format(hex_file)) |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 153 | parser.add_argument('--lc-state-def', |
| 154 | type=Path, |
| 155 | metavar='<path>', |
| 156 | default=lc_state_def_file, |
| 157 | help=''' |
| 158 | Life cycle state definition file in Hjson format. |
| 159 | ''') |
| 160 | parser.add_argument('--mmap-def', |
| 161 | type=Path, |
| 162 | metavar='<path>', |
| 163 | default=mmap_def_file, |
| 164 | help=''' |
| 165 | OTP memory map file in Hjson format. |
| 166 | ''') |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 167 | parser.add_argument('--img-cfg', |
| 168 | type=Path, |
| 169 | metavar='<path>', |
| 170 | default=img_def_file, |
| 171 | help=''' |
| 172 | Image configuration file in Hjson format. |
| 173 | Defaults to {} |
| 174 | '''.format(img_def_file)) |
| 175 | parser.add_argument('--add-cfg', |
| 176 | type=Path, |
| 177 | metavar='<path>', |
| 178 | action='extend', |
| 179 | nargs='+', |
| 180 | default=[], |
| 181 | help=''' |
| 182 | Additional image configuration file in Hjson format. |
| 183 | |
| 184 | This switch can be specified multiple times. |
| 185 | Image configuration files are parsed in the same |
| 186 | order as they are specified on the command line, |
| 187 | and partition item values that are specified multiple |
| 188 | times are overridden in that order. |
| 189 | |
| 190 | Note that seed values in additional configuration files |
| 191 | are ignored. |
| 192 | ''') |
Michael Schaffner | 82c2181 | 2021-06-23 15:07:39 -0700 | [diff] [blame] | 193 | parser.add_argument('--data-perm', |
| 194 | type=_permutation_string, |
| 195 | metavar='<map>', |
| 196 | default='', |
| 197 | help=''' |
| 198 | This is a post-processing option and allows permuting |
| 199 | the bit positions before writing the hexfile. |
| 200 | The bit mapping needs to be supplied as a comma separated list |
| 201 | of bit slices, where the numbers refer to the bit positions in |
| 202 | the original data word before remapping, for example: |
| 203 | |
| 204 | "[7:0],[16:8]". |
| 205 | |
| 206 | The mapping must be bijective - otherwise this will generate |
| 207 | an error. |
| 208 | ''') |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 209 | |
| 210 | args = parser.parse_args() |
| 211 | |
Rupert Swarbrick | 717b0c0 | 2021-04-09 15:57:43 +0100 | [diff] [blame] | 212 | if args.quiet: |
| 213 | log.getLogger().setLevel(log.WARNING) |
| 214 | |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 215 | log.info('Loading LC state definition file {}'.format(args.lc_state_def)) |
| 216 | with open(args.lc_state_def, 'r') as infile: |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 217 | lc_state_cfg = hjson.load(infile) |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 218 | log.info('Loading OTP memory map definition file {}'.format(args.mmap_def)) |
| 219 | with open(args.mmap_def, 'r') as infile: |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 220 | otp_mmap_cfg = hjson.load(infile) |
| 221 | log.info('Loading main image configuration file {}'.format(args.img_cfg)) |
| 222 | with open(args.img_cfg, 'r') as infile: |
| 223 | img_cfg = hjson.load(infile) |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 224 | |
| 225 | # Set the initial random seed so that the generated image is |
Srikrishna Iyer | 0913e13 | 2021-11-23 17:28:12 -0800 | [diff] [blame] | 226 | # deterministically randomized. |
| 227 | random.seed(args.seed) |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 228 | |
| 229 | # If specified, override the seeds. |
| 230 | _override_seed(args, 'lc_seed', lc_state_cfg) |
| 231 | _override_seed(args, 'otp_seed', otp_mmap_cfg) |
| 232 | _override_seed(args, 'img_seed', img_cfg) |
| 233 | |
| 234 | try: |
Timothy Trippel | 5c592dd | 2022-04-18 11:07:57 -0700 | [diff] [blame] | 235 | otp_mem_img = OtpMemImg(lc_state_cfg, otp_mmap_cfg, img_cfg, |
| 236 | args.data_perm) |
Michael Schaffner | bdcb2cd | 2021-01-28 15:54:37 -0800 | [diff] [blame] | 237 | |
| 238 | for f in args.add_cfg: |
| 239 | log.info( |
| 240 | 'Processing additional image configuration file {}'.format(f)) |
| 241 | log.info('') |
| 242 | with open(f, 'r') as infile: |
| 243 | cfg = hjson.load(infile) |
| 244 | otp_mem_img.override_data(cfg) |
| 245 | log.info('') |
| 246 | |
| 247 | except RuntimeError as err: |
| 248 | log.error(err) |
| 249 | exit(1) |
| 250 | |
| 251 | # Print all defined args into header comment for referqence |
| 252 | argstr = '' |
| 253 | for arg, argval in sorted(vars(args).items()): |
| 254 | if argval: |
| 255 | if not isinstance(argval, list): |
| 256 | argval = [argval] |
| 257 | for a in argval: |
| 258 | argname = '-'.join(arg.split('_')) |
| 259 | # Get absolute paths for all files specified. |
| 260 | a = a.resolve() if isinstance(a, Path) else a |
| 261 | argstr += ' \\\n// --' + argname + ' ' + str(a) + '' |
| 262 | |
| 263 | dt = datetime.datetime.now(datetime.timezone.utc) |
| 264 | dtstr = dt.strftime("%a, %d %b %Y %H:%M:%S %Z") |
| 265 | memfile_header = '// Generated on {} with\n// $ gen-otp-img.py {}\n//\n'.format( |
| 266 | dtstr, argstr) |
| 267 | |
| 268 | hexfile_content = memfile_header + otp_mem_img.streamout_hexfile() |
| 269 | |
| 270 | with open(args.out, 'w') as outfile: |
| 271 | outfile.write(hexfile_content) |
| 272 | |
| 273 | |
| 274 | if __name__ == "__main__": |
| 275 | main() |