blob: e9a741c1a5c6706ea5dd9135c1a745a575c09a9a [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 re
import logging as log
from collections import OrderedDict
from enum import Enum
from typing import Dict, List
from reggen.validate import check_keys
from reggen.ip_block import IpBlock
# For the reference
# 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 `{}`"],
# '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)"]
# }
# Required/optional field in top hjson
top_required = {
'name': ['s', 'Top name'],
'type': ['s', 'type of hjson. Shall be "top" always'],
'clocks': ['g', 'group of clock properties'],
'resets': ['l', 'list of resets'],
'module': ['l', 'list of modules to instantiate'],
'memory': ['l', 'list of memories. At least one memory '
'is needed to run the software'],
'xbar': ['l', 'List of the xbar used in the top'],
'pinout': ['g', 'Pinout configuration'],
'targets': ['l', ' Target configurations'],
'pinmux': ['g', 'pinmux configuration'],
}
top_optional = {
'alert_async': ['l', 'async alerts (generated)'],
'alert': ['lnw', 'alerts (generated)'],
'alert_module': [
'l',
'list of the modules that connects to alert_handler'
],
'datawidth': ['pn', "default data width"],
'exported_clks': ['g', 'clock signal routing rules'],
'host': ['g', 'list of host-only components in the system'],
'inter_module': ['g', 'define the signal connections between the modules'],
'interrupt': ['lnw', 'interrupts (generated)'],
'interrupt_module': ['l', 'list of the modules that connects to rv_plic'],
'num_cores': ['pn', "number of computing units"],
'power': ['g', 'power domains supported by the design'],
'port': ['g', 'assign special attributes to specific ports'],
'rnd_cnst_seed': ['int', "Seed for random netlist constant computation"]
}
top_added = {}
pinmux_required = {}
pinmux_optional = {
'num_wkup_detect': [
'd', 'Number of wakeup detectors'
],
'wkup_cnt_width': [
'd', 'Number of bits in wakeup detector counters'
],
'signals': ['l', 'List of Dedicated IOs.'],
}
pinmux_added = {
'ios': ['l', 'Full list of IO'],
}
pinmux_sig_required = {
'instance': ['s', 'Module instance name'],
'connection': ['s', 'Specification of connection type, '
'can be direct, manual or muxed'],
}
pinmux_sig_optional = {
'port': ['s', 'Port name of module'],
'pad': ['s', 'Pad name for direct connections'],
'desc': ['s', 'Signal description'],
'attr': ['s', 'Pad type for generating the correct attribute CSR']
}
pinmux_sig_added = {}
pinout_required = {
'banks': ['l', 'List of IO power banks'],
'pads': ['l', 'List of pads']
}
pinout_optional = {
}
pinout_added = {}
pad_required = {
'name': ['l', 'Pad name'],
'type': ['s', 'Pad type'],
'bank': ['s', 'IO power bank for the pad'],
'connection': ['s', 'Specification of connection type, '
'can be direct, manual or muxed'],
}
pad_optional = {
'desc': ['s', 'Pad description'],
'port_type': ['s', 'Special port type other than `inout wire`']
}
pad_added = {}
target_required = {
'name': ['s', 'Name of target'],
'pinout': ['g', 'Target-specific pinout configuration'],
'pinmux': ['g', 'Target-specific pinmux configuration']
}
target_optional = {
}
target_added = {}
target_pinmux_required = {
'special_signals': ['l', 'List of special signals and the pad they are mapped to.'],
}
target_pinmux_optional = {}
target_pinmux_added = {}
target_pinout_required = {
'remove_ports': ['l', 'List of port names to remove from the port list'],
'remove_pads': ['l', 'List of pad names to remove and stub out'],
'add_pads': ['l', 'List of manual pads to add'],
}
target_pinout_optional = {}
target_pinout_added = {}
straps_required = {
'tap0': ['s', 'Name of tap0 pad'],
'tap1': ['s', 'Name of tap1 pad'],
'dft0': ['s', 'Name of dft0 pad'],
'dft1': ['s', 'Name of dft1 pad'],
}
straps_optional = {}
straps_added = {}
straps_required = {
'tap0': ['s', 'Name of tap0 pad'],
'tap1': ['s', 'Name of tap1 pad'],
'dft0': ['s', 'Name of dft0 pad'],
'dft1': ['s', 'Name of dft1 pad'],
}
straps_optional = {}
straps_added = {}
special_sig_required = {
'name': ['s', 'DIO name'],
'pad': ['s', 'Pad name'],
}
special_sig_optional = {
'desc': ['s', 'Description of signal connection'],
}
special_sig_added = {}
eflash_required = {
'type': ['s', 'string indicating type of memory'],
'banks': ['d', 'number of flash banks'],
'pages_per_bank': ['d', 'number of data pages per flash bank'],
'program_resolution': ['d', 'maximum number of flash words allowed to program'],
}
eflash_optional = {}
eflash_added = {}
module_required = {
'name': ['s', 'name of the instance'],
'type': ['s', 'comportable IP type'],
'clock_srcs': ['g', 'dict with clock sources'],
'clock_group': ['s', 'clock group'],
'reset_connections': ['g', 'dict with reset sources'],
}
module_optional = {
'domain': ['l', 'optional list of power domains, defaults to Domain0'],
'clock_reset_export': ['l', 'optional list with prefixes for exported '
'clocks and resets at the chip level'],
'attr': ['s', 'optional attribute indicating whether the IP is '
'"templated" or "reggen_only"'],
'base_addr': ['s', 'hex start address of the peripheral '
'(if the IP has only a single TL-UL interface)'],
'base_addrs': ['d', 'hex start addresses of the peripheral '
' (if the IP has multiple TL-UL interfaces)'],
'memory': ['g', 'optional dict with memory region attributes'],
'param_decl': ['g', 'optional dict that allows to override instantiation parameters']
}
module_added = {
'clock_connections': ['g', 'generated clock connections']
}
memory_required = {
'label': ['s', 'region label for the linker script'],
'swaccess': ['s', 'access attributes for the memory region (ro, rw)'],
'exec': ['pb', 'executable region indication for the linker script'],
'byte_write': ['pb', 'indicate whether the memory supports byte write accesses'],
}
memory_optional = {
'size': ['d', 'memory region size in bytes for the linker script, '
'xbar and RTL parameterisations'],
'config': ['d', 'Extra configuration for a particular memory'],
'data_intg_passthru': [
'pb',
'Integrity bits are passed through directly from the memory'
]
}
memory_added = {
}
reset_connection_required = {
'name': ['s', 'name of the connecting reset'],
'domain': ['s', 'connected domain'],
}
reset_connection_optional = {}
reset_connection_added = {}
# Supported PAD types.
# Needs to coincide with enum definition in prim_pad_wrapper_pkg.sv
class PadType(Enum):
INPUT_STD = 'InputStd'
BIDIR_STD = 'BidirStd'
BIDIR_TOL = 'BidirTol'
BIDIR_OD = 'BidirOd'
ANALOG_IN0 = 'AnalogIn0'
ANALOG_IN1 = 'AnalogIn1'
DUAL_BIDIR_TOL = 'DualBidirTol'
def is_valid_pad_type(obj):
try:
PadType(obj)
except ValueError:
return False
return True
class TargetType(Enum):
MODULE = "module"
XBAR = "xbar"
class Target:
"""Target class informs the checkers if we are validating a module or xbar
"""
def __init__(self, target_type):
# The type of this target
self.target_type = target_type
# The key to search against
if target_type == TargetType.MODULE:
self.key = "type"
else:
self.key = "name"
class Flash:
"""Flash class contains information regarding parameter defaults.
For now, only expose banks / pages_per_bank for user configuration.
For now, also enforce power of 2 requiremnt.
"""
max_banks = 4
max_pages_per_bank = 1024
def __init__(self, mem, base_addr=0):
self.base_addr = int(base_addr, 16)
self.banks = mem.get('banks', 2)
self.pages_per_bank = mem.get('pages_per_bank', 8)
self.program_resolution = mem.get('program_resolution', 128)
self.words_per_page = 256
self.data_width = 64
self.metadata_width = 12
self.info_types = 3
self.infos_per_bank = [10, 1, 2]
self.word_bytes = int(self.data_width / 8)
self.pgm_resolution_bytes = int(self.program_resolution * self.word_bytes)
self.check_values()
# populate size variable
self.bytes_per_page = self.word_bytes * self.words_per_page
self.bytes_per_bank = self.bytes_per_page * self.pages_per_bank
self.total_bytes = self.bytes_per_bank * self.banks
size_int = int(self.total_bytes)
self.size = hex(size_int)
self.end_addr = self.base_addr + size_int
def is_pow2(self, n):
return (n != 0) and (n & (n - 1) == 0)
def check_values(self):
pow2_check = (self.is_pow2(self.banks) and
self.is_pow2(self.pages_per_bank) and
self.is_pow2(self.program_resolution))
limit_check = ((self.banks <= Flash.max_banks) and
(self.pages_per_bank <= Flash.max_pages_per_bank))
if not pow2_check:
raise ValueError('flash power of 2 check failed. A supplied parameter '
'is not power of 2')
if not limit_check:
raise ValueError('flash number of banks and pages per bank too large')
def _asdict(self):
return {
'banks': self.banks,
'pages_per_bank': self.pages_per_bank,
'program_resolution': self.program_resolution,
'pgm_resolution_bytes': self.pgm_resolution_bytes,
'bytes_per_page': self.bytes_per_page,
'bytes_per_bank': self.bytes_per_bank,
'size': self.size
}
# Check to see if each module/xbar defined in top.hjson exists as ip/xbar.hjson
# Also check to make sure there are not multiple definitions of ip/xbar.hjson for each
# top level definition
# If it does, return a dictionary of instance names to index in ip/xbarobjs
def check_target(top, objs, tgtobj):
error = 0
idxs = OrderedDict()
# Collect up counts of object names. We support entries of objs that are
# either dicts (for top-levels) or IpBlock objects.
name_indices = {}
for idx, obj in enumerate(objs):
if isinstance(obj, IpBlock):
name = obj.name.lower()
else:
name = obj['name'].lower()
log.info("%d Order is %s" % (idx, name))
name_indices.setdefault(name, []).append(idx)
tgt_type = tgtobj.target_type.value
inst_key = tgtobj.key
for cfg in top[tgt_type]:
cfg_name = cfg['name'].lower()
log.info("Checking target %s %s" % (tgt_type, cfg_name))
indices = name_indices.get(cfg[inst_key], [])
if not indices:
log.error("Could not find %s.hjson" % cfg_name)
error += 1
elif len(indices) > 1:
log.error("Duplicate %s.hjson" % cfg_name)
error += 1
else:
idxs[cfg_name] = indices[0]
log.info("Current state %s" % idxs)
return error, idxs
def check_pad(top: Dict,
pad: Dict,
known_pad_names: Dict,
valid_connections: List[str],
prefix: str) -> int:
error = 0
error += check_keys(pad, pad_required, pad_optional,
pad_added, prefix)
# check name uniqueness
if pad['name'] in known_pad_names:
log.warning('Pad name {} is not unique'.format(pad['name']))
error += 1
known_pad_names[pad['name']] = 1
if not is_valid_pad_type(pad['type']):
log.warning('Unkown pad type {}'.format(pad['type']))
error += 1
if pad['bank'] not in top['pinout']['banks']:
log.warning('Unkown io power bank {}'.format(pad['bank']))
error += 1
if pad['connection'] not in valid_connections:
log.warning('Connection type {} of pad {} is invalid'
.format(pad['connection'], pad['name']))
error += 1
pad.setdefault('port_type', 'inout')
return error
def check_pinout(top: Dict, prefix: str) -> int:
error = check_keys(top['pinout'], pinout_required, pinout_optional,
pinout_added, prefix + ' Pinout')
known_names = {}
for pad in top['pinout']['pads']:
error += check_keys(pad, pad_required, pad_optional,
pad_added, prefix + ' Pinout')
error += check_pad(top, pad, known_names,
['direct', 'manual', 'muxed'],
prefix + ' Pad')
return error
def check_pinmux(top: Dict, prefix: str) -> int:
error = check_keys(top['pinmux'], pinmux_required, pinmux_optional,
pinmux_added, prefix + ' Pinmux')
# This is used for the direct connection accounting below,
# where we tick off already connected direct pads.
known_direct_pads = {}
direct_pad_attr = {}
for pad in top['pinout']['pads']:
if pad['connection'] == 'direct':
known_direct_pads[pad['name']] = 1
direct_pad_attr[pad['name']] = pad['type']
# Note: the actual signal crosscheck is deferred until the merge stage,
# since we have no idea at this point which IOs comportable IPs expose.
for sig in top['pinmux']['signals']:
error += check_keys(sig, pinmux_sig_required, pinmux_sig_optional,
pinmux_sig_added, prefix + ' Pinmux signal')
if sig['connection'] not in ['direct', 'manual', 'muxed']:
log.warning('Invalid connection type {}'.format(sig['connection']))
error += 1
# The pad needs to refer to a valid pad name in the pinout that is of
# connection type "direct". We tick off all direct pads that have been
# referenced in order to make sure there are no double connections
# and unconnected direct pads.
padname = sig.setdefault('pad', '')
if padname != '':
if padname in known_direct_pads:
if known_direct_pads[padname] == 1:
known_direct_pads[padname] = 0
padattr = direct_pad_attr[padname]
else:
log.warning('Warning, direct pad {} is already connected'
.format(padname))
error += 1
else:
log.warning('Unknown direct pad {}'.format(padname))
error += 1
# Check port naming scheme.
port = sig.setdefault('port', '')
pattern = r'^[a-zA-Z0-9_]*(\[[0-9]*\]){0,1}'
matches = re.match(pattern, port)
if matches is None:
log.warning('Port name {} has wrong format'
.format(port))
error += 1
# Check that only direct connections have pad keys
if sig['connection'] == 'direct':
if sig.setdefault('attr', '') != '':
log.warning('Direct connection of instance {} port {} '
'must not have an associated pad attribute field'
.format(sig['instance'],
sig['port']))
error += 1
# Since the signal is directly connected, we can automatically infer
# the pad type needed to instantiate the correct attribute CSR WARL
# module inside the pinmux.
sig['attr'] = padattr
if padname == '':
log.warning('Instance {} port {} connection is of direct type '
'and therefore must have an associated pad name.'
.format(sig['instance'],
sig['port']))
error += 1
if port == '':
log.warning('Instance {} port {} connection is of direct type '
'and therefore must have an associated port name.'
.format(sig['instance'],
sig['port']))
error += 1
elif sig['connection'] == 'muxed':
# Muxed signals do not have a corresponding pad and attribute CSR,
# since they first go through the pinmux matrix.
if sig.setdefault('attr', '') != '':
log.warning('Muxed connection of instance {} port {} '
'must not have an associated pad attribute field'
.format(sig['instance'],
sig['port']))
error += 1
if padname != '':
log.warning('Muxed connection of instance {} port {} '
'must not have an associated pad'
.format(sig['instance'],
sig['port']))
error += 1
elif sig['connection'] == 'manual':
# This pad attr key is only allowed in the manual case,
# as there is no way to infer the pad type automatically.
sig.setdefault('attr', 'BidirStd')
if padname != '':
log.warning('Manual connection of instance {} port {} '
'must not have an associated pad'
.format(sig['instance'],
sig['port']))
error += 1
# At this point, all direct pads should have been ticked off.
for key, val in known_direct_pads.items():
if val == 1:
log.warning('Direct pad {} has not been connected'
.format(key))
error += 1
return error
def check_implementation_targets(top: Dict, prefix: str) -> int:
error = 0
known_names = {}
for target in top['targets']:
error += check_keys(target, target_required, target_optional,
target_added, prefix + ' Targets')
# check name uniqueness
if target['name'] in known_names:
log.warning('Target name {} is not unique'.format(target['name']))
error += 1
known_names[target['name']] = 1
error += check_keys(target['pinmux'], target_pinmux_required, target_pinmux_optional,
target_pinmux_added, prefix + ' Target pinmux')
error += check_keys(target['pinout'], target_pinout_required, target_pinout_optional,
target_pinout_added, prefix + ' Target pinout')
# Check special pad signals
known_entry_names = {}
for entry in target['pinmux']['special_signals']:
error += check_keys(entry, special_sig_required, special_sig_optional,
special_sig_added, prefix + ' Special signal')
# check name uniqueness
if entry['name'] in known_entry_names:
log.warning('Special pad name {} is not unique'.format(entry['name']))
error += 1
known_entry_names[entry['name']] = 1
# The pad key needs to refer to a valid pad name.
is_muxed = False
for pad in top['pinout']['pads']:
if entry['pad'] == pad['name']:
is_muxed = pad['connection'] == 'muxed'
break
else:
log.warning('Unknown pad {}'.format(entry['pad']))
error += 1
if not is_muxed:
# If this is not a muxed pad, we need to make sure this refers to
# DIO that is NOT a manual pad.
for sig in top['pinmux']['signals']:
if entry['pad'] == sig['pad']:
break
else:
log.warning('Special pad {} cannot refer to a manual pad'.format(entry['pad']))
error += 1
# Check ports to remove
for entry in target['pinout']['remove_ports']:
# The pad key needs to refer to a valid pad name.
for pad in top['pinout']['pads'] + target['pinout']['add_pads']:
if entry == pad['name']:
break
else:
log.warning('Unknown pad {}'.format(entry))
error += 1
# Check pads to remove and stub out
for entry in target['pinout']['remove_pads']:
# The pad key needs to refer to a valid pad name.
for pad in top['pinout']['pads']:
if entry == pad['name']:
break
else:
log.warning('Unknown pad {}'.format(entry))
error += 1
# Check pads to add
known_pad_names = {}
for pad in top['pinout']['pads']:
known_pad_names.update({pad['name']: 1})
for pad in target['pinout']['add_pads']:
error += check_pad(top, pad, known_pad_names, ['manual'],
prefix + ' Additional Pad')
return error
def check_clocks_resets(top, ipobjs, ip_idxs, xbarobjs, xbar_idxs):
error = 0
# there should only be one each of pwrmgr/clkmgr/rstmgr
pwrmgrs = [m for m in top['module'] if m['type'] == 'pwrmgr']
clkmgrs = [m for m in top['module'] if m['type'] == 'clkmgr']
rstmgrs = [m for m in top['module'] if m['type'] == 'rstmgr']
if len(pwrmgrs) == 1 * len(clkmgrs) == 1 * len(rstmgrs) != 1:
log.error("Incorrect number of pwrmgr/clkmgr/rstmgr")
error += 1
# all defined clock/reset nets
reset_nets = [reset['name'] for reset in top['resets']['nodes']]
clock_srcs = list(top['clocks'].all_srcs.keys())
# Check clock/reset port connection for all IPs
for ipcfg in top['module']:
ipcfg_name = ipcfg['name'].lower()
log.info("Checking clock/resets for %s" % ipcfg_name)
error += validate_reset(ipcfg, ipobjs[ip_idxs[ipcfg_name]], reset_nets)
error += validate_clock(ipcfg, ipobjs[ip_idxs[ipcfg_name]], clock_srcs)
if error:
log.error("module clock/reset checking failed")
break
# Check clock/reset port connection for all xbars
for xbarcfg in top['xbar']:
xbarcfg_name = xbarcfg['name'].lower()
log.info("Checking clock/resets for xbar %s" % xbarcfg_name)
error += validate_reset(xbarcfg, xbarobjs[xbar_idxs[xbarcfg_name]],
reset_nets, "xbar")
error += validate_clock(xbarcfg, xbarobjs[xbar_idxs[xbarcfg_name]],
clock_srcs, "xbar")
if error:
log.error("xbar clock/reset checking failed")
break
return error
# Checks the following
# For each defined reset connection in top*.hjson, there exists a defined port at the destination
# and defined reset net
# There are the same number of defined connections as there are ports
def validate_reset(top, inst, reset_nets, prefix=""):
# Gather inst port list
error = 0
# Handle either an IpBlock (generated by reggen) or an OrderedDict
# (generated by topgen for a crossbar)
if isinstance(inst, IpBlock):
name = inst.name
reset_signals = inst.clocking.reset_signals()
else:
name = inst['name']
reset_signals = ([inst.get('reset_primary', 'rst_ni')] +
inst.get('other_reset_list', []))
log.info("%s %s resets are %s" %
(prefix, name, reset_signals))
# Check if reset connections are properly formatted
# There are two options
# The reset connection for a particular port must be a str
# The reset connection for a paritcular port must be a dict
# If value is a string, the module can only have ONE domain
# If value is a dict, it must have the keys name / domain, and the
# value of domain must match that defined for the module.
for port, reset in top["reset_connections"].items():
if isinstance(reset, str):
top["reset_connections"][port] = {}
top["reset_connections"][port]['name'] = reset
if len(top["domain"]) > 1:
raise ValueError(f"{top['name']} reset connection {reset} "
"has no assigned domain")
else:
top["reset_connections"][port]['domain'] = top["domain"][0]
if isinstance(reset, dict):
error += check_keys(reset,
reset_connection_required,
reset_connection_optional,
reset_connection_added,
'dict structure for reset connections')
if reset['domain'] not in top["domain"]:
error += 1
log.error(f"domain {reset['domain']} defined for reset {reset['name']} "
f"is not a domain of {top['name']}")
# Check if the reset connections are fully populated
if len(top['reset_connections']) != len(reset_signals):
error += 1
log.error("%s %s mismatched number of reset ports and nets" %
(prefix, name))
missing_port = [
port for port in top['reset_connections'].keys()
if port not in reset_signals
]
if missing_port:
error += 1
log.error("%s %s Following reset ports do not exist:" %
(prefix, name))
[log.error("%s" % port) for port in missing_port]
missing_net = [
net['name'] for net in top['reset_connections'].values()
if net['name'] not in reset_nets
]
if missing_net:
error += 1
log.error("%s %s Following reset nets do not exist:" %
(prefix, name))
[log.error("%s" % net) for net in missing_net]
return error
# Checks the following
# For each defined clock_src in top*.hjson, there exists a defined port at the destination
# and defined clock source
# There are the same number of defined connections as there are ports
def validate_clock(top, inst, clock_srcs, prefix=""):
# Gather inst port list
error = 0
# Handle either an IpBlock (generated by reggen) or an OrderedDict
# (generated by topgen for a crossbar)
if isinstance(inst, IpBlock):
name = inst.name
clock_signals = inst.clocking.clock_signals(False)
else:
name = inst['name']
clock_signals = ([inst.get('clock_primary', 'rst_ni')] +
inst.get('other_clock_list', []))
if len(top['clock_srcs']) != len(clock_signals):
error += 1
log.error("%s %s mismatched number of clock ports and nets" %
(prefix, name))
missing_port = [
port for port in top['clock_srcs'].keys()
if port not in clock_signals
]
if missing_port:
error += 1
log.error("%s %s Following clock ports do not exist:" %
(prefix, name))
[log.error("%s" % port) for port in missing_port]
missing_net = []
for port, net in top['clock_srcs'].items():
net_name = net['clock'] if isinstance(net, Dict) else net
if net_name not in clock_srcs:
missing_net.append(net)
if missing_net:
error += 1
log.error("%s %s Following clock nets do not exist:" %
(prefix, name))
[log.error("%s" % net) for net in missing_net]
return error
def check_flash(top):
for mem in top['memory']:
if mem['type'] == "eflash":
raise ValueError('top level flash memory definition not supported. Please use '
'the flash embedded inside flash_ctrl instead. If there is a '
'need for top level flash memory, please file an issue.')
def check_power_domains(top):
# check that the default domain is valid
if top['power']['default'] not in top['power']['domains']:
raise ValueError(f"Default power domain {top['power']['default']} is "
"not a valid domain")
# Check that each module, xbar, memory has a power domain defined.
# If not, give it a default.
# If there is one defined, check that it is a valid definition
for end_point in top['module'] + top['memory'] + top['xbar']:
if 'domain' not in end_point:
end_point['domain'] = [top['power']['default']]
for d in end_point['domain']:
if d not in top['power']['domains']:
raise ValueError(f"{end_point['name']} defined invalid domain {d}")
def check_modules(top, prefix):
error = 0
for m in top['module']:
modname = m.get("name", "unnamed module")
error += check_keys(m, module_required, module_optional, module_added,
prefix + " " + modname)
# these fields are mutually exclusive
if 'base_addr' in m and 'base_addrs' in m:
log.error("{} {} a module cannot define both the 'base_addr' "
"and 'base_addrs' keys at the same time"
.format(prefix, modname))
error += 1
if 'base_addrs' in m and 'memory' in m:
for intf, value in m['memory'].items():
error += check_keys(value, memory_required,
memory_optional, memory_added,
prefix + " " + modname + " " + intf)
# if size is not declared, there must be extra config to determine it
if 'size' not in value and 'config' not in value:
raise ValueError(f'{m["name"]} memory declaration has neither size '
'nor extra configuration. Unable to determine '
'memory size')
if 'size' not in value:
mem_type = value['config'].get('type', "")
if mem_type == "flash":
check_keys(value['config'], eflash_required, eflash_optional,
eflash_added, "Eflash")
flash = Flash(value['config'], m['base_addrs'][intf])
value['size'] = flash.size
value['config'] = flash
else:
raise ValueError(f'{m["name"]} memory config declaration does not have '
'a valid type')
# make sure the memory regions correspond to the TL-UL interfaces
if intf not in m['base_addrs']:
log.error("{} {} memory region {} does not "
"correspond to any of the defined "
"TL-UL interfaces".format(prefix, modname, intf))
error += 1
# make sure the linker region access attribute is valid
attr = value.get('swaccess', 'unknown attribute')
if attr not in ['ro', 'rw']:
log.error('{} {} swaccess attribute {} of memory region {} '
'is not valid'.format(prefix, modname, attr, intf))
error += 1
return error
def validate_top(top, ipobjs, xbarobjs):
# return as it is for now
error = check_keys(top, top_required, top_optional, top_added, "top")
if error != 0:
log.error("Top HJSON has top level errors. Aborting")
return top, error
component = top['name']
# Check module instantiations
error += check_modules(top, component)
# MODULE check
err, ip_idxs = check_target(top, ipobjs, Target(TargetType.MODULE))
error += err
# XBAR check
err, xbar_idxs = check_target(top, xbarobjs, Target(TargetType.XBAR))
error += err
# MEMORY check
check_flash(top)
# Power domain check
check_power_domains(top)
# Clock / Reset check
error += check_clocks_resets(top, ipobjs, ip_idxs, xbarobjs, xbar_idxs)
# RV_PLIC check
# Pinout, pinmux and target checks
# Note that these checks must happen in this order, as
# the pinmux and target configs depend on the pinout.
error += check_pinout(top, component)
error += check_pinmux(top, component)
error += check_implementation_targets(top, component)
return top, error