[topgen/tlgen] Multi-tiered Xbar

The script is to support multi-tiered crossbars on top_earlgrey.

Now any crossbar can have connections to other crossbars. It is allowed
only one connection between two crossbars at this time.

If `xbar: "true"` is defined in the node, the tool does below:

- Recognize it as a crossbar connection
- Gather downstream addresses and calculate the common address range (*)
- If device is a port to another crossbar, the address steering compares
  list of address range not a single `address_from`, `address_to`

Signed-off-by: Eunchan Kim <eunchan@opentitan.org>
diff --git a/hw/ip/tlul/rtl/tlul_fifo_sync.sv b/hw/ip/tlul/rtl/tlul_fifo_sync.sv
index bc772d3..34aced7 100644
--- a/hw/ip/tlul/rtl/tlul_fifo_sync.sv
+++ b/hw/ip/tlul/rtl/tlul_fifo_sync.sv
@@ -72,7 +72,7 @@
                      tl_d_i.d_size  ,
                      tl_d_i.d_source,
                      tl_d_i.d_sink  ,
-                     tl_d_i.d_data  ,
+                     (tl_d_i.d_opcode == tlul_pkg::AccessAckData) ? tl_d_i.d_data : '0 ,
                      tl_d_i.d_user  ,
                      tl_d_i.d_error ,
                      spare_rsp_i}),
diff --git a/hw/top_earlgrey/data/top_earlgrey.sv.tpl b/hw/top_earlgrey/data/top_earlgrey.sv.tpl
index c0f3207..f41c7a1 100644
--- a/hw/top_earlgrey/data/top_earlgrey.sv.tpl
+++ b/hw/top_earlgrey/data/top_earlgrey.sv.tpl
@@ -102,6 +102,22 @@
   tl_d2h_t tl_${m["name"]}_d_d2h;
 % endfor
 
+## Xbar connection
+% for xbar in top["xbar"]:
+<%
+  xbar_devices = [x for x in xbar["nodes"] if x["type"] == "device" and x["xbar"]]
+%>\
+  % for node in xbar_devices:
+  tl_h2d_t tl_${xbar["name"]}_h_h2d;
+  tl_d2h_t tl_${xbar["name"]}_h_d2h;
+  tl_h2d_t tl_${node["name"]}_d_h2d;
+  tl_d2h_t tl_${node["name"]}_d_d2h;
+
+  assign tl_${xbar["name"]}_h_h2d = tl_${node["name"]}_d_h2d;
+  assign tl_${node["name"]}_d_d2h = tl_${xbar["name"]}_h_d2h;
+  % endfor
+% endfor
+
   //reset wires declaration
 % for reset in top['resets']:
   logic ${reset['name']}_rst_n;
@@ -622,8 +638,8 @@
   % endfor
 
     .scanmode_i
-% endfor
   );
+% endfor
 
 % if "pinmux" in top:
   // Pinmux connections
diff --git a/util/tlgen/elaborate.py b/util/tlgen/elaborate.py
index 494eec6..5ae5227 100644
--- a/util/tlgen/elaborate.py
+++ b/util/tlgen/elaborate.py
@@ -166,7 +166,7 @@
             continue
 
         if unode.node_type == NodeType.SOCKET_1N:
-            idx = unode.ds.index(device.us)
+            idx = unode.ds.index(device.us[0])
             unode.dpass = unode.dpass ^ (
                 1 << idx) if no_bypass else unode.dpass
 
