adding decoder logic

This commit is contained in:
Arnav Sacheti
2025-10-16 22:35:36 -07:00
parent 2937624ee7
commit 0c66453ba0
19 changed files with 354 additions and 326 deletions

View File

@@ -101,6 +101,7 @@ class Exporter(ExporterSubcommandPlugin):
arg_group.add_argument( arg_group.add_argument(
"--unroll", "--unroll",
action="store_true", action="store_true",
default=False,
help="""Unroll arrayed addressable nodes into separate instances in help="""Unroll arrayed addressable nodes into separate instances in
the CPU interface. By default, arrayed nodes are kept as arrays. the CPU interface. By default, arrayed nodes are kept as arrays.
""", """,
@@ -117,5 +118,5 @@ class Exporter(ExporterSubcommandPlugin):
module_name=options.module_name, module_name=options.module_name,
package_name=options.package_name, package_name=options.package_name,
address_width=options.addr_width, address_width=options.addr_width,
unroll=options.unroll, cpuif_unroll=options.unroll,
) )

View File

@@ -1,157 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.node import FieldNode, RegNode
from systemrdl.walker import WalkerAction
from .forloop_generator import RDLForLoopGenerator
from .sv_int import SVInt
from .utils import get_indexed_path
if TYPE_CHECKING:
from systemrdl.node import AddressableNode, AddrmapNode
from .exporter import BusDecoderExporter
class AddressDecode:
def __init__(self, exp: "BusDecoderExporter") -> None:
self.exp = exp
@property
def top_node(self) -> "AddrmapNode":
return self.exp.ds.top_node
def get_implementation(self) -> str:
gen = DecodeLogicGenerator(self)
s = gen.get_content(self.top_node)
assert s is not None
return s
def get_access_strobe(self, node: RegNode | FieldNode, reduce_substrobes: bool = True) -> str:
"""
Returns the Verilog string that represents the register/field's access strobe.
"""
if isinstance(node, FieldNode):
field = node
path = get_indexed_path(self.top_node, node.parent)
regwidth = node.parent.get_property("regwidth")
accesswidth = node.parent.get_property("accesswidth")
if regwidth > accesswidth:
# Is wide register.
# Determine the substrobe(s) relevant to this field
sidx_hi = field.msb // accesswidth
sidx_lo = field.lsb // accesswidth
if sidx_hi == sidx_lo:
suffix = f"[{sidx_lo}]"
else:
suffix = f"[{sidx_hi}:{sidx_lo}]"
path += suffix
if sidx_hi != sidx_lo and reduce_substrobes:
return "|decoded_reg_strb." + path
else:
path = get_indexed_path(self.top_node, node)
return "decoded_reg_strb." + path
def get_external_block_access_strobe(self, node: "AddressableNode") -> str:
assert node.external
assert not isinstance(node, RegNode)
path = get_indexed_path(self.top_node, node)
return "decoded_reg_strb." + path
class DecodeLogicGenerator(RDLForLoopGenerator):
def __init__(self, addr_decode: AddressDecode) -> None:
self.addr_decode = addr_decode
super().__init__()
# List of address strides for each dimension
self._array_stride_stack: list[int] = []
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
super().enter_AddressableComponent(node)
if node.array_dimensions:
assert node.array_stride is not None
# Collect strides for each array dimension
current_stride = node.array_stride
strides: list[int] = []
for dim in reversed(node.array_dimensions):
strides.append(current_stride)
current_stride *= dim
strides.reverse()
self._array_stride_stack.extend(strides)
if node.external and not isinstance(node, RegNode):
# Is an external block
addr_str = self._get_address_str(node)
strb = self.addr_decode.get_external_block_access_strobe(node)
rhs = f"cpuif_req_masked & (cpuif_addr >= {addr_str}) & (cpuif_addr <= {addr_str} + {SVInt(node.size - 1, self.addr_decode.exp.ds.addr_width)})"
self.add_content(f"{strb} = {rhs};")
self.add_content(f"is_external |= {rhs};")
return WalkerAction.SkipDescendants
return WalkerAction.Continue
def _get_address_str(self, node: "AddressableNode", subword_offset: int = 0) -> str:
expr_width = self.addr_decode.exp.ds.addr_width
a = str(
SVInt(
node.raw_absolute_address - self.addr_decode.top_node.raw_absolute_address + subword_offset,
expr_width,
)
)
for i, stride in enumerate(self._array_stride_stack):
a += f" + ({expr_width})'(i{i}) * {SVInt(stride, expr_width)}"
return a
def enter_Reg(self, node: RegNode) -> None:
regwidth = node.get_property("regwidth")
accesswidth = node.get_property("accesswidth")
if regwidth == accesswidth:
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node)})"
s = f"{self.addr_decode.get_access_strobe(node)} = {rhs};"
self.add_content(s)
if node.external:
readable = node.has_sw_readable
writable = node.has_sw_writable
if readable and writable:
self.add_content(f"is_external |= {rhs};")
elif readable and not writable:
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
elif not readable and writable:
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
else:
raise RuntimeError
else:
# Register is wide. Create a substrobe for each subword
n_subwords = regwidth // accesswidth
subword_stride = accesswidth // 8
for i in range(n_subwords):
rhs = f"cpuif_req_masked & (cpuif_addr == {self._get_address_str(node, subword_offset=(i * subword_stride))})"
s = f"{self.addr_decode.get_access_strobe(node)}[{i}] = {rhs};"
self.add_content(s)
if node.external:
readable = node.has_sw_readable
writable = node.has_sw_writable
if readable and writable:
self.add_content(f"is_external |= {rhs};")
elif readable and not writable:
self.add_content(f"is_external |= {rhs} & !cpuif_req_is_wr;")
elif not readable and writable:
self.add_content(f"is_external |= {rhs} & cpuif_req_is_wr;")
else:
raise RuntimeError
def exit_AddressableComponent(self, node: "AddressableNode") -> None:
super().exit_AddressableComponent(node)
if not node.array_dimensions:
return
for _ in node.array_dimensions:
self._array_stride_stack.pop()

