adding decoder logic
This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
|
||||||
15
src/peakrdl_busdecoder/body/__init__.py
Normal file
15
src/peakrdl_busdecoder/body/__init__.py
Normal 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",
|
||||||
|
]
|
||||||
17
src/peakrdl_busdecoder/body/body.py
Normal file
17
src/peakrdl_busdecoder/body/body.py
Normal 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
|
||||||
8
src/peakrdl_busdecoder/body/combinational_body.py
Normal file
8
src/peakrdl_busdecoder/body/combinational_body.py
Normal 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"""
|
||||||
15
src/peakrdl_busdecoder/body/for_loop_body.py
Normal file
15
src/peakrdl_busdecoder/body/for_loop_body.py
Normal 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"""
|
||||||
84
src/peakrdl_busdecoder/body/if_body.py
Normal file
84
src/peakrdl_busdecoder/body/if_body.py
Normal 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)
|
||||||
13
src/peakrdl_busdecoder/body/while_loop_body.py
Normal file
13
src/peakrdl_busdecoder/body/while_loop_body.py
Normal 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"""
|
||||||
@@ -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
|
||||||
@@ -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]"
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}]"
|
||||||
|
|||||||
132
src/peakrdl_busdecoder/decoder.py
Normal file
132
src/peakrdl_busdecoder/decoder.py
Normal 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])
|
||||||
@@ -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)
|
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
0
src/peakrdl_busdecoder/py.typed
Normal file
0
src/peakrdl_busdecoder/py.typed
Normal 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)
|
||||||
|
|||||||
Reference in New Issue
Block a user