text: # Copyright lowRISC contributors.
  # Licensed under the Apache License, Version 2.0, see LICENSE for details.
  # SPDX-License-Identifier: Apache-2.0
  
  import logging as log
  import re
  from collections import OrderedDict
  from enum import Enum
  from typing import Dict, List, Tuple
  
  from reggen.validate import check_int
  from topgen import lib
  
  IM_TYPES = ['uni', 'req_rsp']
  IM_ACTS = ['req', 'rsp', 'rcv']
  IM_VALID_TYPEACT = {'uni': ['req', 'rcv'], 'req_rsp': ['req', 'rsp']}
  IM_CONN_TYPE = ['1-to-1', '1-to-N', 'broadcast']
  
  
  class ImType(Enum):
      Uni = 1
      ReqRsp = 2
  
  
  class ImAct(Enum):
      Req = 1
      Rsp = 2
      Rcv = 3
  
  
  class ImConn(Enum):
      OneToOne = 1  # req <-> {rsp,rcv} with same width
      OneToN = 2  # req width N <-> N x {rsp,rcv}s width 1
      Broadcast = 3  # req width 1 <-> N x rcvs width 1
  
  
  def intersignal_format(req: Dict) -> str:
      """Determine the signal format of the inter-module connections
  
      @param[req] Request struct. It has instance name, package format
                  and etc.
      """
  
      # TODO: Handle array signal
      result = "{req}_{struct}".format(req=req["inst_name"], struct=req["name"])
  
      # check signal length if exceeds 100
  
      # 7 : space + .
      # 3 : _{i|o}(
      # 6 : _{req|rsp}),
      req_length = 7 + len(req["name"]) + 3 + len(result) + 6
  
      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")
      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 add_intermodule_connection(obj: OrderedDict, req_m: str, req_s: str,
                                 rsp_m: str, rsp_s: str):
      """Add if doesn't exist the connection
  
      Add a connection into obj['inter_module']['connect'] dictionary if doesn't exist.
  
      Parameters:
          obj:   Top dictionary object
          req_m: Requester module name
          req_s: Requester signal name
          rsp_m: Responder module name
          rsp_s: Responder signal name
  
      Returns:
          No return type for this function
      """
      req_key = "{}.{}".format(req_m, req_s)
      rsp_key = "{}.{}".format(rsp_m, rsp_s)
  
      connect = obj["inter_module"]["connect"]
      if req_key in connect:
          # check if rsp has data
          if rsp_key in connect[req_key]:
              return
          req_key.append(rsp_key)
          return
  
      # req_key is not in connect:
      # check if rsp_key
      if rsp_key in connect:
          # check if rsp has data
          if req_key in connect[rsp_key]:
              return
          rsp_key.append(req_key)
          return
  
      # Add new key and connect
      connect[req_key] = [rsp_key]
  
  
  def autoconnect_xbar(topcfg: OrderedDict, xbar: OrderedDict):
      ports = [x for x in xbar["nodes"] if x["type"] in ["host", "device"]]
      for port in ports:
          if port["xbar"]:
              if port["type"] == "host":
                  # Skip as device will add connection
                  continue
  
              # Device port adds signal
              add_intermodule_connection(obj=topcfg,
                                         req_m=xbar["name"],
                                         req_s="tl_%s" % port["name"],
                                         rsp_m=port["name"],
                                         rsp_s="tl_%s" % xbar["name"])
              continue  # xbar port case
  
          # check if name exists in ["module", "memory"]
          ips = [
              x for x in topcfg["module"] + topcfg["memory"]
              if x["name"] == port["name"]
          ]
  
          assert len(ips) <= 1
  
          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"] + 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
  
          ip = ips[0]
  
          # get the port name
          def get_signame(ip: OrderedDict, package: str, struct: str,
                          act: str) -> OrderedDict:
              ims = [
                  x for x in ip["inter_signal_list"]
                  if (x['package'] == package if 'package' in x else False) and
                  x['struct'] == struct and x['act'] == act
              ]
              return ims
  
          if port["type"] == "device":
              ims = get_signame(ip, "tlul_pkg", "tl", "rsp")
          else:  # host type
              ims = get_signame(ip, "tlul_pkg", "tl", "req")
  
          assert len(ims) == 1
          sig_name = ims[0]["name"]
  
          add_intermodule_connection(obj=topcfg,
                                     req_m=port["name"],
                                     req_s=sig_name,
                                     rsp_m=xbar["name"],
                                     rsp_s="tl_%s" % port["name"])
          continue  # len() == 1
  
  
  def autoconnect(topcfg: OrderedDict):
      """Matching the connection based on the naming rule
      between {memory, module} <-> Xbar.
      """
  
      # Add xbar connection to the modules, memories
      for xbar in topcfg["xbar"]:
          autoconnect_xbar(topcfg, xbar)
  
  
  def elab_intermodule(topcfg: OrderedDict):
      """Check the connection of inter-module and categorize them
  
      In the top template, it uses updated inter_module fields to create
      connections between the modules (incl. memories). This function is to
      create and check the validity of the connections `inter_module` using IPs'
      `inter_signal_list`.
      """
  
      list_of_intersignals = []
  
      if "inter_signal" not in topcfg:
          topcfg["inter_signal"] = OrderedDict()
  
      # Gather the inter_signal_list
      instances = topcfg["module"] + topcfg["memory"] + topcfg["xbar"] + \
          topcfg["host"] + topcfg["port"]
  
      intermodule_instances = [x for x in instances if "inter_signal_list" in x]
  
      for x in intermodule_instances:
          for sig in x["inter_signal_list"]:
              # Add instance name to the entry and add to list_of_intersignals
              sig["inst_name"] = x["name"]
              list_of_intersignals.append(sig)
  
      # Add field to the topcfg
      topcfg["inter_signal"]["signals"] = list_of_intersignals
  
      # 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 == 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: {
      #    'connect': {
      #      '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
  
      for req, rsps in topcfg["inter_module"]["connect"].items():
          log.info("{req} --> {rsps}".format(req=req, rsps=rsps))
  
          # Split index
          req_module, req_signal, req_index = filter_index(req)
  
          # get the module signal
          req_struct = find_intermodule_signal(list_of_intersignals, req_module,
                                               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
          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_suffix),
                               ('signame', sig_name + "_req"),
                               ('width', req_struct["width"]),
                               ('type', req_struct["type"]),
                               ('end_idx', req_struct["end_idx"]),
                               ('act', req_struct["act"]),
                               ('suffix', "req"),
                               ('default', req_struct["default"])]))
              definitions.append(
                  OrderedDict([('package', package),
                               ('struct', req_struct["struct"] + rsp_suffix),
                               ('signame', sig_name + "_rsp"),
                               ('width', req_struct["width"]),
                               ('type', req_struct["type"]),
                               ('end_idx', req_struct["end_idx"]),
                               ('act', req_struct["act"]),
                               ('suffix', "rsp"),
                               ('default', req_struct["default"])]))
          else:
              # unidirection
              definitions.append(
                  OrderedDict([('package', package),
                               ('struct', req_struct["struct"]),
                               ('signame', sig_name),
                               ('width', req_struct["width"]),
                               ('type', req_struct["type"]),
                               ('end_idx', req_struct["end_idx"]),
                               ('act', req_struct["act"]),
                               ('suffix', ""),
                               ('default', req_struct["default"])]))
  
          req_struct["index"] = -1
  
          for i, rsp in enumerate(rsps):
              # Split index
              rsp_module, rsp_signal, rsp_index = filter_index(rsp)
  
              rsp_struct = find_intermodule_signal(list_of_intersignals,
                                                   rsp_module, rsp_signal)
  
              # determine the signal name
  
              rsp_struct["top_signame"] = sig_name
              if req_struct["type"] == "uni" and req_struct[
                      "top_type"] == "broadcast":
                  rsp_struct["index"] = -1
              elif rsp_struct["width"] == req_struct["width"] and len(rsps) == 1:
                  rsp_struct["index"] = -1
              else:
                  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"] == "logic":
                  assert req_struct[
                      "type"] != "req_rsp", "logic signal cannot have req_rsp type"
  
              if rsp_len != 1:
                  log.warning("{req}[{i}] -> {rsp}".format(req=req, i=i,
                                                           rsp=rsp))
              else:
                  log.warning("{req} -> {rsp}".format(req=req, rsp=rsp))
  
              # increase Unique ID
              uid += 1
  
      # TODO: Check unconnected port
      if "top" not in topcfg["inter_module"]:
          topcfg["inter_module"]["top"] = []
  
      for s in topcfg["inter_module"]["top"]:
          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)
          sig["top_signame"] = sig_name
          if "index" not in sig:
              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_suffix),
                               ('signame', sig_name + "_req"),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('end_idx', -1),
                               ('default', sig["default"])]))
              definitions.append(
                  OrderedDict([('package', sig["package"]),
                               ('struct', sig["struct"] + rsp_suffix),
                               ('signame', sig_name + "_rsp"),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('end_idx', -1),
                               ('default', sig["default"])]))
          else:  # if sig["type"] == "uni":
              definitions.append(
                  OrderedDict([('package', sig["package"]),
                               ('struct', sig["struct"]), ('signame', sig_name),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('end_idx', -1),
                               ('default', sig["default"])]))
  
      topcfg["inter_module"].setdefault('external', [])
      topcfg["inter_signal"].setdefault('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)
  
          # To make netname `_o` or `_i`
          sig['external'] = True
  
          sig_name = port if port != "" else intersignal_format(sig)
  
          # if top signame already defined, then a previous connection category
          # is already connected to external pin.  Sig name is only used for
          # port definition
          if "top_signame" not in sig:
              sig["top_signame"] = sig_name
  
          if "index" not in sig:
              sig["index"] = -1
  
          # Add the port definition to top external ports
          index = sig["index"]
          netname = sig["top_signame"]
          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" + req_sigsuffix),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('default', sig["default"]),
                               ('direction',
                                'out' if sig['act'] == "req" else 'in'),
                               ('index', index),
                               ('netname', netname + req_suffix)]))
              topcfg["inter_signal"]["external"].append(
                  OrderedDict([('package', sig["package"]),
                               ('struct', sig["struct"] + rsp_suffix),
                               ('signame', sig_name + "_rsp" + rsp_sigsuffix),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('default', sig["default"]),
                               ('direction',
                                'in' if sig['act'] == "req" else 'out'),
                               ('index', index),
                               ('netname', netname + rsp_suffix)]))
          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 + sigsuffix),
                               ('width', sig["width"]), ('type', sig["type"]),
                               ('default', sig["default"]),
                               ('direction',
                                'out' if sig['act'] == "req" else 'in'),
                               ('index', index),
                               ('netname', netname)]))
  
      for sig in topcfg["inter_signal"]["signals"]:
          # Check if it exist in definitions
          if "top_signame" in sig:
              continue
  
          # Set index to -1
          sig["index"] = -1
  
          # TODO: Handle the unconnected port rule
  
      if "definitions" not in topcfg["inter_signal"]:
          topcfg["inter_signal"]["definitions"] = definitions
  
  
  def filter_index(signame: str) -> Tuple[str, str, int]:
      """If the signal has array indicator `[N]` then split and return name and
      array index. If not, array index is -1.
  
      param signame module.sig{[N]}
  
      result (module_name, signal_name, array_index)
      """
      m = re.match(r'(\w+)\.(\w+)(\[(\d+)\])*', signame)
  
      if not m:
          # Cannot match the pattern
          return "", "", -1
  
      if m.group(3):
          # array index is not None
          return m.group(1), m.group(2), m.group(4)
  
      return m.group(1), m.group(2), -1
  
  
  def find_intermodule_signal(sig_list, m_name, s_name) -> Dict:
      """Return the intermodule signal structure
      """
  
      filtered = [
          x for x in sig_list if x["name"] == s_name and x["inst_name"] == m_name
      ]
  
      if len(filtered) == 1:
          return filtered[0]
  
      log.error("Found {num} entry/entries for {m_name}.{s_name}:".format(
          num=len(filtered), m_name=m_name, s_name=s_name))
      return None
  
  
  # Validation
  def check_intermodule_field(obj: OrderedDict, prefix: str = "") -> int:
      error = 0
  
      # type check
      if obj["type"] not in IM_TYPES:
          log.error("{prefix} Inter_signal {name} "
                    "type {type} is incorrect.".format(prefix=prefix,
                                                       name=obj["name"],
                                                       type=obj["type"]))
          error += 1
  
      if obj["act"] not in IM_ACTS:
          log.error("{prefix} Inter_signal {name} "
                    "act {act} is incorrect.".format(prefix=prefix,
                                                     name=obj["name"],
                                                     act=obj["act"]))
          error += 1
  
      # Check if type and act are matched
      if error == 0:
          if obj["act"] not in IM_VALID_TYPEACT[obj['type']]:
              log.error("{type} and {act} of {name} are not a valid pair."
                        "".format(type=obj['type'],
                                  act=obj['act'],
                                  name=obj['name']))
              error += 1
      # Check 'width' field
      width = 1
      if "width" not in obj:
          obj["width"] = 1
      elif not isinstance(obj["width"], int):
          width, err = check_int(obj["width"], obj["name"])
          if err:
              log.error("{prefix} Inter-module {inst}.{sig} 'width' "
                        "should be int type.".format(prefix=prefix,
                                                     inst=obj["inst_name"],
                                                     sig=obj["name"]))
              error += 1
          else:
              # convert to int value
              obj["width"] = width
  
      # Add empty string if no explicit default for dangling pins is given.
      # In that case, dangling pins of type struct will be tied to the default
      # parameter in the corresponding package, and dangling pins of type logic
      # will be tied off to '0.
      if "default" not in obj:
          obj["default"] = ""
  
      return error
  
  
  def find_otherside_modules(topcfg: OrderedDict, m,
                             s) -> List[Tuple[str, str, str]]:
      """Find far-end port based on given module and signal name
      """
      # TODO: handle special cases
      special_inst_names = {
          ('main', 'tl_rom'): ('tl_adapter_rom', 'tl'),
          ('main', 'tl_ram_main'): ('tl_adapter_ram_main', 'tl'),
          ('main', 'tl_eflash'): ('tl_adapter_eflash', 'tl'),
          ('peri', 'tl_ram_ret_aon'): ('tl_adapter_ram_ret_aon', 'tl'),
          ('main', 'tl_corei'): ('rv_core_ibex', 'tl_i'),
          ('main', 'tl_cored'): ('rv_core_ibex', 'tl_d'),
          ('main', 'tl_dm_sba'): ('dm_top', 'tl_h'),
          ('main', 'tl_debug_mem'): ('dm_top', 'tl_d'),
          ('peri', 'tl_ast_wrapper'): ('ast', 'tl')
      }
      for pair in special_inst_names:
          if pair == (m, s):
              result = special_inst_names.get(pair)
              return [('top', result[0], result[1])]
  
      signame = "{}.{}".format(m, s)
      for req, rsps in topcfg["inter_module"]["connect"].items():
          if req.startswith(signame):
              # return rsps after splitting module instance name and the port
              result = []
              for rsp in rsps:
                  rsp_m, rsp_s, rsp_i = filter_index(rsp)
                  result.append(('connect', rsp_m, rsp_s))
              return result
  
          for rsp in rsps:
              if signame == rsp:
                  req_m, req_s, req_i = filter_index(req)
                  return [('connect', req_m, req_s)]
  
      # if reaches here, it means either the format is wrong, or floating port.
      log.error("`find_otherside_modules()`: "
                "No such signal {}.{} exists.".format(m, s))
      return []
  
  
  def check_intermodule(topcfg: Dict, prefix: str) -> int:
      if "inter_module" not in topcfg:
          return 0
  
      total_error = 0
  
      for req, rsps in topcfg["inter_module"]["connect"].items():
          error = 0
          # checking the key, value are in correct format
          # Allowed format
          #   1. module.signal
          #   2. module.signal[index] // Remember array is not yet supported
          #                           // But allow in format checker
          #
          # Example:
          #   inter_module: {
          #     'connect': {
          #       'flash_ctrl.flash': ['eflash.flash_ctrl'],
          #       'life_cycle.provision': ['debug_tap.dbg_en', 'dft_ctrl.en'],
          #       'otp.pwr_hold': ['pwrmgr.peri[0]'],
          #       'flash_ctrl.pwr_hold': ['pwrmgr.peri[1]'],
          #     }
          #   }
          #
          # If length of value list is > 1, then key should be array (width need to match)
          # If key is format #2, then lenght of value list shall be 1
          # If one of the value is format #2, then the key should be 1 bit width and
          # entries of value list should be 1
          req_m, req_s, req_i = filter_index(req)
  
          if req_s == "":
              log.error(
                  "Cannot parse the inter-module signal key '{req}'".format(
                      req=req))
              error += 1
  
          # Check rsps signal format is list
          if not isinstance(rsps, list):
              log.error("Value of key '{req}' should be a list".format(req=req))
              error += 1
              continue
  
          req_struct = find_intermodule_signal(topcfg["inter_signal"]["signals"],
                                               req_m, req_s)
  
          error += check_intermodule_field(req_struct)
  
          if req_i != -1 and len(rsps) != 1:
              # Array format should have one entry
              log.error(
                  "If key {req} has index, only one entry is allowed.".format(
                      req=req))
              error += 1
  
          total_width = 0
          widths = []
  
          # Check rsp format
          for i, rsp in enumerate(rsps):
              rsp_m, rsp_s, rsp_i = filter_index(rsp)
              if rsp_s == "":
                  log.error(
                      "Cannot parse the inter-module signal key '{req}->{rsp}'".
                      format(req=req, rsp=rsp))
                  error += 1
  
              rsp_struct = find_intermodule_signal(
                  topcfg["inter_signal"]["signals"], rsp_m, rsp_s)
  
              error += check_intermodule_field(rsp_struct)
  
              total_width += rsp_struct["width"]
              widths.append(rsp_struct["width"])
  
              # Type check
              if "package" not in rsp_struct:
                  rsp_struct["package"] = req_struct["package"]
              elif req_struct["package"] != rsp_struct["package"]:
                  log.error(
                      "Inter-module package should be matched: "
                      "{req}->{rsp} exp({expected}), actual({actual})".format(
                          req=req_struct["name"],
                          rsp=rsp_struct["name"],
                          expected=req_struct["package"],
                          actual=rsp_struct["package"]))
                  error += 1
              if req_struct["type"] != rsp_struct["type"]:
                  log.error(
                      "Inter-module type should be matched: "
                      "{req}->{rsp} exp({expected}), actual({actual})".format(
                          req=req_struct["name"],
                          rsp=rsp_struct["name"],
                          expected=req_struct["type"],
                          actual=rsp_struct["type"]))
                  error += 1
  
              # If len(rsps) is 1, then the width should be matched to req
              if req_struct["width"] != 1:
                  if rsp_struct["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"
                          .format(req=req, rsp=rsp))
                      error += 1
  
                  elif rsp_i != -1:
                      # If rsp has index, req should be width 1
                      log.error(
                          "If rsp {rsp} has an array index, only one-to-one map is allowed."
                          .format(rsp=rsp))
                      error += 1
  
          # Determine if broadcast or one-to-N
          log.debug("Handling inter-sig {} {}".format(req_struct['name'], total_width))
          req_struct["end_idx"] = -1
          if req_struct["width"] > 1 or len(rsps) != 1:
              # If req width is same to the every width of rsps ==> broadcast
              if len(rsps) * [req_struct["width"]] == widths:
                  log.debug("broadcast type")
                  req_struct["top_type"] = "broadcast"
  
              # If req width is same as total width of rsps ==> one-to-N
              elif req_struct["width"] == total_width:
                  log.debug("one-to-N type")
                  req_struct["top_type"] = "one-to-N"
  
              # one-to-N connection is not fully populated
              elif req_struct["width"] > total_width:
                  log.debug("partial one-to-N type")
                  req_struct["top_type"] = "partial-one-to-N"
                  req_struct["end_idx"] = len(rsps)
  
              # If not, error
              else:
                  log.error("'uni' type connection {req} should be either "
                            "OneToN or Broadcast".format(req=req))
                  error += 1
  
          elif req_struct["type"] == "uni":
              # one-to-one connection
              req_struct["top_type"] = "broadcast"
  
          # If req is array, it is not allowed to have partial connections.
          # Doing for loop again here: Make code separate from other checker
          # for easier maintenance
          total_error += error
  
          if error != 0:
              # Skip the check
              continue
  
      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))
              total_error += 1
          sig_struct = find_intermodule_signal(topcfg["inter_signal"]["signals"],
                                               sig_m, sig_s)
          total_error += check_intermodule_field(sig_struct)
  
      return total_error
  
  
  # Template functions
  def im_defname(obj: OrderedDict) -> str:
      """return definition struct name
  
      e.g. flash_ctrl::flash_req_t
      """
      if obj["package"] == "":
          # should be logic
          return "logic"
  
      return "{package}::{struct}_t".format(package=obj["package"],
                                            struct=obj["struct"])
  
  
  def im_netname(obj: OrderedDict, suffix: str = "", default_name=False) -> str:
      """return top signal name with index
  
      It also adds suffix for external signal.
  
      The default name input forces function to return default name, even if object
      has a connection.
      """
  
      # Basic check and add missing fields
      check_intermodule_field(obj)
  
      # Floating signals
      # TODO: Find smarter way to assign default?
      if "top_signame" not in obj or default_name:
          if obj["act"] == "req" and suffix == "req":
              return ""
          if obj["act"] == "rsp" and suffix == "rsp":
              return ""
          if obj["act"] == "req" and suffix == "rsp":
              # 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":
              # custom default has been specified
              if obj["default"]:
                  return obj["default"]
              return "'0"
          if obj["act"] == "rcv" and suffix == "":
              # custom default has been specified
              if obj["default"]:
                  return obj["default"]
              return "{package}::{struct}_DEFAULT".format(
                  package=obj["package"], struct=obj["struct"].upper())
  
          return ""
  
      # Connected signals
      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,
          index=lib.index(obj["index"]))
  
  
  def im_portname(obj: OrderedDict, suffix: str = "") -> str:
      """return IP's port name
  
      e.g signame_o for requester req signal
      """
      if suffix == "":
          suffix_s = "_o" if obj["act"] == "req" else "_i"
      elif suffix == "req":
          suffix_s = "_o" if obj["act"] == "req" else "_i"
      else:
          suffix_s = "_o" if obj["act"] == "rsp" else "_i"
  
      return "{signame}{suffix}".format(signame=obj["name"], suffix=suffix_s)
  
  
  def get_dangling_im_def(objs: OrderedDict) -> str:
      """return partial inter-module definitions
  
      Dangling intermodule connections happen when a one-to-N assignment
      is not fully populated.
  
      This can result in two types of dangling:
      - outgoing requests not used
      - incoming responses not driven
  
      The determination of which category we fall into follows similar rules
      as those used by im_netname.
  
      When the direction of the net is the same as the active direction of the
      the connecting module, it is "unused".
  
      When the direction of the net is opposite of the active direction of the
      the connecting module, it is "undriven".
  
      As an example, edn is defined as "rsp" of a "req_rsp" pair. It is also used
      as the "active" module in inter-module connection. If there are not enough
      connecting modules, the 'req' line is undriven, while the 'rsp' line is
      unused.
  
      """
      unused_def = [obj for obj in objs if obj['end_idx'] > 0 and
                    obj['act'] == obj['suffix']]
  
      undriven_def = [obj for obj in objs if obj['end_idx'] > 0 and
                      (obj['act'] == 'req' and obj['suffix'] == 'rsp' or
                       obj['act'] == 'rsp' and obj['suffix'] == 'req')]
  
      return unused_def, undriven_def
  