1008 lines
37 KiB
Python
1008 lines
37 KiB
Python
# 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']
|
|
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) -> 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
|
|
|
|
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)]))
|
|
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 = {
|
|
('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'),
|
|
('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
|
|
|
|
|
|
# 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(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"]
|
|
|
|
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
|
|
"""
|
|
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"
|
|
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
|