[util] Slight refactor of secded_gen.py

- list all secded flavors in util/design/data/secded_cfg.hjson
- generate single package file for portable re-use
- nothing else is touched

Signed-off-by: Timothy Chen <timothytim@google.com>
diff --git a/util/design/secded_gen.py b/util/design/secded_gen.py
index f061209..3e9d768 100755
--- a/util/design/secded_gen.py
+++ b/util/design/secded_gen.py
@@ -16,13 +16,18 @@
 import logging as log
 import math
 import random
+import hjson
 
 COPYRIGHT = """// Copyright lowRISC contributors.
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 //
 """
-CODE_OPTIONS = ['hsiao', 'hamming']
+CODE_OPTIONS = {'hsiao': '', 'hamming': '_hamming'}
+PRINT_OPTIONS = {"logic": "assign ", "function": ""}
+
+# secded configurations
+SECDED_CFG_FILE = "util/design/data/secded_cfg.hjson"
 
 
 def min_paritysize(k):
@@ -75,15 +80,28 @@
     return fanin_masks
 
 
+def print_fn(n, k, m, codes, suffix, codetype):
+    enc_out = print_enc(n, k, m, codes)
+
+    module_name = "prim_secded%s_%d_%d" % (suffix, n, k)
+
+    outstr = '''
+  function automatic logic [{}:0] {}_enc (logic [{}:0] in);
+    logic [{}:0] out;
+{}    return out;
+  endfunction
+'''.format((n - 1), module_name, (k - 1), (n - 1), enc_out)
+
+    return outstr
+
+
 def print_enc(n, k, m, codes):
