255 lines
9.1 KiB
Python
255 lines
9.1 KiB
Python
# Copyright lowRISC contributors.
|
|
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
'''Parsing support code for reggen'''
|
|
|
|
import re
|
|
from typing import Dict, List, Optional, cast
|
|
|
|
|
|
# Names that are prohibited (used as reserved keywords in systemverilog)
|
|
_VERILOG_KEYWORDS = {
|
|
'alias', 'always', 'always_comb', 'always_ff', 'always_latch', 'and',
|
|
'assert', 'assign', 'assume', 'automatic', 'before', 'begin', 'bind',
|
|
'bins', 'binsof', 'bit', 'break', 'buf', 'bufif0', 'bufif1', 'byte',
|
|
'case', 'casex', 'casez', 'cell', 'chandle', 'class', 'clocking', 'cmos',
|
|
'config', 'const', 'constraint', 'context', 'continue', 'cover',
|
|
'covergroup', 'coverpoint', 'cross', 'deassign', 'default', 'defparam',
|
|
'design', 'disable', 'dist', 'do', 'edge', 'else', 'end', 'endcase',
|
|
'endclass', 'endclocking', 'endconfig', 'endfunction', 'endgenerate',
|
|
'endgroup', 'endinterface', 'endmodule', 'endpackage', 'endprimitive',
|
|
'endprogram', 'endproperty', 'endspecify', 'endsequence', 'endtable',
|
|
'endtask', 'enum', 'event', 'expect', 'export', 'extends', 'extern',
|
|
'final', 'first_match', 'for', 'force', 'foreach', 'forever', 'fork',
|
|
'forkjoin', 'function', 'generate', 'genvar', 'highz0', 'highz1', 'if',
|
|
'iff', 'ifnone', 'ignore_bins', 'illegal_bins', 'import', 'incdir',
|
|
'include', 'initial', 'inout', 'input', 'inside', 'instance', 'int',
|
|
'integer', 'interface', 'intersect', 'join', 'join_any', 'join_none',
|
|
'large', 'liblist', 'library', 'local', 'localparam', 'logic', 'longint',
|
|
'macromodule', 'matches', 'medium', 'modport', 'module', 'nand', 'negedge',
|
|
'new', 'nmos', 'nor', 'noshowcancelled', 'not', 'notif0', 'notif1', 'null',
|
|
'or', 'output', 'package', 'packed', 'parameter', 'pmos', 'posedge',
|
|
'primitive', 'priority', 'program', 'property', 'protected', 'pull0',
|
|
'pull1', 'pulldown', 'pullup', 'pulsestyle_onevent', 'pulsestyle_ondetect',
|
|
'pure', 'rand', 'randc', 'randcase', 'randsequence', 'rcmos', 'real',
|
|
'realtime', 'ref', 'reg', 'release', 'repeat', 'return', 'rnmos', 'rpmos',
|
|
'rtran', 'rtranif0', 'rtranif1', 'scalared', 'sequence', 'shortint',
|
|
'shortreal', 'showcancelled', 'signed', 'small', 'solve', 'specify',
|
|
'specparam', 'static', 'string', 'strong0', 'strong1', 'struct', 'super',
|
|
'supply0', 'supply1', 'table', 'tagged', 'task', 'this', 'throughout',
|
|
'time', 'timeprecision', 'timeunit', 'tran', 'tranif0', 'tranif1', 'tri',
|
|
'tri0', 'tri1', 'triand', 'trior', 'trireg', 'type', 'typedef', 'union',
|
|
'unique', 'unsigned', 'use', 'uwire', 'var', 'vectored', 'virtual', 'void',
|
|
'wait', 'wait_order', 'wand', 'weak0', 'weak1', 'while', 'wildcard',
|
|
'wire', 'with', 'within', 'wor', 'xnor', 'xor'
|
|
}
|
|
|
|
|
|
def check_str_dict(obj: object, what: str) -> Dict[str, object]:
|
|
if not isinstance(obj, dict):
|
|
raise ValueError("{} is expected to be a dict, but was actually a {}."
|
|
.format(what, type(obj).__name__))
|
|
|
|
for key in obj:
|
|
if not isinstance(key, str):
|
|
raise ValueError('{} has a key {!r}, which is not a string.'
|
|
.format(what, key))
|
|
|
|
return cast(Dict[str, object], obj)
|
|
|
|
|
|
def check_keys(obj: object,
|
|
what: str,
|
|
required_keys: List[str],
|
|
optional_keys: List[str]) -> Dict[str, object]:
|
|
'''Check that obj is a dict object with the expected keys
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
od = check_str_dict(obj, what)
|
|
|
|
allowed = set()
|
|
missing = []
|
|
for key in required_keys:
|
|
assert key not in allowed
|
|
allowed.add(key)
|
|
if key not in od:
|
|
missing.append(key)
|
|
|
|
for key in optional_keys:
|
|
assert key not in allowed
|
|
allowed.add(key)
|
|
|
|
unexpected = []
|
|
for key in od:
|
|
if key not in allowed:
|
|
unexpected.append(key)
|
|
|
|
if missing or unexpected:
|
|
mstr = ('The following required fields were missing: {}.'
|
|
.format(', '.join(missing)) if missing else '')
|
|
ustr = ('The following unexpected fields were found: {}.'
|
|
.format(', '.join(unexpected)) if unexpected else '')
|
|
raise ValueError("{} doesn't have the right keys. {}{}{}"
|
|
.format(what,
|
|
mstr,
|
|
' ' if mstr and ustr else '',
|
|
ustr))
|
|
|
|
return od
|
|
|
|
|
|
def check_str(obj: object, what: str) -> str:
|
|
'''Check that the given object is a string
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
if not isinstance(obj, str):
|
|
raise ValueError('{} is of type {}, not a string.'
|
|
.format(what, type(obj).__name__))
|
|
return obj
|
|
|
|
|
|
def check_name(obj: object, what: str) -> str:
|
|
'''Check that obj is a string that's a valid name.
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
as_str = check_str(obj, what)
|
|
|
|
# Allow the usual symbol constituents (alphanumeric plus underscore; no
|
|
# leading numbers)
|
|
if not re.match(r'[a-zA-Z_][a-zA-Z_0-9]*$', as_str):
|
|
raise ValueError("{} is {!r}, which isn't a valid symbol in "
|
|
"C / Verilog, so isn't allowed as a name."
|
|
.format(what, as_str))
|
|
|
|
# Also check that this isn't a reserved word.
|
|
if as_str in _VERILOG_KEYWORDS:
|
|
raise ValueError("{} is {!r}, which is a reserved word in "
|
|
"SystemVerilog, so isn't allowed as a name."
|
|
.format(what, as_str))
|
|
|
|
return as_str
|
|
|
|
|
|
def check_bool(obj: object, what: str) -> bool:
|
|
'''Check that obj is a bool or a string that parses to a bool.
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
if isinstance(obj, str):
|
|
as_bool = {
|
|
'true': True,
|
|
'false': False,
|
|
'1': True,
|
|
'0': False
|
|
}.get(obj.lower())
|
|
if as_bool is None:
|
|
raise ValueError('{} is {!r}, which cannot be parsed as a bool.'
|
|
.format(what, obj))
|
|
return as_bool
|
|
|
|
if obj is True or obj is False:
|
|
return obj
|
|
|
|
raise ValueError('{} is of type {}, not a bool.'
|
|
.format(what, type(obj).__name__))
|
|
|
|
|
|
def check_list(obj: object, what: str) -> List[object]:
|
|
'''Check that the given object is a list
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
if not isinstance(obj, list):
|
|
raise ValueError('{} is of type {}, not a list.'
|
|
.format(what, type(obj).__name__))
|
|
return obj
|
|
|
|
|
|
def check_str_list(obj: object, what: str) -> List[str]:
|
|
'''Check that the given object is a list of strings
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
lst = check_list(obj, what)
|
|
for idx, elt in enumerate(lst):
|
|
if not isinstance(elt, str):
|
|
raise ValueError('Element {} of {} is of type {}, '
|
|
'not a string.'
|
|
.format(idx, what, type(elt).__name__))
|
|
return cast(List[str], lst)
|
|
|
|
|
|
def check_int(obj: object, what: str) -> int:
|
|
'''Check that obj is an integer or a string that parses to an integer.
|
|
|
|
If not, raise a ValueError; the what argument names the object.
|
|
|
|
'''
|
|
if isinstance(obj, int):
|
|
return obj
|
|
|
|
if isinstance(obj, str):
|
|
try:
|
|
return int(obj, 0)
|
|
except ValueError:
|
|
raise ValueError('{} is {!r}, which cannot be parsed as an int.'
|
|
.format(what, obj)) from None
|
|
|
|
raise ValueError('{} is of type {}, not an integer.'
|
|
.format(what, type(obj).__name__))
|
|
|
|
|
|
def check_xint(obj: object, what: str) -> Optional[int]:
|
|
'''Check that obj is an integer, a string that parses to an integer or "x".
|
|
|
|
On success, return an integer value if there is one or None if the value
|
|
was 'x'. On failure, raise a ValueError; the what argument names the
|
|
object.
|
|
|
|
'''
|
|
if isinstance(obj, int):
|
|
return obj
|
|
|
|
if isinstance(obj, str):
|
|
if obj == 'x':
|
|
return None
|
|
try:
|
|
return int(obj, 0)
|
|
except ValueError:
|
|
raise ValueError('{} is {!r}, which is not "x", '
|
|
'nor can it be parsed as an int.'
|
|
.format(what, obj)) from None
|
|
|
|
raise ValueError('{} is of type {}, not an integer.'
|
|
.format(what, type(obj).__name__))
|
|
|
|
|
|
def check_optional_str(obj: object, what: str) -> Optional[str]:
|
|
'''Check that obj is a string or None'''
|
|
return None if obj is None else check_str(obj, what)
|
|
|
|
|
|
def check_optional_name(obj: object, what: str) -> Optional[str]:
|
|
'''Check that obj is a valid name or None'''
|
|
return None if obj is None else check_name(obj, what)
|
|
|
|
|
|
def get_basename(name: str) -> str:
|
|
'''Strip trailing _number (used as multireg suffix) from name'''
|
|
# TODO: This is a workaround, should solve this as part of parsing a
|
|
# multi-reg.
|
|
match = re.search(r'_[0-9]+$', name)
|
|
assert match
|
|
assert match.start() > 0
|
|
return name[0:match.start()]
|