Files
PeakRDL-BusDecoder/src/peakrdl_busdecoder/decode_logic_gen.py
Copilot d7481e71ba Fix max_decode_depth to control decoder hierarchy and port generation (#18)
* 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>
2025-10-28 23:38:54 -07:00

169 lines
5.7 KiB
Python

from collections import deque
from enum import Enum
from typing import cast
from systemrdl.node import AddressableNode
from systemrdl.walker import WalkerAction
from .body import Body, ForLoopBody, IfBody
from .design_state import DesignState
from .listener import BusDecoderListener
from .sv_int import SVInt
from .utils import get_indexed_path
class DecodeLogicFlavor(Enum):
READ = "rd"
WRITE = "wr"
@property
def cpuif_address(self) -> str:
return f"cpuif_{self.value}_addr"
@property
def cpuif_select(self) -> str:
return f"cpuif_{self.value}_sel"
class DecodeLogicGenerator(BusDecoderListener):
def __init__(
self,
ds: DesignState,
flavor: DecodeLogicFlavor,
) -> None:
super().__init__(ds)
self._flavor = flavor
self._decode_stack: deque[Body] = deque() # Tracks decoder body
self._cond_stack: deque[str] = deque() # Tracks conditions nested for loops
# Initial Stack Conditions
self._decode_stack.append(IfBody())
def cpuif_addr_predicate(self, node: AddressableNode, total_size: bool = True) -> list[str]:
# Generate address bounds
addr_width = self._ds.addr_width
l_bound = SVInt(
node.raw_absolute_address,
addr_width,
)
if total_size:
u_bound = l_bound + SVInt(node.total_size, addr_width)
else:
u_bound = l_bound + SVInt(node.size, addr_width)
array_stack = list(self._array_stride_stack)
if total_size and node.array_dimensions:
array_stack = array_stack[: -len(node.array_dimensions)]
# Handle arrayed components
l_bound_comp = [str(l_bound)]
u_bound_comp = [str(u_bound)]
for i, stride in enumerate(array_stack):
l_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
u_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
lower_expr = f"{self._flavor.cpuif_address} >= ({'+'.join(l_bound_comp)})"
upper_expr = f"{self._flavor.cpuif_address} < ({'+'.join(u_bound_comp)})"
predicates: list[str] = []
# Avoid generating a redundant >= 0 comparison, which triggers Verilator warnings.
if not (l_bound.value == 0 and len(l_bound_comp) == 1):
predicates.append(lower_expr)
predicates.append(upper_expr)
return predicates
def cpuif_prot_predicate(self, node: AddressableNode) -> list[str]:
if self._flavor == DecodeLogicFlavor.READ:
# Can we have PROT on read? (axi full?)
return []
# TODO: Implement
return []
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
action = super().enter_AddressableComponent(node)
should_decode = action == WalkerAction.SkipDescendants
if not should_decode and self._ds.max_decode_depth == 0:
# When decoding all levels, treat leaf registers as decode boundary
for child in node.children():
if isinstance(child, AddressableNode):
break
else:
should_decode = True
# Only generate select logic if we're at the decode boundary
if not should_decode:
return action
conditions: list[str] = []
conditions.extend(self.cpuif_addr_predicate(node))
conditions.extend(self.cpuif_prot_predicate(node))
condition = " && ".join(f"({c})" for c in conditions)
# Generate condition string and manage stack
if node.array_dimensions:
# arrayed component with new if-body
self._cond_stack.append(condition)
for i, dim in enumerate(
node.array_dimensions,
):
fb = ForLoopBody(
"int",
f"i{i}",
dim,
)
self._decode_stack.append(fb)
self._decode_stack.append(IfBody())
elif isinstance(self._decode_stack[-1], IfBody):
# non-arrayed component with if-body
ifb = cast(IfBody, self._decode_stack[-1])
with ifb.cm(condition) as b:
b += f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
else:
raise RuntimeError("Invalid decode stack state")
return action
def exit_AddressableComponent(self, node: AddressableNode) -> None:
if not node.array_dimensions:
return
ifb = self._decode_stack.pop()
if not ifb and isinstance(ifb, IfBody):
conditions: list[str] = []
conditions.extend(self.cpuif_addr_predicate(node, total_size=False))
condition = " && ".join(f"({c})" for c in conditions)
with ifb.cm(condition) as b:
b += f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
self._decode_stack[-1] += ifb
for _ in node.array_dimensions:
b = self._decode_stack.pop()
if not b:
continue
if isinstance(self._decode_stack[-1], IfBody):
ifb = cast(IfBody, self._decode_stack[-1])
with ifb.cm(self._cond_stack.pop()) as parent_b:
parent_b += b
else:
self._decode_stack[-1] += b
super().exit_AddressableComponent(node)
def __str__(self) -> str:
body = self._decode_stack[-1]
if isinstance(body, IfBody):
if len(body) == 0:
return f"{self._flavor.cpuif_select}.cpuif_err = 1'b1;"
with body.cm(...) as b:
b += f"{self._flavor.cpuif_select}.cpuif_err = 1'b1;"
return str(body)