[util] Load HJSON config once to speed up ECC encoding

This commit add a function to load the secded config HJSON file and
updates several functions to inject the config instead of loading it at
every call.

This commit also removes the memoization decorator from `ecc_encode`
since `config` is not a hashable type.

Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/hw/ip/rom_ctrl/util/mem.py b/hw/ip/rom_ctrl/util/mem.py
index be914d0..1f96eeb 100644
--- a/hw/ip/rom_ctrl/util/mem.py
+++ b/hw/ip/rom_ctrl/util/mem.py
@@ -6,10 +6,10 @@
 import re
 import subprocess
 import tempfile
-from typing import BinaryIO, IO, List, Optional, TextIO, Tuple
+from typing import Any, BinaryIO, Dict, IO, List, Optional, TextIO, Tuple
 
 from elftools.elf.elffile import ELFFile  # type: ignore
-from util.design.secded_gen import ecc_encode_some  # type: ignore
+from util.design.secded_gen import ecc_encode_some, load_secded_config  # type: ignore
 
 
 class MemChunk:
@@ -47,20 +47,21 @@
                 toks.append(f'{word:0{word_chars}X}')
             outfile.write(' '.join(toks) + '\n')
 
-    def add_ecc32(self) -> None:
+    def add_ecc32(self, config: Dict[str, Any]) -> None:
         '''Add ECC32 integrity bits
 
         This extends the input words (which are assumed to be 32-bit) by 7
         bits, to make 39-bit words.
 
         '''
-        self.words = ecc_encode_some('inv_hsiao', 32, self.words)[0]
+        self.words = ecc_encode_some(config, 'inv_hsiao', 32, self.words)[0]
 
 
 class MemFile:
     def __init__(self, width: int, chunks: List[MemChunk]):
         self.width = width
         self.chunks = chunks
