[topgen] Updates for declaring memory regions within IPs

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
index 324fcfa..0780f93 100644
--- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
@@ -1317,6 +1317,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -1604,6 +1605,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -2040,12 +2042,6 @@
         rst_edn_ni: rstmgr_aon_resets.rst_sys_n[rstmgr_pkg::Domain0Sel]
       }
       attr: templated
-      localparam:
-      {
-        EscCntDw: 32
-        AccuCntDw: 16
-        LfsrSeed: 0x7FFFFFFF
-      }
       clock_connections:
       {
         clk_i: clkmgr_aon_clocks.clk_io_div4_timers
@@ -2053,6 +2049,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -2956,6 +2953,7 @@
         clk_aon_i: clkmgr_aon_clocks.clk_aon_powerup
       }
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -3438,6 +3436,7 @@
         clk_otp_i: clkmgr_aon_clocks.clk_io_div4_infra
       }
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -3627,6 +3626,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -3889,6 +3889,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -4073,6 +4074,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -4325,6 +4327,7 @@
         clk_edn_i: clkmgr_aon_clocks.clk_main_kmac
       }
       domain: "0"
+      memory: {}
       param_list:
       [
         {
@@ -4449,6 +4452,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -4771,6 +4775,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -4909,6 +4914,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -5200,6 +5206,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -5386,6 +5393,7 @@
       }
       domain: "0"
       param_decl: {}
+      memory: {}
       param_list:
       [
         {
@@ -5555,6 +5563,15 @@
         rom: 0x00008000
         regs: 0x411e0000
       }
+      memory:
+      {
+        rom:
+        {
+          label: rom
+          swaccess: rx
+          size: 0x4000
+        }
+      }
       clock_connections:
       {
         clk_i: clkmgr_aon_clocks.clk_main_infra
diff --git a/hw/top_earlgrey/data/top_earlgrey.hjson b/hw/top_earlgrey/data/top_earlgrey.hjson
index 439778b..63c5668 100644
--- a/hw/top_earlgrey/data/top_earlgrey.hjson
+++ b/hw/top_earlgrey/data/top_earlgrey.hjson
@@ -413,11 +413,6 @@
       reset_connections: {rst_ni: "sys_io_div4", rst_edn_ni: "sys"},
       base_addr: "0x40150000",
       attr: "templated",
-      localparam: {
-        EscCntDw:  32,
-        AccuCntDw: 16,
-        LfsrSeed:  "0x7FFFFFFF"
-      }
     },
     // dummy module to capture the alert handler escalation signals
     // and test them by converting them into IRQs
@@ -631,6 +626,13 @@
       clock_group: "infra",
       reset_connections: {rst_ni: "sys"},
       base_addrs: {rom: "0x00008000", regs: "0x411e0000"}
+      memory: {
+        rom: {
+          label:    "rom",
+          swaccess: "rx",
+          size:     "0x4000"
+        }
+      }
     },
     { name: "rv_core_ibex_peri",
       type: "rv_core_ibex_peri",
diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey_pkg.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey_pkg.sv
index f6cf6cb..34769ff 100644
--- a/hw/top_earlgrey/rtl/autogen/top_earlgrey_pkg.sv
+++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey_pkg.sv
@@ -500,6 +500,16 @@
    */
   parameter int unsigned TOP_EARLGREY_EFLASH_SIZE_BYTES = 32'h100000;
 
+  /**
+   * Memory base address for rom in top earlgrey.
+   */
+  parameter int unsigned TOP_EARLGREY_ROM_BASE_ADDR = 32'h8000;
+
+  /**
+   * Memory size for rom in top earlgrey.
+   */
+  parameter int unsigned TOP_EARLGREY_ROM_SIZE_BYTES = 32'h4000;
+
 
   // Enumeration of IO power domains.
   // Only used in ASIC target.
diff --git a/hw/top_earlgrey/sw/autogen/top_earlgrey.h b/hw/top_earlgrey/sw/autogen/top_earlgrey.h
index 9683d5d..f98efc3 100644
--- a/hw/top_earlgrey/sw/autogen/top_earlgrey.h
+++ b/hw/top_earlgrey/sw/autogen/top_earlgrey.h
@@ -884,6 +884,16 @@
  */
 #define TOP_EARLGREY_EFLASH_SIZE_BYTES 0x100000u
 
+/**
+ * Memory base address for rom in top earlgrey.
+ */
+#define TOP_EARLGREY_ROM_BASE_ADDR 0x8000u
+
+/**
+ * Memory size for rom in top earlgrey.
+ */
+#define TOP_EARLGREY_ROM_SIZE_BYTES 0x4000u
+
 
 /**
  * PLIC Interrupt Source Peripheral.
diff --git a/hw/top_earlgrey/sw/autogen/top_earlgrey_memory.h b/hw/top_earlgrey/sw/autogen/top_earlgrey_memory.h
index 63db6fc..124d5e5 100644
--- a/hw/top_earlgrey/sw/autogen/top_earlgrey_memory.h
+++ b/hw/top_earlgrey/sw/autogen/top_earlgrey_memory.h
@@ -20,16 +20,18 @@
 // Include guard for assembler
 #ifdef __ASSEMBLER__
 
+
 /**
- * Memory base address for rom in top earlgrey.
+ * Memory base for rom_ctrl_rom in top earlgrey.
  */
 #define TOP_EARLGREY_ROM_BASE_ADDR 0x00008000
 
 /**
- * Memory size for rom in top earlgrey.
+ * Memory size for rom_ctrl_rom in top earlgrey.
  */
 #define TOP_EARLGREY_ROM_SIZE_BYTES 0x4000
 
+
 /**
  * Memory base address for ram_main in top earlgrey.
  */
diff --git a/util/reggen/gen_cheader.py b/util/reggen/gen_cheader.py
index f68bd39..ba84258 100644
--- a/util/reggen/gen_cheader.py
+++ b/util/reggen/gen_cheader.py
@@ -25,14 +25,16 @@
 def genout(outfile: TextIO, msg: str) -> None:
     outfile.write(msg)
 
+
 def to_snake_case(s: str) -> str:
     val = []
     for i, ch in enumerate(s):
-      if i > 0 and ch.isupper():
-        val.append('_')
-      val.append(ch)
+        if i > 0 and ch.isupper():
+            val.append('_')
+        val.append(ch)
     return ''.join(val)
 
+
 def as_define(s: str) -> str:
     s = s.upper()
     r = ''
diff --git a/util/reggen/params.py b/util/reggen/params.py
index 69ee38e..541625b 100644
--- a/util/reggen/params.py
+++ b/util/reggen/params.py
@@ -115,6 +115,14 @@
         return rd
 
 
+class MemSizeParameter(BaseParam):
+    def __init__(self,
+                 name: str,
+                 desc: Optional[str],
+                 param_type: str):
+        super().__init__(name, desc, param_type)
+
+
 def _parse_parameter(where: str, raw: object) -> BaseParam:
     rd = check_keys(raw, where,
                     list(REQUIRED_FIELDS.keys()),
@@ -198,6 +206,36 @@
                              "with RndCnst."
                              .format(where=where, name=name, fld=fld))
 
+    if name.lower().startswith('memsize'):
+        r_type = rd.get('type')
+        if r_type is None:
+            raise ValueError('At {}, parameter {} has no type field (which is '
+                             'required for memory size parameters).'
+                             .format(where, name))
+        param_type = check_str(r_type, 'type field of ' + where)
+
+        if rd.get('type') != "int":
+            raise ValueError('At {}, memory size parameter {} must be of type integer.'
+                             .format(where, name))
+
+        local = check_bool(rd.get('local', 'false'), 'local field of ' + where)
+        if local:
+            raise ValueError('At {}, the parameter {} specifies local = true, '
+                             'meaning that it is a localparam. This is '
+                             'incompatible with being a memory size parameter.'
+                             .format(where, name))
+
+        expose = check_bool(rd.get('expose', 'false'),
+                            'expose field of ' + where)
+        if expose:
+            raise ValueError('At {}, the parameter {} specifies expose = '
+                             'true, meaning that the parameter is exposed to '
+                             'the top-level. This is incompatible with '
+                             'being a memory size parameter.'
+                             .format(where, name))
+
+        return MemSizeParameter(name, desc, param_type)
+
     r_type = rd.get('type')
     if r_type is None:
         param_type = 'int'
diff --git a/util/topgen.py b/util/topgen.py
index 2d3dde0..d473632 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -122,16 +122,6 @@
 
     topname = top["name"]
 
-    # check if there are any params to be passed through reggen and placed into
-    # the generated package
-    ip_list_in_top = [x["name"].lower() for x in top["module"]]
-    ah_idx = ip_list_in_top.index("alert_handler")
-    if 'localparam' in top['module'][ah_idx]:
-        if 'EscCntDw' in top['module'][ah_idx]['localparam']:
-            esc_cnt_dw = int(top['module'][ah_idx]['localparam']['EscCntDw'])
-        if 'AccuCntDw' in top['module'][ah_idx]['localparam']:
-            accu_cnt_dw = int(top['module'][ah_idx]['localparam']['AccuCntDw'])
-
     if esc_cnt_dw < 1:
         log.error("EscCntDw must be larger than 0")
     if accu_cnt_dw < 1:
@@ -151,7 +141,7 @@
             for k in range(alert['width']):
                 async_on = str(alert['async']) + async_on
         # convert to hexstring to shorten line length
-        async_on = ("%d'h" % n_alerts) + hex(int(async_on,2))[2:]
+        async_on = ("%d'h" % n_alerts) + hex(int(async_on, 2))[2:]
 
     log.info("alert handler parameterization:")
     log.info("NAlerts   = %d" % n_alerts)
diff --git a/util/topgen/c.py b/util/topgen/c.py
index 58760a3..b10d741 100644
--- a/util/topgen/c.py
+++ b/util/topgen/c.py
@@ -147,11 +147,25 @@
         return ret
 
     def memories(self):
-        return [(m["name"],
-                 MemoryRegion(self._top_name + Name.from_snake_case(m["name"]),
-                              int(m["base_addr"], 0),
-                              int(m["size"], 0)))
-                for m in self.top["memory"]]
+        ret = []
+        for m in self.top["memory"]:
+            ret.append((m["name"],
+                        MemoryRegion(self._top_name +
+                                     Name.from_snake_case(m["name"]),
+                                     int(m["base_addr"], 0),
+                                     int(m["size"], 0))))
+
+        for inst in self.top['module']:
+            if "memory" in inst:
+                for if_name, val in inst["memory"].items():
+                    base, size = get_base_and_size(self._name_to_block,
+                                                   inst, if_name)
+
+                    name = self._top_name + Name.from_snake_case(val["label"])
+                    region = MemoryRegion(name, base, size)
+                    ret.append((val["label"], region))
+
+        return ret
 
     def _init_plic_targets(self):
         enum = CEnum(self._top_name + Name(["plic", "target"]))
diff --git a/util/topgen/lib.py b/util/topgen/lib.py
index a1354fd..1f1e848 100644
--- a/util/topgen/lib.py
+++ b/util/topgen/lib.py
@@ -401,16 +401,31 @@
         # that corresponds to ifname
         rb = block.reg_blocks.get(ifname)
         if rb is None:
-            log.error('Cannot connect to non-existent {} device interface '
-                      'on {!r} (an instance of the {!r} block)'
-                      .format('default' if ifname is None else repr(ifname),
-                              inst['name'], block.name))
-            bytes_used = 0
+            raise RuntimeError(
+                'Cannot connect to non-existent {} device interface '
+                'on {!r} (an instance of the {!r} block).'
+                .format('default' if ifname is None else repr(ifname),
+                        inst['name'], block.name))
         else:
             bytes_used = 1 << rb.get_addr_width()
 
         base_addr = inst['base_addrs'][ifname]
 
+        # If an instance has a nonempty "memory" field, take the memory
+        # size configuration from there.
+        if "memory" in inst:
+            if ifname in inst["memory"]:
+                memory_size = int(inst["memory"][ifname]["size"], 0)
+                if bytes_used > memory_size:
+                    raise RuntimeError(
+                        'Memory region on {} device interface '
+                        'on {!r} (an instance of the {!r} block) '
+                        'is smaller than the corresponding register block.'
+                        .format('default' if ifname is None else repr(ifname),
+                                inst['name'], block.name))
+
+                bytes_used = memory_size
+
     # Round up to min_device_spacing if necessary
     size_byte = max(bytes_used, min_device_spacing)
 
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index d29f9b1..41ce4bd 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -11,7 +11,7 @@
 
 from topgen import c, lib
 from reggen.ip_block import IpBlock
-from reggen.params import LocalParam, Parameter, RandParameter
+from reggen.params import LocalParam, Parameter, RandParameter, MemSizeParameter
 
 
 def _get_random_data_hex_literal(width):
@@ -74,6 +74,7 @@
         instance["param_decl"] = {}
 
     mod_name = instance["name"]
+    cc_mod_name = c.Name.from_snake_case(mod_name).as_camel_case()
 
     # Check to see if all declared parameters exist
     param_decl_accounting = [decl for decl in instance["param_decl"].keys()]
@@ -89,6 +90,10 @@
 
         param_expose = param.expose if isinstance(param, Parameter) else False
 
+        # assign an empty entry if this is not present
+        if "memory" not in instance:
+            instance["memory"] = {}
+
         # Check for security-relevant parameters that are not exposed,
         # adding a top-level name.
         if param.name.lower().startswith("sec") and not param_expose:
@@ -97,8 +102,7 @@
                             mod_name, param.name))
 
         # Move special prefixes to the beginnining of the parameter name.
