peakrdl-busdecoder 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. peakrdl_busdecoder/__init__.py +3 -0
  2. peakrdl_busdecoder/__peakrdl__.py +136 -0
  3. peakrdl_busdecoder/body/__init__.py +14 -0
  4. peakrdl_busdecoder/body/body.py +22 -0
  5. peakrdl_busdecoder/body/combinational_body.py +10 -0
  6. peakrdl_busdecoder/body/for_loop_body.py +16 -0
  7. peakrdl_busdecoder/body/if_body.py +91 -0
  8. peakrdl_busdecoder/body/struct_body.py +25 -0
  9. peakrdl_busdecoder/cpuif/__init__.py +3 -0
  10. peakrdl_busdecoder/cpuif/apb3/__init__.py +4 -0
  11. peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py +67 -0
  12. peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py +68 -0
  13. peakrdl_busdecoder/cpuif/apb3/apb3_interface.py +56 -0
  14. peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv +33 -0
  15. peakrdl_busdecoder/cpuif/apb4/__init__.py +4 -0
  16. peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py +70 -0
  17. peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py +70 -0
  18. peakrdl_busdecoder/cpuif/apb4/apb4_interface.py +60 -0
  19. peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv +36 -0
  20. peakrdl_busdecoder/cpuif/axi4lite/__init__.py +4 -0
  21. peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif.py +84 -0
  22. peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif_flat.py +86 -0
  23. peakrdl_busdecoder/cpuif/axi4lite/axi4lite_interface.py +84 -0
  24. peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv +60 -0
  25. peakrdl_busdecoder/cpuif/base_cpuif.py +118 -0
  26. peakrdl_busdecoder/cpuif/fanin_gen.py +64 -0
  27. peakrdl_busdecoder/cpuif/fanout_gen.py +50 -0
  28. peakrdl_busdecoder/cpuif/interface.py +190 -0
  29. peakrdl_busdecoder/decode_logic_gen.py +152 -0
  30. peakrdl_busdecoder/design_scanner.py +46 -0
  31. peakrdl_busdecoder/design_state.py +74 -0
  32. peakrdl_busdecoder/exporter.py +142 -0
  33. peakrdl_busdecoder/identifier_filter.py +263 -0
  34. peakrdl_busdecoder/listener.py +58 -0
  35. peakrdl_busdecoder/module_tmpl.sv +79 -0
  36. peakrdl_busdecoder/package_tmpl.sv +21 -0
  37. peakrdl_busdecoder/py.typed +0 -0
  38. peakrdl_busdecoder/struct_gen.py +57 -0
  39. peakrdl_busdecoder/sv_int.py +21 -0
  40. peakrdl_busdecoder/udps/__init__.py +5 -0
  41. peakrdl_busdecoder/utils.py +80 -0
  42. peakrdl_busdecoder/validate_design.py +185 -0
  43. peakrdl_busdecoder-0.2.0.dist-info/METADATA +40 -0
  44. peakrdl_busdecoder-0.2.0.dist-info/RECORD +48 -0
  45. peakrdl_busdecoder-0.2.0.dist-info/WHEEL +5 -0
  46. peakrdl_busdecoder-0.2.0.dist-info/entry_points.txt +2 -0
  47. peakrdl_busdecoder-0.2.0.dist-info/licenses/LICENSE +165 -0
  48. peakrdl_busdecoder-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,50 @@