View File

@@ -0,0 +1,15 @@
from .body import Body, SupportsStr
from .for_loop_body import ForLoopBody
from .while_loop_body import WhileLoopBody
from .if_body import IfBody
from .combinational_body import CombinationalBody
__all__ = [
"Body",
"SupportsStr",
"ForLoopBody",
"WhileLoopBody",
"IfBody",
"CombinationalBody",
]

View File

@@ -0,0 +1,17 @@
from typing import Protocol, Self
class SupportsStr(Protocol):
def __str__(self) -> str: ...
class Body:
def __init__(self) -> None:
self.lines: list[SupportsStr] = []
def __str__(self) -> str:
return "\n".join(map(str, self.lines))
def __add__(self, other: SupportsStr) -> Self:
self.lines.append(other)
return self

View File

@@ -0,0 +1,8 @@
from textwrap import indent
from .body import Body
class CombinationalBody(Body):
def __str__(self) -> str:
return f"""always_comb begin
{indent(super().__str__(), "\t")}
end"""

View File

@@ -0,0 +1,15 @@
from textwrap import indent
from .body import Body
class ForLoopBody(Body):
def __init__(self, type: str, iterator: str, dim: int):
super().__init__()
self._type = type
self._iterator = iterator
self._dim = dim
def __str__(self) -> str:
return f"""for ({self._type} {self._iterator} = 0; {self._iterator} < {self._dim}; {self._iterator}++) begin
{indent(super().__str__(), "\t")}
end"""

View File

