[topgen] Convert Xbar connection to inter-module signal

Purpose: Make Xbar to be comportable IP

This is 2nd modification to make Xbar being comportable IP.
Now topgen reads comportable Xbar hjson object and amends to xbar object
in top cfg. Then the `inter_signal_list` is utilized while connecting
the modules.

Remained Items:

- Defining the connection in `inter_module.connect` is still hand-made.
  need to be automated
- memory port is better to use inter_module connection rather than
  current manual connection
- `corei`, `cored`, `dm_sba`, `debug_mem` are still manual. Ibex, RV_DM
  should have *virtual* comportable IP hjson to make inter-module
  connect-able

This commit addresses #3031 partially.

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/util/topgen.py b/util/topgen.py
index 08f5e74..b2034c5 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -11,6 +11,7 @@
 from collections import OrderedDict
 from io import StringIO
 from pathlib import Path
+from copy import deepcopy
 
 import hjson
 from mako import exceptions
@@ -20,6 +21,7 @@
 from reggen import gen_dv, gen_rtl, validate
 from topgen import (amend_clocks, get_hjsonobj_xbars, merge_top, search_ips,
                     validate_top)
+from topgen.intermodule import elab_intermodule
 
 # Common header for generated files
 genhdr = '''// Copyright lowRISC contributors.
@@ -81,6 +83,21 @@
         # generate testbench for xbar
         tlgen.generate_tb(xbar, dv_path, "top_" + top["name"])
 
+        # Read back the comportable IP and amend to Xbar
+        xbar_ipfile = ip_path / ("data/autogen/xbar_%s.hjson" % obj["name"])
+        with xbar_ipfile.open() as fxbar:
+            xbar_ipobj = hjson.load(fxbar,
+                                    use_decimal=True,
+                                    object_pairs_hook=OrderedDict)
+
+            # Deepcopy of the inter_signal_list.
+            # As of writing the code, it is not expected to write-back the
+            # read xbar objects into files. Still, as `inter_signal_list` is
+            # modified in the `elab_intermodule()` stage, it is better to keep
+            # the original content.
+            obj["inter_signal_list"] = deepcopy(
+                xbar_ipobj["inter_signal_list"])
+
 
 def generate_alert_handler(top, out_path):
     # default values
@@ -837,20 +854,22 @@
 
         completecfg = merge_top(topcfg, ip_objs, xbar_objs)
 
+        if args.top_ral:
+            generate_top_ral(completecfg, ip_objs, out_path)
+
+    if args.hjson_only:
+        hjson_dir = Path(args.topcfg).parent
         genhjson_path = hjson_dir / ("autogen/top_%s.gen.hjson" %
                                      completecfg["name"])
         gencmd = (
             "// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson --hjson-only "
             "-o hw/top_{topname}/\n".format(topname=topname))
 
-        if args.top_ral:
-            generate_top_ral(completecfg, ip_objs, out_path)
-        else:
-            genhjson_path.write_text(genhdr + gencmd +
-                                     hjson.dumps(completecfg, for_json=True))
+        genhjson_path.write_text(genhdr + gencmd +
+                                 hjson.dumps(completecfg, for_json=True))
 
-    if args.hjson_only:
-        log.info("hjson is generated. Exiting...")
+        log.info("hjson is generated. "
+                 "Content is absent of inter-module signal. Exiting...")
         sys.exit()
 
     if args.no_gen_hjson:
@@ -887,8 +906,23 @@
     if not args.no_xbar or args.xbar_only:
         generate_xbars(completecfg, out_path)
 
+    # All IPs are generated. Connect phase now
+    elab_intermodule(completecfg)
+
     top_name = completecfg["name"]
 
+    # Generate top.gen.hjson right before rendering
+    if not args.no_gen_hjson:
+        hjson_dir = Path(args.topcfg).parent
+        genhjson_path = hjson_dir / ("autogen/top_%s.gen.hjson" %
+                                     completecfg["name"])
+        gencmd = (
+            "// util/topgen.py -t hw/top_{topname}/data/top_{topname}.hjson --hjson-only "
+            "-o hw/top_{topname}/\n".format(topname=topname))
+
+        genhjson_path.write_text(genhdr + gencmd +
+                                 hjson.dumps(completecfg, for_json=True))
+
     if not args.no_top or args.top_only:
         tpl_path = Path(args.tpl)
 
@@ -951,7 +985,7 @@
                        stdout=subprocess.DEVNULL,
                        stderr=subprocess.DEVNULL,
                        check=True,
-                       cwd=str(SRCTREE_TOP))
+                       cwd=str(SRCTREE_TOP))  # yapf: disable
 
         # generate chip level xbar TB
         tb_files = ["xbar_env_pkg__params.sv", "tb__xbar_connect.sv"]
diff --git a/util/topgen/intermodule.py b/util/topgen/intermodule.py
index 2390457..b179926 100644
--- a/util/topgen/intermodule.py
+++ b/util/topgen/intermodule.py
@@ -58,6 +58,17 @@
     return result
 
 
+def get_suffixes(ims: OrderedDict) -> (str, str):
+    """Get suffixes of the struct.
+
+    TL-UL struct uses `h2d`, `d2h` suffixes for req, rsp pair.
+    """
+    if ims["package"] == "tlul_pkg" and ims["struct"] == "tl":
+        return ("_h2d", "_d2h")
+
+    return ("_req", "_rsp")
+
+
 def elab_intermodule(topcfg: OrderedDict):
     """Check the connection of inter-module and categorize them
 
@@ -73,7 +84,7 @@
         topcfg["inter_signal"] = OrderedDict()
 
     # Gather the inter_signal_list
-    instances = topcfg["module"] + topcfg["memory"]
+    instances = topcfg["module"] + topcfg["memory"] + topcfg["xbar"]
 
     intermodule_instances = [x for x in instances if "inter_signal_list" in x]
 
@@ -144,17 +155,18 @@
 
         # Add to definition
         if req_struct["type"] == "req_rsp":
+            req_suffix, rsp_suffix = get_suffixes(req_struct)
             # Add two definitions
             definitions.append(
                 OrderedDict([('package', package),
-                             ('struct', req_struct["struct"] + "_req"),
+                             ('struct', req_struct["struct"] + req_suffix),
                              ('signame', sig_name + "_req"),
                              ('width', req_struct["width"]),
                              ('type', req_struct["type"]),
                              ('default', req_struct["default"])]))
             definitions.append(
                 OrderedDict([('package', package),
-                             ('struct', req_struct["struct"] + "_rsp"),
+                             ('struct', req_struct["struct"] + rsp_suffix),
                              ('signame', sig_name + "_rsp"),
                              ('width', req_struct["width"]),
                              ('type', req_struct["type"]),
@@ -218,16 +230,17 @@
             sig["index"] = -1
 
         if sig["type"] == "req_rsp":
+            req_suffix, rsp_suffix = get_suffixes(sig)
             # Add two definitions
             definitions.append(
                 OrderedDict([('package', sig["package"]),
-                             ('struct', sig["struct"] + "_req"),
+                             ('struct', sig["struct"] + req_suffix),
                              ('signame', sig_name + "_req"),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"])]))
             definitions.append(
                 OrderedDict([('package', sig["package"]),
-                             ('struct', sig["struct"] + "_rsp"),
+                             ('struct', sig["struct"] + rsp_suffix),
                              ('signame', sig_name + "_rsp"),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"])]))
@@ -258,9 +271,10 @@
         # 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)
             topcfg["inter_signal"]["external"].append(
                 OrderedDict([('package', sig["package"]),
-                             ('struct', sig["struct"] + "_req"),
+                             ('struct', sig["struct"] + req_suffix),
                              ('signame', sig_name + "_req"),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"]),
@@ -268,7 +282,7 @@
                               'out' if sig['act'] == "req" else 'in')]))
             topcfg["inter_signal"]["external"].append(
                 OrderedDict([('package', sig["package"]),
-                             ('struct', sig["struct"] + "_rsp"),
+                             ('struct', sig["struct"] + rsp_suffix),
                              ('signame', sig_name + "_rsp"),
                              ('width', sig["width"]), ('type', sig["type"]),
                              ('default', sig["default"]),
@@ -628,12 +642,18 @@
             # custom default has been specified
             if obj["default"]:
                 return obj["default"]
+            if obj["package"] == "tlul_pkg" and obj["struct"] == "tl":
+                return "{package}::{struct}_D2H_DEFAULT".format(
+                    package=obj["package"], struct=obj["struct"].upper())
             return "{package}::{struct}_RSP_DEFAULT".format(
                 package=obj["package"], struct=obj["struct"].upper())
         if obj["act"] == "rsp" and suffix == "req":
             # custom default has been specified
             if obj["default"]:
                 return obj["default"]
+            if obj["package"] == "tlul_pkg" and obj["struct"] == "tl":
+                return "{package}::{struct}_H2D_DEFAULT".format(
+                    package=obj["package"], struct=obj["struct"].upper())
             return "{package}::{struct}_REQ_DEFAULT".format(
                 package=obj["package"], struct=obj["struct"].upper())
         if obj["act"] == "rcv" and suffix == "" and obj["struct"] == "logic":
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index f2e8442..f54cb0e 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -8,7 +8,6 @@
 from collections import OrderedDict
 
 from topgen import lib
-from .intermodule import elab_intermodule
 
 
 def amend_ip(top, ip):
@@ -747,9 +746,6 @@
     # Combine the wakeups
     amend_wkup(gencfg)
 
-    # Inter-module signals
-    elab_intermodule(gencfg)
-
     # Combine the interrupt (should be processed prior to xbar)
     amend_interrupt(gencfg)