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.
- peakrdl_busdecoder/__init__.py +3 -0
- peakrdl_busdecoder/__peakrdl__.py +136 -0
- peakrdl_busdecoder/body/__init__.py +14 -0
- peakrdl_busdecoder/body/body.py +22 -0
- peakrdl_busdecoder/body/combinational_body.py +10 -0
- peakrdl_busdecoder/body/for_loop_body.py +16 -0
- peakrdl_busdecoder/body/if_body.py +91 -0
- peakrdl_busdecoder/body/struct_body.py +25 -0
- peakrdl_busdecoder/cpuif/__init__.py +3 -0
- peakrdl_busdecoder/cpuif/apb3/__init__.py +4 -0
- peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py +67 -0
- peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py +68 -0
- peakrdl_busdecoder/cpuif/apb3/apb3_interface.py +56 -0
- peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv +33 -0
- peakrdl_busdecoder/cpuif/apb4/__init__.py +4 -0
- peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py +70 -0
- peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py +70 -0
- peakrdl_busdecoder/cpuif/apb4/apb4_interface.py +60 -0
- peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv +36 -0
- peakrdl_busdecoder/cpuif/axi4lite/__init__.py +4 -0
- peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif.py +84 -0
- peakrdl_busdecoder/cpuif/axi4lite/axi4_lite_cpuif_flat.py +86 -0
- peakrdl_busdecoder/cpuif/axi4lite/axi4lite_interface.py +84 -0
- peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv +60 -0
- peakrdl_busdecoder/cpuif/base_cpuif.py +118 -0
- peakrdl_busdecoder/cpuif/fanin_gen.py +64 -0
- peakrdl_busdecoder/cpuif/fanout_gen.py +50 -0
- peakrdl_busdecoder/cpuif/interface.py +190 -0
- peakrdl_busdecoder/decode_logic_gen.py +152 -0
- peakrdl_busdecoder/design_scanner.py +46 -0
- peakrdl_busdecoder/design_state.py +74 -0
- peakrdl_busdecoder/exporter.py +142 -0
- peakrdl_busdecoder/identifier_filter.py +263 -0
- peakrdl_busdecoder/listener.py +58 -0
- peakrdl_busdecoder/module_tmpl.sv +79 -0
- peakrdl_busdecoder/package_tmpl.sv +21 -0
- peakrdl_busdecoder/py.typed +0 -0
- peakrdl_busdecoder/struct_gen.py +57 -0
- peakrdl_busdecoder/sv_int.py +21 -0
- peakrdl_busdecoder/udps/__init__.py +5 -0
- peakrdl_busdecoder/utils.py +80 -0
- peakrdl_busdecoder/validate_design.py +185 -0
- peakrdl_busdecoder-0.2.0.dist-info/METADATA +40 -0
- peakrdl_busdecoder-0.2.0.dist-info/RECORD +48 -0
- peakrdl_busdecoder-0.2.0.dist-info/WHEEL +5 -0
- peakrdl_busdecoder-0.2.0.dist-info/entry_points.txt +2 -0
- peakrdl_busdecoder-0.2.0.dist-info/licenses/LICENSE +165 -0
- 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)
|