Files
PeakRDL-BusDecoder/src/peakrdl_busdecoder/validate_design.py
Arnav Sacheti eb5e64b151 format
2025-10-19 21:52:12 -07:00

186 lines
8.6 KiB
Python

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,
)