from typing import TYPE_CHECKING, Set, List, Optional from collections import OrderedDict from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from systemrdl.node import SignalNode, AddressableNode if TYPE_CHECKING: from systemrdl.node import Node, RegNode, FieldNode from .exporter import RegblockExporter class DesignScanner(RDLListener): """ Scans through the register model and validates that any unsupported features are not present. Also collects any information that is required prior to the start of the export process. """ def __init__(self, exp:'RegblockExporter') -> None: self.exp = exp self.cpuif_data_width = 0 self.msg = exp.top_node.env.msg # Keep track of max accesswidth encountered in a given block self.max_accesswidth_stack = [] # type: List[int] # Collections of signals that were actually referenced by the design self.in_hier_signal_paths = set() # type: Set[str] self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode] def _get_out_of_hier_field_reset(self) -> None: current_node = self.exp.top_node.parent while current_node is not None: for signal in current_node.signals(): if signal.get_property('field_reset'): path = signal.get_path() self.out_of_hier_signals[path] = signal return current_node = current_node.parent def do_scan(self) -> None: # Collect cpuif reset, if any. cpuif_reset = self.exp.top_node.cpuif_reset if cpuif_reset is not None: path = cpuif_reset.get_path() rel_path = cpuif_reset.get_rel_path(self.exp.top_node) if rel_path.startswith("^"): self.out_of_hier_signals[path] = cpuif_reset else: self.in_hier_signal_paths.add(path) # collect out-of-hier field_reset, if any self._get_out_of_hier_field_reset() # Ensure addrmap is not a bridge. This concept does not make sense for # terminal components. if self.exp.top_node.get_property('bridge'): self.msg.error( "Regblock generator does not support exporting bridge address maps", self.exp.top_node.inst.property_src_ref.get('bridge', self.exp.top_node.inst.inst_src_ref) ) RDLWalker().walk(self.exp.top_node, self) if self.msg.had_error: self.msg.fatal( "Unable to export due to previous errors" ) raise ValueError def enter_Reg(self, node: 'RegNode') -> None: accesswidth = node.get_property('accesswidth') self.max_accesswidth_stack[-1] = max(self.max_accesswidth_stack[-1], accesswidth) # The CPUIF's bus width is sized according to the largest accesswidth in the design self.cpuif_data_width = max(self.cpuif_data_width, accesswidth) # TODO: remove this limitation eventually if accesswidth != self.cpuif_data_width: self.msg.error( "register blocks with non-uniform accesswidth are not supported yet", node.inst.property_src_ref.get('accesswidth', node.inst.inst_src_ref) ) # TODO: remove this limitation eventually if accesswidth != node.get_property('regwidth'): self.msg.error( "Registers that have an accesswidth different from the register width are not supported yet", node.inst.property_src_ref.get('accesswidth', node.inst.inst_src_ref) ) def enter_AddressableComponent(self, node: AddressableNode) -> None: self.max_accesswidth_stack.append(0) def exit_AddressableComponent(self, node: AddressableNode) -> None: max_block_accesswidth = self.max_accesswidth_stack.pop() if self.max_accesswidth_stack: self.max_accesswidth_stack[-1] = max(self.max_accesswidth_stack[-1], max_block_accesswidth) alignment = int(max_block_accesswidth / 8) if (node.raw_address_offset % alignment) != 0: self.msg.error( f"Unaligned registers are not supported. Address offset of instance '{node.inst_name}' must be a multiple of {alignment}", node.inst.inst_src_ref ) if node.is_array and (node.array_stride % alignment) != 0: self.msg.error( f"Unaligned registers are not supported. Address stride of instance array '{node.inst_name}' must be a multiple of {alignment}", node.inst.inst_src_ref ) def enter_Component(self, node: 'Node') -> Optional[WalkerAction]: if node.external and (node != self.exp.top_node): self.msg.error( "Exporter does not support external components", node.inst.inst_src_ref ) # Do not inspect external components. None of my business return WalkerAction.SkipDescendants return None def enter_Signal(self, node: 'SignalNode') -> None: # If encountering a CPUIF reset that is nested within the register model, # warn that it will be ignored. # Only cpuif resets in the top-level node or above will be honored if node.get_property('cpuif_reset') and (node.parent != self.exp.top_node): self.msg.warning( "Only cpuif_reset signals that are instantiated in the top-level " + "addrmap or above will be honored. Any cpuif_reset signals nested " + "within children of the addrmap being exported will be ignored.", node.inst.inst_src_ref ) if node.get_property('field_reset'): path = node.get_path() self.in_hier_signal_paths.add(path) def enter_Field(self, node: 'FieldNode') -> None: for prop_name in node.list_properties(): value = node.get_property(prop_name) if isinstance(value, SignalNode): path = value.get_path() rel_path = value.get_rel_path(self.exp.top_node) if rel_path.startswith("^"): self.out_of_hier_signals[path] = value else: self.in_hier_signal_paths.add(path) # 10.6.1-f: Any field that is software-writable or clear on read shall # not span multiple software accessible sub-words (e.g., a 64-bit # register with a 32-bit access width may not have a writable field with # bits in both the upper and lower half of the register). # # Interpreting this further - this rule applies any time a field is # software-modifiable by any means, including rclr, rset, ruser # TODO: suppress this check for registers that have the appropriate # buffer_writes/buffer_reads UDP set parent_accesswidth = node.parent.get_property('accesswidth') parent_regwidth = node.parent.get_property('regwidth') if ((parent_accesswidth < parent_regwidth) and (node.lsb // parent_accesswidth) != (node.msb // parent_accesswidth) and (node.is_sw_writable or node.get_property('onread') is not None)): # Field spans across sub-words self.msg.error( "Software-modifiable field '%s' shall not span multiple software-accessible subwords." % node.inst_name, node.inst.inst_src_ref )