def _get_class_name(self, node: Node) -> str: """ Returns the class type name. Shall be unique enough to prevent type name collisions """ if self.reuse_class_definitions: scope_path = self._get_resolved_scope_path(node, "__") if scope_path is not None: class_name = scope_path else: # Unable to determine a reusable type name. Fall back to hierarchical path class_name = node.get_rel_path( self.top.parent, hier_separator="__", array_suffix="", empty_array_suffix="" ) # Add prefix to prevent collision when mixing namespace methods class_name = "xtern__" + class_name else: class_name = node.get_rel_path( self.top.parent, hier_separator="__", array_suffix="", empty_array_suffix="" ) return class_name
def get_node_uid(node: Node) -> str: """ Returns the node's UID string """ node_path = node.get_path(array_suffix="", empty_array_suffix="") path_hash = hashlib.sha1(node_path.encode('utf-8')).hexdigest() return path_hash
def _get_class_friendly_name(self, node: Node) -> str: """ Returns a useful string that helps identify the class definition in a comment """ if self.reuse_class_definitions: scope_path = self._get_resolved_scope_path(node) if scope_path is not None: friendly_name = scope_path else: # Unable to determine a reusable type name. Fall back to hierarchical path friendly_name = node.get_rel_path(self.top.parent) else: friendly_name = node.get_rel_path(self.top.parent) return type(node.inst).__name__ + " - " + friendly_name
def has_extra_property_doc(self, node: Node) -> bool: """ Returns True if node has a property set that is to be explicitly documented. """ for prop in self.extra_properties: if prop in node.list_properties(): return True return False
def _get_bus_width(self, node: Node) -> int: """ Returns group-like node's bus width (in bytes) """ width = self.bus_width_db[node.get_path()] # Divide by 8, rounded up if width % 8: return width // 8 + 1 else: return width // 8
def check_udp(self, prop_name: str, node: Node) -> bool: """ Checks if the property name is a udp """ prop_ups = node.list_properties(include_native=False, include_udp=True) if prop_name in prop_ups: return True else: return False
def _get_class_name_new(self, node: Node) -> str: """ Returns the class type name. Shall be unique enough to prevent type name collisions """ if self.reuse_class_definitions: pass else: class_name = node.get_rel_path( self.top.parent, hier_separator="__", array_suffix="", empty_array_suffix="" ) return class_name
def _get_class_friendly_name(self, node: Node) -> str: """ Returns a useful string that helps identify the class definition in a comment """ if self.reuse_class_definitions: scope_path = node.inst.get_scope_path() if (scope_path is not None) and (node.type_name is not None): if scope_path: friendly_name = scope_path + "::" + node.type_name else: friendly_name = node.type_name else: # Unable to determine a reusable type name. Fall back to hierarchical path friendly_name = node.get_rel_path(self.top.parent, hier_separator="__", array_suffix="", empty_array_suffix="") else: friendly_name = node.get_rel_path(self.top.parent) return type(node.inst).__name__ + " - " + friendly_name
def _get_signal_name(self, node: Node, index: str = '', prop: str = '') -> str: """ Returns unique-in-addrmap name for signals """ prefix = node.get_rel_path(node.owning_addrmap, '', '_', '', '') # check for override, otherwise use prop suffix = self.signal_overrides.get(prop, prop) if prop: return "{}_{}{}".format(prefix, suffix, index) else: return "{}{}".format(prefix, index)
def has_description(node: Node) -> bool: """ Test if node has a description defined """ return "desc" in node.list_properties()
def get_node_html_desc(self, node: Node, increment_heading: int = 0) -> 'Optional[str]': """ Wrapper function to get HTML description If no description, returns None Performs the following transformations on top of the built-in HTML desc output: - Increment any heading tags - Transform img paths that point to local files. Copy referenced image to output """ desc = node.get_html_desc(self.markdown_inst) if desc is None: return desc # Keep HTML semantically correct by promoting heading tags if desc ends # up as a child of existing headings. if increment_heading > 0: def heading_replace_callback(m: 're.Match') -> str: new_heading = "<%sh%d>" % ( m.group(1), min(int(m.group(2)) + increment_heading, 6)) return new_heading desc = re.sub(r'<(/?)[hH](\d)>', heading_replace_callback, desc) # Transform image references # If an img reference points to a file on the local filesystem, then # copy it to the output and transform the reference if increment_heading > 0: def img_transform_callback(m: 're.Match') -> str: dom = xml.dom.minidom.parseString(m.group(0)) img_src = dom.childNodes[0].attributes["src"].value if os.path.isabs(img_src): # Absolute local path, or root URL pass elif re.match(r'(https?|file)://', img_src): # Absolute URL pass else: # Looks like a relative path # See if it points to something relative to the source file path = self.try_resolve_rel_path(node.inst.def_src_ref, img_src) if path is not None: img_src = path if os.path.exists(img_src): with open(img_src, 'rb') as f: md5 = hashlib.md5(f.read()).hexdigest() new_path = os.path.join( self.output_dir, "content", "%s_%s" % (md5[0:8], os.path.basename(img_src))) shutil.copyfile(img_src, new_path) dom.childNodes[0].attributes["src"].value = os.path.join( "content", "%s_%s" % (md5[0:8], os.path.basename(img_src))) return dom.childNodes[0].toxml() return m.group(0) desc = re.sub(r'<\s*img.*/>', img_transform_callback, desc) return desc
def visit_addressable_node(self, node: Node, parent_id: 'Optional[int]' = None) -> int: self.current_id += 1 this_id = self.current_id child_ids = [] # type: List[int] ral_entry = { 'parent': parent_id, 'children': child_ids, 'name': node.inst.inst_name, 'offset': BigInt(node.inst.addr_offset), 'size': BigInt(node.size), } if node.inst.is_array: ral_entry['dims'] = node.inst.array_dimensions ral_entry['stride'] = BigInt(node.inst.array_stride) ral_entry['idxs'] = [0] * len(node.inst.array_dimensions) if isinstance(node, RegNode): ral_fields = [] for field in node.fields(): field_reset = field.get_property("reset", default=0) if isinstance(field_reset, Node): # Reset value is a reference. Dynamic RAL data does not # support this, so stuff a 0 in its place field_reset = 0 ral_field = { 'name': field.inst.inst_name, 'lsb': field.inst.lsb, 'msb': field.inst.msb, 'reset': BigInt(field_reset), 'disp': 'H' } field_enum = field.get_property("encode") if field_enum is not None: encode = OrderedDict() for member in field_enum: encode[member.name] = BigInt(member.value) ral_field['encode'] = encode ral_field['disp'] = 'E' ral_fields.append(ral_field) ral_entry['fields'] = ral_fields # Insert entry now to ensure proper position in list self.RALIndex.append(ral_entry) # Insert root nodes to list if parent_id is None: self.RootNodeIds.append(this_id) # Recurse to children children = OrderedDict() for child in node.children(): if not isinstance(child, AddressableNode): continue child_id = self.visit_addressable_node(child, this_id) child_ids.append(child_id) children[child_id] = child # Generate page for this node self.write_page(this_id, node, children) return this_id
def export(self, node: Node, path: str, **kwargs): """ Perform the export! Parameters ---------- node: systemrdl.Node Top-level node to export. Can be the top-level `RootNode` or any internal `AddrmapNode`. path: str Output file. export_as_package: bool If True (Default), UVM register model is exported as a SystemVerilog package. Package name is based on the output file name. If False, register model is exported as an includable header. reuse_class_definitions: bool If True (Default), exporter attempts to re-use class definitions where possible. Class names are based on the lexical scope of the original SystemRDL definitions. If False, class definitions are not reused. Class names are based on the instance's hierarchical path. use_uvm_factory: bool If True, class definitions and class instances are created using the UVM factory. If False (Default), UVM factory is disabled. Classes are created directly via new() constructors. """ export_as_package = kwargs.pop("export_as_package", True) use_uvm_factory = kwargs.pop("use_uvm_factory", False) self.reuse_class_definitions = kwargs.pop("reuse_class_definitions", True) # Check for stray kwargs if kwargs: raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0]) # If it is the root node, skip to top addrmap if isinstance(node, RootNode): node = node.top self.top = node if isinstance(node, AddrmapNode) and node.get_property('bridge'): node.env.msg.warning( "UVM RAL generator does not have proper support for bridge addmaps yet. The 'bridge' property will be ignored.", node.inst.property_src_ref.get('bridge', node.inst.inst_src_ref) ) # First, traverse the model and collect some information self.bus_width_db = {} RDLWalker().walk(self.top, PreExportListener(self)) context = { 'top_node': node, 'RegNode': RegNode, 'RegfileNode': RegfileNode, 'AddrmapNode': AddrmapNode, 'MemNode': MemNode, 'AddressableNode': AddressableNode, 'isinstance': isinstance, 'class_needs_definition': self._class_needs_definition, 'get_class_name': self._get_class_name, 'get_class_friendly_name': self._get_class_friendly_name, 'get_inst_name': self._get_inst_name, 'get_field_access': self._get_field_access, 'get_array_address_offset_expr': self._get_array_address_offset_expr, 'get_endianness': self._get_endianness, 'get_bus_width': self._get_bus_width, 'get_mem_access': self._get_mem_access, 'roundup_to': self._roundup_to, 'roundup_pow2': self._roundup_pow2, 'use_uvm_factory': use_uvm_factory, } context.update(self.user_template_context) if export_as_package: context['package_name'] = self._get_package_name(path) template = self.jj_env.get_template("top_pkg.sv") else: context['include_guard'] = self._get_include_guard(path) template = self.jj_env.get_template("top_include.svh") stream = template.stream(context) stream.dump(path)
def export(self, node: Node, path: str, **kwargs): """ Perform the export! Parameters ---------- node: systemrdl.Node Top-level node to export. Can be the top-level `RootNode` or any internal `AddrmapNode`. path: str Output file. signal_overrides: dict Mapping to override default signal suffixes , e.g. {'incr' : 'increment'} bus_type: str bus type for the SW interface (default: native) """ self.signal_overrides = kwargs.pop("signal_overrides") or dict() bus_type = kwargs.pop("bus_type", "native") try: with open( os.path.join(os.path.dirname(__file__), "busses", "{}.ports.sv".format(bus_type))) as f: sw_ports = f.read() with open( os.path.join(os.path.dirname(__file__), "busses", "{}.impl.sv".format(bus_type))) as f: sw_impl = f.read() except FileNotFoundError as e: raise TypeError( "didn't recognise bus_type '{}'. Please check for typos.". format(bus_type)) from e if type(self.signal_overrides) != dict: raise TypeError( "got an unexpected signal_overrides argument of type {} instead of dict" .format(type(self.signal_overrides))) # Check for stray kwargs if kwargs: raise TypeError("got an unexpected keyword argument '%s'" % list(kwargs.keys())[0]) # If it is the root node, skip to top addrmap if isinstance(node, RootNode): node = node.top # go through top level regfiles modules = [] for desc in node.descendants(): if ((isinstance(desc, (RegfileNode, RegNode))) and isinstance(desc.parent, AddrmapNode)): if desc.parent not in modules: modules.append(desc.parent) for block in modules: self.top = block # First, traverse the model and collect some information #self.bus_width_db = {} #RDLWalker().walk(self.top) context = { 'print': print, 'type': type, 'top_node': block, 'sw_ports': sw_ports, 'sw_impl': sw_impl, 'FieldNode': FieldNode, 'RegNode': RegNode, 'RegfileNode': RegfileNode, 'AddrmapNode': AddrmapNode, 'MemNode': MemNode, 'AddressableNode': AddressableNode, 'SignalNode': SignalNode, 'OnWriteType': OnWriteType, 'OnReadType': OnReadType, 'InterruptType': InterruptType, 'PropertyReference': PropertyReference, 'isinstance': isinstance, 'signal': self._get_signal_name, 'full_idx': self._full_idx, 'get_inst_name': self._get_inst_name, 'get_prop_value': self._get_prop_value, 'get_counter_value': self._get_counter_value, } context.update(self.user_template_context) # ensure directory exists Path(path).mkdir(parents=True, exist_ok=True) template = self.jj_env.get_template("module.sv") stream = template.stream(context) stream.dump(os.path.join(path, node.inst_name + '_rf.sv')) template = self.jj_env.get_template("tb.sv") stream = template.stream(context) stream.dump(os.path.join(path, node.inst_name + '_tb.sv')) template = self.jj_env.get_template("tb.cpp") stream = template.stream(context) stream.dump(os.path.join(path, node.inst_name + '_tb.cpp')) return [self._get_inst_name(m) for m in modules]
def get_desc(self, node: Node) -> str: s = (node.get_property("desc", default="")).replace("\n", " ") s = s.replace(" ", " ") return s
def get_name(self, node: Node) -> str: s = node.get_property("name") return s
def stringify_component_ref(value: node.Node, owner_node: node.Node) -> str: return value.get_rel_path(owner_node)
def signal_prefix(self, node: Node) -> str: """ Returns unique-in-addrmap prefix for signals """ return node.get_rel_path(node.owning_addrmap, '', '_', '_', '')