|  | #!/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"""SECDED encoder/decoder generator | 
|  |  | 
|  | Current version doesn't optimize Fan-In. It uses Hsiao code (modified version | 
|  | of Hamming code + parity). Please refer https://arxiv.org/pdf/0803.1217.pdf | 
|  | """ | 
|  |  | 
|  | # TODO: Add FPV assertions in the encoder/decoder module | 
|  |  | 
|  | import argparse | 
|  | import itertools | 
|  | import logging as log | 
|  | import math | 
|  | import os | 
|  | import random | 
|  | import sys | 
|  | import time | 
|  | from pathlib import PurePath | 
|  |  | 
|  | COPYRIGHT = """// Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | """ | 
|  |  | 
|  |  | 
|  | def min_paritysize(k): | 
|  | # SECDED --> Hamming distance 'd': 4 | 
|  | # 2^(m-1) should cover (m+k) | 
|  | for m in range(2, 10): | 
|  | if 2**m >= (k + m + 1): | 
|  | return m + 1 | 
|  | return -1 | 
|  |  | 
|  |  | 
|  | def ideal_fanin(k, m): | 
|  | """Compute Ideal Max Fanin of any bit in the ecc codes.""" | 
|  | fanin = 0 | 
|  | needed = k | 
|  | for select in range(3, m + 1, 2): | 
|  | combinations = list(itertools.combinations(range(m), select)) | 
|  | if len(combinations) <= needed: | 
|  | fanin += int(math.ceil(float(len(combinations) * select) / m)) | 
|  | needed -= len(combinations) | 
|  | else: | 
|  | fanin += int(math.ceil(float(needed * select) / m)) | 
|  | needed = 0 | 
|  | if not needed: | 
|  | break | 
|  | return fanin | 
|  |  | 
|  |  | 
|  | def calc_fanin(width, codes): | 
|  | """Sum the ones in a column""" | 
|  | fanins = [0] * width | 
|  | log.info("Calc Code: {}".format(codes)) | 
|  | for i in codes: | 
|  | for e in i: | 
|  | fanins[e] += 1 | 
|  |  | 
|  | return fanins | 
|  |  | 
|  |  | 
|  | def print_comb(n, | 
|  | k, | 
|  | m, | 
|  | cur_m, | 
|  | codes, | 
|  | start_cnt, | 
|  | max_width=100, | 
|  | prefix="", | 
|  | first_indent=0): | 
|  | """Print XOR comb. | 
|  |  | 
|  | @param[max_width]    Maximum Width of str | 
|  | @param[prefix]       The prepend string at the first line | 
|  | @param[first_indent] The number of character that indented at the first line | 
|  | e.g. first_indent := 2 | 
|  | {prefix}in[nn] ... | 
|  | ^ in[nn] ^ in[nn] | 
|  |  | 
|  | result: | 
|  | {prefix}in[nn] ^ ... in[nn] | 
|  | ^ in[nn] ^ ... in[nn]; | 
|  | """ | 
|  | outstr = "" | 
|  | line = prefix | 
|  | prepend_len = len(prefix) | 
|  | cnt = start_cnt | 
|  | first = True | 
|  | for j in range(k): | 
|  | temp_str = "" | 
|  | if cur_m in codes[j]: | 
|  | if not first: | 
|  | temp_str += " ^" | 
|  | if first: | 
|  | first = False | 
|  | temp_str += " in[%d]" % (j) | 
|  | temp_len = len(temp_str) | 
|  |  | 
|  | if len(line) + temp_len > max_width: | 
|  | outstr += line + "\n" | 
|  | line = ' ' * (prepend_len - first_indent) + temp_str | 
|  | else: | 
|  | line += temp_str | 
|  | outstr += line + ";\n" | 
|  | return outstr | 
|  |  | 
|  |  | 
|  | def print_enc(n, k, m, codes): | 
|  | outstr = "" | 
|  | for i in range(k): | 
|  | outstr += "  assign out[%d] = in[%d] ;\n" % (i, i) | 
|  |  | 
|  | for i in range(m): | 
|  | # Print parity computation | 
|  | outstr += print_comb(n, k, m, i, codes, 0, 100, | 
|  | "  assign out[%d] =" % (i + k), 2) | 
|  | return outstr | 
|  |  | 
|  |  | 
|  | def calc_syndrome(code): | 
|  | return sum(map((lambda x: 2**x), code)) | 
|  |  | 
|  |  | 
|  | def print_dec(n, k, m, codes): | 
|  | outstr = "" | 
|  | outstr += "  logic single_error;\n" | 
|  | outstr += "\n" | 
|  | outstr += "  // Syndrome calculation\n" | 
|  | for i in range(m): | 
|  | # Print combination | 
|  | outstr += print_comb(n, k, m, i, codes, 1, 100, | 
|  | "  assign syndrome_o[%d] = in[%d] ^" % (i, k + i), | 
|  | len(" in[%d] ^" % (k + i)) + 2) | 
|  |  | 
|  | outstr += "\n" | 
|  | outstr += "  // Corrected output calculation\n" | 
|  | for i in range(k): | 
|  | synd_v = calc_syndrome(codes[i]) | 
|  | outstr += "  assign d_o[%d] = (syndrome_o == %d'h%x) ^ in[%d];\n" % ( | 
|  | i, m, calc_syndrome(codes[i]), i) | 
|  | outstr += "\n" | 
|  | outstr += "  // err_o calc. bit0: single error, bit1: double error\n" | 
|  | outstr += "  assign single_error = ^syndrome_o;\n" | 
|  | outstr += "  assign err_o[0] =  single_error;\n" | 
|  | outstr += "  assign err_o[1] = ~single_error & (|syndrome_o);\n" | 
|  | return outstr | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser( | 
|  | prog="secded_gen", | 
|  | description='''This tool generates Single Error Correction Double Error | 
|  | Detection(SECDED) encoder and decoder modules in SystemVerilog. | 
|  | ''') | 
|  | parser.add_argument( | 
|  | '-m', | 
|  | type=int, | 
|  | default=7, | 
|  | help= | 
|  | 'parity length. If fan-in is too big, increasing m helps. (default: %(default)s)' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '-k', | 
|  | type=int, | 
|  | default=32, | 
|  | help= | 
|  | 'code length. Minimum \'m\' is calculated by the tool (default: %(default)s)' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--outdir', | 
|  | default='../rtl', | 
|  | help= | 
|  | 'output directory. The output file will be named `prim_secded_<n>_<k>_enc/dec.sv` (default: %(default)s)' | 
|  | ) | 
|  | parser.add_argument('--verbose', '-v', action='store_true', help='Verbose') | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | if (args.verbose): | 
|  | log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG) | 
|  | else: | 
|  | log.basicConfig(format="%(levelname)s: %(message)s") | 
|  |  | 
|  | # Error checking | 
|  | if (args.k <= 1 or args.k > 120): | 
|  | log.error("Current tool doesn't support the value k (%d)", args.k) | 
|  | k = args.k | 
|  |  | 
|  | if (args.m <= 1 or args.m > 20): | 
|  | log.error("Current tool doesn't support the value m (%d)", args.m) | 
|  |  | 
|  | # Calculate 'm' (parity size) | 
|  | min_m = min_paritysize(k) | 
|  | if (args.m < min_m): | 
|  | log.error("given \'m\' argument is smaller than minimum requirement") | 
|  | m = min_m | 
|  | else: | 
|  | m = args.m | 
|  |  | 
|  | n = m + k | 
|  | log.info("n(%d), k(%d), m(%d)", n, k, m) | 
|  |  | 
|  | random.seed(time.time()) | 
|  |  | 
|  | # using itertools combinations, generate odd number of 1 in a row | 
|  |  | 
|  | required_row = k  # k rows are needed, decreasing everytime when it acquite | 
|  |  | 
|  | fanin_ideal = ideal_fanin(k, m) | 
|  | log.info("Ideal Fan-In value: %d" % fanin_ideal) | 
|  |  | 
|  | # Each entry represents a row in below parity matrix | 
|  | # Entry is tuple and the value inside is the position of ones | 
|  | # e.g. (0,1,2) in m:=7 | 
|  | # row -> [1 1 1 0 0 0 0] | 
|  | codes = [] | 
|  |  | 
|  | ## Find code matrix ======================================================= | 
|  | # This is main part to find the parity matrix. | 
|  | # For example, find SECDED for 4bit message is to find 4x4 matrix as below | 
|  | # | 1 0 0 0 x x x x | | 
|  | # | 0 1 0 0 x x x x | | 
|  | # | 0 0 1 0 x x x x | | 
|  | # | 0 0 0 1 x x x x | | 
|  | # Then message _k_ X matrix_code ==> original message with parity | 
|  | # | 
|  | # Make a row to have even number of 1 including the I matrix. | 
|  | # This helps to calculate the syndrom at the decoding stage. | 
|  | # To reduce the max fan-in, Starting with smallest number 3. | 
|  | # the number means the number of one in a row. | 
|  | # Small number of ones means smaller fan-in overall. | 
|  |  | 
|  | for step in range(3, m + 1, 2): | 
|  | # starting from 3 as I matrix represents data | 
|  | # Increased by 2 as number of 1 should be even in a row (odd excluding I) | 
|  |  | 
|  | # get the list of combinations [0, .., m-1] with `step` | 
|  | # e.g. step := 3 ==> [(0,1,2), (0,1,3), ... ] | 
|  | candidate = list(itertools.combinations(range(m), step)) | 
|  |  | 
|  | if len(candidate) <= required_row: | 
|  | # we need more round use all of them | 
|  | codes.extend(candidate) | 
|  | required_row -= len(candidate) | 
|  | else: | 
|  | ## Find optimized fan-in ========================================== | 
|  |  | 
|  | # Calculate each row fan-in with current | 
|  | fanins = calc_fanin(m, codes) | 
|  | while required_row != 0: | 
|  | # Let's shuffle | 
|  | # Shuffling makes the sequence randomized --> it reduces the | 
|  | # fanin as the code takes randomly at the end of the round | 
|  |  | 
|  | # TODO: There should be a clever way to find the subset without | 
|  | # random retrying. | 
|  | # Suggested this algorithm | 
|  | #    https://en.wikipedia.org/wiki/Assignment_problem | 
|  | random.shuffle(candidate) | 
|  |  | 
|  | # Take a subset | 
|  | subset = candidate[0:required_row] | 
|  |  | 
|  | subset_fanins = calc_fanin(m, subset) | 
|  | # Check if it exceeds Ideal Fan-In | 
|  | ideal = True | 
|  | for i in range(m): | 
|  | if fanins[i] + subset_fanins[i] > fanin_ideal: | 
|  | # Exceeded. Retry | 
|  | ideal = False | 
|  | break | 
|  |  | 
|  | if ideal: | 
|  | required_row = 0 | 
|  |  | 
|  | # Append to the code matrix | 
|  | codes.extend(subset) | 
|  |  | 
|  | if required_row == 0: | 
|  | # Found everything! | 
|  | break | 
|  |  | 
|  | log.info(codes) | 
|  |  | 
|  | # Print Encoder | 
|  | enc_out = print_enc(n, k, m, codes) | 
|  | #log.info(enc_out) | 
|  |  | 
|  | module_name = "prim_secded_%d_%d" % (n, k) | 
|  |  | 
|  | with open(args.outdir + "/" + module_name + "_enc.sv", "w") as f: | 
|  | f.write(COPYRIGHT) | 
|  | f.write("// SECDED Encoder generated by secded_gen.py\n\n") | 
|  |  | 
|  | f.write("module " + module_name + "_enc (\n") | 
|  | f.write("  input        [%d:0] in,\n" % (k - 1)) | 
|  | f.write("  output logic [%d:0] out\n" % (n - 1)) | 
|  | f.write(");\n\n") | 
|  | f.write(enc_out) | 
|  | f.write("endmodule\n\n") | 
|  |  | 
|  | dec_out = print_dec(n, k, m, codes) | 
|  |  | 
|  | with open(args.outdir + "/" + module_name + "_dec.sv", "w") as f: | 
|  | f.write(COPYRIGHT) | 
|  | f.write("// SECDED Decoder generated by secded_gen.py\n\n") | 
|  |  | 
|  | f.write("module " + module_name + "_dec (\n") | 
|  | f.write("  input        [%d:0] in,\n" % (n - 1)) | 
|  | f.write("  output logic [%d:0] d_o,\n" % (k - 1)) | 
|  | f.write("  output logic [%d:0] syndrome_o,\n" % (m - 1)) | 
|  | f.write("  output logic [1:0] err_o\n") | 
|  | f.write(");\n\n") | 
|  | f.write(dec_out) | 
|  | f.write("endmodule\n\n") | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |