[topgen] Support custom external name format

Current Inter-module signal has fixed naming rule for every inter-module
signals. It creates weird naming format for external ports. External
ports doesn't have `_o`, `_i` suffixes and also it always has the
instance name in front of the signal name such as `clkmgr_clk_main`.

This commit is to support custom top signal name for external type
(other type will be addressed in following PRs). To support it,
top_earlgrey.hjson format is slightly revised. Please see the
`inter_module`.`external` data field.

This is related to Issue #3095

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/top_earlgrey/data/top_earlgrey.hjson b/hw/top_earlgrey/data/top_earlgrey.hjson
index d7a6277..d6949c7 100644
--- a/hw/top_earlgrey/data/top_earlgrey.hjson
+++ b/hw/top_earlgrey/data/top_earlgrey.hjson
@@ -372,7 +372,12 @@
     ],
 
     // ext is to create port in the top.
-    'external': ['clkmgr.clk_main', 'clkmgr.clk_io', 'clkmgr.clk_usb', 'clkmgr.clk_aon'],
+    'external': {
+        'clkmgr.clk_main': 'clk_main',
+        'clkmgr.clk_io': 'clk_io',
+        'clkmgr.clk_usb': 'clk_usb',
+        'clkmgr.clk_aon': 'clk_aon'
+    },
   },
 
   debug_mem_base_addr: "0x1A110000",
diff --git a/hw/top_earlgrey/data/top_earlgrey.sv.tpl b/hw/top_earlgrey/data/top_earlgrey.sv.tpl
index 31a03fe..413f9db 100644
--- a/hw/top_earlgrey/data/top_earlgrey.sv.tpl
+++ b/hw/top_earlgrey/data/top_earlgrey.sv.tpl
@@ -689,6 +689,6 @@
 % endif
 
   // make sure scanmode_i is never X (including during reset)
-  `ASSERT_KNOWN(scanmodeKnown, scanmode_i, clkmgr_clk_main, 0)
+  `ASSERT_KNOWN(scanmodeKnown, scanmode_i, clk_main_i, 0)
 
 endmodule
diff --git a/util/topgen/intermodule.py b/util/topgen/intermodule.py
index 17e9ed5..1a7cb4f 100644
--- a/util/topgen/intermodule.py
+++ b/util/topgen/intermodule.py
@@ -136,8 +136,8 @@
         if len(ips) == 0:
             # if not in module, memory, should be existed in top or ext field
             module_key = "{}.tl_{}".format(xbar["name"], port["name"])
-            if module_key not in topcfg["inter_module"]["top"] + topcfg[
-                    "inter_module"]["external"]:
+            if module_key not in topcfg["inter_module"]["top"] + list(
+                    topcfg["inter_module"]["external"].keys()):
                 log.error("Inter-module key {} cannot be found in module, "
                           "memory, top, or external lists.".format(module_key))
             continue
@@ -362,31 +362,35 @@
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"])]))
 
-    if "external" not in topcfg["inter_module"]:
-        topcfg["inter_module"]["external"] = []
-        topcfg["inter_signal"]["external"] = []
+    topcfg["inter_module"].setdefault('external', [])
+    topcfg["inter_signal"].setdefault('external', [])
 
-    if "external" not in topcfg["inter_signal"]:
-        topcfg["inter_signal"]["external"] = []
-
-    for s in topcfg["inter_module"]["external"]:
+    for s, port in topcfg["inter_module"]["external"].items():
         sig_m, sig_s, sig_i = filter_index(s)
         assert sig_i == -1, 'top net connection should not use bit index'
         sig = find_intermodule_signal(list_of_intersignals, sig_m, sig_s)
-        sig_name = intersignal_format(sig)
+
+        # To make netname `_o` or `_i`
+        sig['external'] = True
+
+        sig_name = port if port != "" else intersignal_format(sig)
         sig["top_signame"] = sig_name
+
         if "index" not in sig:
             sig["index"] = -1
 
         # Add the port definition to top external ports
-        # TODO: Handle the suffix `_i`, `_o` correctly.
-        # For now, external doesn't create _i, _o
         if sig["type"] == "req_rsp":
             req_suffix, rsp_suffix = get_suffixes(sig)
+            if sig["act"] == "req":
+                req_sigsuffix, rsp_sigsuffix = ("_o", "_i")
+            else:
+                req_sigsuffix, rsp_sigsuffix = ("_i", "_o")
+
             topcfg["inter_signal"]["external"].append(
                 OrderedDict([('package', sig["package"]),
                              ('struct', sig["struct"] + req_suffix),
-                             ('signame', sig_name + "_req"),
+                             ('signame', sig_name + "_req" + req_sigsuffix),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"]),
                              ('direction',
@@ -394,15 +398,20 @@
             topcfg["inter_signal"]["external"].append(
                 OrderedDict([('package', sig["package"]),
                              ('struct', sig["struct"] + rsp_suffix),
-                             ('signame', sig_name + "_rsp"),
+                             ('signame', sig_name + "_rsp" + rsp_sigsuffix),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"]),
                              ('direction',
                               'in' if sig['act'] == "req" else 'out')]))
         else:  # uni
+            if sig["act"] == "req":
+                sigsuffix = "_o"
+            else:
+                sigsuffix = "_i"
             topcfg["inter_signal"]["external"].append(
                 OrderedDict([('package', sig["package"]),
-                             ('struct', sig["struct"]), ('signame', sig_name),
+                             ('struct', sig["struct"]),
+                             ('signame', sig_name + sigsuffix),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"]),
                              ('direction',
@@ -708,8 +717,8 @@
                 .format(req_struct["width"], rsps_width))
             error += 1
 
-    for item in topcfg["inter_module"]["top"] + topcfg["inter_module"][
-            "external"]:
+    for item in topcfg["inter_module"]["top"] + list(
+            topcfg["inter_module"]["external"].keys()):
         sig_m, sig_s, sig_i = filter_index(item)
         if sig_i != -1:
             log.error("{item} cannot have index".format(item=item))
@@ -737,6 +746,8 @@
 
 def im_netname(obj: OrderedDict, suffix: str = "") -> str:
     """return top signal name with index
+
+    It also adds suffix for external signal
     """
 
     # sanity check and add missing fields
@@ -785,6 +796,20 @@
     assert suffix in ["", "req", "rsp"]
 
     suffix_s = "_{suffix}".format(suffix=suffix) if suffix != "" else suffix
+
+    # External signal handling
+    if "external" in obj and obj["external"]:
+        pairs = {
+            # act , suffix: additional suffix
+            ("req", "req"): "_o",
+            ("req", "rsp"): "_i",
+            ("rsp", "req"): "_i",
+            ("rsp", "rsp"): "_o",
+            ("req", ""): "_o",
+            ("rcv", ""): "_i"
+        }
+        suffix_s += pairs[(obj['act'], suffix)]
+
     return "{top_signame}{suffix}{index}".format(
         top_signame=obj["top_signame"],
         suffix=suffix_s,