@@ -0,0 +1,84 @@
from __future__ import annotations
from types import EllipsisType
from typing import Self
from .body import Body, SupportsStr
class IfBody(Body):
def __init__(self, *, indent: int = 4) -> None:
super().__init__()
# (None means 'else')
self._branches: list[tuple[SupportsStr | None, Body]] = []
self._has_else = False
self._indent = " " * indent
# --- Item access: if/else-if via condition; else via Ellipsis/None ---
def __getitem__(self, condition: SupportsStr | EllipsisType | None) -> Body:
if self._has_else:
raise RuntimeError("Cannot add branches after an 'else' branch.")
if condition is Ellipsis or condition is None:
if self._has_else:
raise RuntimeError("Only one 'else' branch is allowed.")
self._has_else = True
b = Body()
self._branches.append((None, b))
return b
# conditional branch
b = Body()
self._branches.append((condition, b))
return b
# --- In-place or: if/else-if via (cond, Body); else via Body ---
def __ior__(self, other: tuple[SupportsStr, Body] | Body) -> Self:
if isinstance(other, Body):
if self._has_else:
raise RuntimeError("Only one 'else' branch is allowed.")
if self._has_else or (self._branches and self._branches[-1][0] is None):
raise RuntimeError("Cannot add branches after an 'else' branch.")
self._branches.append((None, other))
self._has_else = True
return self
cond, body = other
if self._has_else:
raise RuntimeError("Cannot add branches after an 'else' branch.")
self._branches.append((cond, body))
return self
# --- Context manager for a branch ---
class _BranchCtx:
def __init__(self, outer: IfBody, condition: SupportsStr | None) -> None:
self._outer = outer
# route through __getitem__ to reuse validation logic
self._body = outer[Ellipsis if condition is None else condition]
def __enter__(self) -> Body:
return self._body
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: object | None,
) -> bool:
return False
def cm(self, condition: SupportsStr | None) -> "IfBody._BranchCtx":
"""Use with: with ifb.cm('cond') as b: ... ; use None for else."""
return IfBody._BranchCtx(self, condition)
# --- Rendering ---
def __str__(self) -> str:
out: list[str] = []
for i, (cond, body) in enumerate(self._branches):
if i == 0 and cond is not None:
out.append(f"if ({cond}) begin")
elif cond is not None:
out.append(f"else if ({cond}) begin")
else:
out.append("else begin")
body_str = str(body)
if body_str:
out.extend(self._indent + ln for ln in body_str.splitlines())
out.append("end")
return "\n".join(out)

View File

@@ -0,0 +1,13 @@
from textwrap import indent
from .body import Body
class WhileLoopBody(Body):
def __init__(self, condition: str):
super().__init__()
self._condition = condition
def __str__(self) -> str:
return f"""while ({self._condition}) begin
{indent(super().__str__(), "\t")}
end"""

View File

@@ -12,7 +12,7 @@ class APB3Cpuif(BaseCpuif):
if not child.is_array: if not child.is_array:
return base return base
if child.current_idx is not None: if child.current_idx is not None:
return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]" return f"{base}_{'_'.join(map(str, child.current_idx))}"
return f"{base} [N_{child.inst_name.upper()}S]" return f"{base} [N_{child.inst_name.upper()}S]"
@property @property
@@ -75,4 +75,4 @@ class APB3Cpuif(BaseCpuif):
address on the bus matches the address range of the given node. address on the bus matches the address range of the given node.
""" """
addr_pred = self.get_address_predicate(node) addr_pred = self.get_address_predicate(node)
return addr_pred return addr_pred

View File

@@ -61,3 +61,4 @@ class APB3CpuifFlat(BaseCpuif):
if idx is not None: if idx is not None:
return f"{base}_{signal}[{idx}]" return f"{base}_{signal}[{idx}]"
return f"{base}_{signal}[N_{node.inst_name.upper()}S]" return f"{base}_{signal}[N_{node.inst_name.upper()}S]"

View File

