from typing import TYPE_CHECKING from systemrdl.node import AddressableNode, AddrmapNode, FieldNode, Node, RegfileNode, RegNode, SignalNode from systemrdl.rdltypes.references import PropertyReference from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from .utils import is_pow2, ref_is_internal, roundup_pow2 if TYPE_CHECKING: from .exporter import BusDecoderExporter class DesignValidator(RDLListener): """ Performs additional rule-checks on the design that check for limitations imposed by this exporter. """ def __init__(self, exp: "BusDecoderExporter") -> None: self.exp = exp self.ds = exp.ds self.msg = self.top_node.env.msg self._contains_external_block_stack: list[bool] = [] self.contains_external_block = False @property def top_node(self) -> "AddrmapNode": return self.exp.ds.top_node def do_validate(self) -> None: RDLWalker().walk(self.top_node, self) if self.msg.had_error: self.msg.fatal("Unable to export due to previous errors") def enter_Component(self, node: "Node") -> WalkerAction | None: if node.external and (node != self.top_node): # Do not inspect external components. None of my business return WalkerAction.SkipDescendants # Check if any property references reach across the internal/external boundary for prop_name in node.list_properties(): value = node.get_property(prop_name) if isinstance(value, (PropertyReference, Node)): if not ref_is_internal(self.top_node, value): if isinstance(value, PropertyReference): src_ref = value.src_ref else: src_ref = node.inst.property_src_ref.get(prop_name, node.inst.inst_src_ref) self.msg.error( "Property is assigned a reference that points to a component not internal to the busdecoder being exported.", src_ref, ) 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.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, ) def enter_AddressableComponent(self, node: "AddressableNode") -> None: # All registers must be aligned to the internal data bus width alignment = self.exp.cpuif.data_width_bytes if (node.raw_address_offset % alignment) != 0: self.msg.error( "Unaligned registers are not supported. Address offset of " f"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: # type: ignore # is_array implies stride is not none self.msg.error( "Unaligned registers are not supported. Address stride of " f"instance array '{node.inst_name}' must be a multiple of {alignment}", node.inst.inst_src_ref, ) if not isinstance(node, RegNode): # Entering a block-like node if node == self.top_node: # Ignore top addrmap's external property when entering self._contains_external_block_stack.append(False) else: self._contains_external_block_stack.append(node.external) def enter_Regfile(self, node: RegfileNode) -> None: self._check_sharedextbus(node) def enter_Addrmap(self, node: AddrmapNode) -> None: self._check_sharedextbus(node) def _check_sharedextbus(self, node: RegfileNode | AddrmapNode) -> None: if node.get_property("sharedextbus"): self.msg.error( "This exporter does not support enabling the 'sharedextbus' property yet.", node.inst.property_src_ref.get("sharedextbus", node.inst.inst_src_ref), ) def enter_Reg(self, node: "RegNode") -> None: # accesswidth of wide registers must be consistent within the register block accesswidth = node.get_property("accesswidth") regwidth = node.get_property("regwidth") if accesswidth < regwidth: # register is 'wide' if accesswidth != self.exp.cpuif.data_width: self.msg.error( f"Multi-word registers that have an accesswidth ({accesswidth}) " "that are inconsistent with this busdecoder's CPU bus width " f"({self.exp.cpuif.data_width}) are not supported.", node.inst.inst_src_ref, ) def enter_Field(self, node: "FieldNode") -> None: 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 ): # field spans multiple sub-words if node.is_sw_writable and not node.parent.get_property("buffer_writes"): # ... and is writable without the protection of double-buffering # Enforce 10.6.1-f self.msg.error( f"Software-writable field '{node.inst_name}' shall not span" " multiple software-accessible subwords. Consider enabling" " write double-buffering.\n" "For more details, see: https://peakrdl-busdecoder.readthedocs.io/en/latest/udps/write_buffering.html", node.inst.inst_src_ref, ) if node.get_property("onread") is not None and not node.parent.get_property("buffer_reads"): # ... is modified by an onread action without the atomicity of read buffering # Enforce 10.6.1-f self.msg.error( f"The field '{node.inst_name}' spans multiple software-accessible" " subwords and is modified on-read, making it impossible to" " access its value correctly. Consider enabling read" " double-buffering. \n" "For more details, see: https://peakrdl-busdecoder.readthedocs.io/en/latest/udps/read_buffering.html", node.inst.inst_src_ref, ) def exit_AddressableComponent(self, node: AddressableNode) -> None: if not isinstance(node, RegNode): # Exiting block-like node contains_external_block = self._contains_external_block_stack.pop() if self._contains_external_block_stack: # Still in the design. Update stack self._contains_external_block_stack[-1] |= contains_external_block else: # Exiting top addrmap. Resolve final answer self.contains_external_block = contains_external_block if contains_external_block: # Check that addressing follows strict alignment rules to allow # for simplified address bit-pruning if node.external: err_suffix = "is external" else: err_suffix = "contains an external addrmap/regfile/mem" req_align = roundup_pow2(node.size) if (node.raw_address_offset % req_align) != 0: self.msg.error( f"Address offset +0x{node.raw_address_offset:x} of instance '{node.inst_name}' is not a power of 2 multiple of its size 0x{node.size:x}. " f"This is required by the busdecoder exporter if a component {err_suffix}.", node.inst.inst_src_ref, ) if node.is_array: assert node.array_stride is not None if not is_pow2(node.array_stride): self.msg.error( f"Address stride of instance array '{node.inst_name}' is not a power of 2" f"This is required by the busdecoder exporter if a component {err_suffix}.", node.inst.inst_src_ref, )