| # 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 |