blob: 4ba2ce953221bb5cf978c83b43df32df7a24f68f [file] [log] [blame]
# 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