-        param_prefixes = ["Sec", "RndCnst"]
-        cc_mod_name = c.Name.from_snake_case(mod_name).as_camel_case()
+        param_prefixes = ["Sec", "RndCnst", "MemSize"]
         name_top = cc_mod_name + param.name
         for prefix in param_prefixes:
             if param.name.lower().startswith(prefix.lower()):
@@ -122,6 +126,18 @@
 
             new_param['default'] = new_default
             new_param['randwidth'] = randwidth
+
+        elif isinstance(param, MemSizeParameter):
+            key = param.name[7:].lower()
+            # Set the parameter to the specified memory size.
+            if key in instance["memory"]:
+                new_default = int(instance["memory"][key]["size"], 0)
+                new_param['default'] = new_default
+            else:
+                log.error("Missing memory configuration for "
+                          "memory {} in instance {}"
+                          .format(key, instance["name"]))
+
         # if this exposed parameter is listed in the `param_decl` dict,
         # override its default value.
         elif param.name in instance["param_decl"].keys():
diff --git a/util/topgen/templates/toplevel_memory.h.tpl b/util/topgen/templates/toplevel_memory.h.tpl
index bfb0274..021beb5 100644
--- a/util/topgen/templates/toplevel_memory.h.tpl
+++ b/util/topgen/templates/toplevel_memory.h.tpl
@@ -20,15 +20,23 @@
 // Include guard for assembler
 #ifdef __ASSEMBLER__
 
