|  | # 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 | 
|  | import re | 
|  | import sys | 
|  | from collections import OrderedDict | 
|  | from copy import deepcopy | 
|  | from pathlib import Path | 
|  | from typing import Dict, List, Optional, Tuple | 
|  |  | 
|  | import hjson | 
|  | from reggen.ip_block import IpBlock | 
|  |  | 
|  | # Ignore flake8 warning as the function is used in the template | 
|  | # disable isort formating, as conflicting with flake8 | 
|  | from .intermodule import find_otherside_modules  # noqa : F401 # isort:skip | 
|  | from .intermodule import im_portname, im_defname, im_netname # noqa : F401 # isort:skip | 
|  | from .intermodule import get_direction # noqa : F401 # isort:skip | 
|  | from .intermodule import get_dangling_im_def # noqa : F401 # isort:skip | 
|  |  | 
|  |  | 
|  | class Name: | 
|  | """ | 
|  | We often need to format names in specific ways; this class does so. | 
|  |  | 
|  | To simplify parsing and reassembling of name strings, this class | 
|  | stores the name parts as a canonical list of strings internally | 
|  | (in self.parts). | 
|  |  | 
|  | The "from_*" functions parse and split a name string into the canonical | 
|  | list, whereas the "as_*" functions reassemble the canonical list in the | 
|  | format specified. | 
|  |  | 
|  | For example, ex = Name.from_snake_case("example_name") gets split into | 
|  | ["example", "name"] internally, and ex.as_camel_case() reassembles this | 
|  | internal representation into "ExampleName". | 
|  | """ | 
|  | def __add__(self, other): | 
|  | return Name(self.parts + other.parts) | 
|  |  | 
|  | @staticmethod | 
|  | def from_snake_case(input: str) -> 'Name': | 
|  | return Name(input.split("_")) | 
|  |  | 
|  | def __init__(self, parts: List[str]): | 
|  | self.parts = parts | 
|  | for p in parts: | 
|  | assert len(p) > 0, "cannot add zero-length name piece" | 
|  |  | 
|  | def as_snake_case(self) -> str: | 
|  | return "_".join([p.lower() for p in self.parts]) | 
|  |  | 
|  | def as_camel_case(self) -> str: | 
|  | out = "" | 
|  | for p in self.parts: | 
|  | # If we're about to join two parts which would introduce adjacent | 
|  | # numbers, put an underscore between them. | 
|  | if out[-1:].isnumeric() and p[:1].isnumeric(): | 
|  | out += "_" + p | 
|  | else: | 
|  | out += p.capitalize() | 
|  | return out | 
|  |  | 
|  | def as_c_define(self) -> str: | 
|  | return "_".join([p.upper() for p in self.parts]) | 
|  |  | 
|  | def as_c_enum(self) -> str: | 
|  | return "k" + self.as_camel_case() | 
|  |  | 
|  | def as_c_type(self) -> str: | 
|  | return self.as_snake_case() + "_t" | 
|  |  | 
|  | def remove_part(self, part_to_remove: str) -> "Name": | 
|  | return Name([p for p in self.parts if p != part_to_remove]) | 
|  |  | 
|  |  | 
|  | def is_ipcfg(ip: Path) -> bool:  # return bool | 
|  | log.info("IP Path: %s" % repr(ip)) | 
|  | ip_name = ip.parents[1].name | 
|  | hjson_name = ip.name | 
|  |  | 
|  | log.info("IP Name(%s) and HJSON name (%s)" % (ip_name, hjson_name)) | 
|  |  | 
|  | if ip_name + ".hjson" == hjson_name or ip_name + "_reg.hjson" == hjson_name: | 
|  | return True | 
|  | return False | 
|  |  | 
|  |  | 
|  | def search_ips(ip_path):  # return list of config files | 
|  | # list the every Hjson file | 
|  | p = ip_path.glob('*/data/*.hjson') | 
|  |  | 
|  | # filter only ip_name/data/ip_name{_reg|''}.hjson | 
|  | ips = [x for x in p if is_ipcfg(x)] | 
|  |  | 
|  | log.info("Filtered-in IP files: %s" % repr(ips)) | 
|  | return ips | 
|  |  | 
|  |  | 
|  | def is_xbarcfg(xbar_obj): | 
|  | if "type" in xbar_obj and xbar_obj["type"] == "xbar": | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | def get_hjsonobj_xbars(xbar_path): | 
|  | """ Search crossbars Hjson files from given path. | 
|  |  | 
|  | Search every Hjson in the directory and check Hjson type. | 
|  | It could be type: "top" or type: "xbar" | 
|  | returns [(name, obj), ... ] | 
|  | """ | 
|  | p = xbar_path.glob('*.hjson') | 
|  | try: | 
|  | xbar_objs = [ | 
|  | hjson.load(x.open('r'), | 
|  | use_decimal=True, | 
|  | object_pairs_hook=OrderedDict) for x in p | 
|  | ] | 
|  | except ValueError: | 
|  | raise SystemExit(sys.exc_info()[1]) | 
|  |  | 
|  | xbar_objs = [x for x in xbar_objs if is_xbarcfg(x)] | 
|  |  | 
|  | return xbar_objs | 
|  |  | 
|  |  | 
|  | def get_module_by_name(top, name): | 
|  | """Search in top["module"] by name | 
|  | """ | 
|  | module = None | 
|  | for m in top["module"]: | 
|  | if m["name"] == name: | 
|  | module = m | 
|  | break | 
|  |  | 
|  | return module | 
|  |  | 
|  |  | 
|  | def intersignal_to_signalname(top, m_name, s_name) -> str: | 
|  |  | 
|  | # TODO: Find the signal in the `inter_module_list` and get the correct signal name | 
|  |  | 
|  | return "{m_name}_{s_name}".format(m_name=m_name, s_name=s_name) | 
|  |  | 
|  |  | 
|  | def get_package_name_by_intermodule_signal(top, struct) -> str: | 
|  | """Search inter-module signal package with the struct name | 
|  |  | 
|  | For instance, if `flash_ctrl` has inter-module signal package, | 
|  | this function returns the package name | 
|  | """ | 
|  | instances = top["module"] + top["memory"] | 
|  |  | 
|  | intermodule_instances = [ | 
|  | x["inter_signal_list"] for x in instances if "inter_signal_list" in x | 
|  | ] | 
|  |  | 
|  | for m in intermodule_instances: | 
|  | if m["name"] == struct and "package" in m: | 
|  | return m["package"] | 
|  | return "" | 
|  |  | 
|  |  | 
|  | def get_signal_by_name(module, name): | 
|  | """Return the signal struct with the type input/output/inout | 
|  | """ | 
|  | result = None | 
|  | for s in module["available_input_list"] + module[ | 
|  | "available_output_list"] + module["available_inout_list"]: | 
|  | if s["name"] == name: | 
|  | result = s | 
|  | break | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def add_module_prefix_to_signal(signal, module): | 
|  | """Add module prefix to module signal format { name: "sig_name", width: NN } | 
|  | """ | 
|  | result = deepcopy(signal) | 
|  |  | 
|  | if "name" not in signal: | 
|  | raise SystemExit("signal {} doesn't have name field".format(signal)) | 
|  |  | 
|  | result["name"] = module + "_" + signal["name"] | 
|  | result["module_name"] = module | 
|  |  | 
|  | return result | 
|  |  | 
|  |  | 
|  | def get_ms_name(name): | 
|  | """Split module_name.signal_name to module_name , signal_name | 
|  | """ | 
|  |  | 
|  | tokens = name.split('.') | 
|  |  | 
|  | if len(tokens) == 0: | 
|  | raise SystemExit("This to be catched in validate.py") | 
|  |  | 
|  | module = tokens[0] | 
|  | signal = None | 
|  | if len(tokens) == 2: | 
|  | signal = tokens[1] | 
|  |  | 
|  | return module, signal | 
|  |  | 
|  |  | 
|  | def parse_pad_field(padstr): | 
|  | """Parse PadName[NN...NN] or PadName[NN] or just PadName | 
|  | """ | 
|  | match = re.match(r'^([A-Za-z0-9_]+)(\[([0-9]+)(\.\.([0-9]+))?\]|)', padstr) | 
|  | return match.group(1), match.group(3), match.group(5) | 
|  |  | 
|  |  | 
|  | def get_pad_list(padstr): | 
|  | pads = [] | 
|  |  | 
|  | pad, first, last = parse_pad_field(padstr) | 
|  | if first is None: | 
|  | first = 0 | 
|  | last = 0 | 
|  | elif last is None: | 
|  | last = first | 
|  | first = int(first, 0) | 
|  | last = int(last, 0) | 
|  | # width = first - last + 1 | 
|  |  | 
|  | for p in range(first, last + 1): | 
|  | pads.append(OrderedDict([("name", pad), ("index", p)])) | 
|  |  | 
|  | return pads | 
|  |  | 
|  |  | 
|  | # Template functions | 
|  | def ljust(x, width): | 
|  | return "{:<{width}}".format(x, width=width) | 
|  |  | 
|  |  | 
|  | def bitarray(d, width): | 
|  | """Print Systemverilog bit array | 
|  |  | 
|  | @param d the bit width of the signal | 
|  | @param width max character width of the signal group | 
|  |  | 
|  | For instance, if width is 4, the max d value in the signal group could be | 
|  | 9999. If d is 2, then this function pads 3 spaces at the end of the bit | 
|  | slice. | 
|  |  | 
|  | "[1:0]   " <- d:=2, width=4 | 
|  | "[9999:0]" <- max d-1 value | 
|  |  | 
|  | If d is 1, it means array slice isn't necessary. So it returns empty spaces | 
|  | """ | 
|  |  | 
|  | if d <= 0: | 
|  | log.error("lib.bitarray: Given value {} is smaller than 1".format(d)) | 
|  | raise ValueError | 
|  | if d == 1: | 
|  | return " " * (width + 4)  # [x:0] needs 4 more space than char_width | 
|  |  | 
|  | out = "[{}:0]".format(d - 1) | 
|  | return out + (" " * (width - len(str(d)))) | 
|  |  | 
|  |  | 
|  | def parameterize(text): | 
|  | """Return the value wrapping with quote if not integer nor bits | 
|  | """ | 
|  | if re.match(r'(\d+\'[hdb]\s*[0-9a-f_A-F]+|[0-9]+)', text) is None: | 
|  | return "\"{}\"".format(text) | 
|  |  | 
|  | return text | 
|  |  | 
|  |  | 
|  | def index(i: int) -> str: | 
|  | """Return index if it is not -1 | 
|  | """ | 
|  | return "[{}]".format(i) if i != -1 else "" | 
|  |  | 
|  |  | 
|  | def get_clk_name(clk): | 
|  | """Return the appropriate clk name | 
|  | """ | 
|  | if clk == 'main': | 
|  | return 'clk_i' | 
|  | else: | 
|  | return "clk_{}_i".format(clk) | 
|  |  | 
|  |  | 
|  | def is_shadowed_port(block: IpBlock, port: str) -> bool: | 
|  | """Return boolean indication whether a port is a shadow reset port | 
|  | """ | 
|  | shadowed_port = block.clocking.primary.reset if block.has_shadowed_reg() \ | 
|  | else None | 
|  |  | 
|  | return port == shadowed_port | 
|  |  | 
|  |  | 
|  | def shadow_name(name: str) -> str: | 
|  | """Return the appropriate shadow reset name based on port name | 
|  | """ | 
|  | match = re.match(r'^rst_([A-Za-z0-9_]+)_ni?', name) | 
|  | if match: | 
|  | return f'rst_{match.group(1)}_shadowed_ni' | 
|  | else: | 
|  | return 'rst_shadowed_ni' | 
|  |  | 
|  |  | 
|  | def get_reset_path(top: object, reset: str, shadow_sel: bool = False): | 
|  | """Return the appropriate reset path given name | 
|  | """ | 
|  | return top['resets'].get_path(reset['name'], reset['domain'], shadow_sel) | 
|  |  | 
|  |  | 
|  | def get_reset_lpg_path(top: object, reset: str, shadow_sel: bool = False, domain: bool = None): | 
|  | """Return the appropriate LPG reset path given name | 
|  | """ | 
|  | if domain is not None: | 
|  | return top['resets'].get_lpg_path(reset['name'], domain, shadow_sel) | 
|  | else: | 
|  | return top['resets'].get_lpg_path(reset['name'], reset['domain'], shadow_sel) | 
|  |  | 
|  |  | 
|  | def get_unused_resets(top): | 
|  | """Return dict of unused resets and associated domain | 
|  | """ | 
|  | return top['resets'].get_unused_resets(top['power']['domains']) | 
|  |  | 
|  |  | 
|  | def get_templated_modules(top): | 
|  | """Returns list of all templated modules. | 
|  | """ | 
|  | return [m['type'] for m in top['module'] if is_templated(m)] | 
|  |  | 
|  |  | 
|  | def get_ipgen_modules(top): | 
|  | """Returns list of all ipgen modules. | 
|  | """ | 
|  | return [m['type'] for m in top['module'] if is_ipgen(m)] | 
|  |  | 
|  |  | 
|  | def get_top_reggen_modules(top): | 
|  | """Returns list of all ipgen modules. | 
|  | """ | 
|  | return [m['type'] for m in top['module'] if is_top_reggen(m)] | 
|  |  | 
|  |  | 
|  | def is_templated(module): | 
|  | """Returns an indication where a particular module is templated | 
|  | """ | 
|  | return module.get('attr') in ["templated"] | 
|  |  | 
|  |  | 
|  | def is_ipgen(module): | 
|  | """Returns an indication where a particular module is ipgen | 
|  | """ | 
|  | return module.get('attr') in ["ipgen"] | 
|  |  | 
|  |  | 
|  | def is_top_reggen(module): | 
|  | """Returns an indication where a particular module is NOT templated | 
|  | and requires top level specific reggen | 
|  | """ | 
|  | return module.get('attr') in ["reggen_top", "reggen_only"] | 
|  |  | 
|  |  | 
|  | def is_reggen_only(module): | 
|  | """Returns an indication where a particular module is NOT templated, | 
|  | requires top level specific reggen and is NOT instantiated in the | 
|  | top | 
|  | """ | 
|  | return module.get('attr') == "reggen_only" | 
|  |  | 
|  |  | 
|  | def is_inst(module): | 
|  | """Returns an indication where a particular module should be instantiated | 
|  | in the top level | 
|  | """ | 
|  | top_level_module = False | 
|  | top_level_mem = False | 
|  |  | 
|  | if "attr" not in module: | 
|  | top_level_module = True | 
|  | elif module["attr"] in ["normal", "templated", "ipgen", "reggen_top"]: | 
|  | top_level_module = True | 
|  | elif module["attr"] in ["reggen_only"]: | 
|  | top_level_module = False | 
|  | else: | 
|  | raise ValueError('Attribute {} in {} is not valid' | 
|  | .format(module['attr'], module['name'])) | 
|  |  | 
|  | if module['type'] in ['rom', 'ram_1p_scr', 'eflash']: | 
|  | top_level_mem = True | 
|  |  | 
|  | return top_level_mem or top_level_module | 
|  |  | 
|  |  | 
|  | def get_base_and_size(name_to_block: Dict[str, IpBlock], | 
|  | inst: Dict[str, object], | 
|  | ifname: Optional[str]) -> Tuple[int, int]: | 
|  |  | 
|  | block = name_to_block.get(inst['type']) | 
|  | if block is None: | 
|  | # If inst isn't the instantiation of a block, it came from some memory. | 
|  | # Memories have their sizes defined, so we can just look it up there. | 
|  | bytes_used = int(inst['size'], 0) | 
|  |  | 
|  | # Memories don't have multiple or named interfaces, so this will only | 
|  | # work if ifname is None. | 
|  | assert ifname is None | 
|  | base_addr = inst['base_addr'] | 
|  |  | 
|  | else: | 
|  | # If inst is the instantiation of some block, find the register block | 
|  | # that corresponds to ifname | 
|  | rb = block.reg_blocks.get(ifname) | 
|  | if rb is None: | 
|  | raise RuntimeError( | 
|  | 'Cannot connect to non-existent {} device interface ' | 
|  | 'on {!r} (an instance of the {!r} block).' | 
|  | .format('default' if ifname is None else repr(ifname), | 
|  | inst['name'], block.name)) | 
|  | else: | 
|  | bytes_used = 1 << rb.get_addr_width() | 
|  |  | 
|  | base_addr = inst['base_addrs'][ifname] | 
|  |  | 
|  | # If an instance has a nonempty "memory" field, take the memory | 
|  | # size configuration from there. | 
|  | if "memory" in inst: | 
|  | if ifname in inst["memory"]: | 
|  | memory_size = int(inst["memory"][ifname]["size"], 0) | 
|  | if bytes_used > memory_size: | 
|  | raise RuntimeError( | 
|  | 'Memory region on {} device interface ' | 
|  | 'on {!r} (an instance of the {!r} block) ' | 
|  | 'is smaller than the corresponding register block.' | 
|  | .format('default' if ifname is None else repr(ifname), | 
|  | inst['name'], block.name)) | 
|  |  | 
|  | bytes_used = memory_size | 
|  |  | 
|  | # Round up to next power of 2. | 
|  | size_byte = 1 << (bytes_used - 1).bit_length() | 
|  |  | 
|  | if isinstance(base_addr, str): | 
|  | base_addr = int(base_addr, 0) | 
|  | else: | 
|  | assert isinstance(base_addr, int) | 
|  |  | 
|  | return (base_addr, size_byte) | 
|  |  | 
|  |  | 
|  | def get_io_enum_literal(sig: Dict, prefix: str) -> str: | 
|  | """Returns the DIO pin enum literal with value assignment""" | 
|  | name = Name.from_snake_case(prefix) + Name.from_snake_case(sig["name"]) | 
|  | # In this case, the signal is a multibit signal, and hence | 
|  | # we have to make the signal index part of the parameter | 
|  | # name to uniquify it. | 
|  | if sig['width'] > 1: | 
|  | name += Name([str(sig['idx'])]) | 
|  | return name.as_camel_case() | 
|  |  | 
|  |  | 
|  | def make_bit_concatenation(sig_name: str, | 
|  | indices: List[int], | 
|  | end_indent: int) -> str: | 
|  | '''Return SV code for concatenating certain indices from a signal | 
|  |  | 
|  | sig_name is the name of the signal and indices is a non-empty list of the | 
|  | indices to use, MSB first. So | 
|  |  | 
|  | make_bit_concatenation("foo", [0, 100, 20]) | 
|  |  | 
|  | should give | 
|  |  | 
|  | {foo[0], foo[100], foo[20]} | 
|  |  | 
|  | Adjacent bits turn into a range select. For example: | 
|  |  | 
|  | make_bit_concatenation("foo", [0, 1, 2]) | 
|  |  | 
|  | should give | 
|  |  | 
|  | foo[0:2] | 
|  |  | 
|  | If there are multiple ranges, they are printed one to a line. end_indent | 
|  | gives the indentation of the closing brace and the range selects in between | 
|  | get indented to end_indent + 2. | 
|  |  | 
|  | ''' | 
|  | assert 0 <= end_indent | 
|  |  | 
|  | ranges = [] | 
|  | cur_range_start = indices[0] | 
|  | cur_range_end = indices[0] | 
|  | for idx in indices[1:]: | 
|  | if idx == cur_range_end + 1 and cur_range_start <= cur_range_end: | 
|  | cur_range_end += 1 | 
|  | continue | 
|  | if idx == cur_range_end - 1 and cur_range_start >= cur_range_end: | 
|  | cur_range_end -= 1 | 
|  | continue | 
|  | ranges.append((cur_range_start, cur_range_end)) | 
|  | cur_range_start = idx | 
|  | cur_range_end = idx | 
|  | ranges.append((cur_range_start, cur_range_end)) | 
|  |  | 
|  | items = [] | 
|  | for range_start, range_end in ranges: | 
|  | if range_start == range_end: | 
|  | select = str(range_start) | 
|  | else: | 
|  | select = '{}:{}'.format(range_start, range_end) | 
|  | items.append('{}[{}]'.format(sig_name, select)) | 
|  |  | 
|  | if len(items) == 1: | 
|  | return items[0] | 
|  |  | 
|  | item_indent = '\n' + (' ' * (end_indent + 2)) | 
|  |  | 
|  | acc = ['{', item_indent, items[0]] | 
|  | for item in items[1:]: | 
|  | acc += [',', item_indent, item] | 
|  | acc += ['\n', ' ' * end_indent, '}'] | 
|  | return ''.join(acc) | 
|  |  | 
|  |  | 
|  | def is_rom_ctrl(modules): | 
|  | '''Return true if rom_ctrl (and thus boot-up rom integrity checking) | 
|  | exists in the design | 
|  | ''' | 
|  | for m in modules: | 
|  | if m['type'] == 'rom_ctrl': | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | def is_lc_ctrl(modules): | 
|  | '''Return true if lc_ctrl exists in the design | 
|  | ''' | 
|  | for m in modules: | 
|  | if m['type'] == 'lc_ctrl': | 
|  | return True | 
|  |  | 
|  | return False |