[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()