[util] Move design-related helper scripts to util/design

This also updates the paths and consolidates some subfunctions

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
index 0083608..edf1ba7 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson
@@ -5,7 +5,7 @@
 // HJSON with partition metadata.
 //
 // DO NOT EDIT THIS FILE DIRECTLY.
-// It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
+// It has been generated with ./util/design/gen-otp-mmap.py
 
 
 { name: "otp_ctrl",
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl b/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
index cbc1aec..c440fd8 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
+++ b/hw/ip/otp_ctrl/data/otp_ctrl.hjson.tpl
@@ -5,7 +5,7 @@
 // HJSON with partition metadata.
 //
 // DO NOT EDIT THIS FILE DIRECTLY.
-// It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
+// It has been generated with ./util/design/gen-otp-mmap.py
 
 <%
   num_part = len(otp_mmap.config["partitions"])
diff --git a/hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson b/hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson
index 90a1453..c785172 100644
--- a/hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson
+++ b/hw/ip/otp_ctrl/data/otp_ctrl_mmap.hjson
@@ -5,8 +5,7 @@
 // Use the gen-otp-mmap.py script to update dependent files (like documentation
 // tables the comportable hjson and metadata SV package):
 //
-// $ cd ${PROJ_ROOT}/hw/ip/otp_ctrl/util/
-// $ ./gen-otp-mmap.py
+// $ ./util/design/gen-otp-mmap.py
 //
 // Make sure to regenerate the CSRs after converting the memory map:
 //
diff --git a/hw/ip/otp_ctrl/doc/otp_ctrl_digests.md b/hw/ip/otp_ctrl/doc/otp_ctrl_digests.md
index ffc83bf..7e51be6 100644
--- a/hw/ip/otp_ctrl/doc/otp_ctrl_digests.md
+++ b/hw/ip/otp_ctrl/doc/otp_ctrl_digests.md
@@ -1,6 +1,6 @@
 <!--
 DO NOT EDIT THIS FILE DIRECTLY.
-It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
+It has been generated with ./util/design/gen-otp-mmap.py
 -->
 
 |                      Digest Name                      |   Affected Partition  |  Calculated by HW  |
diff --git a/hw/ip/otp_ctrl/doc/otp_ctrl_mmap.md b/hw/ip/otp_ctrl/doc/otp_ctrl_mmap.md
index 2ccdcbc..379c1b1 100644
--- a/hw/ip/otp_ctrl/doc/otp_ctrl_mmap.md
+++ b/hw/ip/otp_ctrl/doc/otp_ctrl_mmap.md
@@ -1,6 +1,6 @@
 <!--
 DO NOT EDIT THIS FILE DIRECTLY.
-It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
+It has been generated with ./util/design/gen-otp-mmap.py
 -->
 
 |  Index  |   Partition    |  Size [B]  |  Access Granule  |                         Item                          |  Byte Address  |  Size [B]  |
diff --git a/hw/ip/otp_ctrl/doc/otp_ctrl_partitions.md b/hw/ip/otp_ctrl/doc/otp_ctrl_partitions.md
index d06005c..4a19ec9 100644
--- a/hw/ip/otp_ctrl/doc/otp_ctrl_partitions.md
+++ b/hw/ip/otp_ctrl/doc/otp_ctrl_partitions.md
@@ -1,6 +1,6 @@
 <!--
 DO NOT EDIT THIS FILE DIRECTLY.
-It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
+It has been generated with ./util/design/gen-otp-mmap.py
 -->
 
 |   Partition    |  Secret  |  Buffered  |  WR Lockable  |  RD Lockable  |                                                                                                                                                                                                                                                                    Description                                                                                                                                                                                                                                                                     |
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv
index d8e4481..ed0a029 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv
@@ -6,7 +6,7 @@
 //
 // DO NOT EDIT THIS FILE DIRECTLY.
 // It has been generated with
-// $ cd hw/ip/otp_ctrl/util/ && ./gen-otp-mmap.py --seed 10556718629619452145
+// $ ./util/design/gen-otp-mmap.py --seed 10556718629619452145
 //
 
 package otp_ctrl_part_pkg;
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv.tpl b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv.tpl
index 03346f4..8b0c72e 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv.tpl
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv.tpl
@@ -6,7 +6,7 @@
 //
 // DO NOT EDIT THIS FILE DIRECTLY.
 // It has been generated with
-// $ cd hw/ip/otp_ctrl/util/ && ./gen-otp-mmap.py --seed ${otp_mmap.config['seed']}
+// $ ./util/design/gen-otp-mmap.py --seed ${otp_mmap.config['seed']}
 //
 <%
   def PascalCase(inp):
diff --git a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
index c041d52..4c1b57e 100644
--- a/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
+++ b/hw/ip/otp_ctrl/rtl/otp_ctrl_pkg.sv
@@ -294,7 +294,7 @@
   ///////////////////////////////////////////
 
   // These LFSR parameters have been generated with
-  // $ hw/ip/prim/util/gen-lfsr-seed.py --width 40 --seed 4247488366
+  // $ util/design/gen-lfsr-seed.py --width 40 --seed 4247488366
   localparam int LfsrWidth = 40;
   typedef logic [LfsrWidth-1:0]                        lfsr_seed_t;
   typedef logic [LfsrWidth-1:0][$clog2(LfsrWidth)-1:0] lfsr_perm_t;
diff --git a/hw/ip/otp_ctrl/util/LcStEnc.py b/hw/ip/otp_ctrl/util/LcStEnc.py
deleted file mode 100644
index 16256d1..0000000
--- a/hw/ip/otp_ctrl/util/LcStEnc.py
+++ /dev/null
@@ -1,342 +0,0 @@
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-r"""Contains life cycle state encoding class which is
-used to generate new life cycle encodings.
-"""
-import logging as log
-import random
-
-from common import check_int
-
-
-def _is_valid_codeword(config, codeword):
-    '''Checks whether the bitstring is a valid ECC codeword.'''
-
-    data_width = config['secded']['data_width']
-    ecc_width = config['secded']['ecc_width']
-    if len(codeword) != (data_width + ecc_width):
-        log.error("Invalid codeword length {}".format(len(codeword)))
-        exit(1)
-
-    # Build syndrome and check whether it is zero.
-    syndrome = [0 for k in range(ecc_width)]
-
-    # The bitstring must be formatted as "data bits[N-1:0]" + "ecc bits[M-1:0]".
-    for j, fanin in enumerate(config['secded']['ecc_matrix']):
-        syndrome[j] = int(codeword[ecc_width - 1 - j])
-        for k in fanin:
-            syndrome[j] ^= int(codeword[ecc_width + data_width - 1 - k])
-
-    return sum(syndrome) == 0
-
-
-def _ecc_encode(config, dataword):
-    '''Calculate and prepend ECC bits.'''
-    if len(dataword) != config['secded']['data_width']:
-        log.error("Invalid codeword length {}".format(len(dataword)))
-        exit(1)
-
-    # Build syndrome
-    eccbits = ""
-    for fanin in config['secded']['ecc_matrix']:
-        bit = 0
-        for k in fanin:
-            bit ^= int(dataword[config['secded']['data_width'] - 1 - k])
-        eccbits += format(bit, '01b')
-
-    return eccbits[::-1] + dataword
-
-
-def _is_incremental_codeword(word1, word2):
-    '''Test whether word2 is incremental wrt word1.'''
-    if len(word1) != len(word2):
-        log.error('Words are not of equal size')
-        exit(1)
-
-    _word1 = int(word1, 2)
-    _word2 = int(word2, 2)
-
-    # This basically checks that the second word does not
-    # clear any bits that are set to 1 in the first word.
-    return ((_word1 & _word2) == _word1)
-
-
-def _scatter_bits(mask, bits):
-    '''Scatter the bits into unset positions of mask.'''
-    j = 0
-    scatterword = ''
-    for b in mask:
-        if b == '1':
-            scatterword += '1'
-        else:
-            scatterword += bits[j]
-            j += 1
-
-    return scatterword
-
-
-def _get_hd(word1, word2):
-    '''Calculate Hamming distance between two words.'''
-    if len(word1) != len(word2):
-        log.error('Words are not of equal size')
-        exit(1)
-    return bin(int(word1, 2) ^ int(word2, 2)).count('1')
-
-
-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):
-            log.error('Codeword {} at index {} is not valid'.format(w, k))
-            exit(1)
-        # 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']:
-            log.error(
-                'Codeword {} at index {} has wrong Hamming weight'.format(
-                    w, k))
-            exit(1)
-        # 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']:
-                    log.error(
-                        'Hamming distance between codeword {} at index {} '
-                        'and codeword {} at index {} is too low.'.format(
-                            w, k, w2, k + 1 + k2))
-                    exit(1)
-
-
-def _hist_to_bars(hist, m):
-    '''Convert histogramm list into ASCII bar plot'''
-    bars = []
-    for i, j in enumerate(hist):
-        bar_prefix = "{:2}: ".format(i)
-        spaces = len(str(m)) - len(bar_prefix)
-        hist_bar = bar_prefix + (" " * spaces)
-        for k in range(j * 20 // max(hist)):
-            hist_bar += "|"
-        hist_bar += " ({:.2f}%)".format(100.0 * j / sum(hist)) if j else "--"
-        bars += [hist_bar]
-    return bars
-
-
-def hd_histogram(existing_words):
-    '''Build Hamming distance histogram'''
-    minimum_hd = len(existing_words[0])
-    maximum_hd = 0
-    minimum_hw = len(existing_words[0])
-    maximum_hw = 0
-    hist = [0] * (len(existing_words[0]) + 1)
-    for i, j in enumerate(existing_words):
-        minimum_hw = min(j.count('1'), minimum_hw)
-        maximum_hw = max(j.count('1'), maximum_hw)
-        if i < len(existing_words) - 1:
-            for k in existing_words[i + 1:]:
-                dist = _get_hd(j, k)
-                hist[dist] += 1
-                minimum_hd = min(dist, minimum_hd)
-                maximum_hd = max(dist, maximum_hd)
-
-    stats = {}
-    stats["hist"] = hist
-    stats["bars"] = _hist_to_bars(hist, len(existing_words))
-    stats["min_hd"] = minimum_hd
-    stats["max_hd"] = maximum_hd
-    stats["min_hw"] = minimum_hw
-    stats["max_hw"] = maximum_hw
-    return stats
-
-
-class LcStEnc():
-    '''Life cycle state encoding generator class
-
-    The constructor expects the parsed configuration
-    hjson to be passed in.
-    '''
-
-    # This holds the config dict.
-    config = {}
-    # Holds generated life cycle words.
-    gen = {
-        'ab_words': [],
-        'cd_words': [],
-        'ef_words': [],
-        'stats': [],
-    }
-
-    def __init__(self, config):
-        '''The constructor validates the configuration dict.'''
-
-        log.info('')
-        log.info('Generate life cycle state')
-        log.info('')
-
-        if 'seed' not in config:
-            log.error('Missing seed in configuration')
-            exit(1)
-
-        if 'secded' not in config:
-            log.error('Missing secded configuration')
-            exit(1)
-
-        config['secded'].setdefault('data_width', 0)
-        config['secded'].setdefault('ecc_width', 0)
-        config['secded'].setdefault('ecc_matrix', [[]])
-        config.setdefault('num_ab_words', 0)
-        config.setdefault('num_cd_words', 0)
-        config.setdefault('num_ef_words', 0)
-        config.setdefault('min_hw', 0)
-        config.setdefault('max_hw', 0)
-        config.setdefault('min_hd', 0)
-
-        config['seed'] = check_int(config['seed'])
-
-        log.info('Seed: {0:x}'.format(config['seed']))
-        log.info('')
-
-        config['secded']['data_width'] = check_int(
-            config['secded']['data_width'])
-        config['secded']['ecc_width'] = check_int(
-            config['secded']['ecc_width'])
-        config['num_ab_words'] = check_int(config['num_ab_words'])
-        config['num_cd_words'] = check_int(config['num_cd_words'])
-        config['num_ef_words'] = check_int(config['num_ef_words'])
-        config['min_hw'] = check_int(config['min_hw'])
-        config['max_hw'] = check_int(config['max_hw'])
-        config['min_hd'] = check_int(config['min_hd'])
-
-        total_width = config['secded']['data_width'] + config['secded'][
-            'ecc_width']
-
-        if config['min_hw'] >= total_width or \
-           config['max_hw'] > total_width or \
-           config['min_hw'] >= config['max_hw']:
-            log.error('Hamming weight constraints are inconsistent.')
-            exit(1)
-
-        if config['max_hw'] - config['min_hw'] + 1 < config['min_hd']:
-            log.error('Hamming distance constraint is inconsistent.')
-            exit(1)
-
-        if config['secded']['ecc_width'] != len(
-                config['secded']['ecc_matrix']):
-            log.error('ECC matrix does not have correct number of rows')
-            exit(1)
-
-        log.info('SECDED Matrix:')
-        for i, l in enumerate(config['secded']['ecc_matrix']):
-            log.info('ECC Bit {} Fanin: {}'.format(i, l))
-            for j, e in enumerate(l):
-                e = check_int(e)
-                config['secded']['ecc_matrix'][i][j] = e
-
-        log.info('')
-
-        self.config = config
-
-        # Re-initialize with seed to make results reproducible.
-        random.seed(int(self.config['seed']))
-
-        # Generate new encoding words
-        word_types = ['ab_words', 'cd_words', 'ef_words']
-        existing_words = []
-        for w in word_types:
-            while len(self.gen[w]) < self.config['num_' + w]:
-                new_word = _get_new_state_word_pair(self.config,
-                                                    existing_words)
-                self.gen[w].append(new_word)
-
-        # Validate words (this must not fail at this point).
-        _validate_words(self.config, existing_words)
-
-        # Print out HD histogram
-        self.gen['stats'] = hd_histogram(existing_words)
-
-        log.info('')
-        log.info('Hamming distance histogram:')
-        log.info('')
-        for bar in self.gen['stats']["bars"]:
-            log.info(bar)
-        log.info('')
-        log.info('Minimum HD: {}'.format(self.gen['stats']['min_hd']))
-        log.info('Maximum HD: {}'.format(self.gen['stats']['max_hd']))
-        log.info('Minimum HW: {}'.format(self.gen['stats']['min_hw']))
-        log.info('Maximum HW: {}'.format(self.gen['stats']['max_hw']))
-
-        log.info('')
-        log.info('Successfully generated life cycle state.')
-        log.info('')
diff --git a/hw/ip/otp_ctrl/util/OtpMemMap.py b/hw/ip/otp_ctrl/util/OtpMemMap.py
deleted file mode 100644
index d843437..0000000
--- a/hw/ip/otp_ctrl/util/OtpMemMap.py
+++ /dev/null
@@ -1,288 +0,0 @@
-#!/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"""OTP memory map class, used to create the associated RTL and
-documentation, and to create OTP memory images for preloading.
-"""
-
-import logging as log
-import random
-from math import ceil, log2
-
-from tabulate import tabulate
-
-from common import check_bool, check_int
-
-DIGEST_SUFFIX = "_DIGEST"
-DIGEST_SIZE = 8
-
-
-class OtpMemMap():
-
-    # This holds the config dict.
-    config = {}
-
-    def __init__(self, config):
-
-        log.info('')
-        log.info('Parse and translate OTP memory map.')
-        log.info('')
-
-        if "seed" not in config:
-            log.error("Missing seed in configuration.")
-            exit(1)
-
-        config["seed"] = check_int(config["seed"])
-
-        # Initialize RNG.
-        random.seed(int(config['seed']))
-
-        offset = 0
-        num_part = 0
-        for part in config["partitions"]:
-            num_part += 1
-            # Defaults
-            part.setdefault("offset", offset)
-            part.setdefault("name", "unknown_name")
-            part.setdefault("variant", "Unbuffered")
-            part.setdefault("size", "0")
-            part.setdefault("secret", "false")
-            part.setdefault("sw_digest", "false")
-            part.setdefault("hw_digest", "false")
-            part.setdefault("write_lock", "none")
-            part.setdefault("read_lock", "none")
-            part.setdefault("key_sel", "NoKey")
-            log.info("Partition {} at offset {} with size {}".format(
-                part["name"], part["offset"], part["size"]))
-
-            # make sure these are boolean types (simplifies the mako templates)
-            part["secret"] = check_bool(part["secret"])
-            part["sw_digest"] = check_bool(part["sw_digest"])
-            part["hw_digest"] = check_bool(part["hw_digest"])
-            part["bkout_type"] = check_bool(part["bkout_type"])
-
-            # basic checks
-            if part["variant"] not in ["Unbuffered", "Buffered", "LifeCycle"]:
-                log.error("Invalid partition type {}".format(part["variant"]))
-                exit(1)
-
-            if part["key_sel"] not in [
-                    "NoKey", "Secret0Key", "Secret1Key", "Secret2Key"
-            ]:
-                log.error("Invalid key sel {}".format(part["key_sel"]))
-                exit(1)
-
-            if check_bool(part["secret"]) and part["key_sel"] == "NoKey":
-                log.error(
-                    "A secret partition needs a key select value other than NoKey"
-                )
-                exit(1)
-
-            if part["write_lock"].lower() not in ["digest", "csr", "none"]:
-                log.error("Invalid value for write_lock")
-                exit(1)
-
-            if part["read_lock"].lower() not in ["digest", "csr", "none"]:
-                log.error("Invalid value for read_lock")
-                exit(1)
-
-            if part["sw_digest"] and part["hw_digest"]:
-                log.error(
-                    "Partition cannot support both a SW and a HW digest at the same time."
-                )
-                exit(1)
-
-            if part["variant"] == "Unbuffered" and not part["sw_digest"]:
-                log.error(
-                    "Unbuffered partitions without digest are not supported at the moment."
-                )
-                exit(1)
-
-            if not part["sw_digest"] and not part["hw_digest"]:
-                if part["write_lock"].lower(
-                ) == "digest" or part["read_lock"].lower() == "digest":
-                    log.error(
-                        "A partition can only be write/read lockable if it has a hw or sw digest."
-                    )
-                    exit(1)
-
-            if check_int(part["offset"]) % 8:
-                log.error("Partition offset must be 64bit aligned")
-                exit(1)
-
-            if check_int(part["size"]) % 8:
-                log.error("Partition size must be 64bit aligned")
-                exit(1)
-
-            # Loop over items within a partition
-            for item in part["items"]:
-                item.setdefault("name", "unknown_name")
-                item.setdefault("size", "0")
-                item.setdefault("isdigest", "false")
-                item.setdefault("offset", offset)
-                # Generate random constant to be used when partition has
-                # not been initialized yet or when it is in error state.
-                if check_bool(item.setdefault("rand_inv_default", "false")):
-                    inv_default = random.getrandbits(
-                        check_int(item["size"]) * 8)
-                else:
-                    inv_default = 0
-                item.setdefault(
-                    "inv_default", "{}'h{:0X}".format(
-                        check_int(item["size"]) * 8, inv_default))
-                log.info("> Item {} at offset {} with size {}".format(
-                    item["name"], offset, item["size"]))
-                offset += check_int(item["size"])
-
-            # Place digest at the end of a partition.
-            if part["sw_digest"] or part["hw_digest"]:
-                part["items"].append({
-                    "name":
-                    part["name"] + DIGEST_SUFFIX,
-                    "size":
-                    DIGEST_SIZE,
-                    "offset":
-                    check_int(part["offset"]) + check_int(part["size"]) -
-                    DIGEST_SIZE,
-                    "isdigest":
-                    "True",
-                    "inv_default":
-                    "{256{1'b1}}"
-                })
-
-                log.info("> Adding digest {} at offset {} with size {}".format(
-                    part["name"] + DIGEST_SUFFIX, offset, DIGEST_SIZE))
-                offset += DIGEST_SIZE
-
-            if len(part["items"]) == 0:
-                log.warning("Partition does not contain any items.")
-
-            # check offsets and size
-            if offset > check_int(part["offset"]) + check_int(part["size"]):
-                log.error("Not enough space in partitition "
-                          "{} to accommodate all items. Bytes available "
-                          "= {}, bytes requested = {}".format(
-                              part["name"], part["size"],
-                              offset - part["offset"]))
-                exit(1)
-
-            offset = check_int(part["offset"]) + check_int(part["size"])
-
-        otp_size = check_int(config["otp"]["depth"]) * check_int(
-            config["otp"]["width"])
-        config["otp"]["size"] = otp_size
-        config["otp"]["addr_width"] = ceil(
-            log2(check_int(config["otp"]["depth"])))
-        config["otp"]["byte_addr_width"] = ceil(log2(check_int(otp_size)))
-
-        if offset > otp_size:
-            log.error(
-                "OTP is not big enough to store all partitions. "
-                "Bytes available {}, bytes required {}",
-                otp_size, offset)
-            exit(1)
-
-        log.info("Total number of partitions: {}".format(num_part))
-        log.info("Bytes available in OTP: {}".format(otp_size))
-        log.info("Bytes required for partitions: {}".format(offset))
-
-        self.config = config
-
-        log.info('')
-        log.info('Successfully parsed and translated OTP memory map.')
-        log.info('')
-
-
-    def create_partitions_table(self):
-        header = [
-            "Partition", "Secret", "Buffered", "WR Lockable", "RD Lockable",
-            "Description"
-        ]
-        table = [header]
-        colalign = ("center", ) * len(header)
-
-        for part in self.config["partitions"]:
-            is_secret = "yes" if check_bool(part["secret"]) else "no"
-            is_buffered = "yes" if part["variant"] in [
-                "Buffered", "LifeCycle"
-            ] else "no"
-            wr_lockable = "no"
-            if part["write_lock"].lower() in ["csr", "digest"]:
-                wr_lockable = "yes (" + part["write_lock"] + ")"
-            rd_lockable = "no"
-            if part["read_lock"].lower() in ["csr", "digest"]:
-                rd_lockable = "yes (" + part["read_lock"] + ")"
-            # remove newlines
-            desc = ' '.join(part["desc"].split())
-            row = [
-                part["name"], is_secret, is_buffered, wr_lockable, rd_lockable,
-                desc
-            ]
-            table.append(row)
-
-        return tabulate(table,
-                        headers="firstrow",
-                        tablefmt="pipe",
-                        colalign=colalign)
-
-    def create_mmap_table(self):
-        header = [
-            "Index", "Partition", "Size [B]", "Access Granule", "Item",
-            "Byte Address", "Size [B]"
-        ]
-        table = [header]
-        colalign = ("center", ) * len(header)
-
-        for k, part in enumerate(self.config["partitions"]):
-            for j, item in enumerate(part["items"]):
-                granule = "64bit" if check_bool(part["secret"]) else "32bit"
-
-                if check_bool(item["isdigest"]):
-                    granule = "64bit"
-                    name = "[{}](#Reg_{}_0)".format(item["name"],
-                                                    item["name"].lower())
-                else:
-                    name = item["name"]
-
-                if j == 0:
-                    row = [str(k), part["name"], str(part["size"]), granule]
-                else:
-                    row = ["", "", "", granule]
-
-                row.extend([
-                    name, "0x{:03X}".format(check_int(item["offset"])),
-                    str(item["size"])
-                ])
-
-                table.append(row)
-
-        return tabulate(table,
-                        headers="firstrow",
-                        tablefmt="pipe",
-                        colalign=colalign)
-
-    def create_digests_table(self):
-        header = ["Digest Name", " Affected Partition", "Calculated by HW"]
-        table = [header]
-        colalign = ("center", ) * len(header)
-
-        for part in self.config["partitions"]:
-            if check_bool(part["hw_digest"]) or check_bool(part["sw_digest"]):
-                is_hw_digest = "yes" if check_bool(part["hw_digest"]) else "no"
-                for item in part["items"]:
-                    if check_bool(item["isdigest"]):
-                        name = "[{}](#Reg_{}_0)".format(
-                            item["name"], item["name"].lower())
-                        row = [name, part["name"], is_hw_digest]
-                        table.append(row)
-                        break
-                else:
-                    log.error(
-                        "Partition with digest does not contain a digest item")
-                    exit(1)
-
-        return tabulate(table,
-                        headers="firstrow",
-                        tablefmt="pipe",
-                        colalign=colalign)
diff --git a/hw/ip/otp_ctrl/util/common.py b/hw/ip/otp_ctrl/util/common.py
deleted file mode 100644
index 6e482c3..0000000
--- a/hw/ip/otp_ctrl/util/common.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-r"""Shared subfunctions.
-"""
-import logging as log
-import textwrap
-
-
-def wrapped_docstring():
-    '''Return a text-wrapped version of the module docstring'''
-    paras = []
-    para = []
-    for line in __doc__.strip().split('\n'):
-        line = line.strip()
-        if not line:
-            if para:
-                paras.append('\n'.join(para))
-                para = []
-        else:
-            para.append(line)
-    if para:
-        paras.append('\n'.join(para))
-
-    return '\n\n'.join(textwrap.fill(p) for p in paras)
-
-
-def check_bool(x):
-    """check_bool checks if input 'x' either a bool or
-       one of the following strings: ["true", "false"]
-
-        It returns value as Bool type.
-    """
-    if isinstance(x, bool):
-        return x
-    if not x.lower() in ["true", "false"]:
-        log.error("{} is not a boolean value.".format(x))
-        exit(1)
-    else:
-        return (x.lower() == "true")
-
-
-def check_int(x):
-    """check_int checks if input 'x' is decimal integer.
-
-        It returns value as an int type.
-    """
-    if isinstance(x, int):
-        return x
-    if not x.isdecimal():
-        log.error("{} is not a decimal number".format(x))
-        exit(1)
-    return int(x)
diff --git a/hw/ip/otp_ctrl/util/gen-lc-state-enc.py b/hw/ip/otp_ctrl/util/gen-lc-state-enc.py
deleted file mode 100755
index d2284e3..0000000
--- a/hw/ip/otp_ctrl/util/gen-lc-state-enc.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/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"""Given an ECC encoding matrix, this script generates random life cycle
-state encodings that can be incrementally written to a memory protected with
-the ECC code specified.
-"""
-import argparse
-import logging as log
-import random
-from pathlib import Path
-
-import hjson
-from mako.template import Template
-
-from LcStEnc import LcStEnc
-from common import wrapped_docstring
-
-# State encoding definition
-LC_STATE_DEFINITION_FILE = "../../lc_ctrl/data/lc_ctrl_state.hjson"
-# Code templates to render
-TEMPLATES = ["../../lc_ctrl/rtl/lc_ctrl_state_pkg.sv.tpl"]
-
-
-def main():
-    log.basicConfig(level=log.INFO,
-                    format="%(asctime)s - %(message)s",
-                    datefmt="%Y-%m-%d %H:%M")
-
-    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
-        lc_st_enc = LcStEnc(config)
-
-        # 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))
-
-
-if __name__ == "__main__":
-    main()
diff --git a/hw/ip/otp_ctrl/util/gen-otp-mmap.py b/hw/ip/otp_ctrl/util/gen-otp-mmap.py
deleted file mode 100755
index 333fb57..0000000
--- a/hw/ip/otp_ctrl/util/gen-otp-mmap.py
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/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"""Generate RTL and documentation collateral from OTP memory
-map definition file (hjson).
-"""
-import argparse
-import logging as log
-import random
-from pathlib import Path
-
-import hjson
-from mako.template import Template
-
-from common import wrapped_docstring
-# Import OTP memory map generator.
-from OtpMemMap import OtpMemMap
-
-TABLE_HEADER_COMMENT = '''<!--
-DO NOT EDIT THIS FILE DIRECTLY.
-It has been generated with hw/ip/otp_ctrl/util/gen-otp-mmap.py
--->
-
-'''
-
-# memory map source
-MMAP_DEFINITION_FILE = "../data/otp_ctrl_mmap.hjson"
-# documentation tables to generate
-PARTITIONS_TABLE_FILE = "../doc/otp_ctrl_partitions.md"
-DIGESTS_TABLE_FILE = "../doc/otp_ctrl_digests.md"
-MMAP_TABLE_FILE = "../doc/otp_ctrl_mmap.md"
-# code templates to render
-TEMPLATES = ["../data/otp_ctrl.hjson.tpl", "../rtl/otp_ctrl_part_pkg.sv.tpl"]
-
-
-def main():
-    log.basicConfig(level=log.INFO,
-                    format="%(asctime)s - %(message)s",
-                    datefmt="%Y-%m-%d %H:%M")
-
-    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))
-
-        otp_mmap = OtpMemMap(config)
-
-        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))
-
-
-if __name__ == "__main__":
-    main()