@@ -1,4 +1,4 @@
{%- if cpuif.is_interface -%} {%- if cpuif.is_interface %}
`ifndef SYNTHESIS `ifndef SYNTHESIS
initial begin initial begin
assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH) assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH)
@@ -16,14 +16,14 @@ assign cpuif_rd_en = !{{cpuif.signal("PWRITE")}};
{%- for child in cpuif.addressable_children -%} {%- for child in cpuif.addressable_children -%}
{%- if child is array -%} {%- if child is array -%}
for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}} for (genvar g_idx = 0; g_idx < N_{{child.inst_name|upper}}S; g_idx++) begin : g_passthrough_{{child.inst_name|lower}}
assign {{cpuif.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.signal("PCLK")}}; assign {{cpuif.signal("PCLK", child, "g_idx")}} = {{cpuif.signal("PCLK")}};
assign {{cpuif.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.signal("PRESETn")}}; assign {{cpuif.signal("PRESETn", child, "g_idx")}} = {{cpuif.signal("PRESETn")}};
assign {{cpuif.signal("PSELx", child, f"g_{child.inst_name.lower()}_idx")}} = (cpuif_wr_req[{{loop.indx}}] || cpuif_rd_req[{{loop.indx}}]) ? 1'b1 : 1'b0; assign {{cpuif.signal("PSELx", child, "g_idx")}} = (cpuif_wr_req[{{loop.index}}] || cpuif_rd_req[{{loop.index}}]) ? 1'b1 : 1'b0;
assign {{cpuif.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.signal("PENABLE")}}; assign {{cpuif.signal("PENABLE", child, "g_idx")}} = {{cpuif.signal("PENABLE")}};
assign {{cpuif.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0; assign {{cpuif.signal("PWRITE", child, "g_idx")}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
assign {{cpuif.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.get_address_slice(cpuif_wr_addr, child)}}; assign {{cpuif.signal("PADDR", child, "g_idx")}} = cpuif_wr_addr{{child|address_slice}};
assign {{cpuif.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = cpuif_wr_data; assign {{cpuif.signal("PWDATA", child, "g_idx")}} = cpuif_wr_data;
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}}; assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}}; assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}}; assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
@@ -34,13 +34,13 @@ assign {{cpuif.signal("PRESETn", child)}} = {{cpuif.signal("PRESETn")}};
assign {{cpuif.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index0}}] || cpuif_rd_sel[{{loop.index0}}]) ? 1'b1 : 1'b0; assign {{cpuif.signal("PSELx", child)}} = (cpuif_wr_sel[{{loop.index0}}] || cpuif_rd_sel[{{loop.index0}}]) ? 1'b1 : 1'b0;
assign {{cpuif.signal("PENABLE", child)}} = {{cpuif.signal("PENABLE")}}; assign {{cpuif.signal("PENABLE", child)}} = {{cpuif.signal("PENABLE")}};
assign {{cpuif.signal("PWRITE", child)}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0; assign {{cpuif.signal("PWRITE", child)}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0;
assign {{cpuif.signal("PADDR", child)}} = {{cpuif.get_address_slice(cpuif_wr_addr, child)}}; assign {{cpuif.signal("PADDR", child)}} = cpuif_wr_addr{{child|address_slice}};
assign {{cpuif.signal("PWDATA", child)}} = cpuif_wr_data; assign {{cpuif.signal("PWDATA", child)}} = cpuif_wr_data;
assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}}; assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}};
assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}}; assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}};
assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}}; assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}};
{%- endif -%} {%- endif -%}
{%- endfor -%} {%- endfor%}
always_comb begin always_comb begin
{{cpuif.signal("PREADY")}} = 1'b0; {{cpuif.signal("PREADY")}} = 1'b0;

View File