diff --git a/util/tlgen/item.py b/util/tlgen/item.py
index 841edfe..1c43fa7 100644
--- a/util/tlgen/item.py
+++ b/util/tlgen/item.py
@@ -11,7 +11,6 @@
     a Node can be a host port, output of async_fifo, port in a socket,
     or a device port.
     """
-
     def __init__(self, us, ds):
         self.us = us
         self.ds = ds
@@ -45,12 +44,12 @@
     resets = []  # Resets  # resets of the node
     # e.g. async_fifo in : clk_core , out : clk_main
 
-
     # If NodeType is Socket out from 1:N then address steering is used
     # But this value is also propagated up to a Host from multiple Devices
     # Device Node should have address_from, address_to
-    address_from = 0  #: int
-    address_to = 0  #: int
+    #address_from = 0  #: int
+    #address_to = 0  #: int
+    addr_range = []
 
     us = []  # Edges  # Number of Ports depends on the NodeType
     # 1 for Host, Device, 2 for Async FIFO, N for Sockets
@@ -72,3 +71,4 @@
         self.resets = [reset]
         self.us = []
         self.ds = []
+        self.addr_range = []
diff --git a/util/tlgen/lib.py b/util/tlgen/lib.py
new file mode 100644
index 0000000..528de26
--- /dev/null
+++ b/util/tlgen/lib.py
@@ -0,0 +1,21 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import math
+import logging as log
+
+
+def is_pow2(v):
+    """Return true if value is power of two
+    """
+    if not isinstance(v, int):
+        log.warning("is_pow2 received non-integer value {}".format(v))
+        return False
+    t = 1
+    while t <= v:
+        if t == v:
+            return True
+        t = t * 2
+
+    return False
diff --git a/util/tlgen/validate.py b/util/tlgen/validate.py
index 11b6f31..9cf2813 100644
--- a/util/tlgen/validate.py
+++ b/util/tlgen/validate.py
@@ -46,13 +46,17 @@
     },
     'optional': {
         'clock': ['s', 'main clock of the port'],
+        'reset': ['s', 'main reset of the port'],
         'base_addr': ['d', 'Base address of the device'\
                       ' It is required for the device'],
         'size_byte': ['d', 'Memory space of the device'\
                       ' It is required for the device'],
         'pipeline': ['pb', 'If true, pipeline is added in front of the port'],
         'pipeline_byp': ['pb', 'Pipeline bypass. If true, '\
-                         'request/response are not latched']
+                         'request/response are not latched'],
+        'inst_type': ['s', 'Instance type'],
+        'xbar': ['pb', 'If true, the node is connected to another Xbar'],
+        'xbar_addr': ['l', 'Xbar address. List type']
     },
     'added': {}
 }
@@ -65,12 +69,14 @@
         'name': ['s', 'Name of the crossbar'],
         'clock': ['s', 'Main clock. Internal components use this clock.'\
                   ' If not specified, it is assumed to be in main clock domain'],
+        'reset': ['s', 'Main reset'],
         'connections':
         ['g', "List of edge. Key is host, entry in value list is device"],
+        'clock_connections': ['g', 'list of clocks'],
         'nodes': ['lg', node]
     },
     'optional': {
-        'type': ['s', 'Indicate Hjson type. "xbar" always if exist']
+        'type': ['s', 'Indicate Hjson type. "xbar" always if exist'],
     },
     'added': {
         'reset_connections': ['g', "Generated by topgen. Key is the reset signal inside IP"\
@@ -145,6 +151,9 @@
             if err:
                 error += 1
 
+        elif checker[0] == 'l':
+            if not isinstance(obj[k], list):
+                error += 1
         else:
             log.error(prefix_name +
                       " is not supported in this configuration format")
@@ -164,6 +173,7 @@
     elif t == "socket_m1":
         return NodeType.SOCKET_M1
 
+    log.error("Cannot process type {}".format(t))
     raise
 
 
@@ -227,13 +237,14 @@
                     clock=clock,
                     reset=reset)
 
-        if node.node_type == NodeType.DEVICE:
+        if node.node_type == NodeType.DEVICE and nodeobj["xbar"] == False:
             # Add address obj["base_addr"], obj["size"])
-            node.address_from = int(nodeobj["base_addr"], 0)
+            node.xbar = False
+            address_from = int(nodeobj["base_addr"], 0)
             size = int(nodeobj["size_byte"], 0)
-            node.address_to = node.address_from + size - 1
+            address_to = address_from + size - 1
 
-            addr = (node.address_from, node.address_to)
+            addr = (address_from, address_to)
 
             if checkAddressOverlap(addr, addr_ranges):
                 log.error(
@@ -242,6 +253,27 @@
                 raise SystemExit("Address overlapping error occurred")
 
             addr_ranges.append(addr)
+            node.addr_range = [addr]
+
+        if node.node_type == NodeType.DEVICE and nodeobj["xbar"] == True:
+            node.xbar = True
+            node.addr_range = []
+
+            for addr in nodeobj["xbar_addr"]:
+                address_from = int(addr["base_addr"], 0)
+                size = int(addr["size_byte"], 0)
+                address_to = address_from + size - 1
+
+                addr_entry = (address_from, address_to)
+
+                if checkAddressOverlap(addr_entry, addr_ranges):
+                    log.error(
+                        "Address is overlapping. Check the config. Addr(0x%x - 0x%x)"
+                        % (addr_entry[0], addr_entry[1]))
+                    raise SystemExit("Address overlapping error occurred")
+
+                addr_ranges.append(addr_entry)
+                node.addr_range.append(addr_entry)
 
         if node.node_type in [NodeType.DEVICE, NodeType.HOST
                               ] and "pipeline" in nodeobj:
diff --git a/util/tlgen/xbar.pkg.sv.tpl b/util/tlgen/xbar.pkg.sv.tpl
index a44d48f..7df61cc 100644
--- a/util/tlgen/xbar.pkg.sv.tpl
+++ b/util/tlgen/xbar.pkg.sv.tpl
@@ -11,13 +11,30 @@
 
 % for device in xbar.devices:
   ## Address
-  localparam logic [31:0] ADDR_SPACE_${device.name.upper().ljust(name_len)} = 32'h ${"%08x" % device.address_from};
+  % if device.xbar == False:
+  localparam logic [31:0] ADDR_SPACE_${device.name.upper().ljust(name_len)} = 32'h ${"%08x" % device.addr_range[0][0]};
+  % else:
+  ## Xbar device
+  localparam logic [${len(device.addr_range)-1}:0][31:0] ADDR_SPACE_${device.name.upper().ljust(name_len)} = {
+    % for addr in device.addr_range:
+    32'h ${"%08x" % addr[0]}${"," if not loop.last else ""}
+    % endfor
+  };
+  % endif
 % endfor
 
 % for device in xbar.devices:
   ## Mask
-  localparam logic [31:0] ADDR_MASK_${device.name.upper().ljust(name_len)} = 32'h ${"%08x" % (device.address_to -
-  device.address_from)};
+  % if device.xbar == False:
+  localparam logic [31:0] ADDR_MASK_${device.name.upper().ljust(name_len)} = 32'h ${"%08x" % (device.addr_range[0][1] - device.addr_range[0][0])};
+  % else:
+  ## Xbar
+  localparam logic [${len(device.addr_range)-1}:0][31:0] ADDR_MASK_${device.name.upper().ljust(name_len)} = {
+    % for addr in device.addr_range:
+    32'h ${"%08x" % (addr[1] - addr[0])}${"," if not loop.last else ""}
+    % endfor
+  };
+  % endif
 % endfor
 
   localparam int N_HOST   = ${len(xbar.hosts)};
diff --git a/util/tlgen/xbar.rtl.sv.tpl b/util/tlgen/xbar.rtl.sv.tpl
index 44ec805..a5b59a8 100644
--- a/util/tlgen/xbar.rtl.sv.tpl
+++ b/util/tlgen/xbar.rtl.sv.tpl
@@ -6,6 +6,9 @@
 // all reset signals should be generated from one reset signal to not make any deadlock
 //
 // Interconnect
+<%
+  import tlgen.lib as lib
+%>\
 % for host in xbar.hosts:
 ${xbar.repr_tree(host, 0)}
 % endfor
@@ -157,15 +160,34 @@
   leaf = xbar.get_leaf_from_s1n(block, loop.index);
   name_space = "ADDR_SPACE_" + leaf.name.upper();
   name_mask  = "ADDR_MASK_" + leaf.name.upper();
+  prefix = "if (" if loop.first else "end else if ("
 %>\
-  % if loop.first:
-    if ((${addr_sig} & ~(${name_mask})) == ${name_space}) begin
-  % else:
-    end else if ((${addr_sig} & ~(${name_mask})) == ${name_space}) begin
-  % endif
+  % if len(leaf.addr_range) == 1:
+      % if lib.is_pow2((leaf.addr_range[0][1]-leaf.addr_range[0][0])+1):
+    ${prefix}(${addr_sig} & ~(${name_mask})) == ${name_space}) begin
       dev_sel_${block.name} = ${"%d'd%d" % (sel_len, loop.index)};
-  % if loop.last:
-    end
+      % else:
+      ((${addr_sig} <= (${name_mask} + ${name_space})) &&
+       (${addr_sig} >= ${name_space}))${" ||" if not loop.last else ""}
+      % endif
+    ${"end" if loop.last else ""}
+  % else:
+    ## Xbar device port
+<%
+  num_range = len(leaf.addr_range)
+%>\
+    ${prefix}
+    % for i in range(num_range):
+      % if lib.is_pow2(leaf.addr_range[i][1]-leaf.addr_range[0][0]+1):
+      ((${addr_sig} & ~(${name_mask}[${i}])) == ${name_space}[${i}])${" ||" if not loop.last else ""}
+      % else:
+      ((${addr_sig} <= (${name_mask}[${i}] + ${name_space}[${i}])) &&
+       (${addr_sig} >= ${name_space}[${i}]))${" ||" if not loop.last else ""}
+      % endif
+    % endfor
+    ) begin
+      dev_sel_${block.name} = ${"%d'd%d" % (sel_len, loop.index)};
+    ${"end" if loop.last else ""}
   % endif
 % endfor
   end
diff --git a/util/topgen.py b/util/topgen.py
index 8e2dfd0..41507d2 100755
--- a/util/topgen.py
+++ b/util/topgen.py
@@ -42,12 +42,12 @@
 
 
 def generate_xbars(top, out_path):
-    xbar_path = out_path / 'ip/xbar/data/autogen'
-    xbar_path.mkdir(parents=True, exist_ok=True)
     gencmd = ("// util/topgen.py -t hw/top_earlgrey/data/top_earlgrey.hjson "
               "-o hw/top_earlgrey/\n\n")
 
     for obj in top["xbar"]:
+        xbar_path = out_path / 'ip/xbar_{}/data/autogen'.format(obj["name"])
+        xbar_path.mkdir(parents=True, exist_ok=True)
         xbar = tlgen.validate(obj)
 
         # Generate output of crossbar with complete fields
@@ -59,13 +59,13 @@
             log.error("Elaboration failed." + repr(xbar))
 
         try:
-            out_rtl, out_pkg, out_bind = tlgen.generate(xbar)
+            out_rtl, out_pkg, out_core = tlgen.generate(xbar)
         except:
             log.error(exceptions.text_error_template().render())
 
-        rtl_path = out_path / 'ip/xbar/rtl/autogen'
+        rtl_path = out_path / 'ip/xbar_{}/rtl/autogen'.format(obj["name"])
         rtl_path.mkdir(parents=True, exist_ok=True)
-        dv_path = out_path / 'ip/xbar/dv/autogen'
+        dv_path = out_path / 'ip/xbar_{}/dv/autogen'.format(obj["name"])
         dv_path.mkdir(parents=True, exist_ok=True)
 
         rtl_filename = "xbar_%s.sv" % (xbar.name)
@@ -78,19 +78,20 @@
         with pkg_filepath.open(mode='w', encoding='UTF-8') as fout:
             fout.write(out_pkg)
 
-        bind_filename = "xbar_%s_bind.sv" % (xbar.name)
-        bind_filepath = dv_path / bind_filename
-        with bind_filepath.open(mode='w', encoding='UTF-8') as fout:
-            fout.write(out_bind)
+        core_filename = "xbar_%s.core" % (xbar.name)
+        core_filepath = rtl_path / core_filename
+        with core_filepath.open(mode='w', encoding='UTF-8') as fout:
+            fout.write(out_core)
+
 
 def generate_alert_handler(top, out_path):
     # default values
-    esc_cnt_dw=32
-    accu_cnt_dw=16
-    lfsr_seed=2**31-1
-    async_on="'0"
+    esc_cnt_dw = 32
+    accu_cnt_dw = 16
+    lfsr_seed = 2**31 - 1
+    async_on = "'0"
     # leave this constant
-    n_classes=4
+    n_classes = 4
 
     # check if there are any params to be passed through reggen and placed into
     # the generated package
@@ -118,7 +119,7 @@
         # set number of alerts to 1 such that the config is still valid
         # that input will be tied off
         n_alerts = 1
-        log.warning("no alerts are defined in the system");
+        log.warning("no alerts are defined in the system")
     else:
         async_on = ""
         for alert in top['alert']:
@@ -132,7 +133,6 @@
     log.info("LfsrSeed  = %d" % lfsr_seed)
     log.info("AsyncOn   = %s" % async_on)
 
-
     # Define target path
     rtl_path = out_path / 'ip/alert_handler/rtl/autogen'
     rtl_path.mkdir(parents=True, exist_ok=True)
@@ -179,8 +179,6 @@
     gen_rtl.gen_rtl(hjson_obj, str(rtl_path))
 
 
-
-
 def generate_plic(top, out_path):
     # Count number of interrupts
     src = sum([x["width"] if "width" in x else 1 for x in top["interrupt"]])
@@ -439,7 +437,8 @@
             "'no' series options cannot be used with 'only' series options")
         raise SystemExit(sys.exc_info()[1])
 
-    if not (args.hjson_only or args.plic_only or args.alert_handler_only or args.tpl):
+    if not (args.hjson_only or args.plic_only or args.alert_handler_only or
+            args.tpl):
         log.error(
             "Template file can be omitted only if '--hjson-only' is true")
         raise SystemExit(sys.exc_info()[1])
diff --git a/util/topgen/merge.py b/util/topgen/merge.py
index 6b7b3e0..bc3ee71 100644
--- a/util/topgen/merge.py
+++ b/util/topgen/merge.py
@@ -10,8 +10,6 @@
 import hjson
 
 
-
-
 def amend_ip(top, ip):
     """ Amend additional information into top module
 
