* Initial plan * Fix max_decode_depth to properly control decoder hierarchy and port generation Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Fix test that relied on old depth behavior Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Update documentation for max_decode_depth parameter Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * fix format * Add variable_depth RDL file and smoke tests for max_decode_depth parameter Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * Add variable depth tests for APB3 and AXI4-Lite CPUIFs Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com> * fix * fix * bump --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: arnavsacheti <36746504+arnavsacheti@users.noreply.github.com>
128 lines
4.8 KiB
Python
128 lines
4.8 KiB
Python
from typing import TypedDict
|
|
|
|
from systemrdl.node import AddressableNode, AddrmapNode
|
|
from systemrdl.rdltypes.user_enum import UserEnum
|
|
|
|
from .design_scanner import DesignScanner
|
|
from .identifier_filter import kw_filter as kwf
|
|
from .utils import clog2
|
|
|
|
|
|
class DesignStateKwargs(TypedDict, total=False):
|
|
reuse_hwif_typedefs: bool
|
|
module_name: str
|
|
package_name: str
|
|
address_width: int
|
|
cpuif_unroll: bool
|
|
max_decode_depth: int
|
|
|
|
|
|
class DesignState:
|
|
"""
|
|
Dumping ground for all sorts of variables that are relevant to a particular
|
|
design.
|
|
"""
|
|
|
|
def __init__(self, top_node: AddrmapNode, kwargs: DesignStateKwargs) -> None:
|
|
self.top_node = top_node
|
|
msg = top_node.env.msg
|
|
|
|
# ------------------------
|
|
# Extract compiler args
|
|
# ------------------------
|
|
self.reuse_hwif_typedefs: bool = kwargs.pop("reuse_hwif_typedefs", True)
|
|
self.module_name: str = kwargs.pop("module_name", None) or kwf(self.top_node.inst_name)
|
|
self.package_name: str = kwargs.pop("package_name", None) or f"{self.module_name}_pkg"
|
|
user_addr_width: int | None = kwargs.pop("address_width", None)
|
|
|
|
self.cpuif_unroll: bool = kwargs.pop("cpuif_unroll", False)
|
|
self.max_decode_depth: int = kwargs.pop("max_decode_depth", 1)
|
|
|
|
# ------------------------
|
|
# Info about the design
|
|
# ------------------------
|
|
self.cpuif_data_width = 0
|
|
|
|
# Track any referenced enums
|
|
self.user_enums: list[type[UserEnum]] = []
|
|
|
|
self.has_external_addressable = False
|
|
self.has_external_block = False
|
|
|
|
# Scan the design to fill in above variables
|
|
DesignScanner(self).do_scan()
|
|
|
|
if self.cpuif_data_width == 0:
|
|
# Scanner did not find any registers in the design being exported,
|
|
# so the width is not known.
|
|
# Assume 32-bits
|
|
msg.warning(
|
|
"Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.",
|
|
self.top_node.inst.def_src_ref,
|
|
)
|
|
self.cpuif_data_width = 32
|
|
|
|
# ------------------------
|
|
# Min address width encloses the total size AND at least 1 useful address bit
|
|
self.addr_width = max(clog2(self.top_node.size), clog2(self.cpuif_data_width // 8) + 1)
|
|
|
|
if user_addr_width is None:
|
|
return
|
|
|
|
if user_addr_width < self.addr_width:
|
|
msg.fatal(f"User-specified address width shall be greater than or equal to {self.addr_width}.")
|
|
self.addr_width = user_addr_width
|
|
|
|
def get_addressable_children_at_depth(self, unroll: bool = False) -> list[AddressableNode]:
|
|
"""
|
|
Get addressable children at the decode boundary based on max_decode_depth.
|
|
|
|
max_decode_depth semantics:
|
|
- 0: decode all levels (return leaf registers)
|
|
- 1: decode only top level (return children at depth 1)
|
|
- 2: decode top + 1 level (return children at depth 2)
|
|
- N: decode down to depth N (return children at depth N)
|
|
|
|
Args:
|
|
unroll: Whether to unroll arrayed nodes
|
|
|
|
Returns:
|
|
List of addressable nodes at the decode boundary
|
|
"""
|
|
from systemrdl.node import RegNode
|
|
|
|
def collect_nodes(node: AddressableNode, current_depth: int) -> list[AddressableNode]:
|
|
"""Recursively collect nodes at the decode boundary."""
|
|
result: list[AddressableNode] = []
|
|
|
|
# For depth 0, collect all leaf registers
|
|
if self.max_decode_depth == 0:
|
|
# If this is a register, it's a leaf
|
|
if isinstance(node, RegNode):
|
|
result.append(node)
|
|
else:
|
|
# Recurse into children
|
|
for child in node.children(unroll=unroll):
|
|
if isinstance(child, AddressableNode):
|
|
result.extend(collect_nodes(child, current_depth + 1))
|
|
else:
|
|
# For depth N, collect children at depth N
|
|
if current_depth == self.max_decode_depth:
|
|
# We're at the decode boundary - return this node
|
|
result.append(node)
|
|
elif current_depth < self.max_decode_depth:
|
|
# We haven't reached the boundary yet - recurse
|
|
for child in node.children(unroll=unroll):
|
|
if isinstance(child, AddressableNode):
|
|
result.extend(collect_nodes(child, current_depth + 1))
|
|
|
|
return result
|
|
|
|
# Start collecting from top node's children
|
|
nodes: list[AddressableNode] = []
|
|
for child in self.top_node.children(unroll=unroll):
|
|
if isinstance(child, AddressableNode):
|
|
nodes.extend(collect_nodes(child, 1))
|
|
|
|
return nodes
|