# 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_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 get_reset_path(reset, domain, top): """Return the appropriate reset path given name """ return top['resets'].get_path(reset, domain) def get_unused_resets(top): """Return dict of unused resets and associated domain """ return top['resets'].get_unused_resets(top['power']['domains']) def is_templated(module): """Returns an indication where a particular module is templated """ if "attr" not in module: return False elif module["attr"] in ["templated"]: return True else: return False def is_top_reggen(module): """Returns an indication where a particular module is NOT templated and requires top level specific reggen """ if "attr" not in module: return False elif module["attr"] in ["reggen_top", "reggen_only"]: return True else: return False 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", "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]: min_device_spacing = 0x1000 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 min_device_spacing if necessary size_byte = max(bytes_used, min_device_spacing) 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