def main(): log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") parser = argparse.ArgumentParser( prog="gen-lfsre-perm", description=common.wrapped_docstring(), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('-w', '--width', type=int, default=32, metavar='<#bitwidth>', help='LFSR width.') parser.add_argument('-s', '--seed', type=int, metavar='<seed>', help='Custom seed for RNG.') parser.add_argument('-p', '--prefix', type=str, metavar='name', default="", help='Optional prefix to add to ' 'types and parameters. ' 'Make sure this is PascalCase.') args = parser.parse_args() if args.width <= 0: log.error("LFSR width must be nonzero") exit(1) if args.seed is None: random.seed() args.seed = random.getrandbits(32) random.seed(args.seed) print(SV_INSTRUCTIONS) type_prefix = common.as_snake_case_prefix(args.prefix) outstr = ''' // These LFSR parameters have been generated with // $ ./util/design/gen-lfsr-seed.py --width {} --seed {} --prefix "{}" parameter int {}LfsrWidth = {}; typedef logic [{}LfsrWidth-1:0] {}lfsr_seed_t; typedef logic [{}LfsrWidth-1:0][$clog2({}LfsrWidth)-1:0] {}lfsr_perm_t; parameter {}lfsr_seed_t RndCnst{}LfsrSeedDefault = {}; parameter {}lfsr_perm_t RndCnst{}LfsrPermDefault = {{ {} }}; '''.format(args.width, args.seed, args.prefix, args.prefix, args.width, args.prefix, type_prefix, args.prefix, args.prefix, type_prefix, type_prefix, args.prefix, common.get_random_data_hex_literal(args.width), type_prefix, args.prefix, common.get_random_perm_hex_literal(args.width)) print(outstr)
def main(): log.basicConfig(level=log.WARNING, format="%(levelname)s: %(message)s") parser = argparse.ArgumentParser( prog="gen-otp-mmap", description=wrapped_docstring(), formatter_class=argparse.RawDescriptionHelpFormatter) # Generator options for compile time random netlist constants parser.add_argument('--seed', type=int, metavar='<seed>', help='Custom seed for RNG to compute default values.') args = parser.parse_args() with open(MMAP_DEFINITION_FILE, 'r') as infile: config = hjson.load(infile) # If specified, override the seed for random netlist constant computation. if args.seed: log.warning('Commandline override of seed with {}.'.format( args.seed)) config['seed'] = args.seed # Otherwise, we either take it from the .hjson if present, or # randomly generate a new seed if not. else: random.seed() new_seed = random.getrandbits(64) if config.setdefault('seed', new_seed) == new_seed: log.warning( 'No seed specified, setting to {}.'.format(new_seed)) try: otp_mmap = OtpMemMap(config) except RuntimeError as err: log.error(err) exit(1) with open(PARTITIONS_TABLE_FILE, 'w') as outfile: outfile.write(TABLE_HEADER_COMMENT + otp_mmap.create_partitions_table()) with open(DIGESTS_TABLE_FILE, 'w') as outfile: outfile.write(TABLE_HEADER_COMMENT + otp_mmap.create_digests_table()) with open(MMAP_TABLE_FILE, 'w') as outfile: outfile.write(TABLE_HEADER_COMMENT + otp_mmap.create_mmap_table()) # render all templates for template in TEMPLATES: with open(template, 'r') as tplfile: tpl = Template(tplfile.read()) with open( Path(template).parent.joinpath(Path(template).stem), 'w') as outfile: outfile.write(tpl.render(otp_mmap=otp_mmap))
def main(): log.basicConfig(level=log.WARNING, format="%(levelname)s: %(message)s") parser = argparse.ArgumentParser( prog="gen-lc-state-enc", description=wrapped_docstring(), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument('-s', '--seed', type=int, metavar='<seed>', help='Custom seed for RNG.') args = parser.parse_args() with open(LC_STATE_DEFINITION_FILE, 'r') as infile: config = hjson.load(infile) # If specified, override the seed for random netlist constant computation. if args.seed: log.warning('Commandline override of seed with {}.'.format( args.seed)) config['seed'] = args.seed # Otherwise, we either take it from the .hjson if present, or # randomly generate a new seed if not. else: random.seed() new_seed = random.getrandbits(64) if config.setdefault('seed', new_seed) == new_seed: log.warning( 'No seed specified, setting to {}.'.format(new_seed)) # validate config and generate encoding try: lc_st_enc = LcStEnc(config) except RuntimeError as err: log.error(err) exit(1) # render all templates for template in TEMPLATES: with open(template, 'r') as tplfile: tpl = Template(tplfile.read()) with open( Path(template).parent.joinpath(Path(template).stem), 'w') as outfile: outfile.write(tpl.render(lc_st_enc=lc_st_enc))
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) hex_file = Path(MEMORY_HEX_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=Path, metavar='<path>', default=hex_file, help=''' Custom output path for generated hex file. Defaults to {} '''.format(hex_file)) 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 hexfile. 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(lc_state_def_file)) with open(lc_state_def_file, 'r') as infile: lc_state_cfg = hjson.load(infile) log.info('Loading OTP memory map definition file {}'.format(mmap_def_file)) with open(mmap_def_file, '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 referqence 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) hexfile_content = memfile_header + otp_mem_img.streamout_hexfile() with open(args.out, 'w') as outfile: outfile.write(hexfile_content)
def main(): log.basicConfig(level=log.INFO, format="%(levelname)s: %(message)s") parser = argparse.ArgumentParser( prog="sparse-fsm-encode", description=wrapped_docstring(), formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '-d', type=int, default=5, metavar='<minimum HD>', help='Minimum Hamming distance between encoded states.') parser.add_argument('-m', type=int, default=7, metavar='<#states>', help='Number of states to encode.') parser.add_argument('-n', type=int, default=10, metavar='<#nbits>', help='Encoding length [bit].') parser.add_argument('-s', type=int, metavar='<seed>', help='Custom seed for RNG.') parser.add_argument('--language', choices=['sv', 'c', 'rust'], default='sv', help='Choose the language of the generated enum.') args = parser.parse_args() if args.language in ['c', 'rust']: if args.n not in [8, 16, 32]: log.error("When using C or Rust, widths must be a power-of-two " "at least a byte (8 bits) wide. You chose %d." % (args.n, )) sys.exit(1) if args.m < 2: log.error('Number of states %d must be at least 2.' % (args.m)) sys.exit(1) if args.m > 2**args.n: log.error( 'Statespace 2^%d not large enough to accommodate %d states.' % (args.n, args.m)) sys.exit(1) if (args.d >= args.n) and not (args.d == args.n and args.m == 2): log.error( 'State is only %d bits wide, which is not enough to fulfill a ' 'minimum Hamming distance constraint of %d. ' % (args.n, args.d)) sys.exit(1) if args.d <= 0: log.error('Hamming distance must be > 0.') sys.exit(1) if args.d < 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: random.seed() args.s = random.getrandbits(32) random.seed(args.s) # 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 rnd = random.getrandbits(args.n) encodings = [format(rnd, '0' + str(args.n) + 'b')] while len(encodings) < args.m: # if we iterate for too long, start over. if num_draws >= MAX_DRAWS: num_draws = 0 num_restarts += 1 rnd = random.getrandbits(args.n) encodings = [format(rnd, '0' + str(args.n) + 'b')] # if we restarted for too many times, abort. if num_restarts >= MAX_RESTARTS: log.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. rnd = random.getrandbits(args.n) cand = format(rnd, '0' + str(args.n) + 'b') # disallow all-zero and all-one states pop_cnt = cand.count('1') if pop_cnt < args.n and pop_cnt > 0: for k in encodings: # disallow candidates that are the complement of other states if int(cand, 2) == ~int(k, 2): break # disallow candidates that are too close to other states if get_hd(cand, k) < args.d: break else: encodings.append(cand) # Get Hamming distance statistics. stats = hd_histogram(encodings) if args.language == "sv": print(SV_INSTRUCTIONS) print("// Encoding generated with:\n" "// $ ./util/design/sparse-fsm-encode.py -d {} -m {} -n {} \\\n" "// -s {} --language=sv\n" "//\n" "// Hamming distance histogram:\n" "//".format(args.d, args.m, args.n, args.s)) for bar in stats['bars']: print('// ' + bar) print("//\n" "// Minimum Hamming distance: {}\n" "// Maximum Hamming distance: {}\n" "// Minimum Hamming weight: {}\n" "// Maximum Hamming weight: {}\n" "//\n" "localparam int StateWidth = {};\n" "typedef enum logic [StateWidth-1:0] {{".format( stats['min_hd'], stats['max_hd'], stats['min_hw'], stats['max_hw'], args.n)) fmt_str = " State{} {}= {}'b{}" state_str = "" 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) state_str += " State{}: ;\n".format(j) # print FSM template print('''}} state_e; state_e state_d, state_q; always_comb begin : p_fsm // Default assignments state_d = state_q; unique case (state_q) {} default: ; // Consider triggering an error or alert in this case. endcase end // This primitive is used to place a size-only constraint on the // flops in order to prevent FSM state encoding optimizations. logic [StateWidth-1:0] state_raw_q; assign state_q = state_e'(state_raw_q); prim_flop #( .Width(StateWidth), .ResetValue(StateWidth'(State0)) ) u_state_regs ( .clk_i, .rst_ni, .d_i ( state_d ), .q_o ( state_raw_q ) ); '''.format(state_str)) elif args.language == "c": print(C_INSTRUCTIONS) print("/*\n" " * Encoding generated with\n" " * $ ./util/design/sparse-fsm-encode.py -d {} -m {} -n {} \\\n" " * -s {} --language=c\n" " *\n" " * Hamming distance histogram:\n" " *".format(args.d, args.m, args.n, args.s)) for hist_bar in stats['bars']: print(" * " + hist_bar) print(" *\n" " * Minimum Hamming distance: {}\n" " * Maximum Hamming distance: {}\n" " * Minimum Hamming weight: {}\n" " * Maximum Hamming weight: {}\n" " */\n" "typedef enum my_state {{".format(stats['min_hd'], stats['max_hd'], stats['min_hw'], stats['max_hw'])) fmt_str = " kMyState{0:} {1:}= 0x{3:0" + str(math.ceil( args.n / 4)) + "x}" for j, k in enumerate(encodings): pad = "" for i in range(len(str(args.m)) - len(str(j))): pad += " " print(fmt_str.format(j, pad, args.n, int(k, 2)) + ",") # print FSM template print("} my_state_t;") elif args.language == 'rust': print(RUST_INSTRUCTIONS) print("///```text\n" "/// Encoding generated with\n" "/// $ ./util/design/sparse-fsm-encode.py -d {} -m {} -n {} \\\n" "/// -s {} --language=rust\n" "///\n" "/// Hamming distance histogram:\n" "///".format(args.d, args.m, args.n, args.s)) for hist_bar in stats['bars']: print("/// " + hist_bar) print("///\n" "/// Minimum Hamming distance: {}\n" "/// Maximum Hamming distance: {}\n" "/// Minimum Hamming weight: {}\n" "/// Maximum Hamming weight: {}\n" "///```\n" "#[derive(Clone,Copy,Eq,PartialEq,Ord,ParitalOrd,Hash,Debug)]\n" "#[repr(transparent)]\n" "struct MyState(u{});\n" "\n" "impl MyState {{".format(stats['min_hd'], stats['max_hd'], stats['min_hw'], stats['max_hw'], args.n)) fmt_str = " const MY_STATE{0:}: MyState {1:}= MyState(0x{3:0" + str( math.ceil(args.n / 4)) + "x})" for j, k in enumerate(encodings): pad = "" for i in range(len(str(args.m)) - len(str(j))): pad += " " print(fmt_str.format(j, pad, args.n, int(k, 2)) + ";") print("}")