-    outstr = "  always_comb begin : p_encode\n"
-    outstr += "    out = {}'(in);\n".format(n)
+    outstr = "    out = {}'(in);\n".format(n)
     format_str = "    out[{}] = ^(out & " + str(n) + "'h{:0" + str(
         (n + 3) // 4) + "X});\n"
     # Print parity computation
     for j, mask in enumerate(calc_bitmasks(k, m, codes, False)):
         outstr += format_str.format(j + k, mask)
-    outstr += "  end\n"
     return outstr
 
 
@@ -92,32 +110,38 @@
     return sum(map((lambda x: 2**x), code))
 
 
-def print_dec(n, k, m, codes, codetype):
+def print_dec(n, k, m, codes, codetype, print_type="logic"):
+
+    preamble = PRINT_OPTIONS[print_type]
+
     outstr = ""
-    outstr += "  logic single_error;\n"
+    if codetype == "hsiao":
+        outstr += "  logic single_error;\n"
+
     outstr += "\n"
     outstr += "  // Syndrome calculation\n"
-    format_str = "  assign syndrome_o[{}] = ^(in & " + str(n) + "'h{:0" + str(
-        (n + 3) // 4) + "X});\n"
+    format_str = "  {}".format(preamble) + "syndrome_o[{}] = ^(in & " \
+        + str(n) + "'h{:0" + str((n + 3) // 4) + "X});\n"
+
     # Print syndrome computation
     for j, mask in enumerate(calc_bitmasks(k, m, codes, True)):
         outstr += format_str.format(j, mask)
     outstr += "\n"
     outstr += "  // Corrected output calculation\n"
     for i in range(k):
-        outstr += "  assign d_o[%d] = (syndrome_o == %d'h%x) ^ in[%d];\n" % (
+        outstr += "  {}".format(preamble) + "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"
     # The Hsiao and Hamming syndromes are interpreted slightly differently.
     if codetype == "hamming":
-        outstr += "  assign err_o[0] = syndrome_o[%d];\n" % (m - 1)
-        outstr += "  assign err_o[1] = |syndrome_o[%d:0] & ~syndrome_o[%d];\n" % (
+        outstr += "  {}".format(preamble) + "err_o[0] = syndrome_o[%d];\n" % (m - 1)
+        outstr += "  {}".format(preamble) + "err_o[1] = |syndrome_o[%d:0] & ~syndrome_o[%d];\n" % (
             m - 2, m - 1)
     else:
-        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"
+        outstr += "  {}".format(preamble) + "single_error = ^syndrome_o;\n"
+        outstr += "  {}".format(preamble) + "err_o[0] = single_error;\n"
+        outstr += "  {}".format(preamble) + "err_o[1] = ~single_error & (|syndrome_o);\n"
     return outstr
 
 
@@ -130,6 +154,60 @@
     return (n % 2) > 0
 
 
+def verify(cfgs):
+    error = 0
+    for cfg in cfgs['cfgs']:
+        if (cfg['k'] <= 1 or cfg['k'] > 120):
+            error += 1
+            log.error("Current tool doesn't support the value k (%d)", cfg['k'])
+
+        if (cfg['m'] <= 1 or cfg['m'] > 20):
+            error += 1
+            log.error("Current tool doesn't support the value m (%d)", cfg['m'])
+
+        # Calculate 'm' (parity size)
+        min_m = min_paritysize(cfg['k'])
+        if (cfg['m'] < min_m):
+            error += 1
+            log.error("given \'m\' argument is smaller than minimum requirement " +
+                      "using calculated minimum (%d)", min_m)
+
+        # Error check code selection
+        if (cfg['code_type'] not in CODE_OPTIONS):
+            error += 1
+            log.error("Invalid code {} selected, use one of {}".format(
+                cfg['code_type'], CODE_OPTIONS))
+
+    return error
+
+
+def generate(cfgs, args):
+    pkg_out_str = ""
+    for cfg in cfgs['cfgs']:
+        log.debug("Working on {}".format(cfg))
+        k = cfg['k']
+        m = cfg['m']
+        n = k + m
+        codetype = cfg['code_type']
+        suffix = CODE_OPTIONS[codetype]
+        codes = []
+
+        # update value based on target selection
+        codes = globals()["{}_code".format(codetype)](cfg['k'], cfg['m'])
+
+        # write out rtl files
+        write_enc_dec_files(n, k, m, args.s, codes, suffix, args.outdir, codetype)
+
+        # print out functions
+        pkg_out_str += print_fn(n, k, m, codes, suffix, codetype)
+
+        if not args.no_fpv:
+            write_fpv_files(n, k, m, codes, codetype, args.fpv_outdir)
+
+    # write out package file
+    write_pkg_file(args.s, args.outdir, pkg_out_str)
+
+
 # k = data bits
 # m = parity bits
 # generate hsiao code
@@ -220,7 +298,9 @@
 # k = data bits
 # m = parity bits
 # generate hamming code
-def hamming_code(n, k, m):
+def hamming_code(k, m):
+
+    n = k + m
 
     # construct a list of code tuples.
     # Tuple corresponds to each bit position and shows which parity bit it participates in
@@ -259,6 +339,20 @@
     return codes
 
 
+def write_pkg_file(s, outdir, pkg_str):
+
+    with open(outdir + "/" + "prim_secded_pkg.sv", "w") as f:
+        outstr = '''{}// SECDED Encoder generated by
+// util/design/secded_gen.py -s {} from {}
+
+package prim_secded_pkg;
+{}
+
+endpackage
+'''.format(COPYRIGHT, s, SECDED_CFG_FILE, pkg_str)
+        f.write(outstr)
+
+
 def write_enc_dec_files(n, k, m, s, codes, suffix, outdir, codetype):
     enc_out = print_enc(n, k, m, codes)
 
@@ -273,7 +367,9 @@
   output logic [{}:0] out
 );
 
-{}
+  always_comb begin : p_encode
+{}  end
+
 endmodule : {}_enc
 '''.format(COPYRIGHT, m, k, s, codetype, module_name, (k - 1), (n - 1),
            enc_out, module_name)
@@ -433,24 +529,6 @@
         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(
-        '-c',
-        default='hsiao',
-        help='ECC code used. Options: hsiao / hamming (default: %(default)s)')
     parser.add_argument('-s',
                         type=int,
                         metavar='<seed>',
@@ -479,31 +557,6 @@
     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.warning(
-            "given \'m\' argument is smaller than minimum requirement " +
-            "using calculated minimum")
-        m = min_m
-    else:
-        m = args.m
-
-    n = m + k
-    log.info("n(%d), k(%d), m(%d)", n, k, m)
-
-    if args.s is None:
-        random.seed()
-        args.s = random.getrandbits(32)
-
     # 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.
@@ -513,24 +566,16 @@
 
     random.seed(args.s)
 
-    # Error check code selection
-    codes = []
-    suffix = ''
-    if (args.c == 'hsiao'):
-        codes = hsiao_code(k, m)
-    elif (args.c == 'hamming'):
-        suffix = '_hamming'
-        codes = hamming_code(n, k, m)
-    else:
-        log.error("Invalid code {} selected, use one of {}".format(
-            args.c, CODE_OPTIONS))
-        return
+    with open(SECDED_CFG_FILE, 'r') as infile:
+        config = hjson.load(infile)
 
-    write_enc_dec_files(n, k, m, args.s, codes, suffix, args.outdir, args.c)
+    # Error checking
+    error = verify(config)
+    if (error):
+        exit(1)
 
-    # Print out FPV testbench by default.
-    if not args.no_fpv:
-        write_fpv_files(n, k, m, codes, suffix, args.fpv_outdir)
+    # Generate outputs
+    generate(config, args)
 
 
 if __name__ == "__main__":