@@ -82,6 +82,7 @@ class BaseCpuif:
jj_env.filters["clog2"] = clog2 # type: ignore jj_env.filters["clog2"] = clog2 # type: ignore
jj_env.filters["is_pow2"] = is_pow2 # type: ignore jj_env.filters["is_pow2"] = is_pow2 # type: ignore
jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore
jj_env.filters["address_slice"] = self.get_address_slice # type: ignore
context = { context = {
"cpuif": self, "cpuif": self,
@@ -90,3 +91,34 @@ class BaseCpuif:
template = jj_env.get_template(self.template_path) template = jj_env.get_template(self.template_path)
return template.render(context) return template.render(context)
def get_address_slice(self, node: AddressableNode) -> str:
"""
Returns a SystemVerilog expression that extracts the address bits
relevant to the given node.
"""
addr_mask = (1 << self.addr_width) - 1
addr = node.absolute_address & addr_mask
size = node.size
if size == 0:
raise ValueError(f"Node size '{size:#X}' must be greater than 0")
if (addr % size) != 0:
raise ValueError(f"Node address '{addr:#X}' must be aligned to its size '{size:#X}'")
# Calculate the address range of the node
addr_start = addr
addr_end = addr + size - 1
if addr_end > addr_mask:
raise ValueError("Node address range exceeds address width")
# Calculate the number of bits needed to represent the size
size_bits = size.bit_length() - 1
if size_bits < 0:
size_bits = 0
if size_bits >= self.addr_width:
# Node covers entire address space, so return full address
return ""
# Extract the relevant bits from PADDR
return f"[{self.addr_width - 1}:{size_bits}]"

View File

@@ -0,0 +1,132 @@
from collections import deque
from systemrdl.node import AddressableNode
from systemrdl.walker import RDLListener, RDLSimpleWalker, WalkerAction
from .body import Body, ForLoopBody, IfBody
from .sv_int import SVInt
class AddressDecode:
def __init__(self, node: AddressableNode, addr_width: int) -> None:
self._node = node
self._addr_width = addr_width
def walk(self) -> str:
walker = RDLSimpleWalker()
dlg = DecodeLogicGenerator(self)
walker.walk(self._node, dlg, skip_top=True)
return str(dlg)
@property
def node(self) -> AddressableNode:
return self._node
@property
def addr_width(self) -> int:
return self._addr_width
class DecodeLogicGenerator(RDLListener):
cpuif_addr_signal = "addr"
cpuif_sel_prefix = "cpuif_"
def __init__(
self,
address_decoder: AddressDecode,
max_depth: int = 1,
) -> None:
self._address_decoder = address_decoder
self._depth = 0
self._max_depth = max_depth
self._stack: list[Body] = [IfBody()]
self._conditions: deque[str] = deque()
self._select_signal = [f"{self.cpuif_sel_prefix}{address_decoder.node.inst_name}"]
# Stack to keep track of array strides for nested arrayed components
self._array_stride_stack: list[int] = []
def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
# Generate address bounds
addr_width = self._address_decoder.addr_width
l_bound = SVInt(
node.raw_absolute_address - self._address_decoder.node.raw_absolute_address,
addr_width,
)
u_bound = l_bound + SVInt(node.total_size, addr_width)
# Handle arrayed components
l_bound_str = str(l_bound)
u_bound_str = str(u_bound)
for i, stride in enumerate(self._array_stride_stack):
l_bound_str += f" + (({addr_width})'(i{i}) * {SVInt(stride, addr_width)})"
u_bound_str += f" + (({addr_width})'(i{i}) * {SVInt(stride, addr_width)})"
# Generate condition string
condition = (
f"({self.cpuif_addr_signal} >= ({l_bound_str})) && ({self.cpuif_addr_signal} < ({u_bound_str}))"
)
if node.array_dimensions:
assert node.array_stride is not None, "Array stride should be defined for arrayed components"
# Collect strides for each array dimension
current_stride = node.array_stride
strides: list[int] = []
for dim in reversed(node.array_dimensions):
strides.append(current_stride)
current_stride *= dim
strides.reverse()
self._array_stride_stack.extend(strides)
# Generate condition string and manage stack
signal = node.inst_name
if isinstance(self._stack[-1], IfBody) and node.array_dimensions:
# arrayed component with new if-body
self._conditions.append(condition)
for dim in node.array_dimensions:
fb = ForLoopBody(
"int",
f"i{self._depth}",
dim,
)
self._stack.append(fb)
signal += f"[i{self._depth}]"
self._depth += 1
self._stack.append(IfBody())
elif isinstance(self._stack[-1], IfBody):
# non-arrayed component with if-body
with self._stack[-1].cm(condition) as b:
b += f"{'.'.join([*self._select_signal, signal])} = 1'b1;"
self._select_signal.append(signal)
# if node.external:
# return WalkerAction.SkipDescendants
return WalkerAction.Continue
def exit_AddressableComponent(self, node: AddressableNode) -> None:
self._select_signal.pop()
if not node.array_dimensions:
return
ifb = self._stack.pop()
self._stack[-1] += ifb
for _ in node.array_dimensions:
self._depth -= 1
b = self._stack.pop()
if b.lines:
if isinstance(self._stack[-1], IfBody):
with self._stack[-1].cm(self._conditions.pop()) as parent_b:
parent_b += b
else:
self._stack[-1] += b
self._array_stride_stack.pop()
def __str__(self) -> str:
return str(self._stack[-1])

View File

@@ -1,41 +0,0 @@
from typing import TYPE_CHECKING
from systemrdl.node import AddressableNode, AddrmapNode, FieldNode, RegNode
if TYPE_CHECKING:
from .addr_decode import AddressDecode
from .exporter import BusDecoderExporter, DesignState
class Dereferencer:
"""
This class provides an interface to convert conceptual SystemRDL references
into Verilog identifiers
"""
def __init__(self, exp: "BusDecoderExporter") -> None:
self.exp = exp
@property
def address_decode(self) -> "AddressDecode":
return self.exp.address_decode
@property
def ds(self) -> "DesignState":
return self.exp.ds
@property
def top_node(self) -> AddrmapNode:
return self.exp.ds.top_node
def get_access_strobe(self, obj: RegNode | FieldNode, reduce_substrobes: bool = True) -> str:
"""
Returns the Verilog string that represents the register's access strobe
"""
return self.address_decode.get_access_strobe(obj, reduce_substrobes)
def get_external_block_access_strobe(self, obj: "AddressableNode") -> str:
"""
Returns the Verilog string that represents the external block's access strobe
"""
return self.address_decode.get_external_block_access_strobe(obj)

View File

@@ -1,14 +1,15 @@
import os import os
from datetime import datetime
from importlib.metadata import version
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
import jinja2 as jj import jinja2 as jj
from systemrdl.node import AddrmapNode, RootNode from systemrdl.node import AddrmapNode, RootNode
from systemrdl.rdltypes.user_enum import UserEnum from systemrdl.rdltypes.user_enum import UserEnum
from .addr_decode import AddressDecode
from .cpuif import BaseCpuif from .cpuif import BaseCpuif
from .cpuif.apb4 import APB4Cpuif from .cpuif.apb4 import APB4Cpuif
from .dereferencer import Dereferencer from .decoder import AddressDecode
from .identifier_filter import kw_filter as kwf from .identifier_filter import kw_filter as kwf
from .scan_design import DesignScanner from .scan_design import DesignScanner
from .sv_int import SVInt from .sv_int import SVInt
@@ -22,13 +23,12 @@ if TYPE_CHECKING:
class BusDecoderExporter: class BusDecoderExporter:
cpuif: BaseCpuif cpuif: BaseCpuif
address_decode: AddressDecode address_decode: AddressDecode
dereferencer: Dereferencer
ds: "DesignState" ds: "DesignState"
def __init__(self, **kwargs: Any) -> None: def __init__(self, **kwargs: Any) -> None:
# Check for stray kwargs # Check for stray kwargs
if kwargs: if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'") raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
loader = jj.ChoiceLoader( loader = jj.ChoiceLoader(
[ [
@@ -84,18 +84,19 @@ class BusDecoderExporter:
# Check for stray kwargs # Check for stray kwargs
if kwargs: if kwargs:
raise TypeError(f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'") raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
# Construct exporter components # Construct exporter components
self.cpuif = cpuif_cls(self) self.cpuif = cpuif_cls(self)
self.address_decode = AddressDecode(self) self.address_decode = AddressDecode(top_node, self.ds.addr_width)
self.dereferencer = Dereferencer(self)
# Validate that there are no unsupported constructs # Validate that there are no unsupported constructs
DesignValidator(self).do_validate() DesignValidator(self).do_validate()
# Build Jinja template context # Build Jinja template context
context = { context = {
"current_date": datetime.now().strftime("%Y-%m-%d"),
"version": version("peakrdl-busdecoder"),
"cpuif": self.cpuif, "cpuif": self.cpuif,
"address_decode": self.address_decode, "address_decode": self.address_decode,
"ds": self.ds, "ds": self.ds,
@@ -115,9 +116,6 @@ class BusDecoderExporter:
stream = template.stream(context) stream = template.stream(context)
stream.dump(module_file_path) stream.dump(module_file_path)
if hwif_report_file:
hwif_report_file.close()
class DesignState: class DesignState:
""" """
@@ -147,6 +145,9 @@ class DesignState:
# Track any referenced enums # Track any referenced enums
self.user_enums: list[type[UserEnum]] = [] self.user_enums: list[type[UserEnum]] = []
self.has_external_addressable = False
self.has_external_block = False
# Scan the design to fill in above variables # Scan the design to fill in above variables
DesignScanner(self).do_scan() DesignScanner(self).do_scan()

View File

@@ -1,99 +0,0 @@
import textwrap
from typing import TYPE_CHECKING
from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
if TYPE_CHECKING:
from systemrdl.node import AddressableNode, Node
class Body:
def __init__(self) -> None:
self.children: list[str | Body] = []
def __str__(self) -> str:
s = "\n".join(str(x) for x in self.children)
return s
class LoopBody(Body):
def __init__(self, dim: int, iterator: str, i_type: str) -> None:
super().__init__()
self.dim = dim
self.iterator = iterator
self.i_type = i_type
def __str__(self) -> str:
s = super().__str__()
return (
f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n"
+ textwrap.indent(s, "\t")
+ "\nend"
)
class ForLoopGenerator:
i_type = "int"
loop_body_cls = LoopBody
def __init__(self) -> None:
self._loop_level = 0
self._stack: list[Body] = []
@property
def current_loop(self) -> Body:
return self._stack[-1]
def push_loop(self, dim: int) -> None:
i = f"i{self._loop_level}"
b = self.loop_body_cls(dim, i, self.i_type)
self._stack.append(b)
self._loop_level += 1
def add_content(self, s: str) -> None:
self.current_loop.children.append(s)
def pop_loop(self) -> None:
b = self._stack.pop()
if b.children:
# Loop body is not empty. Attach it to the parent
self.current_loop.children.append(b)
self._loop_level -= 1
def start(self) -> None:
assert not self._stack
b = Body()
self._stack.append(b)
def finish(self) -> str | None:
b = self._stack.pop()
assert not self._stack
if not b.children:
return None
return str(b)
class RDLForLoopGenerator(ForLoopGenerator, RDLListener):
def get_content(self, node: "Node") -> str | None:
self.start()
walker = RDLWalker()
walker.walk(node, self, skip_top=True)
return self.finish()
def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
if not node.array_dimensions:
return None
for dim in node.array_dimensions:
self.push_loop(dim)
return None
def exit_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None:
if not node.array_dimensions:
return None
for _ in node.array_dimensions:
self.pop_loop()
return None

View File

@@ -12,11 +12,11 @@
module {{ds.module_name}} module {{ds.module_name}}
{%- if cpuif.parameters %} #( {%- if cpuif.parameters %} #(
{{-cpuif.parameters|join(",\n")|indent(8)}} {{cpuif.parameters|join(",\n")|indent(4)}}
) {%- endif %} ( ) {%- endif %} (
{{-cpuif.port_declaration|indent(8)}} {{cpuif.port_declaration|indent(4)}}
); );
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// CPU Bus interface logic // CPU Bus interface logic

View File

View File

@@ -13,3 +13,9 @@ class SVInt:
return f"{self.value.bit_length()}'h{self.value:x}" return f"{self.value.bit_length()}'h{self.value:x}"
else: else:
return f"'h{self.value:x}" return f"'h{self.value:x}"
def __add__(self, other: "SVInt") -> "SVInt":
if self.width is not None and other.width is not None:
return SVInt(self.value + other.value, max(self.width, other.width))
else:
return SVInt(self.value + other.value, None)