@@ -124,8 +122,25 @@
 }
 
 
-def xbar_addhost(xbar, host):
-    # TODO: check if host is another crossbar
+def is_xbar(top, name):
+    """Check if the given name is crossbar
+    """
+    xbars = list(filter(lambda node: node["name"] == name, top["xbar"]))
+    if len(xbars) == 0:
+        return False, None
+
+    if len(xbars) > 1:
+        log.error("Matching crossbar {} is more than one.".format(name))
+        raise SystemExit()
+
+    return True, xbars[0]
+
+
+def xbar_addhost(top, xbar, host):
+    """Add host nodes information
+
+    - xbar: bool, true if the host port is from another Xbar
+    """
     # Check and fetch host if exists in nodes
     obj = list(filter(lambda node: node["name"] == host, xbar["nodes"]))
     if len(obj) == 0:
@@ -144,19 +159,26 @@
             "pipeline_byp": "true"
         }
         topxbar["nodes"].append(obj)
-    else:
-        if 'clock' not in obj[0]:
-            obj[0]["clock"] = xbar['clock']
+        return
 
-        if 'reset' not in obj[0]:
-            obj[0]["reset"] = xbar["reset"]
+    xbar_bool, xbar_h = is_xbar(top, host)
+    if xbar_bool:
+        # TODO: Handle Host XBAR port (nothing)
+        log.warning("host {} is a crossbar.".format(host))
 
