From 0c66453ba023aea6e64fb34d40765a72fe5a68fc Mon Sep 17 00:00:00 2001 From: Arnav Sacheti <36746504+arnavsacheti@users.noreply.github.com> Date: Thu, 16 Oct 2025 22:35:36 -0700 Subject: [PATCH] adding decoder logic --- src/peakrdl_busdecoder/__peakrdl__.py | 3 +- src/peakrdl_busdecoder/addr_decode.py | 157 ------------------ src/peakrdl_busdecoder/body/__init__.py | 15 ++ src/peakrdl_busdecoder/body/body.py | 17 ++ .../body/combinational_body.py | 8 + src/peakrdl_busdecoder/body/for_loop_body.py | 15 ++ src/peakrdl_busdecoder/body/if_body.py | 84 ++++++++++ .../body/while_loop_body.py | 13 ++ .../cpuif/apb3/apb3_cpuif.py | 4 +- .../cpuif/apb3/apb3_cpuif_flat.py | 1 + .../cpuif/apb3/apb3_tmpl.sv | 22 +-- src/peakrdl_busdecoder/cpuif/base_cpuif.py | 32 ++++ src/peakrdl_busdecoder/decoder.py | 132 +++++++++++++++ src/peakrdl_busdecoder/dereferencer.py | 41 ----- src/peakrdl_busdecoder/exporter.py | 21 +-- src/peakrdl_busdecoder/forloop_generator.py | 99 ----------- src/peakrdl_busdecoder/module_tmpl.sv | 10 +- src/peakrdl_busdecoder/py.typed | 0 src/peakrdl_busdecoder/sv_int.py | 6 + 19 files changed, 354 insertions(+), 326 deletions(-) delete mode 100644 src/peakrdl_busdecoder/addr_decode.py create mode 100644 src/peakrdl_busdecoder/body/__init__.py create mode 100644 src/peakrdl_busdecoder/body/body.py create mode 100644 src/peakrdl_busdecoder/body/combinational_body.py create mode 100644 src/peakrdl_busdecoder/body/for_loop_body.py create mode 100644 src/peakrdl_busdecoder/body/if_body.py create mode 100644 src/peakrdl_busdecoder/body/while_loop_body.py create mode 100644 src/peakrdl_busdecoder/decoder.py delete mode 100644 src/peakrdl_busdecoder/dereferencer.py delete mode 100644 src/peakrdl_busdecoder/forloop_generator.py create mode 100644 src/peakrdl_busdecoder/py.typed diff --git a/src/peakrdl_busdecoder/__peakrdl__.py b/src/peakrdl_busdecoder/__peakrdl__.py index 1be6b08..c2b4718 100644 --- a/src/peakrdl_busdecoder/__peakrdl__.py +++ b/src/peakrdl_busdecoder/__peakrdl__.py @@ -101,6 +101,7 @@ class Exporter(ExporterSubcommandPlugin): arg_group.add_argument( "--unroll", action="store_true", + default=False, help="""Unroll arrayed addressable nodes into separate instances in the CPU interface. By default, arrayed nodes are kept as arrays. """, @@ -117,5 +118,5 @@ class Exporter(ExporterSubcommandPlugin): module_name=options.module_name, package_name=options.package_name, address_width=options.addr_width, - unroll=options.unroll, + cpuif_unroll=options.unroll, ) diff --git a/src/peakrdl_busdecoder/addr_decode.py b/src/peakrdl_busdecoder/addr_decode.py deleted file mode 100644 index dee5721..0000000 --- a/src/peakrdl_busdecoder/addr_decode.py +++ /dev/null @@ -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() diff --git a/src/peakrdl_busdecoder/body/__init__.py b/src/peakrdl_busdecoder/body/__init__.py new file mode 100644 index 0000000..1584657 --- /dev/null +++ b/src/peakrdl_busdecoder/body/__init__.py @@ -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", +] diff --git a/src/peakrdl_busdecoder/body/body.py b/src/peakrdl_busdecoder/body/body.py new file mode 100644 index 0000000..d37f337 --- /dev/null +++ b/src/peakrdl_busdecoder/body/body.py @@ -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 diff --git a/src/peakrdl_busdecoder/body/combinational_body.py b/src/peakrdl_busdecoder/body/combinational_body.py new file mode 100644 index 0000000..8c090a4 --- /dev/null +++ b/src/peakrdl_busdecoder/body/combinational_body.py @@ -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""" \ No newline at end of file diff --git a/src/peakrdl_busdecoder/body/for_loop_body.py b/src/peakrdl_busdecoder/body/for_loop_body.py new file mode 100644 index 0000000..fdf28e1 --- /dev/null +++ b/src/peakrdl_busdecoder/body/for_loop_body.py @@ -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""" diff --git a/src/peakrdl_busdecoder/body/if_body.py b/src/peakrdl_busdecoder/body/if_body.py new file mode 100644 index 0000000..7fb89d5 --- /dev/null +++ b/src/peakrdl_busdecoder/body/if_body.py @@ -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) diff --git a/src/peakrdl_busdecoder/body/while_loop_body.py b/src/peakrdl_busdecoder/body/while_loop_body.py new file mode 100644 index 0000000..980c2bd --- /dev/null +++ b/src/peakrdl_busdecoder/body/while_loop_body.py @@ -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""" diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py index f58e841..524a490 100644 --- a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py @@ -12,7 +12,7 @@ class APB3Cpuif(BaseCpuif): if not child.is_array: return base 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]" @property @@ -75,4 +75,4 @@ class APB3Cpuif(BaseCpuif): address on the bus matches the address range of the given node. """ addr_pred = self.get_address_predicate(node) - return addr_pred + return addr_pred \ No newline at end of file diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py index 07c9235..981bedf 100644 --- a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py @@ -61,3 +61,4 @@ class APB3CpuifFlat(BaseCpuif): if idx is not None: return f"{base}_{signal}[{idx}]" return f"{base}_{signal}[N_{node.inst_name.upper()}S]" + diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv b/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv index 175d8d5..4067454 100644 --- a/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv @@ -1,4 +1,4 @@ -{%- if cpuif.is_interface -%} +{%- if cpuif.is_interface %} `ifndef SYNTHESIS initial begin 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 -%} {%- 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}} - assign {{cpuif.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.signal("PCLK")}}; - assign {{cpuif.signal("PRESETn", child, f"g_{child.inst_name.lower()}_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("PENABLE", child, f"g_{child.inst_name.lower()}_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("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{cpuif.get_address_slice(cpuif_wr_addr, child)}}; - assign {{cpuif.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = cpuif_wr_data; +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, "g_idx")}} = {{cpuif.signal("PCLK")}}; + assign {{cpuif.signal("PRESETn", child, "g_idx")}} = {{cpuif.signal("PRESETn")}}; + 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, "g_idx")}} = {{cpuif.signal("PENABLE")}}; + assign {{cpuif.signal("PWRITE", child, "g_idx")}} = (cpuif_wr_req[{{loop.index}}]) ? 1'b1 : 1'b0; + assign {{cpuif.signal("PADDR", child, "g_idx")}} = cpuif_wr_addr{{child|address_slice}}; + assign {{cpuif.signal("PWDATA", child, "g_idx")}} = cpuif_wr_data; assign cpuif_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}}; assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", 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("PENABLE", child)}} = {{cpuif.signal("PENABLE")}}; 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_rd_ack[loop.index] = {{cpuif.signal("PREADY", child)}}; assign cpuif_rd_data[loop.index] = {{cpuif.signal("PRDATA", child)}}; assign cpuif_rd_err[loop.index] = {{cpuif.signal("PSLVERR", child)}}; {%- endif -%} -{%- endfor -%} +{%- endfor%} always_comb begin {{cpuif.signal("PREADY")}} = 1'b0; diff --git a/src/peakrdl_busdecoder/cpuif/base_cpuif.py b/src/peakrdl_busdecoder/cpuif/base_cpuif.py index c83c608..72e9028 100644 --- a/src/peakrdl_busdecoder/cpuif/base_cpuif.py +++ b/src/peakrdl_busdecoder/cpuif/base_cpuif.py @@ -82,6 +82,7 @@ class BaseCpuif: jj_env.filters["clog2"] = clog2 # type: ignore jj_env.filters["is_pow2"] = is_pow2 # type: ignore jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore + jj_env.filters["address_slice"] = self.get_address_slice # type: ignore context = { "cpuif": self, @@ -90,3 +91,34 @@ class BaseCpuif: template = jj_env.get_template(self.template_path) 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}]" diff --git a/src/peakrdl_busdecoder/decoder.py b/src/peakrdl_busdecoder/decoder.py new file mode 100644 index 0000000..4b49232 --- /dev/null +++ b/src/peakrdl_busdecoder/decoder.py @@ -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]) diff --git a/src/peakrdl_busdecoder/dereferencer.py b/src/peakrdl_busdecoder/dereferencer.py deleted file mode 100644 index a966547..0000000 --- a/src/peakrdl_busdecoder/dereferencer.py +++ /dev/null @@ -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) diff --git a/src/peakrdl_busdecoder/exporter.py b/src/peakrdl_busdecoder/exporter.py index 724383c..82dcae0 100644 --- a/src/peakrdl_busdecoder/exporter.py +++ b/src/peakrdl_busdecoder/exporter.py @@ -1,14 +1,15 @@ import os +from datetime import datetime +from importlib.metadata import version from typing import TYPE_CHECKING, Any import jinja2 as jj from systemrdl.node import AddrmapNode, RootNode from systemrdl.rdltypes.user_enum import UserEnum -from .addr_decode import AddressDecode from .cpuif import BaseCpuif from .cpuif.apb4 import APB4Cpuif -from .dereferencer import Dereferencer +from .decoder import AddressDecode from .identifier_filter import kw_filter as kwf from .scan_design import DesignScanner from .sv_int import SVInt @@ -22,13 +23,12 @@ if TYPE_CHECKING: class BusDecoderExporter: cpuif: BaseCpuif address_decode: AddressDecode - dereferencer: Dereferencer ds: "DesignState" def __init__(self, **kwargs: Any) -> None: # Check for stray 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( [ @@ -84,18 +84,19 @@ class BusDecoderExporter: # Check for stray 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 self.cpuif = cpuif_cls(self) - self.address_decode = AddressDecode(self) - self.dereferencer = Dereferencer(self) + self.address_decode = AddressDecode(top_node, self.ds.addr_width) # Validate that there are no unsupported constructs DesignValidator(self).do_validate() # Build Jinja template context context = { + "current_date": datetime.now().strftime("%Y-%m-%d"), + "version": version("peakrdl-busdecoder"), "cpuif": self.cpuif, "address_decode": self.address_decode, "ds": self.ds, @@ -115,9 +116,6 @@ class BusDecoderExporter: stream = template.stream(context) stream.dump(module_file_path) - if hwif_report_file: - hwif_report_file.close() - class DesignState: """ @@ -147,6 +145,9 @@ class DesignState: # 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() diff --git a/src/peakrdl_busdecoder/forloop_generator.py b/src/peakrdl_busdecoder/forloop_generator.py deleted file mode 100644 index a1b3611..0000000 --- a/src/peakrdl_busdecoder/forloop_generator.py +++ /dev/null @@ -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 diff --git a/src/peakrdl_busdecoder/module_tmpl.sv b/src/peakrdl_busdecoder/module_tmpl.sv index 0d8a715..402051a 100644 --- a/src/peakrdl_busdecoder/module_tmpl.sv +++ b/src/peakrdl_busdecoder/module_tmpl.sv @@ -12,11 +12,11 @@ module {{ds.module_name}} - {%- if cpuif.parameters %} #( - {{-cpuif.parameters|join(",\n")|indent(8)}} - ) {%- endif %} ( - {{-cpuif.port_declaration|indent(8)}} - ); +{%- if cpuif.parameters %} #( + {{cpuif.parameters|join(",\n")|indent(4)}} +) {%- endif %} ( + {{cpuif.port_declaration|indent(4)}} +); //-------------------------------------------------------------------------- // CPU Bus interface logic diff --git a/src/peakrdl_busdecoder/py.typed b/src/peakrdl_busdecoder/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/peakrdl_busdecoder/sv_int.py b/src/peakrdl_busdecoder/sv_int.py index 94cac01..96d47ec 100644 --- a/src/peakrdl_busdecoder/sv_int.py +++ b/src/peakrdl_busdecoder/sv_int.py @@ -13,3 +13,9 @@ class SVInt: return f"{self.value.bit_length()}'h{self.value:x}" else: 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)