+
+% for m in top["module"]:
+  % if "memory" in m:
+    % for key, val in m["memory"].items():
 /**
- * Memory base address for rom in top earlgrey.
+ * Memory base for ${m["name"]}_${val["label"]} in top ${top["name"]}.
  */
-#define TOP_EARLGREY_ROM_BASE_ADDR 0x00008000
+#define TOP_${top["name"].upper()}_${val["label"].upper()}_BASE_ADDR ${m["base_addrs"][key]}
 
 /**
- * Memory size for rom in top earlgrey.
+ * Memory size for ${m["name"]}_${val["label"]} in top ${top["name"]}.
  */
-#define TOP_EARLGREY_ROM_SIZE_BYTES 0x4000
+#define TOP_${top["name"].upper()}_${val["label"].upper()}_SIZE_BYTES ${val["size"]}
+
+    % endfor
+  % endif
+% endfor
 
 % for m in top["memory"]:
 /**
diff --git a/util/topgen/templates/toplevel_memory.ld.tpl b/util/topgen/templates/toplevel_memory.ld.tpl
index 3bb6271..0a21022 100644
--- a/util/topgen/templates/toplevel_memory.ld.tpl
+++ b/util/topgen/templates/toplevel_memory.ld.tpl
@@ -25,7 +25,13 @@
  * translation base
  */
 MEMORY {
-  rom(rx) : ORIGIN = 0x00008000, LENGTH = 0x4000
+% for m in top["module"]:
+  % if "memory" in m:
+    % for key, val in m["memory"].items():
+  ${val["label"]}(${val["swaccess"]}) : ORIGIN = ${m["base_addrs"][key]}, LENGTH = ${val["size"]}
+    % endfor
+  % endif
+% endfor
 % for m in top["memory"]:
   ${m["name"]}(${memory_to_flags(m)}) : ORIGIN = ${m["base_addr"]}, LENGTH = ${m["size"]}
 % endfor
diff --git a/util/topgen/validate.py b/util/topgen/validate.py
index dfbaa9c..eb4e62c 100644
--- a/util/topgen/validate.py
+++ b/util/topgen/validate.py
@@ -199,7 +199,7 @@
 
 eflash_required = {
     'banks': ['d', 'number of flash banks'],
-    'base_addr': ['s', 'strarting hex address of memory'],
+    'base_addr': ['s', 'hex start address of memory'],
     'clock_connections': ['g', 'generated, elaborated version of clock_srcs'],
     'clock_group': ['s', 'associated clock attribute group'],
     'clock_srcs': ['g', 'clock connections'],
@@ -216,6 +216,45 @@
 
 eflash_added = {}
 
+module_required = {
+    'name': ['s', 'name of the instance'],
+    'type': ['s', 'comportable IP type'],
+    'clock_srcs': ['g', 'dict with clock sources'],
+    'clock_group': ['s', 'clock group'],
+    'reset_connections': ['g', 'dict with reset sources'],
+}
+
+module_optional = {
+    'domain': ['s', 'power domain, defaults to Domain0'],
+    'clock_reset_export': ['l', 'optional list with prefixes for exported '
+                                'clocks and resets at the chip level'],
+    'attr': ['s', 'optional attribute indicating whether the IP is '
+                  '"templated" or "reggen_only"'],
+    'base_addr': ['s', 'hex start address of the peripheral '
+                       '(if the IP has only a single TL-UL interface)'],
+    'base_addrs': ['d', 'hex start addresses of the peripheral '
+                        ' (if the IP has multiple TL-UL interfaces)'],
+    'memory': ['g', 'optional dict with memory region attributes'],
+    'param_decl': ['g', 'optional dict that allows to override instantiation parameters']
+}
+
+module_added = {
+    'clock_connections': ['g', 'generated clock connections']
+}
+
+memory_required = {
+    'label': ['s', 'region label for the linker script'],
+    'swaccess': ['s', 'access attributes for the linker script'],
+    'size': ['d', 'memory region size in bytes for the linker script, '
+                  'xbar and RTL parameterisations'],
+}
+
+memory_optional = {
+}
+
+memory_added = {
+}
+
 
 # Supported PAD types.
 # Needs to coincide with enum definition in prim_pad_wrapper_pkg.sv
@@ -835,6 +874,40 @@
     return error
 
 
+def check_modules(top, prefix):
+    error = 0
+    for m in top['module']:
+        modname = m.get("name", "unnamed module")
+        error += check_keys(m, module_required, module_optional, module_added,
+                            prefix + " " + modname)
+
+        # these fields are mutually exclusive
+        if 'base_addr' in m and 'base_addrs' in m:
+            log.error("{} {} a module cannot define both the 'base_addr' "
+                      "and 'base_addrs' keys at the same time"
+                      .format(prefix, modname))
+            error += 1
+
+        if 'base_addrs' in m and 'memory' in m:
+            for intf, value in m['memory'].items():
+                error += check_keys(value, memory_required,
+                                    memory_optional, memory_added,
+                                    prefix + " " + modname + " " + intf)
+                # make sure the memory regions correspond to the TL-UL interfaces
+                if intf not in m['base_addrs']:
+                    log.error("{} {} memory region {} does not "
+                              "correspond to any of the defined "
+                              "TL-UL interfaces".format(prefix, modname, intf))
+                    error += 1
+                # make sure the linker region access attribute is valid
+                attr = value.get('swaccess', 'unknown attribute')
+                if attr not in ['r', 'rw', 'rx', 'rwx']:
+                    log.error('{} {} swaccess attribute {} of memory region {} '
+                              'is not valid'.format(prefix, modname, attr, intf))
+                    error += 1
+    return error
+
+
 def validate_top(top, ipobjs, xbarobjs):
     # return as it is for now
     error = check_keys(top, top_required, top_optional, top_added, "top")
@@ -845,7 +918,10 @@
 
     component = top['name']
 
-    # MODULE check
+    # Check module instantiations
+    error += check_modules(top, component)
+
+    # MODULE  check
     err, ip_idxs = check_target(top, ipobjs, Target(TargetType.MODULE))
     error += err