| # 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.ip_block import IpBlock | |
| from reggen.inter_signal import InterSignal | |
| from reggen.validate import check_int | |
| from topgen import lib | |
| IM_TYPES = ['uni', 'req_rsp', 'io'] | |
| IM_ACTS = ['req', 'rsp', 'rcv', 'none'] | |
| IM_VALID_TYPEACT = {'uni': ['req', 'rcv'], 'req_rsp': ['req', 'rsp'], 'io': ['none']} | |
| 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) -> Tuple[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, | |
| name_to_block: Dict[str, IpBlock], | |
| xbar: OrderedDict) -> None: | |
| # The crossbar is connecting to modules and memories in topcfg, plus | |
| # possible external connections. Make indices for the modules and memories | |
| # for quick lookup and add some assertions to make sure no name appears in | |
| # multiple places. | |
| name_to_module = {} | |
| for mod in topcfg['module']: | |
| assert mod['name'] not in name_to_module | |
| if lib.is_inst(mod): | |
| name_to_module[mod['name']] = mod | |
| name_to_memory = {} | |
| for mem in topcfg['memory']: | |
| assert mem['name'] not in name_to_memory | |
| if lib.is_inst(mem): | |
| name_to_memory[mem['name']] = mem | |
| # The names of modules and memories should be disjoint | |
| assert not (set(name_to_module.keys()) & set(name_to_memory.keys())) | |
| external_names = (set(topcfg['inter_module']['top']) | | |
| set(topcfg["inter_module"]["external"].keys())) | |
| ports = [x for x in xbar["nodes"] if x["type"] in ["host", "device"]] | |
| for port in ports: | |
| # Here, we expect port_name to either be a single identifier (in which | |
| # case, it's taken as the name of some module or memory) to be a dotted | |
| # pair MOD.INAME where MOD is the name of some module and INAME is the | |
| # associated interface name. | |
| name_parts = port['name'].split('.', 1) | |
| port_base = name_parts[0] | |
| port_iname = name_parts[1] if len(name_parts) > 1 else None | |
| esc_name = port['name'].replace('.', '__') | |
| if port["xbar"]: | |
| if port_iname is not None: | |
| log.error('A crossbar connection may not ' | |
| 'have a target of the form MOD.INAME (saw {!r})' | |
| .format(port['name'])) | |
| continue | |
| 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_" + esc_name, | |
| rsp_m=esc_name, | |
| rsp_s="tl_" + xbar["name"]) | |
| continue # xbar port case | |
| port_mod = name_to_module.get(port_base) | |
| port_mem = name_to_memory.get(port_base) | |
| assert port_mod is None or port_mem is None | |
| if not (port_mod or port_mem): | |
| # if not in module, memory, should be existed in top or ext field | |
| module_key = "{}.tl_{}".format(xbar["name"], esc_name) | |
| if module_key not in external_names: | |
| log.error("Inter-module key {} cannot be found in module, " | |
| "memory, top, or external lists.".format(module_key)) | |
| continue | |
| if port_iname is not None and port_mem is not None: | |
| log.error('Cannot make connection for {!r}: the base of the name ' | |
| 'points to a memory but memories do not support ' | |
| 'interface names.' | |
| .format(port['name'])) | |
| is_host = port['type'] == 'host' | |
| # If the hit is a module, it originally came from reggen (via | |
| # merge.py's amend_ip() function). In this case, we should have a | |
| # BusInterfaces object as well as a list of InterSignal objects. | |
| # | |
| # If not, this is a memory that will just have a dictionary of inter | |
| # signals. | |
| if port_mod is not None: | |
| block = name_to_block[port_mod['type']] | |
| try: | |
| sig_name = block.bus_interfaces.find_port_name(is_host, | |
| port_iname) | |
| except KeyError: | |
| log.error('Cannot make {} connection for {!r}: the base of ' | |
| 'the target module has no matching bus interface.' | |
| .format('host' if is_host else 'device', | |
| port['name'])) | |
| continue | |
| else: | |
| inter_signal_list = port_mem['inter_signal_list'] | |
| act = 'req' if is_host else 'rsp' | |
| matches = [ | |
| x for x in inter_signal_list | |
| if (x.get('package') == 'tlul_pkg' and | |
| x['struct'] == 'tl' and | |
| x['act'] == act) | |
| ] | |
| if not matches: | |
| log.error('Cannot make {} connection for {!r}: the memory ' | |
| 'has no signal with an action of {}.' | |
| .format('host' if is_host else 'device', | |
| port['name'], | |
| act)) | |
| continue | |
| assert len(matches) == 1 | |
| sig_name = matches[0]['name'] | |
| if is_host: | |
| add_intermodule_connection(obj=topcfg, | |
| req_m=xbar["name"], | |
| req_s="tl_" + esc_name, | |
| rsp_m=port_base, | |
| rsp_s=sig_name) | |
| else: | |
| add_intermodule_connection(obj=topcfg, | |
| req_m=port_base, | |
| req_s=sig_name, | |
| rsp_m=xbar["name"], | |
| rsp_s="tl_" + esc_name) | |
| def autoconnect(topcfg: OrderedDict, name_to_block: Dict[str, IpBlock]): | |
| """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, name_to_block, xbar) | |
| def _get_default_name(sig, suffix): | |
| """Generate default for a net if one does not already exist. | |
| """ | |
| # The else case covers the scenario where neither package nor default is provided. | |
| # Specifically, the interface is 'logic' and has no default value. | |
| # In this situation, just return 0's | |
| if sig['default']: | |
| return sig['default'] | |
| elif sig['package']: | |
| return "{}::{}_DEFAULT".format(sig['package'], (sig["struct"] + suffix).upper()) | |
| else: | |
| return "'0" | |
| 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["port"] | |
| for x in instances: | |
| old_isl = x.get('inter_signal_list') | |
| if old_isl is None: | |
| continue | |
| new_isl = [] | |
| for entry in old_isl: | |
| # Convert any InterSignal objects to the expected dictionary format. | |
| sig = (entry.as_dict() | |
| if isinstance(entry, InterSignal) | |
| else entry.copy()) | |
| # Add instance name to the entry and add to list_of_intersignals | |
| sig["inst_name"] = x["name"] | |
| list_of_intersignals.append(sig) | |
| new_isl.append(sig) | |
| x['inter_signal_list'] = new_isl | |
| # 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) | |
| # 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) | |
| req_default = _get_default_name(req_struct, req_suffix) | |
| rsp_default = _get_default_name(req_struct, rsp_suffix) | |
| # based on the active direction of the req_struct, one of the directions does not | |
| # need a default since it will be an output | |
| if (req_struct["act"] == 'req'): | |
| req_default = '' | |
| else: | |
| rsp_default = '' | |
| # 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_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', rsp_default)])) | |
| else: | |
| # unidirection | |
| default = _get_default_name(req_struct, "") | |
| 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', 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" | |
| # 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 | |
| conn_type = False | |
| if "top_signame" not in sig: | |
| sig["top_signame"] = sig_name | |
| else: | |
| conn_type = True | |
| sig["conn_type"] = conn_type | |
| 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'), | |
| ('conn_type', conn_type), | |
| ('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'), | |
| ('conn_type', conn_type), | |
| ('index', index), | |
| ('netname', netname + rsp_suffix)])) | |
| elif sig["type"] == "io": | |
| sigsuffix = "_io" | |
| topcfg["inter_signal"]["external"].append( | |
| OrderedDict([('package', sig.get("package", "")), | |
| ('struct', sig["struct"]), | |
| ('signame', sig_name + sigsuffix), | |
| ('width', sig["width"]), ('type', sig["type"]), | |
| ('default', sig["default"]), | |
| ('direction', "inout"), | |
| ('conn_type', conn_type), | |
| ('index', index), | |
| ('netname', netname)])) | |
| else: # uni | |
| if sig["act"] == "req": | |
| sigsuffix = "_o" | |
| else: | |
| sigsuffix = "_i" | |
| topcfg["inter_signal"]["external"].append( | |
| OrderedDict([('package', sig.get("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'), | |
| ('conn_type', conn_type), | |
| ('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(sig: OrderedDict, | |
| prefix: str = "") -> Tuple[int, OrderedDict]: | |
| error = 0 | |
| # type check | |
| if sig["type"] not in IM_TYPES: | |
| log.error("{prefix} Inter_signal {name} " | |
| "type {type} is incorrect.".format(prefix=prefix, | |
| name=sig["name"], | |
| type=sig["type"])) | |
| error += 1 | |
| if sig["act"] not in IM_ACTS: | |
| log.error("{prefix} Inter_signal {name} " | |
| "act {act} is incorrect.".format(prefix=prefix, | |
| name=sig["name"], | |
| act=sig["act"])) | |
| error += 1 | |
| # Check if type and act are matched | |
| if error == 0: | |
| if sig["act"] not in IM_VALID_TYPEACT[sig['type']]: | |
| log.error("{type} and {act} of {name} are not a valid pair." | |
| "".format(type=sig['type'], | |
| act=sig['act'], | |
| name=sig['name'])) | |
| error += 1 | |
| # Check 'width' field | |
| width = 1 | |
| if "width" not in sig: | |
| sig["width"] = 1 | |
| elif not isinstance(sig["width"], int): | |
| width, err = check_int(sig["width"], sig["name"]) | |
| if err: | |
| log.error("{prefix} Inter-module {inst}.{sig} 'width' " | |
| "should be int type.".format(prefix=prefix, | |
| inst=sig["inst_name"], | |
| sig=sig["name"])) | |
| error += 1 | |
| else: | |
| # convert to int value | |
| sig["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 sig: | |
| sig["default"] = "" | |
| if "package" not in sig: | |
| sig["package"] = "" | |
| return error, sig | |
| 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 = { | |
| ('peri', 'tl_ast'): ('ast', 'tl') | |
| } | |
| special_result = special_inst_names.get((m, s)) | |
| if special_result is not None: | |
| return [('top', special_result[0], special_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 length 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) | |
| err, req_struct = check_intermodule_field(req_struct) | |
| error += err | |
| 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) | |
| err, rsp_struct = check_intermodule_field(rsp_struct) | |
| error += err | |
| total_width += rsp_struct["width"] | |
| widths.append(rsp_struct["width"]) | |
| # Type check | |
| # If no package was declared, it is declared with an empty string | |
| if not rsp_struct["package"]: | |
| rsp_struct["package"] = req_struct.get("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) | |
| err, sig_struct = check_intermodule_field(sig_struct) | |
| total_error += err | |
| return total_error | |
| def get_direction(sig): | |
| if sig["direction"] == "in": | |
| return "input " | |
| elif sig["direction"] == "out": | |
| return "output" | |
| elif sig["direction"] == "inout": | |
| return "inout " | |
| # Template functions | |
| def im_defname(obj: OrderedDict) -> str: | |
| """return definition struct name | |
| e.g. flash_ctrl::flash_req_t | |
| """ | |
| if obj["type"] == "io": | |
| # io type, don't declare anything | |
| return "" | |
| if obj["package"] == "": | |
| # should be logic | |
| return "logic" | |
| return "{package}::{struct}_t".format(package=obj["package"], | |
| struct=obj["struct"]) | |
| def im_netname(sig: 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 | |
| err, obj = check_intermodule_field(sig) | |
| assert not err | |
| # 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.get("package") == "tlul_pkg" and obj["struct"] == "tl": | |
| return "{package}::{struct}_H2D_DEFAULT".format( | |
| package=obj["package"], struct=obj["struct"].upper()) | |
| # default is used for dangling ports in definitions. | |
| # the struct name already has `_req` suffix | |
| return "{package}::{struct}_REQ_DEFAULT".format( | |
| package=obj.get("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", "io"] | |
| # io types have no role | |
| if suffix == "io": | |
| suffix_s = "" | |
| else: | |
| suffix_s = "_{suffix}".format(suffix=suffix) if suffix != "" else suffix | |
| # External signal handling | |
| if "external" in obj and obj["external"]: | |
| if obj["conn_type"]: | |
| pairs = { | |
| # act , suffix: additional suffix | |
| ("req", "req"): "", | |
| ("req", "rsp"): "_i", | |
| ("rsp", "req"): "_i", | |
| ("rsp", "rsp"): "", | |
| ("req", ""): "", | |
| ("rcv", ""): "_i", | |
| ("none", "io"): "" | |
| } | |
| else: | |
| pairs = { | |
| # act , suffix: additional suffix | |
| ("req", "req"): "_o", | |
| ("req", "rsp"): "_i", | |
| ("rsp", "req"): "_i", | |
| ("rsp", "rsp"): "_o", | |
| ("req", ""): "_o", | |
| ("rcv", ""): "_i", | |
| ("none", "io"): "_io" | |
| } | |
| 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 | |
| """ | |
| act = obj['act'] | |
| name = obj['name'] | |
| if suffix == "": | |
| suffix_s = "_o" if act == "req" else "_i" | |
| elif suffix == "req": | |
| suffix_s = "_o" if act == "req" else "_i" | |
| elif suffix == "io": | |
| suffix_s = "_io" | |
| else: | |
| suffix_s = "_o" if act == "rsp" else "_i" | |
| return name + 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 |