[topgen] Array of inter-module signal

This commit is to support an array of inter-module signals.

To achieve the array feature in inter-module, regardless of the act
(requester, responder/receiver), the key shall be an array and the
entries should be `width` as 1. So, inter-module signal only supports
1:1, 1:N, N:1.

Also, the top-level signal name follows the key instance name not
'req->rsp' as before.

```hjson
inter_module: {
  'module_a.sig_a': ['module_b.sig_a', 'module_c.sig_a']
}
```

In the above example, `sig_a` in `module_a` can be requester or
responder/receiver.  The `width` of `module_a.sig_a` shall be 2.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/util/topgen.py b/util/topgen.py
index 73cfab4..0ead8a4 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -37,7 +37,7 @@
 
     try:
         out_rtl = top_rtl_tpl.render(top=top)
-    except:
+    except:  # noqa: E722
         log.error(exceptions.text_error_template().render())
     return out_rtl
 
@@ -62,7 +62,7 @@
 
         try:
             out_rtl, out_pkg, out_core = tlgen.generate(xbar)
-        except:
+        except:  # noqa: E722
             log.error(exceptions.text_error_template().render())
 
         rtl_path = out_path / 'ip/xbar_{}/rtl/autogen'.format(obj["name"])
@@ -160,7 +160,7 @@
                                    lfsr_seed=lfsr_seed,
                                    async_on=async_on,
                                    n_classes=n_classes)
-        except:
+        except:  # noqa: E722
             log.error(exceptions.text_error_template().render())
         log.info("alert_handler hjson: %s" % out)
 
@@ -218,7 +218,7 @@
         hjson_tpl = Template(fin.read())
         try:
             out = hjson_tpl.render(src=src, target=target, prio=prio)
-        except:
+        except:  # noqa: E722
             log.error(exceptions.text_error_template().render())
         log.info("RV_PLIC hjson: %s" % out)
 
@@ -246,7 +246,7 @@
         rtl_tpl = Template(fin.read())
         try:
             out = rtl_tpl.render(src=src, target=target, prio=prio)
-        except:
+        except:  # noqa: E722
             log.error(exceptions.text_error_template().render())
         log.info("RV_PLIC RTL: %s" % out)
 
@@ -303,7 +303,7 @@
             out = hjson_tpl.render(n_periph_in=n_periph_in,
                                    n_periph_out=n_periph_out,
                                    n_mio_pads=num_mio)
-        except:
+        except:  # noqa: E722
             log.error(exceptions.text_error_template().render())
         log.info("PINMUX HJSON: %s" % out)
 
