blob: dd07b8611c3174e066a6ef0c8297fef4a7cbb282 [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""
Register JSON validation
"""
import logging as log
from collections import OrderedDict
from .access import SWAccess, HWAccess
from .bits import Bits
from .field import Field
from .reg_block import RegBlock
from .register import Register
# Routine that can be used for Hjson object_pairs_hook
# The baseline is dict(pairs) i.e. construct a dictonary from pairs
# The usual is OrderedDict(pairs) which is redundant in latest python
# Both of these silently allow repeated keys, which this version detects
def checking_dict(pairs):
d = {}
for x in pairs:
if x[0] in d:
repkey = 'Repeated' + x[0]
log.warn("Repeated key " + x[0] + " added as " + repkey)
d[repkey] = x[1]
else:
d[x[0]] = x[1]
return d
# validating version of int(x, 0)
# returns int value, error flag
# if error flag is True value will be zero
def check_int(x, err_prefix, suppress_err_msg=False):
if isinstance(x, int):
return x, False
if x[0] == '0' and len(x) > 2:
if x[1] in 'bB':
validch = '01'
elif x[1] in 'oO':
validch = '01234567'
elif x[1] in 'xX':
validch = '0123456789abcdefABCDEF'
else:
if not suppress_err_msg:
log.error(err_prefix +
": int must start digit, 0b, 0B, 0o, 0O, 0x or 0X")
return 0, True
for c in x[2:]:
if c not in validch:
if not suppress_err_msg:
log.error(err_prefix + ": Bad character " + c + " in " + x)
return 0, True
else:
if not x.isdecimal():
if not suppress_err_msg:
log.error(err_prefix + ": Number not valid int " + x)
return 0, True
return int(x, 0), False
def check_bool(x, err_prefix):
"""check_bool checks if input 'x' is one of the list:
"true", "false"
It returns value as Bool type and Error condition.
"""
if isinstance(x, bool):
# if Bool returns as it is
return x, False
if not x.lower() in ["true", "false"]:
log.error(err_prefix + ": Bad field value " + x)
return False, True
else:
return (x.lower() == "true"), False
def check_ln(obj, x, withwidth, err_prefix):
error = 0
if not isinstance(obj[x], list):
log.error(err_prefix + ' element ' + x + ' not a list')
return 1
for y in obj[x]:
error += check_keys(y, ln_required, ln_optional if withwidth else {},
{}, err_prefix + ' element ' + x)
if withwidth:
if 'width' in y:
w, err = check_int(y['width'], err_prefix + ' width in ' + x)
if err:
error += 1
w = 1
else:
w = 1
y['width'] = str(w)
return error
def check_lp(obj, x, err_prefix):
error = 0
if not isinstance(obj[x], list):
log.error(err_prefix + ' element ' + x + ' not a list')
return 1
for y in obj[x]:
error += check_keys(y, lp_required, lp_optional, {},
err_prefix + ' element ' + x)
# If this is a random netlist constant, other attributes like local, default and expose
# are automatically set. Throw an error if they already exist in the dict.
randcount = int(y.setdefault('randcount', "0"))
randtype = y.setdefault('randtype', "none")
if randtype != "none":
if randcount <= 0:
log.error(err_prefix + ' randwith for parameter ' + y['name'] +
' must be greater > 0.')
return error + 1
if randtype not in ['perm', 'data']:
log.error(err_prefix + ' parameter ' + y['name'] +
' has unknown randtype ' + randtype)
return error + 1
if y.get('type') is None:
log.error(
err_prefix + ' parameter ' + y['name'] +
' has undefined type. '
'It is required to define the type in the IP package.')
return error + 1
if not y.get('name').lower().startswith('rndcnst'):
log.error(
err_prefix + ' parameter ' + y['name'] +
' is defined as a compile-time '
'random netlist constant. The name must therefore start with RndCnst.'
)
return error + 1
overrides = [('local', 'false'), ('default', ''),
('expose', 'false')]
for key, value in overrides:
if y.setdefault(key, value) != value:
log.error(
err_prefix + ' ' + key + ' for parameter ' +
y['name'] +
' must not be set since it will be defined automatically.'
)
return error + 1
# TODO: Check if PascalCase or ALL_CAPS
y.setdefault('type', 'int')
y.setdefault('local', 'true')
local, ierr = check_bool(y["local"], err_prefix + " local")
if ierr:
error += 1
y["local"] = "true"
y.setdefault('expose', 'false')
local, ierr = check_bool(y["expose"], err_prefix + " expose")
if ierr:
error += 1
y["expose"] = "false"
if y["local"] == "true" and y["expose"] == "true":
log.error(err_prefix + ' element ' + x + '["' + y["name"] + '"]' +
' cannot be local and exposed to top level')
return error + 1
if "default" in y:
if y["type"][:3] == "int":
default, ierr = check_int(y["default"],
err_prefix + " default")
if ierr:
error += 1
y["default"] = "1"
elif y["randtype"] != "none":
# Don't make assumptions for exposed parameters. These must have
# a default.
if y["expose"] == "true":
log.error(err_prefix + ' element ' + x + '["' + y["name"] +
'"]' + ' has no defined default value')
elif y["type"][:3] == "int":
y["default"] = "1"
elif y["type"] == "string":
y["default"] = ""
else:
log.error(err_prefix + ' element ' + x + '["' + y["name"] +
'"]' + ' type is not supported')
return error + 1
return error
def search_param(obj, key):
"""return the param object if found, else return non zero error
"""
for p in obj:
if p["name"] == key:
return p, 0
log.error("Param {} cannot be found".format(key))
return None, 1
def check_keys(obj, required_keys, optional_keys, added_keys, err_prefix):
error = 0
for x in required_keys:
if x not in obj:
error += 1
log.error(err_prefix + " missing required key " + x)
for x in obj:
type = None
if x in required_keys:
type = required_keys[x][0]
elif x in optional_keys:
type = optional_keys[x][0]
elif x not in added_keys:
log.warning(err_prefix + " contains extra key " + x)
if type is not None:
if type[:2] == 'ln':
error += check_ln(obj, x, type == 'lnw', err_prefix)
if type == 'lp':
error += check_lp(obj, x, err_prefix)
return error
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)"]
}
# Toplevel keys
top_required = {
'name': ['s', "name of the component"],
'clock_primary': ['s', "name of the primary clock"],
'bus_device': ['s', "name of the bus interface for the device"],
'registers':
['l', "list of register definition groups and "
"offset control groups"]
}
top_optional = {
'alert_list': ['lnw', "list of peripheral alerts"],
'available_inout_list': ['lnw', "list of available peripheral inouts"],
'available_input_list': ['lnw', "list of available peripheral inputs"],
'available_output_list': ['lnw', "list of available peripheral outputs"],
'bus_host': ['s', "name of the bus interface as host"],
'hier_path': [
None,
'additional hierarchy path before the reg block instance'
],
'interrupt_list': ['lnw', "list of peripheral interrupts"],
'inter_signal_list': ['l', "list of inter-module signals"],
'no_auto_alert_regs': [
's', "Set to true to suppress automatic "
"generation of alert test registers. "
"Defaults to true if no alert_list is present. "
"Otherwise this defaults to false. "
],
'no_auto_intr_regs': [
's', "Set to true to suppress automatic "
"generation of interrupt registers. "
"Defaults to true if no interrupt_list is present. "
"Otherwise this defaults to false. "
],
'other_clock_list': ['l', "list of other chip clocks needed"],
'other_reset_list': ['l', "list of other resets"],
'param_list': ['lp', "list of parameters of the IP"],
'regwidth': ['d', "width of registers in bits (default 32)"],
'reset_primary': ['s', "primary reset used by the module"],
'reset_request_list': ['l', 'list of signals requesting reset'],
'scan': ['pb', 'Indicates the module have `scanmode_i`'],
'scan_reset': ['pb', 'Indicates the module have `test_rst_ni`'],
'SPDX-License-Identifier': [
's', "License ientifier (if using pure json) "
"Only use this if unable to put this "
"information in a comment at the top of the "
"file."
],
'wakeup_list': ['lnw', "list of peripheral wakeups"]
}
top_added = {
'genrnames': ['pl', "list of register names"],
'genautoregs': ['pb', "Registers were generated from config info"],
'gensize': [
'pi', "address space size needed for registers. "
"Generated by tool as next power of 2."
]
}
# ln type has list of groups with only name and description
# (was called "subunit" in cfg_validate)
ln_required = {
'name': ['s', "name of the item"],
'desc': ['s', "description of the item"],
}
ln_optional = {
'width': ['d', "bit width of the item (if not 1)"],
}
# lp type
lp_required = {
'name': ['s', "name of the item"],
}
lp_optional = {
'desc': ['s', "description of the item"],
'type': ['s', "item type. int by default"],
'default': ['s', "item default value"],
'local': ['pb', "to be localparam"],
'expose': ['pb', "to be exposed to top"],
'randcount':
['s', "number of bits to randomize in the parameter. 0 by default."],
'randtype': ['s', "type of randomization to perform. none by default"],
}
# Registers list may have embedded keys
list_optone = {
'reserved': ['d', "number of registers to reserve space for"],
'skipto': ['d', "set next register offset to value"],
'window': [
'g', "group defining an address range "
"for something other than standard registers"
],
'multireg':
['g', "group defining registers generated "
"from a base instance."]
}
key_use = {'r': "required", 'o': "optional", 'a': "added by tool"}
def make_intr_alert_reg(reg_block, regs, name, swaccess, hwaccess, desc):
if name == 'ALERT_TEST':
signal_list = regs['alert_list']
else:
signal_list = regs['interrupt_list']
# these names will be converted into test registers
testreg_names = ['INTR_TEST', 'ALERT_TEST']
swaccess_obj = SWAccess('make_intr_alert_reg()', swaccess)
hwaccess_obj = HWAccess('make_intr_alert_reg()', hwaccess)
fields = []
cur_bit = 0
for (field_idx, bit) in enumerate(signal_list):
w = 1
if 'width' in bit and bit['width'] != '1':
w = int(bit['width'], 0)
field_bits = Bits(cur_bit + w - 1, cur_bit)
else:
field_bits = Bits(cur_bit, cur_bit)
cur_bit += w
field_name = bit['name']
if name == 'INTR_ENABLE':
field_desc = ('Enable interrupt when {}!!INTR_STATE.{} is set.'
.format('corresponding bit in ' if w > 1 else '',
field_name))
elif name == 'INTR_TEST':
field_desc = ('Write 1 to force {}!!INTR_STATE.{} to 1.'
.format('corresponding bit in ' if w > 1 else '',
field_name))
elif name == 'ALERT_TEST':
field_desc = 'Write 1 to trigger one alert event of this kind.'
else:
field_desc = bit['desc']
# Put the automatically generated information back into
# `interrupt_list`, so that it can be used to generate C preprocessor
# definitions if needed.
signal_list[field_idx]['bits'] = field_bits.as_str()
signal_list[field_idx]['bitinfo'] = (field_bits.bitmask(),
field_bits.width(),
field_bits.lsb)
fields.append(Field(field_name,
field_desc,
bit.get('tags', []),
swaccess_obj,
hwaccess_obj,
hwqe=name in testreg_names,
hwre=False,
bits=field_bits,
resval=0,
enum=None))
hwext = 'true' if name in testreg_names else 'false'
if name == 'INTR_TEST':
# intr_test csr is WO which - it reads back 0s
reg_tags = ["excl:CsrNonInitTests:CsrExclWrite"]
elif name == 'INTR_STATE':
# intr_state csr is affected by writes to other csrs - skip write-check
reg_tags = ["excl:CsrNonInitTests:CsrExclWriteCheck"]
else:
reg_tags = []
bool_hwext = hwext.lower() == 'true'
reg = Register(reg_block.offset,
name,
desc,
swaccess_obj,
hwaccess_obj,
hwext=bool_hwext,
hwqe=bool_hwext,
hwre=False,
regwen=None,
tags=reg_tags,
resval=None,
shadowed=False,
fields=fields,
update_err_alert=None,
storage_err_alert=None)
reg_block.add_register(reg)
return reg
def make_intr_regs(reg_block, regs, fullwidth):
iregs = []
intrs = regs['interrupt_list']
num_intrs = sum([int(x.get('width', '1'), 0) for x in intrs])
if num_intrs > fullwidth:
log.error('More than ' + str(fullwidth) + ' interrupts in list')
return iregs, 1
try:
new_reg = make_intr_alert_reg(reg_block, regs, 'INTR_STATE', 'rw1c',
'hrw', 'Interrupt State Register')
iregs.append(new_reg)
new_reg = make_intr_alert_reg(reg_block, regs, 'INTR_ENABLE', 'rw',
'hro', 'Interrupt Enable Register')
iregs.append(new_reg)
new_reg = make_intr_alert_reg(reg_block, regs, 'INTR_TEST',
'wo', 'hro', 'Interrupt Test Register')
iregs.append(new_reg)
except ValueError as err:
log.error(str(err))
return iregs, 1
return iregs, 0
def make_alert_regs(reg_block, regs, fullwidth):
alert_regs = []
alerts = regs['alert_list']
num_alerts = sum([int(x.get('width', '1'), 0) for x in alerts])
if num_alerts > fullwidth:
log.error('More than ' + str(fullwidth) + ' alerts in list')
return alert_regs, 1
try:
new_reg = make_intr_alert_reg(reg_block, regs, 'ALERT_TEST',
'wo', 'hro', 'Alert Test Register')
alert_regs.append(new_reg)
except ValueError as err:
log.error(str(err))
return alert_regs, 1
return alert_regs, 0
def validate(regs, **kwargs):
if "params" in kwargs:
params = kwargs["params"]
else:
params = []
if 'name' not in regs:
log.error("Component has no name. Aborting.")
return 1
component = regs['name']
regs.setdefault('param_list', [])
error = check_keys(regs, top_required, top_optional, top_added, component)
if (error > 0):
log.error("Component has top level errors. Aborting.")
return error
regs['genrnames'] = []
error = 0
if 'regwidth' in regs:
fullwidth, ierr = check_int(regs['regwidth'], "regwidth")
if ierr:
fullwidth = 32
error += 1
else:
fullwidth = 32
log.warning('regwidth not specified, assuming 32.')
regs['regwidth'] = str(fullwidth)
if ((fullwidth % 8) != 0):
addrsep = (fullwidth // 8) + 1
log.warning("regwidth is not a multiple of 8 bits!")
else:
addrsep = fullwidth // 8
reg_block = RegBlock(addrsep, fullwidth, regs.get('param_list', []))
autoregs = []
# auto header generation would go here and update autoregs
if 'no_auto_intr_regs' in regs:
no_auto_intr, err = check_bool(regs['no_auto_intr_regs'],
'no_auto_intr_regs')
if err:
error += 1
else:
if 'interrupt_list' not in regs:
no_auto_intr = True
else:
no_auto_intr = False
if 'no_auto_alert_regs' in regs:
no_auto_alerts, err = check_bool(regs['no_auto_alert_regs'],
'no_auto_alert_regs')
if err:
error += 1
else:
if 'alert_list' not in regs:
no_auto_alerts = True
else:
no_auto_alerts = False
if 'interrupt_list' in regs and 'genautoregs' not in regs and not no_auto_intr:
iregs, err = make_intr_regs(reg_block, regs, fullwidth)
error += err
autoregs.extend(iregs)
# Generate a NumAlerts parameter for provided alert_list.
if regs.setdefault('alert_list', []):
# Generate alert test registers.
if 'genautoregs' not in regs and not no_auto_alerts:
aregs, err = make_alert_regs(reg_block, regs, fullwidth)
error += err
autoregs.extend(aregs)
num_alerts = 0
for alert in regs['alert_list']:
alert_width = int(alert.get('width', '1'), 0)
num_alerts += alert_width
if alert_width > 1:
log.warning(
"{}: Consider naming each alert individually instead of "
"declaring an alert signal with width > 1.".format(
alert['name']))
# check alert naming scheme
if alert['name'] == "":
log.error("{}: Alert name cannot be empty".format(alert['name']))
error += 1
prefix = alert['name'].split('_')
if prefix[0] not in ['recov', 'fatal']:
log.error(
"{}: Alerts must be prefixed with either 'recov_' or "
"'fatal_'.".format(alert['name']))
error += 1
if num_alerts != 0:
param = ''
for p in regs['param_list']:
if p['name'] == 'NumAlerts':
param = p
if param:
# We already have an NumAlerts parameter.
if (param['type'] != 'int' or
param['default'] != str(num_alerts) or
param['local'] != 'true'):
log.error(
'Conflicting definition of NumAlerts parameter found.')
error += 1
else:
# Generate the NumAlerts parameter.x
regs['param_list'].append({
'name': 'NumAlerts',
'type': 'int',
'default': str(num_alerts),
'desc': 'Number of alerts',
'local': 'true',
'expose': 'false',
})
# Change default param value if exists.
# Assumed param list is already validated in above `check_keys` function
if "param_list" in regs and len(regs["param_list"]) != 0:
for p in params:
if p == '':
continue
tokens = p.split('=')
if len(tokens) != 2:
error += 1
log.error("Parameter format isn't correct. {}".format(p))
key, value = tokens[0], tokens[1]
param, err = search_param(regs["param_list"], key)
if err != 0:
error += err
continue
value, err = check_int(
value, component + " param[{}]".format(param["name"]))
if err != 0:
error += err
continue
param["default"] = value
if "scan" in regs:
scan, err = check_bool(regs["scan"], component + " scan")
else:
regs["scan"] = "false"
reg_block.add_raw_registers(regs['registers'])
regs['gensize'] = 1 << (reg_block.offset - 1).bit_length()
try:
reg_block.validate()
except ValueError as err:
log.error(str(err))
error += 1
if autoregs:
regs['genautoregs'] = True
regs['registers'] = reg_block
regs['genrnames'] = list(reg_block.name_to_offset.keys())
log.debug("Validated, size = " + hex(regs['gensize']) + " errors=" +
str(error) + " names are " + str(regs['genrnames']))
if (error > 0):
log.error("Register description had " + str(error) + " error" +
"s" if error > 1 else "")
regs.setdefault('inter_signal_list', [])
regs.setdefault('bus_device', '')
regs.setdefault('bus_host', '')
if regs["bus_device"] == "tlul":
# Add to inter_module_signal
port_name = "tl" if regs["bus_host"] in ["none", ""] else "tl_d"
regs["inter_signal_list"].append(
OrderedDict([('struct', 'tl'), ('package', 'tlul_pkg'),
('type', 'req_rsp'), ('act', 'rsp'),
('name', port_name)]))
if regs['bus_host'] == "tlul":
port_name = "tl" if regs["bus_host"] in ["none", ""] else "tl_h"
regs["inter_signal_list"].append(
OrderedDict([('struct', 'tl'), ('package', 'tlul_pkg'),
('type', 'req_rsp'), ('act', 'req'),
('name', port_name)]))
return error