[topgen] Updates for declaring memory regions within IPs

Signed-off-by: Michael Schaffner <msf@opentitan.org>
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