@@ -374,7 +374,7 @@
         '-o',
         help='''Target TOP directory.
              Module is created under rtl/. (default: dir(topcfg)/..)
-             ''') # yapf: disable
+             ''')  # yapf: disable
     parser.add_argument('--verbose', '-v', action='store_true', help="Verbose")
 
     # Generator options: 'no' series. cannot combined with 'only' series
@@ -403,7 +403,7 @@
     parser.add_argument(
         '--top-only',
         action='store_true',
-        help="If defined, the tool generates top RTL only") # yapf:disable
+        help="If defined, the tool generates top RTL only")  # yapf:disable
     parser.add_argument(
         '--xbar-only',
         action='store_true',
@@ -503,7 +503,7 @@
             ip_objs = []
             for x in ips:
                 # Skip if it is not in the module list
-                if not x.stem in [ip["type"] for ip in topcfg["module"]]:
+                if x.stem not in [ip["type"] for ip in topcfg["module"]]:
                     log.info(
                         "Skip module %s as it isn't in the top module list" %
                         x.stem)
@@ -559,7 +559,7 @@
             raise SystemExit(sys.exc_info()[1])
 
     # Generate PLIC
-    if not args.no_plic            and \
+    if not args.no_plic and \
        not args.alert_handler_only and \
        not args.xbar_only:
         generate_plic(completecfg, out_path)
diff --git a/util/topgen/intermodule.py b/util/topgen/intermodule.py
index 1716ff1..2ca2bd2 100644
--- a/util/topgen/intermodule.py
+++ b/util/topgen/intermodule.py
@@ -7,26 +7,19 @@
 from typing import Dict, Tuple
 from collections import OrderedDict
 
-from .lib import *
-
 from reggen.validate import check_int
 
 
-def intersignal_format(uid: int, req: Dict, rsp: Dict) -> str:
+def intersignal_format(req: Dict) -> str:
     """Determine the signal format of the inter-module connections
 
-    @param[uid] Unique ID. Each inter-signal has its own ID at top
-
     @param[req] Request struct. It has instance name, package format
                 and etc.
-
-    @param[rsp] Response struct. Same format as @param[req]
     """
 
     # TODO: Handle array signal
-    result = "{req}_{rsp}_{struct}".format(req=req["inst_name"],
-                                           rsp=rsp["inst_name"],
-                                           struct=req["struct"])
+    result = "{req}_{struct}".format(req=req["inst_name"],
+                                     struct=req["struct"])
 
     # check signal length if exceeds 100
 
@@ -34,9 +27,8 @@
     # 3 : _{i|o}(
     # 6 : _{req|rsp}),
     req_length = 7 + len(req["name"]) + 3 + len(result) + 6
-    rsp_length = 7 + len(rsp["name"]) + 3 + len(result) + 6
 
-    if max(req_length, rsp_length) > 100:
+    if req_length > 100:
         logmsg = "signal {0} length cannot be greater than 100"
         log.warning(logmsg.format(result))
         log.warning("Please consider shorten the instance name")
@@ -74,11 +66,26 @@
     # TODO: Cross check Can be done here not in validate as ipobj is not
     # available in validate
     error = check_intermodule(topcfg, "Inter-module Check")
-    assert error is 0, "Inter-module validation is failed cannot move forward."
+    assert error == 0, "Inter-module validation is failed cannot move forward."
 
     # intermodule
     definitions = []
 
+    # Check the originator
+    # As inter-module connection allow only 1:1, 1:N, or N:1, pick the most
+    # common signals. If a requester connects to multiple responders/receivers,
+    # the requester is main connector so that the definition becomes array.
+    #
+    # For example:
+    #  inter_module: {
+    #    'pwr_mgr.pwrup': ['lc.pwrup', 'otp.pwrup']
+    #  }
+    # The tool adds `struct [1:0] pwr_mgr_pwrup`
+    # It doesn't matter whether `pwr_mgr.pwrup` is requester or responder.
+    # If the key is responder type, then the connection is made in reverse,
+    # such that `lc.pwrup --> pwr_mgr.pwrup[0]` and
+    # `otp.pwrup --> pwr_mgr.pwrup[1]`
+
     uid = 0  # Unique connection ID across the top
 
     # inter_module: {
@@ -98,6 +105,33 @@
                                              req_signal)
 
         rsp_len = len(rsps)
+        # decide signal format based on the `key`
+        sig_name = intersignal_format(req_struct)
+        req_struct["top_signame"] = sig_name
+
+        # Find package in req, rsps
+        if "package" in req_struct:
+            package = req_struct["package"]
+        else:
+            for rsp in rsps:
+                rsp_module, rsp_signal, rsp_index = filter_index(rsp)
+                rsp_struct = find_intermodule_signal(list_of_intersignals,
+                                                     rsp_module, rsp_signal)
+                if "package" in rsp_struct:
+                    package = rsp_struct["package"]
+                    break
+            if not package:
+                package = ""
+
+        # Add to definition
+        definitions.append(
+            OrderedDict([('package', package),
+                         ('struct', req_struct["struct"]),
+                         ('signame', sig_name), ('width', req_struct["width"]),
+                         ('type', req_struct["type"])]))
+
+        req_struct["index"] = -1
+
         for i, rsp in enumerate(rsps):
             assert i == 0, "Handling multiple connections (array) isn't yet supported"
 
@@ -108,32 +142,15 @@
                                                  rsp_module, rsp_signal)
 
             # determine the signal name
-            sig_name = "im{uid}_{req_s}".format(uid=uid,
-                                                req_s=req_struct['struct'])
-            sig_name = intersignal_format(uid, req_struct, rsp_struct)
 
-            req_struct["top_signame"] = sig_name
             rsp_struct["top_signame"] = sig_name
-
-            # Add to definitions
-            if "package" in req_struct:
-                package = req_struct["package"]
-            elif "package" in rsp_struct:
-                package = rsp_struct["package"]
-            else:
-                package = ""
+            rsp_struct["index"] = -1 if req_struct["width"] == 1 else i
 
             # Assume it is logic
             # req_rsp doesn't allow logic
-            if req_struct["struct"] is "logic":
+            if req_struct["struct"] == "logic":
                 assert req_struct[
-                    "type"] is not "req_rsp", "logic signal cannot have req_rsp type"
-            definitions.append(
-                OrderedDict([('package', package),
-                             ('struct', req_struct["struct"]),
-                             ('signame', sig_name),
-                             ('width', req_struct["width"]),
-                             ('type', req_struct["type"])]))
+                    "type"] != "req_rsp", "logic signal cannot have req_rsp type"
 
             if rsp_len != 1:
                 log.warning("{req}[{i}] -> {rsp}".format(req=req, i=i,
@@ -193,7 +210,7 @@
     return filtered[0] if len(filtered) == 1 else None
 
 
-## Validation
+# Validation
 def check_intermodule(topcfg: Dict, prefix: str) -> int:
     if "inter_module" not in topcfg:
         return 0
@@ -222,7 +239,7 @@
         # entries of value list should be 1
         req_m, req_s, req_i = filter_index(req)
 
-        if req_s is "":
+        if req_s == "":
             log.error(
                 "Cannot parse the inter-module signal key '{req}'".format(
                     req=req))
@@ -262,7 +279,7 @@
         # Check rsp format
         for i, rsp in enumerate(rsps):
             rsp_m, rsp_s, rsp_i = filter_index(rsp)
-            if rsp_s is "":
+            if rsp_s == "":
                 log.error(
                     "Cannot parse the inter-module signal key '{req}->{rsp}'".
                     format(req=req, rsp=rsp))
@@ -290,7 +307,8 @@
             if req_struct["width"] != 1:
                 if width not in [1, req_struct["width"]]:
                     log.error(
-                        "If req {req} is an array, rsp {rsp} shall be non-array or array with same width"
+                        "If req {req} is an array, "
+                        "rsp {rsp} shall be non-array or array with same width"
                         .format(req=req, rsp=rsp))
                     error += 1
 
diff --git a/util/topgen/lib.py b/util/topgen/lib.py
index 598482e..3818fab 100644
--- a/util/topgen/lib.py
+++ b/util/topgen/lib.py
@@ -7,6 +7,7 @@
 from pathlib import Path
 from collections import OrderedDict
 import hjson
+import sys
 
 import re
 
@@ -56,7 +57,7 @@
                        object_pairs_hook=OrderedDict) for x in p
         ]
     except ValueError:
-        raise Systemexit(sys.exc_info()[1])
+        raise SystemExit(sys.exc_info()[1])
 
     xbar_objs = [x for x in xbar_objs if is_xbarcfg(x)]
 
@@ -91,8 +92,7 @@
     instances = top["module"] + top["memory"]
 
     intermodule_instances = [
-        x["inter_signal_list"] for x in top["module"] + top["memory"]
-        if "inter_signal_list" in x
+        x["inter_signal_list"] for x in instances if "inter_signal_list" in x
     ]
 
     for m in intermodule_instances:
@@ -119,7 +119,7 @@
     """
     result = deepcopy(signal)
 
-    if not "name" in signal:
+    if "name" not in signal:
         raise SystemExit("signal {} doesn't have name field".format(signal))
 
     result["name"] = prefix + "_" + signal["name"]
@@ -162,7 +162,7 @@
         last = first
     first = int(first, 0)
     last = int(last, 0)
-    width = first - last + 1
+    # width = first - last + 1
 
     for p in range(first, last + 1):
         pads.append(OrderedDict([("name", pad), ("index", p)]))
@@ -204,7 +204,13 @@
 def parameterize(text):
     """Return the value wrapping with quote if not integer nor bits
     """
-    if re.match('(\d+\'[hdb]\s*[0-9a-f_A-F]+|[0-9]+)', text) == None:
+    if re.match(r'(\d+\'[hdb]\s*[0-9a-f_A-F]+|[0-9]+)', text) is None:
         return "\"{}\"".format(text)
 
     return text
+
+
+def index(i: int) -> str:
+    """Return index if it is not -1
+    """
+    return "[{}]".format(i) if i != -1 else ""