def _get_incremental_codewords(config, base_ecc, existing_words): '''Get all possible incremental codewords fulfilling the constraints.''' base_data = base_ecc[config['secded']['ecc_width']:] # We only need to spin through data bits that have not been set yet. # Hence, we first count how many bits are zero (and hence still # modifyable). Then, we enumerate all possible combinations and scatter # the bits of the enumerated values into the correct bit positions using # the scatter_bits() function. incr_cands = [] free_bits = base_data.count('0') for k in range(1, 2**free_bits): # Get incremental dataword by scattering the enumeration bits # into the zero bit positions in base_data. incr_cand = scatter_bits(base_data, format(k, '0' + str(free_bits) + 'b')) incr_cand_ecc = ecc_encode(config, incr_cand) # Dataword is correct by construction, but we need to check whether # the ECC bits are incremental. if _is_incremental_codeword(base_ecc, incr_cand_ecc): # Check whether the candidate fulfills the maximum # Hamming weight constraint. if incr_cand_ecc.count('1') <= config['max_hw']: # Check Hamming distance wrt all existing words. for w in existing_words + [base_ecc]: if get_hd(incr_cand_ecc, w) < config['min_hd']: break else: incr_cands.append(incr_cand_ecc) return incr_cands
def _get_new_state_word_pair(config, existing_words): '''Randomly generate a new incrementally writable word pair''' while 1: # Draw a random number and check whether it is unique and whether # the Hamming weight is in range. width = config['secded']['data_width'] ecc_width = config['secded']['ecc_width'] base = random.getrandbits(width) base = format(base, '0' + str(width) + 'b') base_cand_ecc = ecc_encode(config, base) # disallow all-zero and all-one states pop_cnt = base_cand_ecc.count('1') if pop_cnt >= config['min_hw'] and pop_cnt <= config['max_hw']: # Check Hamming distance wrt all existing words for w in existing_words: if get_hd(base_cand_ecc, w) < config['min_hd']: break else: # Get encoded incremental candidates. incr_cands_ecc = _get_incremental_codewords( config, base_cand_ecc, existing_words) # there are valid candidates, draw one at random. # otherwise we just start over. if incr_cands_ecc: incr_cand_ecc = random.choice(incr_cands_ecc) log.info('word {}: {}|{} -> {}|{}'.format( int(len(existing_words) / 2), base_cand_ecc[ecc_width:], base_cand_ecc[0:ecc_width], incr_cand_ecc[ecc_width:], incr_cand_ecc[0:ecc_width])) existing_words.append(base_cand_ecc) existing_words.append(incr_cand_ecc) return (base_cand_ecc, incr_cand_ecc)
def _validate_words(config, words): '''Validate generated words (base and incremental).''' for k, w in enumerate(words): # Check whether word is valid wrt to ECC polynomial. if not is_valid_codeword(config, w): raise RuntimeError('Codeword {} at index {} is not valid'.format( w, k)) # Check that word fulfills the Hamming weight constraints. pop_cnt = w.count('1') if pop_cnt < config['min_hw'] or pop_cnt > config['max_hw']: raise RuntimeError( 'Codeword {} at index {} has wrong Hamming weight'.format( w, k)) # Check Hamming distance wrt to all other existing words. # If the constraint is larger than 0 this implies uniqueness. if k < len(words) - 1: for k2, w2 in enumerate(words[k + 1:]): if get_hd(w, w2) < config['min_hd']: raise RuntimeError( 'Hamming distance between codeword {} at index {} ' 'and codeword {} at index {} is too low.'.format( w, k, w2, k + 1 + k2))
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("}")