1
+ from collections import deque
2
+ from typing import TYPE_CHECKING
3
+
4
+ from systemrdl.node import AddressableNode
5
+ from systemrdl.walker import WalkerAction
6
+
7
+ from ..body import Body, ForLoopBody
8
+ from ..design_state import DesignState
9
+ from ..listener import BusDecoderListener
10
+
11
+ if TYPE_CHECKING:
12
+ from .base_cpuif import BaseCpuif
13
+
14
+
15
+ class FanoutGenerator(BusDecoderListener):
16
+ def __init__(self, ds: DesignState, cpuif: "BaseCpuif") -> None:
17
+ super().__init__(ds)
18
+ self._cpuif = cpuif
19
+
20
+ self._stack: deque[Body] = deque()
21
+ self._stack.append(Body())
22
+
23
+ def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
24
+ action = super().enter_AddressableComponent(node)
25
+
26
+ if node.array_dimensions:
27
+ for i, dim in enumerate(node.array_dimensions):
28
+ fb = ForLoopBody(
29
+ "genvar",
30
+ f"gi{i}",
31
+ dim,
32
+ )
33
+ self._stack.append(fb)
34
+
35
+ self._stack[-1] += self._cpuif.fanout(node)
36
+
37
+ return action
38
+
39
+ def exit_AddressableComponent(self, node: AddressableNode) -> None:
40
+ if node.array_dimensions:
41
+ for _ in node.array_dimensions:
42
+ b = self._stack.pop()
43
+ if not b:
44
+ continue
45
+ self._stack[-1] += b
46
+
47
+ super().exit_AddressableComponent(node)
48
+
49
+ def __str__(self) -> str:
50
+ return "\n".join(map(str, self._stack))
@@ -0,0 +1,190 @@
1
+ """Interface abstraction for handling flat and non-flat signal declarations."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ from systemrdl.node import AddressableNode
7
+
8
+ if TYPE_CHECKING:
9
+ from .base_cpuif import BaseCpuif
10
+
11
+
12
+ class Interface(ABC):
13
+ """Abstract base class for interface signal handling."""
14
+
15
+ def __init__(self, cpuif: "BaseCpuif") -> None:
16
+ self.cpuif = cpuif
17
+
18
+ @property
19
+ @abstractmethod
20
+ def is_interface(self) -> bool:
21
+ """Whether this uses SystemVerilog interfaces."""
22
+ ...
23
+
24
+ @abstractmethod
25
+ def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
26
+ """
27
+ Generate port declarations for the interface.
28
+
29
+ Args:
30
+ slave_name: Name of the slave interface/signal prefix
31
+ master_prefix: Prefix for master interfaces/signals
32
+
33
+ Returns:
34
+ Port declarations as a string
35
+ """
36
+ ...
37
+
38
+ @abstractmethod
39
+ def signal(
40
+ self,
41
+ signal: str,
42
+ node: AddressableNode | None = None,
43
+ indexer: str | int | None = None,
44
+ ) -> str:
45
+ """
46
+ Generate signal reference.
47
+
48
+ Args:
49
+ signal: Signal name
50
+ node: Optional addressable node for master signals
51
+ indexer: Optional indexer for arrays.
52
+ For SVInterface: str like "i" or "gi" for loop indices
53
+ For FlatInterface: str or int for array subscript
54
+
55
+ Returns:
56
+ Signal reference as a string
57
+ """
58
+ ...
59
+
60
+
61
+ class SVInterface(Interface):
62
+ """SystemVerilog interface-based signal handling."""
63
+
64
+ @property
65
+ def is_interface(self) -> bool:
66
+ return True
67
+
68
+ def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
69
+ """Generate SystemVerilog interface port declarations."""
70
+ slave_ports: list[str] = [f"{self.get_interface_type()}.slave {slave_name}"]
71
+ master_ports: list[str] = []
72
+
73
+ for child in self.cpuif.addressable_children:
74
+ base = f"{self.get_interface_type()}.master {master_prefix}{child.inst_name}"
75
+
76
+ # When unrolled, current_idx is set - append it to the name
77
+ if child.current_idx is not None:
78
+ base = f"{base}_{'_'.join(map(str, child.current_idx))}"
79
+
80
+ # Only add array dimensions if this should be treated as an array
81
+ if self.cpuif.check_is_array(child):
82
+ assert child.array_dimensions is not None
83
+ base = f"{base} {''.join(f'[{dim}]' for dim in child.array_dimensions)}"
84
+
85
+ master_ports.append(base)
86
+
87
+ return ",\n".join(slave_ports + master_ports)
88
+
89
+ def signal(
90
+ self,
91
+ signal: str,
92
+ node: AddressableNode | None = None,
93
+ indexer: str | int | None = None,
94
+ ) -> str:
95
+ """Generate SystemVerilog interface signal reference."""
96
+ from ..utils import get_indexed_path
97
+
98
+ # SVInterface only supports string indexers (loop variable names like "i", "gi")
99
+ if indexer is not None and not isinstance(indexer, str):
100
+ raise TypeError(f"SVInterface.signal() requires string indexer, got {type(indexer).__name__}")
101
+
102
+ if node is None or indexer is None:
103
+ # Node is none, so this is a slave signal
104
+ slave_name = self.get_slave_name()
105
+ return f"{slave_name}.{signal}"
106
+
107
+ # Master signal
108
+ master_prefix = self.get_master_prefix()
109
+ return f"{master_prefix}{get_indexed_path(node.parent, node, indexer, skip_kw_filter=True)}.{signal}"
110
+
111
+ @abstractmethod
112
+ def get_interface_type(self) -> str:
113
+ """Get the SystemVerilog interface type name."""
114
+ ...
115
+
116
+ @abstractmethod
117
+ def get_slave_name(self) -> str:
118
+ """Get the slave interface instance name."""
119
+ ...
120
+
121
+ @abstractmethod
122
+ def get_master_prefix(self) -> str:
123
+ """Get the master interface name prefix."""
124
+ ...
125
+
126
+
127
+ class FlatInterface(Interface):
128
+ """Flat signal-based interface handling."""
129
+
130
+ @property
131
+ def is_interface(self) -> bool:
132
+ return False
133
+
134
+ def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
135
+ """Generate flat port declarations."""
136
+ slave_ports = self._get_slave_port_declarations(slave_name)
137
+ master_ports: list[str] = []
138
+
139
+ for child in self.cpuif.addressable_children:
140
+ master_ports.extend(self._get_master_port_declarations(child, master_prefix))
141
+
142
+ return ",\n".join(slave_ports + master_ports)
143
+
144
+ def signal(
145
+ self,
146
+ signal: str,
147
+ node: AddressableNode | None = None,
148
+ indexer: str | int | None = None,
149
+ ) -> str:
150
+ """Generate flat signal reference."""
151
+ if node is None:
152
+ # Node is none, so this is a slave signal
153
+ slave_prefix = self.get_slave_prefix()
154
+ return f"{slave_prefix}{signal}"
155
+
156
+ # Master signal
157
+ master_prefix = self.get_master_prefix()
158
+ base = f"{master_prefix}{node.inst_name}"
159
+
160
+ if not self.cpuif.check_is_array(node):
161
+ # Not an array or an unrolled element
162
+ if node.current_idx is not None:
163
+ # This is a specific instance of an unrolled array
164
+ return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
165
+ return f"{base}_{signal}"
166
+
167
+ # Is an array
168
+ if indexer is not None:
169
+ return f"{base}_{signal}[{indexer}]"
170
+ return f"{base}_{signal}[N_{node.inst_name.upper()}S]"
171
+
172
+ @abstractmethod
173
+ def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
174
+ """Get slave port declarations."""
175
+ ...
176
+
177
+ @abstractmethod
178
+ def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
179
+ """Get master port declarations for a child node."""
180
+ ...
181
+
182
+ @abstractmethod
183
+ def get_slave_prefix(self) -> str:
184
+ """Get the slave signal name prefix."""
185
+ ...
186
+
187
+ @abstractmethod
188
+ def get_master_prefix(self) -> str:
189
+ """Get the master signal name prefix."""
190
+ ...
@@ -0,0 +1,152 @@
1
+ from collections import deque
2
+ from enum import Enum
3
+ from typing import cast
4
+
5
+ from systemrdl.node import AddressableNode
6
+ from systemrdl.walker import WalkerAction
7
+
8
+ from .body import Body, ForLoopBody, IfBody
9
+ from .design_state import DesignState
10
+ from .listener import BusDecoderListener
11
+ from .sv_int import SVInt
12
+ from .utils import get_indexed_path
13
+
14
+
15
+ class DecodeLogicFlavor(Enum):
16
+ READ = "rd"
17
+ WRITE = "wr"
18
+
19
+ @property
20
+ def cpuif_address(self) -> str:
21
+ return f"cpuif_{self.value}_addr"
22
+
23
+ @property
24
+ def cpuif_select(self) -> str:
25
+ return f"cpuif_{self.value}_sel"
26
+
27
+
28
+ class DecodeLogicGenerator(BusDecoderListener):
29
+ def __init__(
30
+ self,
31
+ ds: DesignState,
32
+ flavor: DecodeLogicFlavor,
33
+ ) -> None:
34
+ super().__init__(ds)
35
+ self._flavor = flavor
36
+
37
+ self._decode_stack: deque[Body] = deque() # Tracks decoder body
38
+ self._cond_stack: deque[str] = deque() # Tracks conditions nested for loops
39
+
40
+ # Initial Stack Conditions
41
+ self._decode_stack.append(IfBody())
42
+
43
+ def cpuif_addr_predicate(self, node: AddressableNode, total_size: bool = True) -> list[str]:
44
+ # Generate address bounds
45
+ addr_width = self._ds.addr_width
46
+ l_bound = SVInt(
47
+ node.raw_absolute_address,
48
+ addr_width,
49
+ )
50
+ if total_size:
51
+ u_bound = l_bound + SVInt(node.total_size, addr_width)
52
+ else:
53
+ u_bound = l_bound + SVInt(node.size, addr_width)
54
+
55
+ array_stack = list(self._array_stride_stack)
56
+ if total_size and node.array_dimensions:
57
+ array_stack = array_stack[: -len(node.array_dimensions)]
58
+
59
+ # Handle arrayed components
60
+ l_bound_comp = [str(l_bound)]
61
+ u_bound_comp = [str(u_bound)]
62
+ for i, stride in enumerate(array_stack):
63
+ l_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
64
+ u_bound_comp.append(f"({addr_width}'(i{i})*{SVInt(stride, addr_width)})")
65
+
66
+ lower_expr = f"{self._flavor.cpuif_address} >= ({'+'.join(l_bound_comp)})"
67
+ upper_expr = f"{self._flavor.cpuif_address} < ({'+'.join(u_bound_comp)})"
68
+
69
+ predicates: list[str] = []
70
+ # Avoid generating a redundant >= 0 comparison, which triggers Verilator warnings.
71
+ if not (l_bound.value == 0 and len(l_bound_comp) == 1):
72
+ predicates.append(lower_expr)
73
+ predicates.append(upper_expr)
74
+
75
+ return predicates
76
+
77
+ def cpuif_prot_predicate(self, node: AddressableNode) -> list[str]:
78
+ if self._flavor == DecodeLogicFlavor.READ:
79
+ # Can we have PROT on read? (axi full?)
80
+ return []
81
+
82
+ # TODO: Implement
83
+ return []
84
+
85
+ def enter_AddressableComponent(self, node: AddressableNode) -> WalkerAction | None:
86
+ action = super().enter_AddressableComponent(node)
87
+
88
+ conditions: list[str] = []
89
+ conditions.extend(self.cpuif_addr_predicate(node))
90
+ conditions.extend(self.cpuif_prot_predicate(node))
91
+ condition = " && ".join(f"({c})" for c in conditions)
92
+
93
+ # Generate condition string and manage stack
94
+ if node.array_dimensions:
95
+ # arrayed component with new if-body
96
+ self._cond_stack.append(condition)
97
+ for i, dim in enumerate(
98
+ node.array_dimensions,
99
+ ):
100
+ fb = ForLoopBody(
101
+ "int",
102
+ f"i{i}",
103
+ dim,
104
+ )
105
+ self._decode_stack.append(fb)
106
+
107
+ self._decode_stack.append(IfBody())
108
+ elif isinstance(self._decode_stack[-1], IfBody):
109
+ # non-arrayed component with if-body
110
+ ifb = cast(IfBody, self._decode_stack[-1])
111
+ with ifb.cm(condition) as b:
112
+ b += f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
113
+ else:
114
+ raise RuntimeError("Invalid decode stack state")
115
+
116
+ return action
117
+
118
+ def exit_AddressableComponent(self, node: AddressableNode) -> None:
119
+ if not node.array_dimensions:
120
+ return
121
+
122
+ ifb = self._decode_stack.pop()
123
+ if not ifb and isinstance(ifb, IfBody):
124
+ conditions: list[str] = []
125
+ conditions.extend(self.cpuif_addr_predicate(node, total_size=False))
126
+ condition = " && ".join(f"({c})" for c in conditions)
127
+
128
+ with ifb.cm(condition) as b:
129
+ b += f"{self._flavor.cpuif_select}.{get_indexed_path(self._ds.top_node, node)} = 1'b1;"
130
+ self._decode_stack[-1] += ifb
131
+
132
+ for _ in node.array_dimensions:
133
+ b = self._decode_stack.pop()
134
+ if not b:
135
+ continue
136
+
137
+ if isinstance(self._decode_stack[-1], IfBody):
138
+ ifb = cast(IfBody, self._decode_stack[-1])
139
+ with ifb.cm(self._cond_stack.pop()) as parent_b:
140
+ parent_b += b
141
+ else:
142
+ self._decode_stack[-1] += b
143
+
144
+ super().exit_AddressableComponent(node)
145
+
146
+ def __str__(self) -> str:
147
+ body = self._decode_stack[-1]
148
+ if isinstance(body, IfBody):
149
+ with body.cm(...) as b:
150
+ b += f"{self._flavor.cpuif_select}.cpuif_err = 1'b1;"
151
+
152
+ return str(body)
@@ -0,0 +1,46 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from systemrdl.node import AddressableNode, AddrmapNode, Node, RegNode
4
+ from systemrdl.walker import RDLListener, RDLWalker, WalkerAction
5
+
6
+ if TYPE_CHECKING:
7
+ from .design_state import DesignState
8
+
9
+
10
+ class DesignScanner(RDLListener):
11
+ """
12
+ Scans through the register model and validates that any unsupported features
13
+ are not present.
14
+
15
+ Also collects any information that is required prior to the start of the export process.
16
+ """
17
+
18
+ def __init__(self, ds: "DesignState") -> None:
19
+ self.ds = ds
20
+ self.msg = self.top_node.env.msg
21
+
22
+ @property
23
+ def top_node(self) -> AddrmapNode:
24
+ return self.ds.top_node
25
+
26
+ def do_scan(self) -> None:
27
+ RDLWalker().walk(self.top_node, self)
28
+ if self.msg.had_error:
29
+ self.msg.fatal("Unable to export due to previous errors")
30
+
31
+ def enter_Component(self, node: Node) -> WalkerAction:
32
+ if node.external and (node != self.top_node):
33
+ # Do not inspect external components. None of my business
34
+ return WalkerAction.SkipDescendants
35
+
36
+ # Collect any signals that are referenced by a property
37
+ for prop_name in node.list_properties():
38
+ _ = node.get_property(prop_name)
39
+
40
+ return WalkerAction.Continue
41
+
42
+ def enter_AddressableComponent(self, node: AddressableNode) -> None:
43
+ if node.external and node != self.top_node:
44
+ self.ds.has_external_addressable = True
45
+ if not isinstance(node, RegNode):
46
+ self.ds.has_external_block = True
@@ -0,0 +1,74 @@
1
+ from typing import TypedDict
2
+
3
+ from systemrdl.node import AddrmapNode
4
+ from systemrdl.rdltypes.user_enum import UserEnum
5
+
6
+ from .design_scanner import DesignScanner
7
+ from .identifier_filter import kw_filter as kwf
8
+ from .utils import clog2
9
+
10
+
11
+ class DesignStateKwargs(TypedDict, total=False):
12
+ reuse_hwif_typedefs: bool
13
+ module_name: str
14
+ package_name: str
15
+ address_width: int
16
+ cpuif_unroll: bool
17
+ max_decode_depth: int
18
+
19
+
20
+ class DesignState:
21
+ """
22
+ Dumping ground for all sorts of variables that are relevant to a particular
23
+ design.
24
+ """
25
+
26
+ def __init__(self, top_node: AddrmapNode, kwargs: DesignStateKwargs) -> None:
27
+ self.top_node = top_node
28
+ msg = top_node.env.msg
29
+
30
+ # ------------------------
31
+ # Extract compiler args
32
+ # ------------------------
33
+ self.reuse_hwif_typedefs: bool = kwargs.pop("reuse_hwif_typedefs", True)
34
+ self.module_name: str = kwargs.pop("module_name", None) or kwf(self.top_node.inst_name)
35
+ self.package_name: str = kwargs.pop("package_name", None) or f"{self.module_name}_pkg"
36
+ user_addr_width: int | None = kwargs.pop("address_width", None)
37
+
38
+ self.cpuif_unroll: bool = kwargs.pop("cpuif_unroll", False)
39
+ self.max_decode_depth: int = kwargs.pop("max_decode_depth", 1)
40
+
41
+ # ------------------------
42
+ # Info about the design
43
+ # ------------------------
44
+ self.cpuif_data_width = 0
45
+
46
+ # Track any referenced enums
47
+ self.user_enums: list[type[UserEnum]] = []
48
+
49
+ self.has_external_addressable = False
50
+ self.has_external_block = False
51
+
52
+ # Scan the design to fill in above variables
53
+ DesignScanner(self).do_scan()
54
+
55
+ if self.cpuif_data_width == 0:
56
+ # Scanner did not find any registers in the design being exported,
57
+ # so the width is not known.
58
+ # Assume 32-bits
59
+ msg.warning(
60
+ "Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.",
61
+ self.top_node.inst.def_src_ref,
62
+ )
63
+ self.cpuif_data_width = 32
64
+
65
+ # ------------------------
66
+ # Min address width encloses the total size AND at least 1 useful address bit
67
+ self.addr_width = max(clog2(self.top_node.size), clog2(self.cpuif_data_width // 8) + 1)
68
+
69
+ if user_addr_width is None:
70
+ return
71
+
72
+ if user_addr_width < self.addr_width:
73
+ msg.fatal(f"User-specified address width shall be greater than or equal to {self.addr_width}.")
74
+ self.addr_width = user_addr_width
@@ -0,0 +1,142 @@
1
+ import os
2
+ from datetime import datetime
3
+ from importlib.metadata import version
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any, TypedDict
6
+
7
+ import jinja2 as jj
8
+ from systemrdl.node import AddrmapNode, RootNode
9
+ from systemrdl.walker import RDLSteerableWalker
10
+ from typing_extensions import Unpack
11
+
12
+ from .cpuif import BaseCpuif
13
+ from .cpuif.apb4 import APB4Cpuif
14
+ from .decode_logic_gen import DecodeLogicFlavor, DecodeLogicGenerator
15
+ from .design_state import DesignState
16
+ from .identifier_filter import kw_filter as kwf
17
+ from .listener import BusDecoderListener
18
+ from .struct_gen import StructGenerator
19
+ from .sv_int import SVInt
20
+ from .utils import clog2
21
+ from .validate_design import DesignValidator
22
+
23
+
24
+ class ExporterKwargs(TypedDict, total=False):
25
+ cpuif_cls: type[BaseCpuif]
26
+ module_name: str
27
+ package_name: str
28
+ address_width: int
29
+ cpuif_unroll: bool
30
+ reuse_hwif_typedefs: bool
31
+ max_decode_depth: int
32
+
33
+
34
+ if TYPE_CHECKING:
35
+ pass
36
+
37
+
38
+ class BusDecoderExporter:
39
+ cpuif: BaseCpuif
40
+ ds: DesignState
41
+
42
+ def __init__(self, **kwargs: Unpack[ExporterKwargs]) -> None:
43
+ # Check for stray kwargs
44
+ if kwargs:
45
+ raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
46
+
47
+ fs_loader = jj.FileSystemLoader(Path(__file__).parent)
48
+ c_loader = jj.ChoiceLoader(
49
+ [
50
+ fs_loader,
51
+ jj.PrefixLoader(
52
+ {"base": fs_loader},
53
+ delimiter=":",
54
+ ),
55
+ ]
56
+ )
57
+
58
+ self.jj_env = jj.Environment(
59
+ loader=c_loader,
60
+ undefined=jj.StrictUndefined,
61
+ )
62
+ self.jj_env.filters["kwf"] = kwf # type: ignore
63
+ self.jj_env.filters["walk"] = self.walk # type: ignore
64
+ self.jj_env.filters["clog2"] = clog2 # type: ignore
65
+
66
+ def export(self, node: RootNode | AddrmapNode, output_dir: str, **kwargs: Unpack[ExporterKwargs]) -> None:
67
+ """
68
+ Parameters
69
+ ----------
70
+ node: AddrmapNode
71
+ Top-level SystemRDL node to export.
72
+ output_dir: str
73
+ Path to the output directory where generated SystemVerilog will be written.
74
+ Output includes two files: a module definition and package definition.
75
+ cpuif_cls: :class:`peakrdl_busdecoder.cpuif.CpuifBase`
76
+ Specify the class type that implements the CPU interface of your choice.
77
+ Defaults to AMBA APB4.
78
+ module_name: str
79
+ Override the SystemVerilog module name. By default, the module name
80
+ is the top-level node's name.
81
+ package_name: str
82
+ Override the SystemVerilog package name. By default, the package name
83
+ is the top-level node's name with a "_pkg" suffix.
84
+ address_width: int
85
+ Override the CPU interface's address width. By default, address width
86
+ is sized to the contents of the busdecoder.
87
+ cpuif_unroll: bool
88
+ Unroll arrayed addressable nodes into separate instances in the CPU
89
+ interface. By default, arrayed nodes are kept as arrays.
90
+ max_decode_depth: int
91
+ Maximum depth for address decoder to descend into nested addressable
92
+ components. By default, the decoder descends 1 level deep.
93
+ """
94
+ # If it is the root node, skip to top addrmap
95
+ if isinstance(node, RootNode):
96
+ top_node = node.top
97
+ else:
98
+ top_node = node
99
+
100
+ self.ds = DesignState(top_node, kwargs)
101
+
102
+ cpuif_cls: type[BaseCpuif] = kwargs.pop("cpuif_cls", None) or APB4Cpuif
103
+
104
+ # Check for stray kwargs
105
+ if kwargs:
106
+ raise TypeError(f"got an unexpected keyword argument '{next(iter(kwargs.keys()))}'")
107
+
108
+ # Construct exporter components
109
+ self.cpuif = cpuif_cls(self)
110
+
111
+ # Validate that there are no unsupported constructs
112
+ DesignValidator(self).do_validate()
113
+
114
+ # Build Jinja template context
115
+ context = { # type: ignore
116
+ "current_date": datetime.now().strftime("%Y-%m-%d"),
117
+ "version": version("peakrdl-busdecoder"),
118
+ "cpuif": self.cpuif,
119
+ "cpuif_decode": DecodeLogicGenerator,
120
+ "cpuif_select": StructGenerator,
121
+ "cpuif_decode_flavor": DecodeLogicFlavor,
122
+ "ds": self.ds,
123
+ "SVInt": SVInt,
124
+ }
125
+
126
+ # Write out design
127
+ os.makedirs(output_dir, exist_ok=True)
128
+ package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv")
129
+ template = self.jj_env.get_template("package_tmpl.sv")
130
+ stream = template.stream(context)
131
+ stream.dump(package_file_path)
132
+
133
+ module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv")
134
+ template = self.jj_env.get_template("module_tmpl.sv")
135
+ stream = template.stream(context)
136
+ stream.dump(module_file_path)
137
+
138
+ def walk(self, listener_cls: type[BusDecoderListener], **kwargs: dict[str, Any]) -> str:
139
+ walker = RDLSteerableWalker()
140
+ listener = listener_cls(self.ds, **kwargs)
141
+ walker.walk(self.ds.top_node, listener, skip_top=True)
142
+ return str(listener)