|  | # 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 random | 
|  | import re | 
|  | from collections import OrderedDict | 
|  | from copy import deepcopy | 
|  | from math import ceil, log2 | 
|  | from typing import Dict, List | 
|  |  | 
|  | from topgen import c, lib | 
|  | from .clocks import Clocks | 
|  | from .resets import Resets | 
|  | from reggen.ip_block import IpBlock | 
|  | from reggen.params import LocalParam, Parameter, RandParameter, MemSizeParameter | 
|  |  | 
|  |  | 
|  | def _get_random_data_hex_literal(width): | 
|  | """ Fetch 'width' random bits and return them as hex literal""" | 
|  | width = int(width) | 
|  | literal_str = hex(random.getrandbits(width)) | 
|  | return literal_str | 
|  |  | 
|  |  | 
|  | def _get_random_perm_hex_literal(numel): | 
|  | """ Compute a random permutation of 'numel' elements and | 
|  | return as packed hex literal""" | 
|  | num_elements = int(numel) | 
|  | width = int(ceil(log2(num_elements))) | 
|  | idx = [x for x in range(num_elements)] | 
|  | random.shuffle(idx) | 
|  | literal_str = "" | 
|  | for k in idx: | 
|  | literal_str += format(k, '0' + str(width) + 'b') | 
|  | # convert to hex for space efficiency | 
|  | literal_str = hex(int(literal_str, 2)) | 
|  | return literal_str | 
|  |  | 
|  |  | 
|  | def elaborate_instances(top, name_to_block: Dict[str, IpBlock]): | 
|  | '''Add additional fields to the elements of top['module'] | 
|  |  | 
|  | These elements represent instantiations of IP blocks. This function adds | 
|  | extra fields to them to carry across information from the IpBlock objects | 
|  | that represent the blocks being instantiated. See elaborate_instance for | 
|  | more details of what gets added. | 
|  |  | 
|  | ''' | 
|  | # Initialize RNG for compile-time netlist constants. | 
|  | random.seed(int(top['rnd_cnst_seed'])) | 
|  |  | 
|  | for instance in top['module']: | 
|  | block = name_to_block[instance['type']] | 
|  | elaborate_instance(instance, block) | 
|  |  | 
|  |  | 
|  | def elaborate_instance(instance, block: IpBlock): | 
|  | """Add additional fields to a single instance of a module. | 
|  |  | 
|  | instance is the instance to be filled in. block is the block that it's | 
|  | instantiating. | 
|  |  | 
|  | Altered fields: | 
|  | - param_list (list of parameters for the instance) | 
|  | - inter_signal_list (list of inter-module signals) | 
|  | - base_addrs (a map from interface name to its base address) | 
|  |  | 
|  | Removed fields: | 
|  | - base_addr (this is reflected in base_addrs) | 
|  |  | 
|  | """ | 
|  |  | 
|  | # create an empty dict if nothing is there | 
|  | if "param_decl" not in instance: | 
|  | instance["param_decl"] = {} | 
|  |  | 
|  | mod_name = instance["name"] | 
|  | cc_mod_name = c.Name.from_snake_case(mod_name).as_camel_case() | 
|  |  | 
|  | # Check to see if all declared parameters exist | 
|  | param_decl_accounting = [decl for decl in instance["param_decl"].keys()] | 
|  |  | 
|  | # param_list | 
|  | new_params = [] | 
|  | for param in block.params.by_name.values(): | 
|  | if isinstance(param, LocalParam): | 
|  | # Remove local parameters. | 
|  | continue | 
|  |  | 
|  | new_param = param.as_dict() | 
|  |  | 
|  | param_expose = param.expose if isinstance(param, Parameter) else False | 
|  |  | 
|  | # assign an empty entry if this is not present | 
|  | if "memory" not in instance: | 
|  | instance["memory"] = {} | 
|  |  | 
|  | # Check for security-relevant parameters that are not exposed, | 
|  | # adding a top-level name. | 
|  | if param.name.lower().startswith("sec") and not param_expose: | 
|  | log.warning("{} has security-critical parameter {} " | 
|  | "not exposed to top".format( | 
|  | mod_name, param.name)) | 
|  |  | 
|  | # Move special prefixes to the beginnining of the parameter name. | 
|  | param_prefixes = ["Sec", "RndCnst", "MemSize"] | 
|  | name_top = cc_mod_name + param.name | 
|  | for prefix in param_prefixes: | 
|  | if not param.name.startswith(prefix): | 
|  | continue | 
|  | else: | 
|  | if param.name == prefix: | 
|  | raise ValueError(f'Module instance {mod_name} has a ' | 
|  | f'parameter {param.name} that is equal ' | 
|  | f'to prefix {prefix}.') | 
|  |  | 
|  | if re.match(prefix + '[A-Z].+$', param.name): | 
|  | name_top = (prefix + cc_mod_name + | 
|  | param.name[len(prefix):]) | 
|  | break | 
|  |  | 
|  | new_param['name_top'] = name_top | 
|  |  | 
|  | # Generate random bits or permutation, if needed | 
|  | if isinstance(param, RandParameter): | 
|  | if param.randtype == 'data': | 
|  | new_default = _get_random_data_hex_literal(param.randcount) | 
|  | # Effective width of the random vector | 
|  | randwidth = param.randcount | 
|  | else: | 
|  | assert param.randtype == 'perm' | 
|  | new_default = _get_random_perm_hex_literal(param.randcount) | 
|  | # Effective width of the random vector | 
|  | randwidth = param.randcount * ceil(log2(param.randcount)) | 
|  |  | 
|  | new_param['default'] = new_default | 
|  | new_param['randwidth'] = randwidth | 
|  |  | 
|  | elif isinstance(param, MemSizeParameter): | 
|  | key = param.name[7:].lower() | 
|  | # Set the parameter to the specified memory size. | 
|  | if key in instance["memory"]: | 
|  | new_default = int(instance["memory"][key]["size"], 0) | 
|  | new_param['default'] = new_default | 
|  | else: | 
|  | log.error("Missing memory configuration for " | 
|  | "memory {} in instance {}" | 
|  | .format(key, instance["name"])) | 
|  |  | 
|  | # if this exposed parameter is listed in the `param_decl` dict, | 
|  | # override its default value. | 
|  | elif param.name in instance["param_decl"].keys(): | 
|  | new_param['default'] = instance["param_decl"][param.name] | 
|  | # remove the parameter from the accounting dict | 
|  | param_decl_accounting.remove(param.name) | 
|  |  | 
|  | new_params.append(new_param) | 
|  |  | 
|  | instance["param_list"] = new_params | 
|  |  | 
|  | # for each module declaration, check to see that the parameter actually exists | 
|  | # and can be set | 
|  | for decl in param_decl_accounting: | 
|  | log.error("{} is not a valid parameter of {} that can be " | 
|  | "set from top level".format(decl, block.name)) | 
|  |  | 
|  | # These objects get added-to in place by code in intermodule.py, so we have | 
|  | # to convert and copy them here. | 
|  | instance["inter_signal_list"] = [s.as_dict() for s in block.inter_signals] | 
|  |  | 
|  | # An instance must either have a 'base_addr' address or a 'base_addrs' | 
|  | # address, but can't have both. | 
|  | base_addrs = instance.get('base_addrs') | 
|  | if base_addrs is None: | 
|  | if 'base_addr' not in instance: | 
|  | log.error('Instance {!r} has neither a base_addr ' | 
|  | 'nor a base_addrs field.' | 
|  | .format(instance['name'])) | 
|  | else: | 
|  | # If the instance has a base_addr field, make sure that the block | 
|  | # has just one device interface. | 
|  | if len(block.reg_blocks) != 1: | 
|  | log.error('Instance {!r} has a base_addr field but it ' | 
|  | 'instantiates the block {!r}, which has {} ' | 
|  | 'device interfaces.' | 
|  | .format(instance['name'], | 
|  | block.name, len(block.reg_blocks))) | 
|  | else: | 
|  | if_name = next(iter(block.reg_blocks)) | 
|  | base_addrs = {if_name: instance['base_addr']} | 
|  |  | 
|  | # Fill in a bogus base address (we don't have proper error handling, so | 
|  | # have to do *something*) | 
|  | if base_addrs is None: | 
|  | base_addrs = {None: 0} | 
|  |  | 
|  | instance['base_addrs'] = base_addrs | 
|  | else: | 
|  | if 'base_addr' in instance: | 
|  | log.error('Instance {!r} has both a base_addr ' | 
|  | 'and a base_addrs field.' | 
|  | .format(instance['name'])) | 
|  |  | 
|  | # Since the instance already has a base_addrs field, make sure that | 
|  | # it's got the same set of keys as the name of the interfaces in the | 
|  | # block. | 
|  | inst_if_names = set(base_addrs.keys()) | 
|  | block_if_names = set(block.reg_blocks.keys()) | 
|  | if block_if_names != inst_if_names: | 
|  | log.error('Instance {!r} has a base_addrs field with keys {} ' | 
|  | 'but the block it instantiates ({!r}) has device ' | 
|  | 'interfaces {}.' | 
|  | .format(instance['name'], inst_if_names, | 
|  | block.name, block_if_names)) | 
|  |  | 
|  | if 'base_addr' in instance: | 
|  | del instance['base_addr'] | 
|  |  | 
|  |  | 
|  | # TODO: Replace this part to be configurable from Hjson or template | 
|  | predefined_modules = { | 
|  | "corei": "rv_core_ibex", | 
|  | "cored": "rv_core_ibex" | 
|  | } | 
|  |  | 
|  |  | 
|  | def is_xbar(top, name): | 
|  | """Check if the given name is crossbar | 
|  | """ | 
|  | xbars = list(filter(lambda node: node["name"] == name, top["xbar"])) | 
|  | if len(xbars) == 0: | 
|  | return False, None | 
|  |  | 
|  | if len(xbars) > 1: | 
|  | log.error("Matching crossbar {} is more than one.".format(name)) | 
|  | raise SystemExit() | 
|  |  | 
|  | return True, xbars[0] | 
|  |  | 
|  |  | 
|  | def xbar_addhost(top, xbar, host): | 
|  | """Add host nodes information | 
|  |  | 
|  | - xbar: bool, true if the host port is from another Xbar | 
|  | """ | 
|  | # Check and fetch host if exists in nodes | 
|  | obj = list(filter(lambda node: node["name"] == host, xbar["nodes"])) | 
|  | if len(obj) == 0: | 
|  | log.warning( | 
|  | "host %s doesn't exist in the node list. Using default values" % | 
|  | host) | 
|  | obj = OrderedDict([ | 
|  | ("name", host), | 
|  | ("clock", xbar['clock']), | 
|  | ("reset", xbar['reset']), | 
|  | ("type", "host"), | 
|  | ("inst_type", ""), | 
|  | ("stub", False), | 
|  | # The default matches RTL default | 
|  | # pipeline_byp is don't care if pipeline is false | 
|  | ("pipeline", "true"), | 
|  | ("pipeline_byp", "true") | 
|  | ]) | 
|  | xbar["nodes"].append(obj) | 
|  | return | 
|  |  | 
|  | xbar_bool, xbar_h = is_xbar(top, host) | 
|  | if xbar_bool: | 
|  | log.info("host {} is a crossbar. Nothing to deal with.".format(host)) | 
|  |  | 
|  | obj[0]["xbar"] = xbar_bool | 
|  |  | 
|  | if 'clock' not in obj[0]: | 
|  | obj[0]["clock"] = xbar['clock'] | 
|  |  | 
|  | if 'reset' not in obj[0]: | 
|  | obj[0]["reset"] = xbar["reset"] | 
|  |  | 
|  | obj[0]["stub"] = False | 
|  | obj[0]["inst_type"] = predefined_modules[ | 
|  | host] if host in predefined_modules else "" | 
|  | obj[0]["pipeline"] = obj[0]["pipeline"] if "pipeline" in obj[0] else "true" | 
|  | obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0][ | 
|  | "pipeline"] == "true" and "pipeline_byp" in obj[0] else "true" | 
|  |  | 
|  |  | 
|  | def process_pipeline_var(node): | 
|  | """Add device nodes pipeline / pipeline_byp information | 
|  |  | 
|  | - Supply a default of true / true if not defined by xbar | 
|  | """ | 
|  | node["pipeline"] = node["pipeline"] if "pipeline" in node else "true" | 
|  | node["pipeline_byp"] = node[ | 
|  | "pipeline_byp"] if "pipeline_byp" in node else "true" | 
|  |  | 
|  |  | 
|  | def xbar_adddevice(top: Dict[str, object], | 
|  | name_to_block: Dict[str, IpBlock], | 
|  | xbar: Dict[str, object], | 
|  | other_xbars: List[str], | 
|  | device: str) -> None: | 
|  | """Add or amend an entry in xbar['nodes'] to represent the device interface | 
|  |  | 
|  | - clock: comes from module if exist, use xbar default otherwise | 
|  | - reset: comes from module if exist, use xbar default otherwise | 
|  | - inst_type: comes from module or memory if exist. | 
|  | - base_addr: comes from module or memory, or assume rv_plic? | 
|  | - size_byte: comes from module or memory | 
|  | - xbar: bool, true if the device port is another xbar | 
|  | - stub: There is no backing module / memory, instead a tlul port | 
|  | is created and forwarded above the current hierarchy | 
|  | """ | 
|  | device_parts = device.split('.', 1) | 
|  | device_base = device_parts[0] | 
|  | device_ifname = device_parts[1] if len(device_parts) > 1 else None | 
|  |  | 
|  | # Try to find a block or memory instance with name device_base. Object | 
|  | # names should be unique, so there should never be more than one hit. | 
|  | instances = [ | 
|  | node for node in top["module"] + top["memory"] | 
|  | if node['name'] == device_base | 
|  | ] | 
|  | assert len(instances) <= 1 | 
|  | inst = instances[0] if instances else None | 
|  |  | 
|  | # Try to find a node in the crossbar called device. Node names should be | 
|  | # unique, so there should never be more than one hit. | 
|  | nodes = [ | 
|  | node for node in xbar['nodes'] | 
|  | if node['name'] == device | 
|  | ] | 
|  | assert len(nodes) <= 1 | 
|  | node = nodes[0] if nodes else None | 
|  |  | 
|  | log.info("Handling xbar device {} (matches instance? {}; matches node? {})" | 
|  | .format(device, inst is not None, node is not None)) | 
|  |  | 
|  | # case 1: another xbar --> check in xbar list | 
|  | if node is None and device in other_xbars: | 
|  | log.error( | 
|  | "Another crossbar %s needs to be specified in the 'nodes' list" % | 
|  | device) | 
|  | return | 
|  |  | 
|  | # If there is no module or memory with the right name, this might still be | 
|  | # ok: we might be connecting to another crossbar or to a predefined module. | 
|  | if inst is None: | 
|  | # case 1: Crossbar handling | 
|  | if device in other_xbars: | 
|  | log.info( | 
|  | "device {} in Xbar {} is connected to another Xbar".format( | 
|  | device, xbar["name"])) | 
|  | assert node is not None | 
|  | node["xbar"] = True | 
|  | node["stub"] = False | 
|  | process_pipeline_var(node) | 
|  | return | 
|  |  | 
|  | # case 2: predefined_modules (debug_mem, rv_plic) | 
|  | # TODO: Find configurable solution not from predefined but from object? | 
|  | if device in predefined_modules: | 
|  | log.error("device %s shouldn't be host type" % device) | 
|  |  | 
|  | return | 
|  |  | 
|  | # case 3: not defined | 
|  | # Crossbar check | 
|  | log.error("Device %s doesn't exist in 'module', 'memory', predefined, " | 
|  | "or as a node object" % device) | 
|  | return | 
|  |  | 
|  | # If we get here, inst points an instance of some block or memory. It | 
|  | # shouldn't point at a crossbar (because that would imply a naming clash) | 
|  | assert device_base not in other_xbars | 
|  | base_addr, size_byte = lib.get_base_and_size(name_to_block, | 
|  | inst, device_ifname) | 
|  | addr_range = {"base_addr": hex(base_addr), "size_byte": hex(size_byte)} | 
|  |  | 
|  | stub = not lib.is_inst(inst) | 
|  |  | 
|  | if node is None: | 
|  | log.error('Cannot connect to {!r} because ' | 
|  | 'the crossbar defines no node for {!r}.' | 
|  | .format(device, device_base)) | 
|  | return | 
|  |  | 
|  | node["inst_type"] = inst["type"] | 
|  | node["addr_range"] = [addr_range] | 
|  | node["xbar"] = False | 
|  | node["stub"] = stub | 
|  | process_pipeline_var(node) | 
|  |  | 
|  |  | 
|  | def amend_xbar(top: Dict[str, object], | 
|  | name_to_block: Dict[str, IpBlock], | 
|  | xbar: Dict[str, object]): | 
|  | """Amend crossbar informations to the top list | 
|  |  | 
|  | Amended fields | 
|  | - clock: Adopt from module clock if exists | 
|  | - inst_type: Module instance some module will be hard-coded | 
|  | the tool searches module list and memory list then put here | 
|  | - base_addr: from top["module"] | 
|  | - size: from top["module"] | 
|  | """ | 
|  | xbar_list = [x["name"] for x in top["xbar"]] | 
|  | if not xbar["name"] in xbar_list: | 
|  | log.info( | 
|  | "Xbar %s doesn't belong to the top %s. Check if the xbar doesn't need" | 
|  | % (xbar["name"], top["name"])) | 
|  | return | 
|  |  | 
|  | topxbar = list( | 
|  | filter(lambda node: node["name"] == xbar["name"], top["xbar"]))[0] | 
|  |  | 
|  | topxbar["connections"] = deepcopy(xbar["connections"]) | 
|  | if "nodes" in xbar: | 
|  | topxbar["nodes"] = deepcopy(xbar["nodes"]) | 
|  | else: | 
|  | topxbar["nodes"] = [] | 
|  |  | 
|  | # xbar primary clock and reset | 
|  | topxbar["clock"] = xbar["clock_primary"] | 
|  | topxbar["reset"] = xbar["reset_primary"] | 
|  |  | 
|  | # Build nodes from 'connections' | 
|  | device_nodes = set() | 
|  | for host, devices in xbar["connections"].items(): | 
|  | # add host first | 
|  | xbar_addhost(top, topxbar, host) | 
|  |  | 
|  | # add device if doesn't exist | 
|  | device_nodes.update(devices) | 
|  |  | 
|  | other_xbars = [x["name"] | 
|  | for x in top["xbar"] | 
|  | if x["name"] != xbar["name"]] | 
|  |  | 
|  | log.info(device_nodes) | 
|  | for device in device_nodes: | 
|  | xbar_adddevice(top, name_to_block, topxbar, other_xbars, device) | 
|  |  | 
|  |  | 
|  | def xbar_cross(xbar, xbars): | 
|  | """Check if cyclic dependency among xbars | 
|  |  | 
|  | And gather the address range for device port (to another Xbar) | 
|  |  | 
|  | @param node_name if not "", the function only search downstream | 
|  | devices starting from the node_name | 
|  | @param visited   The nodes it visited to reach this port. If any | 
|  | downstream port from node_name in visited, it means | 
|  | circular path exists. It should be fatal error. | 
|  | """ | 
|  | # Step 1: Visit devices (gather the address range) | 
|  | log.info("Processing circular path check for {}".format(xbar["name"])) | 
|  | addr = [] | 
|  | for node in [ | 
|  | x for x in xbar["nodes"] | 
|  | if x["type"] == "device" and "xbar" in x and x["xbar"] is False | 
|  | ]: | 
|  | addr.extend(node["addr_range"]) | 
|  |  | 
|  | # Step 2: visit xbar device ports | 
|  | xbar_nodes = [ | 
|  | x for x in xbar["nodes"] | 
|  | if x["type"] == "device" and "xbar" in x and x["xbar"] is True | 
|  | ] | 
|  |  | 
|  | # Now call function to get the device range | 
|  | # the node["name"] is used to find the host_xbar and its connection. The | 
|  | # assumption here is that there's only one connection from crossbar A to | 
|  | # crossbar B. | 
|  | # | 
|  | # device_xbar is the crossbar has a device port with name as node["name"]. | 
|  | # host_xbar is the crossbar has a host port with name as node["name"]. | 
|  | for node in xbar_nodes: | 
|  | xbar_addr = xbar_cross_node(node["name"], xbar, xbars, visited=[]) | 
|  | node["addr_range"] = xbar_addr | 
|  |  | 
|  |  | 
|  | def xbar_cross_node(node_name, device_xbar, xbars, visited=[]): | 
|  | # 1. Get the connected xbar | 
|  | host_xbars = [x for x in xbars if x["name"] == node_name] | 
|  | assert len(host_xbars) == 1 | 
|  | host_xbar = host_xbars[0] | 
|  |  | 
|  | log.info("Processing node {} in Xbar {}.".format(node_name, | 
|  | device_xbar["name"])) | 
|  | result = []  # [(base_addr, size), .. ] | 
|  | # Sweep the devices using connections and gather the address. | 
|  | # If the device is another xbar, call recursive | 
|  | visited.append(host_xbar["name"]) | 
|  | devices = host_xbar["connections"][device_xbar["name"]] | 
|  |  | 
|  | for node in host_xbar["nodes"]: | 
|  | if not node["name"] in devices: | 
|  | continue | 
|  | if "xbar" in node and node["xbar"] is True: | 
|  | if "addr_range" not in node: | 
|  | # Deeper dive into another crossbar | 
|  | xbar_addr = xbar_cross_node(node["name"], host_xbar, xbars, | 
|  | visited) | 
|  | node["addr_range"] = xbar_addr | 
|  |  | 
|  | result.extend(deepcopy(node["addr_range"])) | 
|  |  | 
|  | visited.pop() | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | # find the first instance name of a given type | 
|  | def _find_module_name(modules, module_type): | 
|  | for m in modules: | 
|  | if m['type'] == module_type: | 
|  | return m['name'] | 
|  |  | 
|  | return None | 
|  |  | 
|  |  | 
|  | def extract_clocks(top: OrderedDict): | 
|  | '''Add clock exports to top and connections to endpoints | 
|  |  | 
|  | This function sets up all the clock-related machinery that is needed to | 
|  | generate the (templated) clkmgr code. This runs before we load up IP blocks | 
|  | with reggen, so can only see top-level configuration. | 
|  |  | 
|  | ''' | 
|  | clocks = top['clocks'] | 
|  | assert isinstance(clocks, Clocks) | 
|  |  | 
|  | exported_clks = OrderedDict() | 
|  |  | 
|  | for ep in top['module'] + top['memory'] + top['xbar']: | 
|  | clock_connections = OrderedDict() | 
|  |  | 
|  | # Ensure each module has a default case | 
|  | export_if = ep.get('clock_reset_export', []) | 
|  |  | 
|  | # if no clock group assigned, default is unique | 
|  | ep['clock_group'] = 'secure' if 'clock_group' not in ep else ep[ | 
|  | 'clock_group'] | 
|  | ep_grp = ep['clock_group'] | 
|  |  | 
|  | # end point names and clocks | 
|  | ep_name = ep['name'] | 
|  | ep_clks = [] | 
|  |  | 
|  | group = clocks.groups[ep_grp] | 
|  |  | 
|  | for port, clk in ep['clock_srcs'].items(): | 
|  | ep_clks.append(clk) | 
|  |  | 
|  | name = '' | 
|  | hier_name = clocks.hier_paths[group.src] | 
|  |  | 
|  | if group.src == 'ext': | 
|  | # clock comes from top ports | 
|  | if clk == 'main': | 
|  | name = "i" | 
|  | else: | 
|  | name = "{}_i".format(clk) | 
|  |  | 
|  | elif group.unique: | 
|  | # new unqiue clock name | 
|  | name = "{}_{}".format(clk, ep_name) | 
|  |  | 
|  | else: | 
|  | # new group clock name | 
|  | name = "{}_{}".format(clk, ep_grp) | 
|  |  | 
|  | clk_name = "clk_" + name | 
|  |  | 
|  | # add clock to a particular group | 
|  | clk_sig = clocks.add_clock_to_group(group, clk_name, clk) | 
|  | clk_sig.add_endpoint(ep_name, port) | 
|  |  | 
|  | # add clock connections | 
|  | clock_connections[port] = hier_name + clk_name | 
|  |  | 
|  | # clocks for this module are exported | 
|  | for intf in export_if: | 
|  | log.info("{} export clock name is {}".format(ep_name, name)) | 
|  |  | 
|  | # create dict entry if it does not exit | 
|  | if intf not in exported_clks: | 
|  | exported_clks[intf] = OrderedDict() | 
|  |  | 
|  | # if first time encounter end point, declare | 
|  | if ep_name not in exported_clks[intf]: | 
|  | exported_clks[intf][ep_name] = [] | 
|  |  | 
|  | # append clocks | 
|  | exported_clks[intf][ep_name].append(name) | 
|  |  | 
|  | # Add to endpoint structure | 
|  | ep['clock_connections'] = clock_connections | 
|  |  | 
|  | # add entry to top level json | 
|  | top['exported_clks'] = exported_clks | 
|  |  | 
|  |  | 
|  | def connect_clocks(top, name_to_block: Dict[str, IpBlock]): | 
|  | clocks = top['clocks'] | 
|  | assert isinstance(clocks, Clocks) | 
|  |  | 
|  | # add entry to inter_module automatically | 
|  | clkmgr_name = _find_module_name(top['module'], 'clkmgr') | 
|  | external = top['inter_module']['external'] | 
|  | for intf in top['exported_clks']: | 
|  | external[f'{clkmgr_name}.clocks_{intf}'] = f"clks_{intf}" | 
|  |  | 
|  | typed_clocks = clocks.typed_clocks() | 
|  |  | 
|  | # Set up intermodule connections for idle clocks. Iterating over | 
|  | # hint_names() here ensures that we visit the clocks in the same order as | 
|  | # the code that generates the enumeration in clkmgr_pkg.sv: important, | 
|  | # since the order that we add entries to clkmgr_idle below gives the index | 
|  | # of each hint in the "idle" signal bundle. These *must* match, or we'll | 
|  | # have hard-to-debug mis-connections. | 
|  | clkmgr_idle = [] | 
|  | for clk_name in typed_clocks.hint_names().keys(): | 
|  | sig = typed_clocks.hint_clks[clk_name] | 
|  | ep_names = list(set(ep_name for ep_name, ep_port in sig.endpoints)) | 
|  | if len(ep_names) != 1: | 
|  | raise ValueError(f'There are {len(ep_names)} end-points connected ' | 
|  | f'to the {sig.name} clock: {ep_names}. Where ' | 
|  | f'should the idle signal come from?') | 
|  | ep_name = ep_names[0] | 
|  |  | 
|  | # We've got the name of the endpoint, but that's not enough: we need to | 
|  | # find the corresponding IpBlock. To do this, we have to do a (linear) | 
|  | # search through top['module'] to find the instance that matches the | 
|  | # endpoint, then use that instance's type as a key in name_to_block. | 
|  | ep_inst = None | 
|  | for inst in top['module']: | 
|  | if inst['name'] == ep_name: | 
|  | ep_inst = inst | 
|  | break | 
|  | if ep_inst is None: | 
|  | raise ValueError(f'No module instance with name {ep_name}: only ' | 
|  | f'modules can have hint clocks. Is this a ' | 
|  | f'crossbar or a memory?') | 
|  |  | 
|  | ip_block = name_to_block[ep_inst['type']] | 
|  |  | 
|  | # Walk through the clocking items for the block to find the one that | 
|  | # defines each of the ports. | 
|  | idle_signal = None | 
|  | for ep_name, ep_port in sig.endpoints: | 
|  | ep_idle = None | 
|  | for item in ip_block.clocking.items: | 
|  | if item.clock != ep_port: | 
|  | continue | 
|  | if item.idle is None: | 
|  | raise ValueError(f'Cannot connect the {sig.name} clock to ' | 
|  | f'port {ep_port} of {ep_name}. This is a ' | 
|  | f'hint clock, but the clocking item on ' | 
|  | f'the module defines no idle signal.') | 
|  | if idle_signal is not None and item.idle != idle_signal: | 
|  | raise ValueError(f'Cannot connect the {sig.name} clock to ' | 
|  | f'port {ep_port} of {ep_name}. We ' | 
|  | f'already have a connection to another ' | 
|  | f'clock signal which has an assocated ' | 
|  | f'idle signal called {idle_signal}, but ' | 
|  | f'this clocking item has an idle signal ' | 
|  | f'called {item.idle}.') | 
|  | ep_idle = item.idle | 
|  | break | 
|  | if ep_idle is None: | 
|  | raise ValueError(f'Cannot connect the {sig.name} clock to ' | 
|  | f'port {ep_port} of {ep_name}: no such ' | 
|  | f'clocking item.') | 
|  | idle_signal = ep_idle | 
|  | assert idle_signal is not None | 
|  |  | 
|  | # At this point, there's a slight problem: we use names like "idle_o" | 
|  | # for signals in the hjson, but the inter-module list expects names | 
|  | # like "idle". Drop the trailing "_o". | 
|  | if not idle_signal.endswith('_o'): | 
|  | raise ValueError(f'Idle signal for {ep_port} of {ep_name} is ' | 
|  | f'{idle_signal}, which is missing the expected ' | 
|  | f'"_o" suffix.') | 
|  | idle_signal = idle_signal[:-2] | 
|  |  | 
|  | clkmgr_idle.append(ep_name + '.' + idle_signal) | 
|  |  | 
|  | top['inter_module']['connect']['{}.idle'.format(clkmgr_name)] = clkmgr_idle | 
|  |  | 
|  |  | 
|  | def amend_resets(top, name_to_block): | 
|  | """Generate exported reset structure and automatically connect to | 
|  | intermodule. | 
|  |  | 
|  | Also iterate through and determine need for shadowed reset and | 
|  | domains. | 
|  | """ | 
|  |  | 
|  | top_resets = Resets(top['resets'], top['clocks']) | 
|  | rstmgr_name = _find_module_name(top['module'], 'rstmgr') | 
|  |  | 
|  | # Generate exported reset list | 
|  | exported_rsts = OrderedDict() | 
|  | for module in top["module"]: | 
|  |  | 
|  | block = name_to_block[module['type']] | 
|  | block_clock = block.get_primary_clock() | 
|  | primary_reset = module['reset_connections'][block_clock.reset] | 
|  |  | 
|  | # shadowed determination | 
|  | if block.has_shadowed_reg(): | 
|  | top_resets.mark_reset_shadowed(primary_reset) | 
|  |  | 
|  | # domain determination | 
|  | if module['domain']: | 
|  | for r in block.clocking.items: | 
|  | if r.reset: | 
|  | reset = module['reset_connections'][r.reset] | 
|  | top_resets.add_reset_domain(reset, module['domain']) | 
|  |  | 
|  | # This code is here to ensure if amend_clocks/resets switched order | 
|  | # everything would still work | 
|  | export_if = module.get('clock_reset_export', []) | 
|  |  | 
|  | # There may be multiple export interfaces | 
|  | for intf in export_if: | 
|  | # create dict entry if it does not exit | 
|  | if intf not in exported_rsts: | 
|  | exported_rsts[intf] = OrderedDict() | 
|  |  | 
|  | # grab directly from reset_connections definition | 
|  | rsts = [rst for rst in module['reset_connections'].values()] | 
|  | exported_rsts[intf][module['name']] = rsts | 
|  |  | 
|  | # add entry to top level json | 
|  | top['exported_rsts'] = exported_rsts | 
|  |  | 
|  | # add entry to inter_module automatically | 
|  | for intf in top['exported_rsts']: | 
|  | top['inter_module']['external']['{}.resets_{}'.format( | 
|  | rstmgr_name, intf)] = "rsts_{}".format(intf) | 
|  |  | 
|  | # reset class objects | 
|  | top["resets"] = top_resets | 
|  |  | 
|  |  | 
|  | def ensure_interrupt_modules(top: OrderedDict, name_to_block: Dict[str, IpBlock]): | 
|  | '''Populate top['interrupt_module'] if necessary | 
|  |  | 
|  | Do this by adding each module in top['modules'] that defines at least one | 
|  | interrupt. | 
|  |  | 
|  | ''' | 
|  | if 'interrupt_module' in top: | 
|  | return | 
|  |  | 
|  | modules = [] | 
|  | for module in top['module']: | 
|  | block = name_to_block[module['type']] | 
|  | if block.interrupts: | 
|  | modules.append(module['name']) | 
|  |  | 
|  | top['interrupt_module'] = modules | 
|  |  | 
|  |  | 
|  | def amend_interrupt(top: OrderedDict, name_to_block: Dict[str, IpBlock]): | 
|  | """Check interrupt_module if exists, or just use all modules | 
|  | """ | 
|  | ensure_interrupt_modules(top, name_to_block) | 
|  |  | 
|  | if "interrupt" not in top or top["interrupt"] == "": | 
|  | top["interrupt"] = [] | 
|  |  | 
|  | for m in top["interrupt_module"]: | 
|  | ips = list(filter(lambda module: module["name"] == m, top["module"])) | 
|  | if len(ips) == 0: | 
|  | log.warning( | 
|  | "Cannot find IP %s which is used in the interrupt_module" % m) | 
|  | continue | 
|  |  | 
|  | ip = ips[0] | 
|  | block = name_to_block[ip['type']] | 
|  |  | 
|  | log.info("Adding interrupts from module %s" % ip["name"]) | 
|  | for signal in block.interrupts: | 
|  | sig_dict = signal.as_nwt_dict('interrupt') | 
|  | qual = lib.add_module_prefix_to_signal(sig_dict, | 
|  | module=m.lower()) | 
|  | top["interrupt"].append(qual) | 
|  |  | 
|  |  | 
|  | def ensure_alert_modules(top: OrderedDict, name_to_block: Dict[str, IpBlock]): | 
|  | '''Populate top['alert_module'] if necessary | 
|  |  | 
|  | Do this by adding each module in top['modules'] that defines at least one | 
|  | alert. | 
|  |  | 
|  | ''' | 
|  | if 'alert_module' in top: | 
|  | return | 
|  |  | 
|  | modules = [] | 
|  | for module in top['module']: | 
|  | block = name_to_block[module['type']] | 
|  | if block.alerts: | 
|  | modules.append(module['name']) | 
|  |  | 
|  | top['alert_module'] = modules | 
|  |  | 
|  |  | 
|  | def amend_alert(top: OrderedDict, name_to_block: Dict[str, IpBlock]): | 
|  | """Check interrupt_module if exists, or just use all modules | 
|  | """ | 
|  | ensure_alert_modules(top, name_to_block) | 
|  |  | 
|  | if "alert" not in top or top["alert"] == "": | 
|  | top["alert"] = [] | 
|  |  | 
|  | for m in top["alert_module"]: | 
|  | ips = list(filter(lambda module: module["name"] == m, top["module"])) | 
|  | if len(ips) == 0: | 
|  | log.warning("Cannot find IP %s which is used in the alert_module" % | 
|  | m) | 
|  | continue | 
|  |  | 
|  | ip = ips[0] | 
|  | block = name_to_block[ip['type']] | 
|  |  | 
|  | log.info("Adding alert from module %s" % ip["name"]) | 
|  | # Note: we assume that all alerts are asynchronous in order to make the | 
|  | # design homogeneous and more amenable to DV automation and synthesis | 
|  | # constraint scripting. | 
|  | for alert in block.alerts: | 
|  | alert_dict = alert.as_nwt_dict('alert') | 
|  | alert_dict['async'] = '1' | 
|  | qual_sig = lib.add_module_prefix_to_signal(alert_dict, | 
|  | module=m.lower()) | 
|  | top["alert"].append(qual_sig) | 
|  |  | 
|  |  | 
|  | def amend_wkup(topcfg: OrderedDict, name_to_block: Dict[str, IpBlock]): | 
|  |  | 
|  | pwrmgr_name = _find_module_name(topcfg['module'], 'pwrmgr') | 
|  |  | 
|  | if "wakeups" not in topcfg or topcfg["wakeups"] == "": | 
|  | topcfg["wakeups"] = [] | 
|  |  | 
|  | # create list of wakeup signals | 
|  | for m in topcfg["module"]: | 
|  | log.info("Adding wakeup from module %s" % m["name"]) | 
|  | block = name_to_block[m['type']] | 
|  | for signal in block.wakeups: | 
|  | log.info("Adding signal %s" % signal.name) | 
|  | topcfg["wakeups"].append({ | 
|  | 'name': signal.name, | 
|  | 'width': str(signal.bits.width()), | 
|  | 'module': m["name"] | 
|  | }) | 
|  |  | 
|  | # add wakeup signals to pwrmgr connections | 
|  | signal_names = [ | 
|  | "{}.{}".format(s["module"].lower(), s["name"].lower()) | 
|  | for s in topcfg["wakeups"] | 
|  | ] | 
|  |  | 
|  | topcfg["inter_module"]["connect"]["{}.wakeups".format(pwrmgr_name)] = signal_names | 
|  | log.info("Intermodule signals: {}".format( | 
|  | topcfg["inter_module"]["connect"])) | 
|  |  | 
|  |  | 
|  | # Handle reset requests from modules | 
|  | def amend_reset_request(topcfg: OrderedDict, | 
|  | name_to_block: Dict[str, IpBlock]): | 
|  |  | 
|  | pwrmgr_name = _find_module_name(topcfg['module'], 'pwrmgr') | 
|  |  | 
|  | if "reset_requests" not in topcfg or topcfg["reset_requests"] == "": | 
|  | topcfg["reset_requests"] = [] | 
|  |  | 
|  | # create list of reset signals | 
|  | for m in topcfg["module"]: | 
|  | log.info("Adding reset requests from module %s" % m["name"]) | 
|  | block = name_to_block[m['type']] | 
|  | for signal in block.reset_requests: | 
|  | log.info("Adding signal %s" % signal.name) | 
|  | topcfg["reset_requests"].append({ | 
|  | 'name': signal.name, | 
|  | 'width': str(signal.bits.width()), | 
|  | 'module': m["name"] | 
|  | }) | 
|  |  | 
|  | # add reset requests to pwrmgr connections | 
|  | signal_names = [ | 
|  | "{}.{}".format(s["module"].lower(), s["name"].lower()) | 
|  | for s in topcfg["reset_requests"] | 
|  | ] | 
|  |  | 
|  | topcfg["inter_module"]["connect"]["{}.rstreqs".format(pwrmgr_name)] = signal_names | 
|  | log.info("Intermodule signals: {}".format( | 
|  | topcfg["inter_module"]["connect"])) | 
|  |  | 
|  |  | 
|  | def append_io_signal(temp: Dict, sig_inst: Dict) -> None: | 
|  | '''Appends the signal to the correct list''' | 
|  | if sig_inst['type'] == 'inout': | 
|  | temp['inouts'].append(sig_inst) | 
|  | if sig_inst['type'] == 'input': | 
|  | temp['inputs'].append(sig_inst) | 
|  | if sig_inst['type'] == 'output': | 
|  | temp['outputs'].append(sig_inst) | 
|  |  | 
|  |  | 
|  | def get_index_and_incr(ctrs: Dict, connection: str, io_dir: str) -> Dict: | 
|  | '''Get correct index counter and increment''' | 
|  |  | 
|  | if connection != 'muxed': | 
|  | connection = 'dedicated' | 
|  |  | 
|  | if io_dir in 'inout': | 
|  | result = ctrs[connection]['inouts'] | 
|  | ctrs[connection]['inouts'] += 1 | 
|  | elif connection == 'muxed': | 
|  | # For MIOs, the input/output arrays differ in RTL | 
|  | # I.e., the input array contains {inputs, inouts}, whereas | 
|  | # the output array contains {outputs, inouts}. | 
|  | if io_dir == 'input': | 
|  | result = ctrs[connection]['inputs'] + ctrs[connection]['inouts'] | 
|  | ctrs[connection]['inputs'] += 1 | 
|  | elif io_dir == 'output': | 
|  | result = ctrs[connection]['outputs'] + ctrs[connection]['inouts'] | 
|  | ctrs[connection]['outputs'] += 1 | 
|  | else: | 
|  | assert(0)  # should not happen | 
|  | else: | 
|  | # For DIOs, the input/output arrays are identical in terms of index layout. | 
|  | # Unused inputs are left unconnected and unused outputs are tied off. | 
|  | if io_dir == 'input': | 
|  | result = ctrs[connection]['inputs'] + ctrs[connection]['inouts'] | 
|  | ctrs[connection]['inputs'] += 1 | 
|  | elif io_dir == 'output': | 
|  | result = (ctrs[connection]['outputs'] + | 
|  | ctrs[connection]['inouts'] + | 
|  | ctrs[connection]['inputs']) | 
|  | ctrs[connection]['outputs'] += 1 | 
|  | else: | 
|  | assert(0)  # should not happen | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def amend_pinmux_io(top: Dict, name_to_block: Dict[str, IpBlock]): | 
|  | """ Process pinmux/pinout configuration and assign available IOs | 
|  | """ | 
|  | pinmux = top['pinmux'] | 
|  | pinout = top['pinout'] | 
|  | targets = top['targets'] | 
|  |  | 
|  | temp = {} | 
|  | temp['inouts'] = [] | 
|  | temp['inputs'] = [] | 
|  | temp['outputs'] = [] | 
|  |  | 
|  | for sig in pinmux['signals']: | 
|  | # Get the signal information from the IP block type of this instance/ | 
|  | mod_name = sig['instance'] | 
|  | m = lib.get_module_by_name(top, mod_name) | 
|  |  | 
|  | if m is None: | 
|  | raise SystemExit("Module {} is not searchable.".format(mod_name)) | 
|  |  | 
|  | block = name_to_block[m['type']] | 
|  |  | 
|  | # If the signal is explicitly named. | 
|  | if sig['port'] != '': | 
|  |  | 
|  | # If this is a bus signal with explicit indexes. | 
|  | if '[' in sig['port']: | 
|  | name_split = sig['port'].split('[') | 
|  | sig_name = name_split[0] | 
|  | idx = int(name_split[1][:-1]) | 
|  | else: | 
|  | idx = -1 | 
|  | sig_name = sig['port'] | 
|  |  | 
|  | sig_inst = deepcopy(block.get_signal_by_name_as_dict(sig_name)) | 
|  |  | 
|  | if idx >= 0 and idx >= sig_inst['width']: | 
|  | raise SystemExit("Index {} is out of bounds for signal {}" | 
|  | " with width {}.".format(idx, sig_name, sig_inst['width'])) | 
|  | if idx == -1 and sig_inst['width'] != 1: | 
|  | raise SystemExit("Bus signal {} requires an index.".format(sig_name)) | 
|  |  | 
|  | # If we got this far we know that the signal is valid and exists. | 
|  | # Augment this signal instance with additional information. | 
|  | sig_inst.update({'idx': idx, | 
|  | 'pad': sig['pad'], | 
|  | 'attr': sig['attr'], | 
|  | 'connection': sig['connection'], | 
|  | 'desc': sig['desc']}) | 
|  | sig_inst['name'] = mod_name + '_' + sig_inst['name'] | 
|  | append_io_signal(temp, sig_inst) | 
|  |  | 
|  | # Otherwise the name is a wildcard for selecting all available IO signals | 
|  | # of this block and we need to extract them here one by one signals here. | 
|  | else: | 
|  | sig_list = deepcopy(block.get_signals_as_list_of_dicts()) | 
|  |  | 
|  | for sig_inst in sig_list: | 
|  | # If this is a multibit signal, unroll the bus and | 
|  | # generate a single bit IO signal entry for each one. | 
|  | if sig_inst['width'] > 1: | 
|  | for idx in range(sig_inst['width']): | 
|  | sig_inst_copy = deepcopy(sig_inst) | 
|  | sig_inst_copy.update({'idx': idx, | 
|  | 'pad': sig['pad'], | 
|  | 'attr': sig['attr'], | 
|  | 'connection': sig['connection'], | 
|  | 'desc': sig['desc']}) | 
|  | sig_inst_copy['name'] = sig['instance'] + '_' + sig_inst_copy['name'] | 
|  | append_io_signal(temp, sig_inst_copy) | 
|  | else: | 
|  | sig_inst.update({'idx': -1, | 
|  | 'pad': sig['pad'], | 
|  | 'attr': sig['attr'], | 
|  | 'connection': sig['connection'], | 
|  | 'desc': sig['desc']}) | 
|  | sig_inst['name'] = sig['instance'] + '_' + sig_inst['name'] | 
|  | append_io_signal(temp, sig_inst) | 
|  |  | 
|  | # Now that we've collected all input and output signals, | 
|  | # we can go through once again and stack them into one unified | 
|  | # list, and calculate MIO/DIO global indices. | 
|  | pinmux['ios'] = (temp['inouts'] + | 
|  | temp['inputs'] + | 
|  | temp['outputs']) | 
|  |  | 
|  | # Remember these counts to facilitate the RTL generation | 
|  | pinmux['io_counts'] = {'dedicated': {'inouts': 0, 'inputs': 0, 'outputs': 0, 'pads': 0}, | 
|  | 'muxed': {'inouts': 0, 'inputs': 0, 'outputs': 0, 'pads': 0}} | 
|  |  | 
|  | for sig in pinmux['ios']: | 
|  | glob_idx = get_index_and_incr(pinmux['io_counts'], sig['connection'], sig['type']) | 
|  | sig['glob_idx'] = glob_idx | 
|  |  | 
|  | # Calculate global indices for pads. | 
|  | j = k = 0 | 
|  | for pad in pinout['pads']: | 
|  | if pad['connection'] == 'muxed': | 
|  | pad['idx'] = j | 
|  | j += 1 | 
|  | else: | 
|  | pad['idx'] = k | 
|  | k += 1 | 
|  | pinmux['io_counts']['muxed']['pads'] = j | 
|  | pinmux['io_counts']['dedicated']['pads'] = k | 
|  |  | 
|  | # For each target configuration, calculate the special signal indices. | 
|  | known_muxed_pads = {} | 
|  | for pad in pinout['pads']: | 
|  | if pad['connection'] == 'muxed': | 
|  | known_muxed_pads[pad['name']] = pad | 
|  |  | 
|  | known_mapped_dio_pads = {} | 
|  | for sig in pinmux['ios']: | 
|  | if sig['connection'] in ['muxed', 'manual']: | 
|  | continue | 
|  | if sig['pad'] in known_mapped_dio_pads: | 
|  | raise SystemExit('Cannot have multiple IOs mapped to the same DIO pad {}' | 
|  | .format(sig['pad'])) | 
|  | known_mapped_dio_pads[sig['pad']] = sig | 
|  |  | 
|  | for target in targets: | 
|  | for entry in target['pinmux']['special_signals']: | 
|  | # If this is a muxed pad, the resolution is | 
|  | # straightforward. I.e., we just assign the MIO index. | 
|  | if entry['pad'] in known_muxed_pads: | 
|  | entry['idx'] = known_muxed_pads[entry['pad']]['idx'] | 
|  | # Otherwise we need to find out which DIO this pad is mapped to. | 
|  | # Note that we can't have special_signals that are manual, since | 
|  | # there needs to exist a DIO connection. | 
|  | elif entry['pad'] in known_mapped_dio_pads: | 
|  | # This index refers to the stacked {dio, mio} array | 
|  | # on the chip-level, hence we have to add the amount of MIO pads. | 
|  | idx = (known_mapped_dio_pads[entry['pad']]['glob_idx'] + | 
|  | pinmux['io_counts']['muxed']['pads']) | 
|  | entry['idx'] = idx | 
|  | else: | 
|  | assert(0)  # Entry should be guaranteed to exist at this point | 
|  |  | 
|  |  | 
|  | def merge_top(topcfg: OrderedDict, | 
|  | name_to_block: Dict[str, IpBlock], | 
|  | xbarobjs: OrderedDict) -> OrderedDict: | 
|  |  | 
|  | # Combine ip cfg into topcfg | 
|  | elaborate_instances(topcfg, name_to_block) | 
|  |  | 
|  | # Create clock connections for each block | 
|  | # Assign clocks into appropriate groups | 
|  | # Note, elaborate_instances references clock information to establish async handling | 
|  | # as part of alerts. | 
|  | # amend_clocks(topcfg) | 
|  |  | 
|  | # Combine the wakeups | 
|  | amend_wkup(topcfg, name_to_block) | 
|  | amend_reset_request(topcfg, name_to_block) | 
|  |  | 
|  | # Combine the interrupt (should be processed prior to xbar) | 
|  | amend_interrupt(topcfg, name_to_block) | 
|  |  | 
|  | # Combine the alert (should be processed prior to xbar) | 
|  | amend_alert(topcfg, name_to_block) | 
|  |  | 
|  | # Creates input/output list in the pinmux | 
|  | log.info("Processing PINMUX") | 
|  | amend_pinmux_io(topcfg, name_to_block) | 
|  |  | 
|  | # Combine xbar into topcfg | 
|  | for xbar in xbarobjs: | 
|  | amend_xbar(topcfg, name_to_block, xbar) | 
|  |  | 
|  | # 2nd phase of xbar (gathering the devices address range) | 
|  | for xbar in topcfg["xbar"]: | 
|  | xbar_cross(xbar, topcfg["xbar"]) | 
|  |  | 
|  | # Add path names to declared resets. | 
|  | # Declare structure for exported resets. | 
|  | amend_resets(topcfg, name_to_block) | 
|  |  | 
|  | # remove unwanted fields 'debug_mem_base_addr' | 
|  | topcfg.pop('debug_mem_base_addr', None) | 
|  |  | 
|  | return topcfg |