[rom_ctrl] Add utility script to generate splicable Vivado ROM images

Previously, a combination of srec_cat and a custom Python script was
was used to generate the Vivado ROM image from a .bin file. The new
script directly reads the .vmem file to produce the Vivado .mem file
usable for FPGA ROM splicing. But more importantly, this script is
also compatible with word widths different to 32 bits.

Signed-off-by: Pirmin Vogel <vogelpi@lowrisc.org>
diff --git a/hw/ip/rom_ctrl/util/gen_vivado_mem_image.py b/hw/ip/rom_ctrl/util/gen_vivado_mem_image.py
new file mode 100755
index 0000000..b4b7a84
--- /dev/null
+++ b/hw/ip/rom_ctrl/util/gen_vivado_mem_image.py
@@ -0,0 +1,75 @@
+#!/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
+
+'''Script for generating a splicable Vivado ROM image
+
+This script takes a .vmem file as input and converts that into a format usable
+by Vivado for splicing FPGA bitstreams via updatemem. For details on the
+required file format, refer to UG898 (Chapter 7, "Using UpdateMEM to Update BIT
+files with MMI and ELF Data"):
+https://www.xilinx.com/support/documentation/sw_manuals/xilinx2020_2/ug898-vivado-embedded-design.pdf#page=165
+
+Typical usage:
+>>> ./gen_vivado_mem_image.py boot_rom.scr.32.vmem boot_rom.updatemem.mem
+'''
+
+import argparse
+import sys
+import math
+import re
+
+from mem import MemFile
+
+
+def swap_bytes(width: int, orig: int) -> int:
+    num_bytes = math.ceil(width / 8)
+    swapped = 0
+    for i in range(num_bytes):
+        swapped |= (((orig >> (i * 8)) & 0xFF) << ((num_bytes - i - 1) * 8))
+    return swapped
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument('infile', type=argparse.FileType('rb'))
+    parser.add_argument('outfile', type=argparse.FileType('w'))
+
+    args = parser.parse_args()
+
+    # Extract width from ROM file name.
+    match = re.search(r'([0-9]+).vmem', args.infile.name)
+    if not match:
+        raise ValueError('Cannot extract ROM word width from file name ' +
+                         args.infile.name)
+    else:
+        width = int(match.group(1))
+
+    # Load the input vmem file.
+    vmem = MemFile.load_vmem(width, args.infile)
+
+    # OpenTitan vmem files should always contain one single contiguous chunk.
+    assert len(vmem.chunks) == 1
+
+    # Loop over all words, and:
+    # 1) Generate the address,
+    # 2) convert the endianness, and
+    # 3) write this to the output file.
+    addr_chars = 8
+    word_chars = math.ceil(width / 4)
+    for idx, word in enumerate(vmem.chunks[0].words):
+        # Generate the address.
+        addr = idx * math.ceil(width / 8)
+        # Convert endianness.
+        data = swap_bytes(width, word)
+        # Write to file.
+        toks = [f'@{addr:0{addr_chars}X}']
+        toks.append(f'{data:0{word_chars}X}')
+        args.outfile.write(' '.join(toks) + '\n')
+
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/util/fpga/addr4x.py b/util/fpga/addr4x.py
deleted file mode 100755
index 4f1b14c..0000000
--- a/util/fpga/addr4x.py
+++ /dev/null
@@ -1,56 +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
-"""Utility script
-Handles linear addresses generated from srec_cat to suit with fpga BRAM
-architecture which need word addressing.. Example
-0x0 0x00000010
-0x1 0x000000FF
-0x2 0x00000088
-
-get converted to
-
-0x0 0x00000010
-0x4 0x000000FF
-0x8 0x00000088 """
-
-import argparse
-import imp
-import logging
-import os
-import sys
-from pathlib import Path
-
-DESC = """addr4x.py script handles the address generated in mem file from
-srec_cat to suit with BRAM memory architecture which need word addressing"""
-
-
-def main(argv):
-    parser = argparse.ArgumentParser(prog="addr4x.py", description=DESC)
-    parser.add_argument('--infile',
-                        '-i',
-                        dest='inputfile',
-                        type=argparse.FileType('r', encoding='UTF-8'),
-                        required=True,
-                        help='Input Mem file')
-    parser.add_argument('--outfile',
-                        '-o',
-                        dest='outputfile',
-                        type=argparse.FileType('w', encoding='UTF-8'),
-                        required=True,
-                        help='Output Mem file')
-    args = parser.parse_args()
-    in_file_path = Path(args.inputfile.name).resolve()
-    with open(in_file_path) as file:
-        for line in file:
-            if "sourceforge" not in line:
-                a = line.split("@")
-                b = a[1].split(" ")
-                mult = int(b[0], 16)
-                final = "@" + hex(mult * 4)[2:] + " " + b[1]
-                args.outputfile.write(final)
-
-
-if __name__ == "__main__":
-    main(sys.argv)
diff --git a/util/fpga/splice_nexysvideo.sh b/util/fpga/splice_nexysvideo.sh
index 9424813..d8ca481 100755
--- a/util/fpga/splice_nexysvideo.sh
+++ b/util/fpga/splice_nexysvideo.sh
@@ -30,10 +30,8 @@
 ./meson_init.sh
 ninja -C "${OBJ_DIR}" "${TARGET_EXPORT}"
 
-srec_cat "${TARGET}.bin" -binary -offset 0x0 -o "${TARGET}.brammem" \
-  -vmem -Output_Block_Size 4;
-
-util/fpga/addr4x.py -i "${TARGET}.brammem" -o "${TARGET}.mem"
+# Create the Vivado image for splicing.
+hw/ip/rom_ctrl/util/gen_vivado_mem_image.py "${TARGET}.32.vmem" "${TARGET}.updatemem.mem"
 
 # The --debug flag is undocumented and causes updatemem to print out the INIT_XX
 # values of the four BRAM cells. These values are also oberservable when opening
@@ -41,7 +39,7 @@
 # the corresponding BRAM cells. This information is very useful when debugging
 # the splicing flow.
 updatemem -force --meminfo "${FPGA_BUILD_DIR}/rom.mmi" \
-  --data "${TARGET}.mem" \
+  --data "${TARGET}.updatemem.mem" \
   --bit "${FPGA_BUILD_DIR}/${FPGA_BIT_NAME}.bit"  --proc dummy \
   --out "${FPGA_BUILD_DIR}/${FPGA_BIT_NAME}.splice.bit" \
   --debug