|  | # 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, check_ln, val_types | 
|  |  | 
|  | from .item import Edge, Node, NodeType | 
|  | 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)"] | 
|  | # } | 
|  | node = { | 
|  | 'name': 'Node configuration', | 
|  | 'description': ''' | 
|  | Crossbar node description. It can be host, device, or internal nodes. | 
|  | ''', | 
|  | 'required': { | 
|  | 'name': ['s', 'Module instance name'], | 
|  | 'type': ['s', 'Module type: {"host", "device", "async", "socket_1n", "socket_m1"}'], | 
|  | }, | 
|  | 'optional': { | 
|  | 'clock': ['s', 'main clock of the port'], | 
|  | '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'], | 
|  | 'pipeline': ['pb', 'If true, pipeline is added in front of the port'], | 
|  | 'pipeline_byp': ['pb', 'Pipeline bypass. If true, '\ | 
|  | 'request/response are not latched'] | 
|  | }, | 
|  | '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'], | 
|  | 'connections': | 
|  | ['g', "List of edge. Key is host, entry in value list is device"], | 
|  | '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 not k 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 | 
|  |  | 
|  | 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 | 
|  |  | 
|  | 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 | 
|  | 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: | 
|  | # Add address obj["base_addr"], obj["size"]) | 
|  | node.address_from = int(nodeobj["base_addr"], 0) | 
|  | size = int(nodeobj["size_byte"], 0) | 
|  | node.address_to = node.address_from + size - 1 | 
|  |  | 
|  | addr = (node.address_from, node.address_to) | 
|  |  | 
|  | if checkAddressOverlap(addr, addr_ranges): | 
|  | log.error( | 
|  | "Address is overlapping. Check the config. Addr(0x%x - 0x%x)" | 
|  | % (addr[0], addr[1])) | 
|  | raise SystemExit("Address overlapping error occurred") | 
|  |  | 
|  | addr_ranges.append(addr) | 
|  |  | 
|  | 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 not "type" in obj: | 
|  | obj["type"] = "xbar" | 
|  | if not "name" 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 |