• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

arnavsacheti / PeakRDL-BusDecoder / 21662537795

04 Feb 2026 07:29AM UTC coverage: 84.693%. First build
21662537795

Pull #44

github

Pull Request #44: Feature/array port parameters

274 of 368 new or added lines in 21 files covered. (74.46%)

1422 of 1679 relevant lines covered (84.69%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/src/peakrdl_busdecoder/cpuif/interface.py
1
"""Interface abstraction for handling flat and non-flat signal declarations."""
2

3
import re
1✔
4
from abc import ABC, abstractmethod
1✔
5
from typing import TYPE_CHECKING
1✔
6

7
from systemrdl.node import AddressableNode
1✔
8

9
from ..utils import get_indexed_path
1✔
10

11
if TYPE_CHECKING:
12
    from .base_cpuif import BaseCpuif
13

14

15
def _open_dim_brackets(top_node: AddressableNode, node: AddressableNode, indexer: str) -> str:
1✔
16
    """Bracket-index string covering every open array dimension of ``node``.
17

18
    Walks the path from the top node so rolled array *ancestors* contribute
1✔
19
    their brackets too (e.g. ``blk[gi0].myreg[gi1]`` -> ``[gi0][gi1]``). The
1✔
20
    loop-variable numbers match those allocated positionally from the open-dim
21
    stride stack (see ``BusDecoderListener.loop_base_index``).
1✔
22
    """
1✔
23
    indexed = get_indexed_path(top_node, node, indexer, skip_kw_filter=True)
1✔
24
    return "".join(re.findall(r"\[[^\]]*\]", indexed))
25

26

27
class Interface(ABC):
1✔
28
    """Abstract base class for interface signal handling."""
1✔
29

30
    def __init__(self, cpuif: "BaseCpuif") -> None:
31
        self.cpuif = cpuif
32

33
    def master_base_name(self, node: AddressableNode) -> str:
34
        """Master port base name for a node (path-qualified on conflicts)."""
35
        return self.cpuif.exp.ds.master_port_name(node)
36

37
    @property
38
    @abstractmethod
39
    def is_interface(self) -> bool:
40
        """Whether this uses SystemVerilog interfaces."""
41
        ...
1✔
42

1✔
43
    @abstractmethod
44
    def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
45
        """
46
        Generate port declarations for the interface.
47

48
        Args:
49
            slave_name: Name of the slave interface/signal prefix
50
            master_prefix: Prefix for master interfaces/signals
51

52
        Returns:
53
            Port declarations as a string
54
        """
55
        ...
56

57
    @abstractmethod
58
    def signal(
59
        self,
60
        signal: str,
61
        node: AddressableNode | None = None,
62
        indexer: str | int | None = None,
63
    ) -> str:
64
        """
1✔
65
        Generate signal reference.
66

67
        Args:
1✔
68
            signal: Signal name
1✔
69
            node: Optional addressable node for master signals
1✔
70
            indexer: Optional indexer for arrays.
71
                     For SVInterface: str like "i" or "gi" for loop indices
1✔
72
                     For FlatInterface: str or int for array subscript
73

1✔
74
        Returns:
1✔
75
            Signal reference as a string
76
        """
1✔
77
        ...
1✔
78

79

80
class SVInterface(Interface):
1✔
81
    """SystemVerilog interface-based signal handling."""
1✔
82

83
    slave_modport_name = "slave"
84
    master_modport_name = "master"
1✔
85

1✔
86
    @property
1✔
87
    def is_interface(self) -> bool:
1✔
88
        return True
89

1✔
90
    def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
91
        """Generate SystemVerilog interface port declarations."""
1✔
92
        slave_ports: list[str] = [f"{self.get_interface_type()}.{self.slave_modport_name} {slave_name}"]
93
        master_ports: list[str] = []
1✔
94

95
        for child in self.cpuif.addressable_children:
1✔
96
            base = (
97
                f"{self.get_interface_type()}.{self.master_modport_name} "
98
                f"{master_prefix}{self.master_base_name(child)}"
99
            )
100

101
            # When unrolled, current_idx is set - append it to the name
102
            if child.current_idx is not None:
103
                base = f"{base}_{'_'.join(map(str, child.current_idx))}"
1✔
104

105
            # Size the interface array by *all* open dimensions (rolled array
1✔
106
            # ancestors' + the node's own), so a boundary under array ancestors
1✔
107
            # gets one interface per element.
108
            dims = self.cpuif.master_array_dims(child)
1✔
NEW
109
            if dims:
×
110
                base = f"{base} {''.join(f'[{dim}]' for dim in dims)}"
111

1✔
112
            master_ports.append(base)
1✔
113

1✔
114
        return ",\n".join(slave_ports + master_ports)
115

1✔
116
    def signal(
1✔
117
        self,
118
        signal: str,
119
        node: AddressableNode | None = None,
120
        indexer: str | int | None = None,
1✔
121
    ) -> str:
1✔
122
        """Generate SystemVerilog interface signal reference."""
123

124
        # SVInterface only supports string indexers (loop variable names like "i", "gi")
125
        if indexer is not None and not isinstance(indexer, str):
1✔
126
            raise TypeError(f"SVInterface.signal() requires string indexer, got {type(indexer).__name__}")
1✔
127

128
        if node is None or indexer is None:
129
            # Node is none, so this is a slave signal
130
            slave_name = self.get_slave_name()
131
            return f"{slave_name}.{signal}"
1✔
132

133
        # Master signal
134
        master_prefix = self.get_master_prefix()
1✔
135
        base = self.master_base_name(node)
1✔
136

1✔
137
        if not self.cpuif.is_master_array(node) and node.current_idx is not None:
138
            # A specific element of an unrolled array: the master port is a
1✔
139
            # scalar interface named with an index suffix (e.g. m_apb_blk_0)
140
            return f"{master_prefix}{base}_{'_'.join(map(str, node.current_idx))}.{signal}"
1✔
141

1✔
142
        # Index by *all* open dimensions: walk from the top node so ancestor
143
        # array brackets (e.g. blk[gi0]) are included, then keep only the
1✔
144
        # bracket expressions to append after the (possibly qualified) base.
1✔
145
        brackets = _open_dim_brackets(self.cpuif.exp.ds.top_node, node, indexer)
146
        return f"{master_prefix}{base}{brackets}.{signal}"
1✔
147

148
    @abstractmethod
1✔
149
    def get_interface_type(self) -> str:
150
        """Get the SystemVerilog interface type name."""
151
        ...
152

153
    @abstractmethod
154
    def get_slave_name(self) -> str:
155
        """Get the slave interface instance name."""
1✔
156
        ...
157

1✔
158
    @abstractmethod
1✔
159
    def get_master_prefix(self) -> str:
160
        """Get the master interface name prefix."""
161
        ...
1✔
162

1✔
163

164
class FlatInterface(Interface):
1✔
165
    """Flat signal-based interface handling."""
166

1✔
167
    @property
168
    def is_interface(self) -> bool:
×
169
        return False
1✔
170

171
    def get_port_declaration(self, slave_name: str, master_prefix: str) -> str:
172
        """Generate flat port declarations."""
1✔
173
        slave_ports = self._get_slave_port_declarations(slave_name)
1✔
174
        master_ports: list[str] = []
1✔
175

1✔
176
        for child in self.cpuif.addressable_children:
1✔
177
            master_ports.extend(self._get_master_port_declarations(child, master_prefix))
178

1✔
179
        return ",\n".join(slave_ports + master_ports)
180

×
181
    def signal(
1✔
182
        self,
183
        signal: str,
1✔
184
        node: AddressableNode | None = None,
1✔
185
        indexer: str | int | None = None,
186
    ) -> str:
187
        """Generate flat signal reference."""
188
        if node is None:
1✔
189
            # Node is none, so this is a slave signal
1✔
190
            slave_prefix = self.get_slave_prefix()
191
            return f"{slave_prefix}{signal}"
192

193
        # Master signal
1✔
194
        master_prefix = self.get_master_prefix()
1✔
195
        base = f"{master_prefix}{self.master_base_name(node)}"
196

197
        if not self.cpuif.is_master_array(node):
198
            # Not an array or an unrolled element
1✔
199
            if node.current_idx is not None:
1✔
200
                # This is a specific instance of an unrolled array
201
                return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}"
202
            return f"{base}_{signal}"
203

204
        # Is an array (possibly by virtue of rolled array ancestors)
205
        if indexer is not None:
206
            if isinstance(indexer, str):
207
                brackets = _open_dim_brackets(self.cpuif.exp.ds.top_node, node, indexer)
208
                return f"{base}_{signal}{brackets}"
209

210
            return f"{base}_{signal}[{indexer}]"
211
        # No indexer: this is a declaration -- size by every open dimension.
212
        dims = self.cpuif.master_array_dims(node)
213
        return f"{base}_{signal}" + "".join(f"[{dim}]" for dim in dims)
214

215
    @abstractmethod
216
    def _get_slave_port_declarations(self, slave_prefix: str) -> list[str]:
217
        """Get slave port declarations."""
218
        ...
219

220
    @abstractmethod
221
    def _get_master_port_declarations(self, child: AddressableNode, master_prefix: str) -> list[str]:
222
        """Get master port declarations for a child node."""
223
        ...
224

225
    @abstractmethod
226
    def get_slave_prefix(self) -> str:
227
        """Get the slave signal name prefix."""
228
        ...
229

230
    @abstractmethod
231
    def get_master_prefix(self) -> str:
232
        """Get the master signal name prefix."""
233
        ...
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc