* 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>
169 lines
5.7 KiB
Python
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)
|