[util/design] Extend OTP mmap script with indexing functions

This adds additional member functions that allow for lookup of
partitions and items by name for indexing purposes.

This is a preparatory step for the OTP image generation script.

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/util/design/lib/OtpMemMap.py b/util/design/lib/OtpMemMap.py
index 0080c94..0763bd4 100644
--- a/util/design/lib/OtpMemMap.py
+++ b/util/design/lib/OtpMemMap.py
@@ -10,8 +10,8 @@
 import random
 from math import ceil, log2
 
-from tabulate import tabulate
 from lib.common import check_bool, check_int, random_or_hexvalue
+from tabulate import tabulate
 
 DIGEST_SUFFIX = "_DIGEST"
 DIGEST_SIZE = 8
@@ -96,8 +96,7 @@
 
     if check_bool(part["secret"]) and part["key_sel"] == "NoKey":
         log.error(
-            "A secret partition needs a key select value other than NoKey"
-        )
+            "A secret partition needs a key select value other than NoKey")
         exit(1)
 
     if part["write_lock"].lower() not in ["digest", "csr", "none"]:
@@ -121,8 +120,8 @@
         exit(1)
 
     if not part["sw_digest"] and not part["hw_digest"]:
-        if part["write_lock"].lower(
-        ) == "digest" or part["read_lock"].lower() == "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."
             )
@@ -165,22 +164,38 @@
 
     offset = 0
     num_part = 0
+    part_index = {}
     for part in config["partitions"]:
-        num_part += 1
         _validate_part(part, offset, key_names)
 
+        if part['name'] in part_index:
+            log.error('Partition name {} is not unique'.format(part['name']))
+            exit(1)
+
         # Loop over items within a partition
+        num_items = 0
+        item_index = {}
         for item in part["items"]:
             _validate_item(item, offset)
+            if item['name'] in item_index:
+                log.error('Item name {} is not unique'.format(item['name']))
+                exit(1)
             log.info("> Item {} at offset {} with size {}".format(
                 item["name"], offset, item["size"]))
             offset += check_int(item["size"])
+            item_index[item['name']] = num_items
+            num_items += 1
 
         # Place digest at the end of a partition.
         if part["sw_digest"] or part["hw_digest"]:
+            digest_name = part["name"] + DIGEST_SUFFIX
+            if digest_name in item_index:
+                log.error('Digest name {} is not unique'.format(digest_name))
+                exit(1)
+            item_index[digest_name] = num_items
             part["items"].append({
                 "name":
-                part["name"] + DIGEST_SUFFIX,
+                digest_name,
                 "size":
                 DIGEST_SIZE,
                 "offset":
@@ -188,42 +203,54 @@
                 DIGEST_SIZE,
                 "isdigest":
                 "True",
-                "inv_default": "<random>"
+                "inv_default":
+                "<random>"
             })
             # Randomize the digest default.
-            random_or_hexvalue(part["items"][-1], "inv_default", DIGEST_SIZE * 8)
+            random_or_hexvalue(part["items"][-1], "inv_default",
+                               DIGEST_SIZE * 8)
 
             log.info("> Adding digest {} at offset {} with size {}".format(
-                part["name"] + DIGEST_SUFFIX, offset, DIGEST_SIZE))
+                digest_name, offset, DIGEST_SIZE))
             offset += DIGEST_SIZE
 
         # 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"]))
+                      "= {}, bytes allocated to items = {}".format(
+                          part["name"], part["size"], offset - part["offset"]))
             exit(1)
 
         offset = check_int(part["offset"]) + check_int(part["size"])
 
+        part_index.setdefault(part['name'], {
+            'index': num_part,
+            'items': item_index
+        })
+        num_part += 1
+
     if offset > config["otp"]["size"]:
         log.error(
             "OTP is not big enough to store all partitions. "
-            "Bytes available {}, bytes required {}",
-            config["otp"]["size"], offset)
+            "Bytes available {}, bytes required {}", config["otp"]["size"],
+            offset)
         exit(1)
 
     log.info("Total number of partitions: {}".format(num_part))
     log.info("Bytes available in OTP: {}".format(config["otp"]["size"]))
     log.info("Bytes required for partitions: {}".format(offset))
 
+    # return the partition/item index dict
+    return part_index
+
 
 class OtpMemMap():
 
     # This holds the config dict.
     config = {}
+    # This holds the partition/item index dict for fast access.
+    part_index = {}
 
     def __init__(self, config):
 
@@ -239,6 +266,8 @@
 
         # Initialize RNG.
         random.seed(OTP_SEED_DIVERSIFIER + int(config['seed']))
+        log.info('Seed: {0:x}'.format(config['seed']))
+        log.info('')
 
         if "otp" not in config:
             log.error("Missing otp configuration.")
@@ -255,7 +284,7 @@
         # Validate scrambling info.
         _validate_scrambling(config["scrambling"])
         # Validate memory map.
-        _validate_mmap(config)
+        self.part_index = _validate_mmap(config)
 
         self.config = config
 
@@ -355,3 +384,14 @@
                         headers="firstrow",
                         tablefmt="pipe",
                         colalign=colalign)
+
+    def get_part_idx(self, part_name):
+        ''' Get partition index, return -1 if it does not exist'''
+        part_index = self.part_index.get(part_name)
+        return -1 if part_index is None else part_index['index']
+
+    def get_item_index(self, part_name, item_name):
+        ''' Get item index, return -1 if it does not exist'''
+        part_index = self.part_index.get(part_name)
+        return (-1 if part_index is None else part_index['items'].get(
+            item_name, -1))