-        obj[0]["inst_type"] = predefined_modules[
-            host] if host in predefined_modules else ""
-        obj[0]["pipeline"] = obj[0]["pipeline"] if "pipeline" in obj[
-            0] else "true"
-        obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0][
-            "pipeline"] == "true" and "pipeline_byp" in obj[0] else "true"
+    obj[0]["xbar"] = xbar_bool
+
+    if 'clock' not in obj[0]:
+        obj[0]["clock"] = xbar['clock']
+
+    if 'reset' not in obj[0]:
+        obj[0]["reset"] = xbar["reset"]
+
+    obj[0]["inst_type"] = predefined_modules[
+        host] if host in predefined_modules else ""
+    obj[0]["pipeline"] = obj[0]["pipeline"] if "pipeline" in obj[0] else "true"
+    obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0][
+        "pipeline"] == "true" and "pipeline_byp" in obj[0] else "true"
 
 
 def process_pipeline_var(node):
@@ -177,6 +199,7 @@
     - inst_type: comes from module or memory if exist.
     - base_addr: comes from module or memory, or assume rv_plic?
     - size_byte: comes from module or memory
+    - xbar: bool, true if the device port is another xbar
     """
     deviceobj = list(
         filter(lambda node: node["name"] == device,
@@ -185,18 +208,29 @@
 
     xbar_list = [x["name"] for x in top["xbar"] if x["name"] != xbar["name"]]
 
+    # case 1: another xbar --> check in xbar list
+    if device in xbar_list and len(nodeobj) == 0:
+        log.error(
+            "Another crossbar %s needs to be specified in the 'nodes' list" %
+            device)
+        return
+
     if len(deviceobj) == 0:
         # doesn't exist,
-        # case 1: another xbar --> check in xbar list
-        if device in xbar_list and len(nodeobj) == 0:
-            log.error(
-                "Another crossbar %s needs to be specified in the 'nodes' list"
-                % device)
+
+        # case 1: Crossbar handling
+        if device in xbar_list:
+            log.warning(
+                "device {} in Xbar {} is connected to another Xbar".format(
+                    device, xbar["name"]))
+            assert len(nodeobj) == 1
+            nodeobj[0]["xbar"] = True
+            process_pipeline_var(nodeobj[0])
             return
 
         # case 2: predefined_modules (debug_mem, rv_plic)
         # TODO: Find configurable solution not from predefined but from object?
-        elif device in predefined_modules:
+        if device in predefined_modules:
             if device == "debug_mem":
                 if len(nodeobj) == 0:
                     # Add new debug_mem
@@ -208,6 +242,7 @@
                         "inst_type": predefined_modules["debug_mem"],
                         "base_addr": top["debug_mem_base_addr"],
                         "size_byte": "0x1000",
+                        "xbar": False,
                         "pipeline" : "true",
                         "pipeline_byp" : "true"
                     }) # yapf: disable
@@ -217,12 +252,14 @@
                     node["inst_type"] = predefined_modules["debug_mem"]
                     node["base_addr"] = top["debug_mem_base_addr"]
                     node["size_byte"] = "0x1000"
+                    node["xbar"] = False
                     process_pipeline_var(node)
             else:
                 log.error("device %s shouldn't be host type" % device)
                 return
         # case 3: not defined
         else:
+            # Crossbar check
             log.error(
                 "device %s doesn't exist in 'module', 'memory', or predefined"
                 % device)
@@ -240,7 +277,8 @@
             "base_addr" : deviceobj[0]["base_addr"],
             "size_byte": deviceobj[0]["size"],
             "pipeline" : "true",
-            "pipeline_byp" : "true"
+            "pipeline_byp" : "true",
+            "xbar" : True if device in xbar_list else False
         }) # yapf: disable
 
     else:
@@ -249,6 +287,7 @@
         node["inst_type"] = deviceobj[0]["type"]
         node["base_addr"] = deviceobj[0]["base_addr"]
         node["size_byte"] = deviceobj[0]["size"]
+        node["xbar"] = True if device in xbar_list else False
         process_pipeline_var(node)
 
 
@@ -286,7 +325,7 @@
     device_nodes = set()
     for host, devices in xbar["connections"].items():
         # add host first
-        xbar_addhost(topxbar, host)
+        xbar_addhost(top, topxbar, host)
 
         # add device if doesn't exist
         device_nodes.update(devices)
@@ -296,6 +335,146 @@
         xbar_adddevice(top, topxbar, device)
 
 
+def xbar_cross(xbar, xbars):
+    """Check if cyclic dependency among xbars
+
+    And gather the address range for device port (to another Xbar)
+
+    @param node_name if not "", the function only search downstream
+                     devices starting from the node_name
+    @param visited   The nodes it visited to reach this port. If any
+                     downstream port from node_name in visited, it means
+                     circular path exists. It should be fatal error.
+    """
+    # Step 1: Visit devices (gather the address range)
+    log.info("Processing circular path check for {}".format(xbar["name"]))
+    addr = []
+    for node in [
+            x for x in xbar["nodes"]
+            if x["type"] == "device" and "xbar" in x and x["xbar"] == False
+    ]:
+        # TODO: Can this be simplified? (Merging contiguous into one)
+        addr.append((node["base_addr"], node["size_byte"]))
+
+    # Step 2: visit xbar device ports
+    xbar_nodes = [
+        x for x in xbar["nodes"]
+        if x["type"] == "device" and "xbar" in x and x["xbar"] == True
+    ]
+
+    # Now call function to get the device range
+    # the node["name"] is used to find the host_xbar and its connection. The
+    # assumption here is that there's only one connection from crossbar A to
+    # crossbar B.
+    #
+    # device_xbar is the crossbar has a device port with name as node["name"].
+    # host_xbar is the crossbar has a host port with name as node["name"].
+    for node in xbar_nodes:
+        xbar_addr = xbar_cross_node(node["name"], xbar, xbars, visited=[])
+        node["xbar_addr"] = xbar_addr
+
+
+def xbar_cross_node(node_name, device_xbar, xbars, visited=[]):
+    # 1. Get the connected xbar
+    host_xbars = [x for x in xbars if x["name"] == node_name]
+    assert len(host_xbars) == 1
+    host_xbar = host_xbars[0]
+
+    log.info("Processing node {} in Xbar {}.".format(node_name,
+                                                     device_xbar["name"]))
+    result = []  # [(base_addr, size), .. ]
+    # Sweep the devices using connections and gather the address.
+    # If the device is another xbar, call recursive
+    visited.append(host_xbar["name"])
+    devices = host_xbar["connections"][device_xbar["name"]]
+
+    for node in host_xbar["nodes"]:
+        if not node["name"] in devices:
+            continue
+        if "xbar" in node and node["xbar"] == True:
+            if not "xbar_addr" in node:
+                # Deeper dive into another crossbar
+                xbar_addr = xbar_cross_node(node["name"], host_xbar, xbars,
+                                            visited)
+                node["xbar_addr"] = xbar_addr
+
+            result.append(node["xbar_addr"])
+            continue
+
+        # Normal device
+        result.append({
+            'base_addr': node["base_addr"],
+            'size_byte': node["size_byte"]
+        })
+
+    visited.pop()
+
+    # TODO: simplify the result? if contiguous, combine into one?
+    return simplify_addr(result, device_xbar)
+
+
+def simplify_addr(addrs, xbar):
+    """If any contiguous regions exist, concatenate them
+
+    For instance, 0x1000 ~ 0x1FFF , 0x2000~ 0x2FFF ==> 0x1000 ~ 0x2FFF
+
+    @param addrs List of Dict[Addr] : {'base_addr':,'size_byte':}
+    """
+
+    # Sort based on the base addr
+    newlist = sorted(addrs, key=lambda k: int(k['base_addr'], 0))
+    # check if overlap or contiguous
+    result = []
+    for e in newlist:
+        if len(result) == 0:
+            result.append(e)
+            continue
+        # if contiguous
+        if int(e["base_addr"], 0) == int(result[-1]["base_addr"], 0) + int(
+                result[-1]["size_byte"], 0):
+            # update previous entry size
+            result[-1]["size_byte"] = "0x{:x}".format(
+                int(result[-1]["size_byte"], 0) + int(e["size_byte"], 0))
+            continue
+
+        # TODO: If no other device in current xbar between the gap?
+        if no_device_in_range(xbar, result[-1], e):
+            result[-1]["size_byte"] = "0x{:x}".format(
+                int(e["base_addr"], 0) + int(e["size_byte"], 0) -
+                int(result[-1]["base_addr"], 0))
+            continue
+
+        # If overlapping (Should it be consider? TlGen will catch it)
+
+        # Normal case
+        result.append(e)
+
+    # return result
+    return result
+
+
+def no_device_in_range(xbar, f, t):
+    """Check if other devices doesn't overlap with the from <= x < to
+    """
+    from_addr = int(f["base_addr"], 0) + int(f["size_byte"], 0)
+    to_addr = int(t["base_addr"], 0)
+
+    for node in [x for x in xbar["nodes"] if x["type"] == "device"]:
+        if not "base_addr" in node:
+            # Xbar?
+            log.info("Xbar type node cannot be compared in this version.",
+                     "Please use in caution")
+            continue
+        b_addr = int(node["base_addr"], 0)
+        e_addr = b_addr + int(node["size_byte"], 0)
+
+        if e_addr <= from_addr or b_addr >= to_addr:
+            # No overlap
+            continue
+        return False
+    return True
+
+
 def amend_interrupt(top):
     """Check interrupt_module if exists, or just use all modules
     """
@@ -317,6 +496,7 @@
             map(partial(add_prefix_to_signal, prefix=m.lower()),
                 ip[0]["interrupt_list"]))
 
+
 def amend_alert(top):
     """Check interrupt_module if exists, or just use all modules
     """
@@ -329,8 +509,8 @@
     for m in top["alert_module"]:
         ip = list(filter(lambda module: module["name"] == m, top["module"]))
         if len(ip) == 0:
-            log.warning(
-                "Cannot find IP %s which is used in the alert_module" % m)
+            log.warning("Cannot find IP %s which is used in the alert_module" %
+                        m)
             continue
 
         log.info("Adding alert from module %s" % ip[0]["name"])
@@ -338,6 +518,7 @@
             map(partial(add_prefix_to_signal, prefix=m.lower()),
                 ip[0]["alert_list"]))
 
+
 def amend_pinmux_io(top):
     """ Check dio_modules/ mio_modules. If not exists, add all modules to mio
     """
@@ -460,7 +641,6 @@
 def merge_top(topcfg, ipobjs, xbarobjs):
     gencfg = deepcopy(topcfg)
 
-
     # Combine ip cfg into topcfg
     for ip in ipobjs:
         amend_ip(gencfg, ip)
@@ -479,6 +659,10 @@
     for xbar in xbarobjs:
         amend_xbar(gencfg, xbar)
 
+    # 2nd phase of xbar (gathering the devices address range)
+    for xbar in gencfg["xbar"]:
+        xbar_cross(xbar, gencfg["xbar"])
+
     # remove unwanted fields 'debug_mem_base_addr'
     gencfg.pop('debug_mem_base_addr', None)