|  | # 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()] |