| # 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 .item import Edge, Node, NodeType |
| from .xbar import Xbar |
| |
| from reggen.validate import val_types, check_int, check_bool, check_ln |
| |
| # 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() |
| |
| addr_ranges = [] |
| |
| obj, err = validate_hjson(obj) # validate hjson format first |
| if err > 0: |
| log.error("Hjson structure error") |
| return |
| |
| # Nodes |
| for nodeobj in obj["nodes"]: |
| clock = nodeobj["clock"].lower() if "clock" in nodeobj.keys( |
| ) else xbar.clock |
| |
| if checkNameExist(nodeobj["name"], xbar): |
| log.error("Duplicated name: %s" % (nodeobj["name"])) |
| raise SystemExit("Duplicated name in the configuration") |
| |
| node = Node(name=nodeobj["name"].lower(), |
| node_type=get_nodetype(nodeobj["type"].lower()), |
| clock=clock) |
| |
| 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 |