| # 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'], | |
| 'req_fifo_pass': ['pb', | |
| 'If true, pipeline fifo has passthrough behavior on req'], | |
| 'rsp_fifo_pass': ['pb', | |
| 'If true, pipeline fifo has passthrough behavior on rsp'], | |
| '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'], | |
| 'clock_group': ['s', "Remnant from auto-generation scripts. Ignore."], | |
| 'clock_srcs': ['g', "Remnant from auto-generation scripts. Ignore."], | |
| 'domain': ['s', 'Power domain for the crossbar'] | |
| }, | |
| 'added': { | |
| 'reset_connections': [ | |
| 'g', "Generated by topgen. Key is the reset signal inside IP" | |
| " and value is the top reset signal" | |
| ], | |
| } | |
| } | |
| # Minimum device spacing that is checked during validation | |
| # by inspecting the base addresses. Note that the validation | |
| # script also ensures that base addresses are aligned with | |
| # to this granularity. | |
| MIN_DEVICE_SPACING = 0x1000 | |
| 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 checker[0] is not None and checker[0] not in val_types: | |
| log.error(prefix + | |
| " field {} is undefined type. Check val_types {}".format( | |
| k, checker[0])) | |
| if checker[0] is None: | |
| pass | |
| elif 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]) | |
| def isNotMinSpacing(range1, range2): # Tuple[int,int] -> Tuple[int,int] -> bool | |
| return not (range2[0] < range1[0] - MIN_DEVICE_SPACING or | |
| range2[0] >= range1[0] + MIN_DEVICE_SPACING) | |
| def isNotAligned(base): # Tuple[int,int] -> bool | |
| return ((base & (MIN_DEVICE_SPACING - 1)) != 0) | |
| # Tuple[int,int] -> List[Tuple[]] -> bool | |
| def checkAddressOverlap(addr, ranges): | |
| result = [x for x in ranges if isOverlap(x, addr)] | |
| return len(result) != 0 | |
| # Tuple[int,int] -> List[Tuple[]] -> bool | |
| def checkAddressSpacing(addr, ranges): | |
| result = [x for x in ranges if isNotMinSpacing(x, addr)] | |
| return len(result) != 0 | |
| # this returns 1 if the size mask overlapps with the address base | |
| def checkBaseSizeOverlap(addr_base, size): | |
| return ((size - 1) & addr_base) | |
| 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 isNotAligned(address_from): | |
| log.error( | |
| "Address bases must be aligned to 0x%x blocks. " | |
| "Check the config. Addr(0x%x - 0x%x)." | |
| % (MIN_DEVICE_SPACING, addr_entry[0], addr_entry[1])) | |
| raise SystemExit("Base alignment error occurred") | |
| if checkBaseSizeOverlap(address_from, size): | |
| log.error( | |
| "Size mask and base address are overlapping. " | |
| " Check the config. Addr(0x%x - 0x%x)" | |
| % (addr_entry[0], addr_entry[1])) | |
| raise SystemExit("Base/size overlapping error occurred") | |
| 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") | |
| if checkAddressSpacing(addr_entry, addr_ranges): | |
| log.error( | |
| "Address bases must be spaced at least 0x%x apart. " | |
| "Check the config. Addr(0x%x - 0x%x)." | |
| % (MIN_DEVICE_SPACING, 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"] else False | |
| else: | |
| node.pipeline = False | |
| if node.node_type in [NodeType.DEVICE, NodeType.HOST]: | |
| node.req_fifo_pass = nodeobj["req_fifo_pass"] \ | |
| if "req_fifo_pass" in nodeobj else False | |
| node.rsp_fifo_pass = nodeobj["rsp_fifo_pass"] \ | |
| if "rsp_fifo_pass" in nodeobj else False | |
| else: | |
| node.req_fifo_pass = False | |
| node.rsp_fifo_pass = False | |
| 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 |