| # 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 |