| # 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 | 
 | from collections import OrderedDict | 
 | from functools import partial | 
 |  | 
 | from reggen.validate import check_bool, check_int, val_types | 
 |  | 
 | from .item import Node, NodeType | 
 | from .lib import simplify_addr | 
 | from .xbar import Xbar | 
 |  | 
 | # val_types = { | 
 | #     'd': ["int", "integer (binary 0b, octal 0o, decimal, hex 0x)"], | 
 | #     'x': ["xint", "x for undefined otherwise int"], | 
 | #     'b': [ | 
 | #         "bitrange", "bit number as decimal integer, \ | 
 | #                     or bit-range as decimal integers msb:lsb" | 
 | #     ], | 
 | #     'l': ["list", "comma separated list enclosed in `[]`"], | 
 | #     'ln': ["name list", 'comma separated list enclosed in `[]` of '\ | 
 | #            'one or more groups that have just name and dscr keys.'\ | 
 | #            ' e.g. `{ name: "name", desc: "description"}`'], | 
 | #     'lnw': ["name list+", 'name list that optionally contains a width'], | 
 | #     'lp': ["parameter list", 'parameter list having default value optionally'], | 
 | #     'g': ["group", "comma separated group of key:value enclosed in `{}`"], | 
 | #     'lg': ["list of group", "comma separated group of key:value enclosed in `{}`"\ | 
 | #            " the second entry of the list is the sub group format"], | 
 | #     's': ["string", "string, typically short"], | 
 | #     't': ["text", "string, may be multi-line enclosed in `'''` "\ | 
 | #           "may use `**bold**`, `*italic*` or `!!Reg` markup"], | 
 | #     'T': ["tuple", "tuple enclosed in ()"], | 
 | #     'pi': ["python int", "Native Python type int (generated)"], | 
 | #     'pb': ["python Bool", "Native Python type Bool (generated)"], | 
 | #     'pl': ["python list", "Native Python type list (generated)"], | 
 | #     'pe': ["python enum", "Native Python type enum (generated)"] | 
 | # } | 
 | addr = { | 
 |     'name': 'Address configuration', | 
 |     'description': | 
 |     '''Device Node address configuration. It contains the base address and the size in bytes. | 
 | ''', | 
 |     'required': { | 
 |         'base_addr': | 
 |         ['d', 'Base address of the device. It is required for the device'], | 
 |         'size_byte': | 
 |         ['d', 'Memory space of the device. It is required for the device'], | 
 |     }, | 
 |     'optional': {}, | 
 |     'added': {} | 
 | } | 
 |  | 
 | node = { | 
 |     'name': 'Node configuration', | 
 |     'description': ''' | 
 | Crossbar node description. It can be host, device, or internal nodes. | 
 | ''', | 
 |     'required': { | 
 |         'name': ['s', 'Module instance name'], | 
 |         'stub': ['pb', 'Real node or stub.  Stubs only occupy address ranges'], | 
 |         'type': [ | 
 |             's', | 
 |             'Module type: {"host", "device", "async", "socket_1n", "socket_m1"}' | 
 |         ], | 
 |     }, | 
 |     'optional': { | 
 |         'clock': ['s', 'main clock of the port'], | 
 |         'reset': ['s', 'main reset of the port'], | 
 |         'pipeline': ['pb', 'If true, pipeline is added in front of the port'], | 
 |         'pipeline_byp': [ | 
 |             'pb', 'Pipeline bypass. If true, ' | 
 |             'request/response are not latched' | 
 |         ], | 
 |         'inst_type': ['s', 'Instance type'], | 
 |         'xbar': ['pb', 'If true, the node is connected to another Xbar'], | 
 |         'addr_range': ['lg', addr] | 
 |     }, | 
 |     'added': {} | 
 | } | 
 |  | 
 | root = { | 
 |     'name': 'Top configuration', | 
 |     'description': ''' | 
 | Crossbar configuration format. | 
 | ''', | 
 |     'required': { | 
 |         'name': ['s', 'Name of the crossbar'], | 
 |         'clock': [ | 
 |             's', 'Main clock. Internal components use this clock.' | 
 |             ' If not specified, it is assumed to be in main clock domain' | 
 |         ], | 
 |         'reset': ['s', 'Main reset'], | 
 |         'connections': | 
 |         ['g', "List of edge. Key is host, entry in value list is device"], | 
 |         'clock_connections': ['g', 'list of clocks'], | 
 |         'nodes': ['lg', node] | 
 |     }, | 
 |     'optional': { | 
 |         'type': ['s', 'Indicate Hjson type. "xbar" always if exist'], | 
 |     }, | 
 |     'added': { | 
 |         'reset_connections': [ | 
 |             'g', "Generated by topgen. Key is the reset signal inside IP" | 
 |             " and value is the top reset signal" | 
 |         ], | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | def check_keys(obj, control, prefix=""): | 
 |     """ Check the keys recursively. | 
 |  | 
 |     The control parameter is a control group to check obj data structure. | 
 |     """ | 
 |     error = 0 | 
 |  | 
 |     # required | 
 |     for k, v in control["required"].items(): | 
 |         if k not in obj: | 
 |             error += 1 | 
 |             log.error(prefix + " missing required key " + k) | 
 |  | 
 |     # Check every fields' correctness | 
 |     for k, v in obj.items(): | 
 |         checker = ['', ''] | 
 |         prefix_name = prefix + " " + k | 
 |  | 
 |         if k in control["required"]: | 
 |             checker = control["required"][k] | 
 |         elif k in control["optional"]: | 
 |             checker = control["optional"][k] | 
 |         elif k in control["added"]: | 
 |             log.warning(prefix + " contains generated key " + k) | 
 |             checker = control["added"][k] | 
 |         else: | 
 |             log.warning(prefix + " contains extra key " + k) | 
 |             continue | 
 |  | 
 |         # Type and value check | 
 |         if not checker[0] in val_types: | 
 |             log.error(prefix + | 
 |                       " field {} is undefined type. Check val_types {}".format( | 
 |                           k, checker[0])) | 
 |         if checker[0] == 'lg': | 
 |             # List of subgroup | 
 |             error += sum( | 
 |                 map( | 
 |                     partial(check_keys, control=checker[1], | 
 |                             prefix=prefix_name), obj[k])) | 
 |         elif checker[0] == 'g': | 
 |             # if second entry isn't string type, call recursively | 
 |             if isinstance(checker[1], str): | 
 |                 log.info( | 
 |                     "Skipping {} as no further control group is given".format( | 
 |                         prefix_name)) | 
 |                 continue | 
 |  | 
 |             error += check_keys(obj=obj[k], | 
 |                                 control=checker[1], | 
 |                                 prefix=prefix_name) | 
 |  | 
 |         elif checker[0] == 'd': | 
 |             int_v, err = check_int(obj[k], prefix_name) | 
 |             if err: | 
 |                 error += 1 | 
 |  | 
 |         elif checker[0] == 's' or checker[0] == 't': | 
 |             # don't care the string | 
 |             pass | 
 |  | 
 |         elif checker[0] == 'pb': | 
 |             b_v, err = check_bool(obj[k], prefix_name) | 
 |             if err: | 
 |                 error += 1 | 
 |  | 
 |         elif checker[0] == 'l': | 
 |             if not isinstance(obj[k], list): | 
 |                 error += 1 | 
 |         else: | 
 |             log.error(prefix_name + | 
 |                       " is not supported in this configuration format") | 
 |  | 
 |     return error | 
 |  | 
 |  | 
 | def get_nodetype(t):  # t: str -> NodeType | 
 |     if t == "host": | 
 |         return NodeType.HOST | 
 |     elif t == "device": | 
 |         return NodeType.DEVICE | 
 |     elif t == "async_fifo": | 
 |         return NodeType.ASYNC_FIFO | 
 |     elif t == "socket_1n": | 
 |         return NodeType.SOCKET_1N | 
 |     elif t == "socket_m1": | 
 |         return NodeType.SOCKET_M1 | 
 |  | 
 |     log.error("Cannot process type {}".format(t)) | 
 |     raise | 
 |  | 
 |  | 
 | def checkNameExist(name, xbar):  # name: str -> xbar: Xbar -> bool | 
 |     return name.lower() in [x.name for x in xbar.nodes] | 
 |  | 
 |  | 
 | def isOverlap(range1, range2):  # Tuple[int,int] -> Tuple[int,int] -> bool | 
 |     return not (range2[1] < range1[0] or range2[0] > range1[1]) | 
 |  | 
 |  | 
 | # Tuple[int,int] -> List[Tuple[]] -> bool | 
 | def checkAddressOverlap(addr, ranges): | 
 |     result = [x for x in ranges if isOverlap(x, addr)] | 
 |     return len(result) != 0 | 
 |  | 
 |  | 
 | def validate(obj: OrderedDict) -> Xbar:  # OrderedDict -> Xbar | 
 |     xbar = Xbar() | 
 |     xbar.name = obj["name"].lower() | 
 |     xbar.clock = obj["clock"].lower() | 
 |     xbar.reset = obj["reset"].lower() | 
 |     addr_ranges = [] | 
 |  | 
 |     obj, err = validate_hjson(obj)  # validate Hjson format first | 
 |     if err > 0: | 
 |         log.error("Hjson structure error") | 
 |         return | 
 |  | 
 |     # collection of all clocks and resets of this xbar | 
 |     xbar.clocks = [clock for clock in obj["clock_connections"].keys()] | 
 |     xbar.resets = [reset for reset in obj["reset_connections"].keys()] | 
 |  | 
 |     # Nodes | 
 |     for nodeobj in obj["nodes"]: | 
 |  | 
 |         if checkNameExist(nodeobj["name"], xbar): | 
 |             log.error("Duplicated name: %s" % (nodeobj["name"])) | 
 |             raise SystemExit("Duplicated name in the configuration") | 
 |  | 
 |         clock = nodeobj["clock"].lower() if "clock" in nodeobj.keys( | 
 |         ) else xbar.clock | 
 |  | 
 |         reset = nodeobj["reset"].lower() if "reset" in nodeobj.keys( | 
 |         ) else xbar.reset | 
 |  | 
 |         if clock not in xbar.clocks: | 
 |             log.error( | 
 |                 "Clock %s for module %s does not exist in xbar_%s, check xbar hjson" | 
 |                 % (clock, nodeobj['name'], obj['name'])) | 
 |             raise SystemExit("Clock does not exist") | 
 |  | 
 |         if reset not in xbar.resets: | 
 |             log.error( | 
 |                 "Reset %s for module %s does not exist in xbar_%s, check xbar hjson" | 
 |                 % (reset, nodeobj['name'], obj['name'])) | 
 |             raise SystemExit("Reset does not exist") | 
 |  | 
 |         node = Node(name=nodeobj["name"].lower(), | 
 |                     node_type=get_nodetype(nodeobj["type"].lower()), | 
 |                     clock=clock, | 
 |                     reset=reset) | 
 |  | 
 |         if node.node_type == NodeType.DEVICE: | 
 |             node.xbar = nodeobj["xbar"] | 
 |             node.addr_range = [] | 
 |  | 
 |             # Compact the address ranges | 
 |             if node.xbar: | 
 |                 nodeobj["addr_range"] = simplify_addr(nodeobj, obj) | 
 |  | 
 |             for addr in nodeobj["addr_range"]: | 
 |                 address_from = int(addr["base_addr"], 0) | 
 |                 size = int(addr["size_byte"], 0) | 
 |                 address_to = address_from + size - 1 | 
 |  | 
 |                 addr_entry = (address_from, address_to) | 
 |  | 
 |                 if checkAddressOverlap(addr_entry, addr_ranges): | 
 |                     log.error( | 
 |                         "Address is overlapping. Check the config. Addr(0x%x - 0x%x)" | 
 |                         % (addr_entry[0], addr_entry[1])) | 
 |                     raise SystemExit("Address overlapping error occurred") | 
 |  | 
 |                 addr_ranges.append(addr_entry) | 
 |                 node.addr_range.append(addr_entry) | 
 |  | 
 |         if node.node_type in [NodeType.DEVICE, NodeType.HOST | 
 |                               ] and "pipeline" in nodeobj: | 
 |             node.pipeline = True if nodeobj["pipeline"].lower() in [ | 
 |                 "true", "1" | 
 |             ] else False | 
 |         else: | 
 |             node.pipeline = False | 
 |         if node.node_type in [NodeType.DEVICE, NodeType.HOST | 
 |                               ] and "pipeline_byp" in nodeobj: | 
 |             node.pipeline_byp = True if nodeobj["pipeline_byp"].lower() in [ | 
 |                 "true", "1" | 
 |             ] else False | 
 |         else: | 
 |             node.pipeline_byp = True | 
 |         xbar.nodes.append(node) | 
 |  | 
 |     # Edge | 
 |     for host in obj["connections"].keys(): | 
 |         # host: [device] | 
 |         for device in obj["connections"][host]: | 
 |             xbar.connect_nodes(host.lower(), device.lower()) | 
 |  | 
 |     return xbar | 
 |  | 
 |  | 
 | def validate_hjson(obj): | 
 |     if "type" not in obj: | 
 |         obj["type"] = "xbar" | 
 |     if "name" not in obj: | 
 |         log.error("Component has no name. Aborting.") | 
 |         return None, 1 | 
 |  | 
 |     component = obj["name"] | 
 |     error = check_keys(obj, root, component) | 
 |  | 
 |     if error > 0: | 
 |         log.error("{} has top level error. Aborting".format(component)) | 
 |         return None, error | 
 |     return obj, 0 |