+        self.config = load_secded_config()
 
     def __str__(self) -> str:
         return ('MemFile(width={}, chunks_len={})'
@@ -319,7 +320,7 @@
         '''
         assert self.width <= 32
         for chunk in self.chunks:
-            chunk.add_ecc32()
+            chunk.add_ecc32(self.config)
         self.width = 39
 
     def collisions(self) -> List[Tuple[int, int]]:
diff --git a/hw/ip/rom_ctrl/util/scramble_image.py b/hw/ip/rom_ctrl/util/scramble_image.py
index fd5c8c6..e4f6bb4 100755
--- a/hw/ip/rom_ctrl/util/scramble_image.py
+++ b/hw/ip/rom_ctrl/util/scramble_image.py
@@ -13,7 +13,7 @@
 from Crypto.Hash import cSHAKE256
 
 from mem import MemChunk, MemFile
-from util.design.secded_gen import ecc_encode_some  # type: ignore
+from util.design.secded_gen import ecc_encode_some, load_secded_config  # type: ignore
 
 ROM_BASE_WORD = 0x8000 // 4
 ROM_SIZE_WORDS = 8192
@@ -285,6 +285,7 @@
         self.nonce = nonce
         self.key = key
         self.rom_size_words = rom_size_words
+        self.config = load_secded_config()
 
         self._addr_width = (rom_size_words - 1).bit_length()
 
@@ -504,7 +505,7 @@
                 w39 = w32 | (chk_bits << 32)
                 clr39 = self.unscramble_word(39, log_addr, w39)
                 clr32 = clr39 & mask32
-                exp39 = ecc_encode_some('inv_hsiao', 32, [clr32])[0][0]
+                exp39 = ecc_encode_some(self.config, 'inv_hsiao', 32, [clr32])[0][0]
                 if clr39 != exp39:
                     # The checksum doesn't match. Excellent!
                     found_mismatch = True
diff --git a/util/design/gen-flash-img.py b/util/design/gen-flash-img.py
index 36d1c66..64bc765 100755
--- a/util/design/gen-flash-img.py
+++ b/util/design/gen-flash-img.py
@@ -11,12 +11,13 @@
 import math
 import re
 from pathlib import Path
+from typing import Dict, Any
 
 import secded_gen
 
 
-def _add_intg_ecc(in_val: int) -> str:
-    result, m = secded_gen.ecc_encode("hamming", 64, in_val)
+def _add_intg_ecc(config: Dict[str, Any], in_val: int) -> str:
+    result, m = secded_gen.ecc_encode(config, "hamming", 64, in_val)
 
     m_nibbles = math.ceil(m / 4)
     result = format(result, '0' + str(16 + m_nibbles) + 'x')
@@ -25,8 +26,8 @@
     return result[1:]
 
 
-def _add_reliability_ecc(in_val: int) -> str:
-    result, m = secded_gen.ecc_encode("hamming", 68, in_val)
+def _add_reliability_ecc(config: Dict[str, Any], in_val: int) -> str:
+    result, m = secded_gen.ecc_encode(config, "hamming", 68, in_val)
 
     m_nibbles = math.ceil((68 + m) / 4)
     result = format(result, '0' + str(m_nibbles) + 'x')
@@ -50,6 +51,8 @@
     # search only for lines that contain data, skip all other comments
     result = re.findall(r"^@.*$", vmem_orig, flags=re.MULTILINE)
 
+    config = secded_gen.load_secded_config()
+
     output = []
     for line in result:
         items = line.split()
@@ -58,8 +61,8 @@
             if re.match(r"^@", item):
                 result += item
             else:
-                data_w_intg_ecc = _add_intg_ecc(int(item, 16))
-                full_ecc = _add_reliability_ecc(int(data_w_intg_ecc, 16))
+                data_w_intg_ecc = _add_intg_ecc(config, int(item, 16))
+                full_ecc = _add_reliability_ecc(config, int(data_w_intg_ecc, 16))
                 result += f' {full_ecc}'
 
         # add processed element to output
diff --git a/util/design/secded_gen.py b/util/design/secded_gen.py
index 5b08aa4..9276ee2 100755
--- a/util/design/secded_gen.py
+++ b/util/design/secded_gen.py
@@ -19,7 +19,7 @@
 import random
 import hjson
 import subprocess
-from typing import List, Tuple
+from typing import Any, Dict, List, Tuple
 from pathlib import Path
 
 COPYRIGHT = """// Copyright lowRISC contributors.
@@ -137,7 +137,7 @@
     if dec:
         for j in range(m):
             fanin_masks[j] += 1 << (k + j)
-    return fanin_masks
+    return tuple(fanin_masks)
 
 
 def print_secded_enum_and_util_fns(cfgs):
@@ -356,9 +356,8 @@
     return error
 
 
-def _ecc_pick_code(codetype: str, k: int) -> Tuple[int, List[int], int]:
+def _ecc_pick_code(config: Dict[str, Any], codetype: str, k: int) -> Tuple[int, List[int], int]:
     # first check to see if bit width is supported among configuration
-    config = hjson.load(SECDED_CFG_PATH.open())
 
     codes = None
     bitmasks = None
@@ -403,11 +402,10 @@
     return codeword
 
 
-@functools.lru_cache(maxsize = 1024)
-def ecc_encode(codetype: str, k: int, dataword: int) -> Tuple[int, int]:
+def ecc_encode(config: Dict[str, Any], codetype: str, k: int, dataword: int) -> Tuple[int, int]:
     log.info(f"Encoding ECC for {hex(dataword)}")
 
-    m, bitmasks, invert = _ecc_pick_code(codetype, k)
+    m, bitmasks, invert = _ecc_pick_code(config, codetype, k)
     codeword = _ecc_encode(k, m, bitmasks, invert, dataword)
 
     # Debug printouts
@@ -416,10 +414,11 @@
     return int(codeword, 2), m
 
 
-def ecc_encode_some(codetype: str,
+def ecc_encode_some(config: Dict[str, Any],
+                    codetype: str,
                     k: int,
                     datawords: int) -> Tuple[List[int], int]:
-    m, bitmasks, invert = _ecc_pick_code(codetype, k)
+    m, bitmasks, invert = _ecc_pick_code(config, codetype, k)
     codewords = [int(_ecc_encode(k, m, bitmasks, invert, w), 2)
                  for w in datawords]
     return codewords, m
@@ -901,6 +900,10 @@
         f.write(outstr)
 
 
+def load_secded_config() -> Dict[str, Any]:
+    return hjson.load(SECDED_CFG_PATH.open())
+
+
 def main():
     parser = argparse.ArgumentParser(
         prog="secded_gen",