cocotbext-interface 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/PKG-INFO +115 -116
- cocotbext_interface-0.1.4/README.md +276 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext/interface/__init__.py +0 -4
- cocotbext_interface-0.1.4/cocotbext/interface/_version.py +1 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext/interface/interface.py +99 -34
- cocotbext_interface-0.1.4/cocotbext/interface/prototypes/__init__.py +7 -0
- {cocotbext_interface-0.1.2/cocotbext/interface → cocotbext_interface-0.1.4/cocotbext/interface/prototypes}/memorymappedinterface.py +1 -2
- {cocotbext_interface-0.1.2/cocotbext/interface → cocotbext_interface-0.1.4/cocotbext/interface/prototypes}/streaminterface.py +1 -1
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext/interface/utils.py +1 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/PKG-INFO +115 -116
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/SOURCES.txt +3 -2
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/pyproject.toml +16 -7
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/tests/test_interface.py +127 -29
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/tests/test_utils.py +4 -1
- cocotbext_interface-0.1.2/README.md +0 -277
- cocotbext_interface-0.1.2/cocotbext/interface/_version.py +0 -1
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/LICENSE +0 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/dependency_links.txt +0 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/requires.txt +0 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/top_level.txt +0 -0
- {cocotbext_interface-0.1.2 → cocotbext_interface-0.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cocotbext-interface
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: System Verilog style interface class for connecting Cocotb to DUT
|
|
5
5
|
Author-email: Rasmus Grøndahl Olsen <simplhdl@gmail.com>
|
|
6
6
|
Maintainer-email: Rasmus Grøndahl Olsen <simplhdl@gmail.com>
|
|
@@ -34,6 +34,7 @@ License: BSD 3-Clause License
|
|
|
34
34
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
35
35
|
|
|
36
36
|
Classifier: Development Status :: 4 - Beta
|
|
37
|
+
Classifier: Framework :: cocotb
|
|
37
38
|
Classifier: Intended Audience :: Developers
|
|
38
39
|
Classifier: Operating System :: OS Independent
|
|
39
40
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
@@ -43,10 +44,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
43
44
|
Classifier: Programming Language :: Python :: 3.11
|
|
44
45
|
Classifier: Programming Language :: Python :: 3.12
|
|
45
46
|
Classifier: Programming Language :: Python :: 3.13
|
|
46
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
47
47
|
Classifier: Topic :: Scientific/Engineering
|
|
48
48
|
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
49
|
-
Requires-Python:
|
|
49
|
+
Requires-Python: <3.14,>=3.8
|
|
50
50
|
Description-Content-Type: text/markdown
|
|
51
51
|
License-File: LICENSE
|
|
52
52
|
Requires-Dist: cocotb>=2
|
|
@@ -54,57 +54,62 @@ Dynamic: license-file
|
|
|
54
54
|
|
|
55
55
|
# Interface Documentation
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
[](https://github.com/RasmusGOlsen/cocotbext-interface/actions)
|
|
58
|
+
[](https://app.codecov.io/gh/RasmusGOlsen/cocotbext-interface/tree/main)
|
|
59
|
+
[](https://pypi.org/project/cocotbext-interface/)
|
|
60
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
61
|
+
[](https://github.com/astral-sh/ruff)
|
|
62
|
+
|
|
63
|
+
This interface package provides a Pythonic implementation of SystemVerilog-style **Interface**, **Modport**, and
|
|
64
|
+
**Clocking Block** for cocotb. It is designed to bridge the gap between hardware-centric verification concepts and the
|
|
65
|
+
flexibility of Python, ensuring race-free simulations and clean, reusable verification IP.
|
|
62
66
|
|
|
63
67
|
---
|
|
64
68
|
|
|
65
69
|
## 1. Project Overview
|
|
66
70
|
|
|
67
|
-
In standard cocotb, signal assignments are immediate and reads are subject to
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
In standard cocotb, signal assignments are immediate and reads are subject to simulator delta-cycle races. This package
|
|
72
|
+
introduces a timing-accurate **Clocking Block** mechanism.
|
|
73
|
+
|
|
74
|
+
By mimicking the SystemVerilog `interface` structure, verification engineers can:
|
|
75
|
+
|
|
76
|
+
- **Group Signals:** Define structural signal groups once in a central class.
|
|
77
|
+
- **Restrict Access:** Use **Modports** to define directional access for different VIP components (e.g., Sources, Sinks
|
|
78
|
+
and Monitors).
|
|
79
|
+
- **Enforce Synchronicity:** Use **Clocking Blocks** to ensure signals are driven and sampled at the correct time
|
|
80
|
+
relative to clock edges, supporting both time-based and event-based skews.
|
|
81
|
+
- **Define Functional APIs:** Using Python methods (`def` and `async def`) within the interface to build API tasks for
|
|
82
|
+
implementing interface protocols such as AXI, Avalon, I2C, SPI, etc.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 2. Installation
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
can:
|
|
88
|
+
Install the package from PyPI using pip:
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* **Enforce Synchronicity:** Use **Clocking Blocks** to ensure signals are
|
|
78
|
-
driven and sampled at the correct time relative to clock edges, supporting
|
|
79
|
-
both time-based and event-based skews.
|
|
80
|
-
* **Define Functional APIs:** Using Python methods (`def` and `async def`)
|
|
81
|
-
within the interface to build API tasks for implementing interface protocols
|
|
82
|
-
such as AXI, Avalon, I2C, SPI, etc.
|
|
90
|
+
```bash
|
|
91
|
+
pip install cocotbext-interface
|
|
92
|
+
```
|
|
83
93
|
|
|
84
94
|
---
|
|
85
95
|
|
|
86
|
-
##
|
|
96
|
+
## 3. Part I: Designing the Interface
|
|
87
97
|
|
|
88
|
-
This section covers how to define the structure, timing, and access rules for a
|
|
89
|
-
interface protocol.
|
|
98
|
+
This section covers how to define the structure, timing, and access rules for a interface protocol.
|
|
90
99
|
|
|
91
|
-
###
|
|
100
|
+
### 3.1 The Interface Class
|
|
92
101
|
|
|
93
|
-
To define a bus, inherit from the Interface class. Signals are now defined as
|
|
94
|
-
|
|
95
|
-
or optional during RTL binding.
|
|
102
|
+
To define a bus, inherit from the Interface class. Signals are now defined as class attributes using type hints, which
|
|
103
|
+
determines whether they are required or optional during RTL binding.
|
|
96
104
|
|
|
97
105
|
#### Defining Signals
|
|
98
106
|
|
|
99
|
-
The framework uses the presence of a default value to distinguish between
|
|
100
|
-
signal types:
|
|
107
|
+
The framework uses the presence of a default value to distinguish between signal types:
|
|
101
108
|
|
|
102
|
-
**Mandatory Signals**: Define these with a type hint (e.g., `LogicArrayObject`).
|
|
103
|
-
|
|
104
|
-
signal cannot be found in the RTL.
|
|
109
|
+
**Mandatory Signals**: Define these with a type hint (e.g., `LogicArrayObject`). If no default value is assigned, the
|
|
110
|
+
framework will raise an error if the signal cannot be found in the RTL.
|
|
105
111
|
|
|
106
|
-
**Optional Signals**: Assign None as a default value. This indicates that the
|
|
107
|
-
signal may or may not exist in the design.
|
|
112
|
+
**Optional Signals**: Assign None as a default value. This indicates that the signal may or may not exist in the design.
|
|
108
113
|
|
|
109
114
|
```python
|
|
110
115
|
class MyBus(Interface):
|
|
@@ -117,32 +122,31 @@ class MyBus(Interface):
|
|
|
117
122
|
|
|
118
123
|
These attribute names are used directly by the framework to search for matching signal names within the RTL hierarchy.
|
|
119
124
|
|
|
120
|
-
###
|
|
125
|
+
### 3.2 Defining Clocking Blocks
|
|
121
126
|
|
|
122
|
-
Use the `@clocking` decorator on an inner class. This defines the temporal
|
|
123
|
-
behavior for synchronous signals.
|
|
127
|
+
Use the `@clocking` decorator on an inner class. This defines the temporal behavior for synchronous signals.
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
- `clock`: The name of the clock signal in the `signals` list.
|
|
130
|
+
- `edge`: The trigger (e.g., `RisingEdge`, `FallingEdge`, `Edge`).
|
|
131
|
+
- `input`: Input skew (Sample delay). Can be a `Timer` or another `Trigger`.
|
|
132
|
+
- `output`: Output skew (Drive delay).
|
|
129
133
|
|
|
130
|
-
###
|
|
134
|
+
### 3.3 Defining Modports
|
|
131
135
|
|
|
132
|
-
Use the `@modport` decorator to group signals and clocking blocks for specific
|
|
133
|
-
roles like `source`, `sink` or `monitor`.
|
|
136
|
+
Use the `@modport` decorator to group signals and clocking blocks for specific roles like `source`, `sink` or `monitor`.
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
- `name`: Name of the modport
|
|
139
|
+
- `clocking`: Name of the clocking block to link.
|
|
140
|
+
- `Input`/`Output`/`InOut`: Signals accessible in this modport.
|
|
141
|
+
- `Import`: Method names (APIs) exposed to this modport.
|
|
139
142
|
|
|
140
|
-
###
|
|
143
|
+
### 3.4 Complete Definition Example
|
|
141
144
|
|
|
142
145
|
```python
|
|
146
|
+
from typing import Callable
|
|
143
147
|
from cocotb.handles import LogicArrayObject, LogicObject
|
|
144
148
|
from cocotb.triggers import RisingEdge, FallingEdge, Timer
|
|
145
|
-
from
|
|
149
|
+
from cocotbext.interface import Interface, modport, clocking, Import, Input, Output
|
|
146
150
|
|
|
147
151
|
class AxiStream(Interface):
|
|
148
152
|
clk: LogicObject
|
|
@@ -182,40 +186,34 @@ class AxiStream(Interface):
|
|
|
182
186
|
|
|
183
187
|
---
|
|
184
188
|
|
|
185
|
-
##
|
|
189
|
+
## 4. Part II: Using the Interface
|
|
186
190
|
|
|
187
|
-
This section covers how to connect the interface to the DUT and use it in a
|
|
188
|
-
test.
|
|
191
|
+
This section covers how to connect the interface to the DUT and use it in a test.
|
|
189
192
|
|
|
190
|
-
###
|
|
193
|
+
### 4.1 Instantiation & Connection
|
|
191
194
|
|
|
192
|
-
Interfaces are connected to the RTL using named constructors. These methods
|
|
193
|
-
|
|
194
|
-
hierarchy.
|
|
195
|
+
Interfaces are connected to the RTL using named constructors. These methods automatically map your class attributes to
|
|
196
|
+
the corresponding signals in the HDL hierarchy.
|
|
195
197
|
|
|
196
198
|
#### Connection Methods
|
|
197
199
|
|
|
198
200
|
##### 1. Direct Mapping (`from_entity`)
|
|
199
201
|
|
|
200
|
-
Use `from_entity` when the signal names in the RTL match your class attribute
|
|
201
|
-
|
|
202
|
-
method that does not support overrides or pattern substitutions.
|
|
202
|
+
Use `from_entity` when the signal names in the RTL match your class attribute names exactly under a specific hierarchy
|
|
203
|
+
level. This is a strict connection method that does not support overrides or pattern substitutions.
|
|
203
204
|
|
|
204
205
|
```python
|
|
205
|
-
# Connects to dut.
|
|
206
|
-
dut.rdy directly
|
|
206
|
+
# Connects to dut.data and dut.rdy directly
|
|
207
207
|
bus = MyBus.from_entity(dut)
|
|
208
208
|
```
|
|
209
209
|
|
|
210
210
|
##### 2. Explicit Assignment (`from_signal`)
|
|
211
211
|
|
|
212
|
-
The `from_signal` constructor allows for manual binding of signal handles to
|
|
213
|
-
|
|
214
|
-
different hierarchies.
|
|
212
|
+
The `from_signal` constructor allows for manual binding of signal handles to attributes. This is used when signals do
|
|
213
|
+
not follow a pattern or exist in different hierarchies.
|
|
215
214
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class are provided.
|
|
215
|
+
- **Behavior:** Uses keyword arguments to map handles to class attributes.
|
|
216
|
+
- **Validation:** Still enforces that all mandatory signals defined in the class are provided.
|
|
219
217
|
|
|
220
218
|
```python
|
|
221
219
|
# Manual connection: Explicitly pass handles for each attribute
|
|
@@ -227,9 +225,8 @@ bus = MyBus.from_signal(
|
|
|
227
225
|
|
|
228
226
|
##### 3. Pattern Matching (`from_pattern`)
|
|
229
227
|
|
|
230
|
-
The `from_pattern` method is used when signals follow a specific naming
|
|
231
|
-
|
|
232
|
-
substituted with each attribute name defined in your class.
|
|
228
|
+
The `from_pattern` method is used when signals follow a specific naming convention. The pattern argument must contain
|
|
229
|
+
the % wildcard, which is substituted with each attribute name defined in your class.
|
|
233
230
|
|
|
234
231
|
```python
|
|
235
232
|
# Replaces % with attribute names: e.g. 'u_axi_tdata'
|
|
@@ -238,10 +235,8 @@ bus = MyBus.from_pattern(dut, pattern="u_axi_%")
|
|
|
238
235
|
|
|
239
236
|
In addition to the % wildcard, `from_pattern` supports flexible discovery:
|
|
240
237
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
* **Regex:** Wrap the pattern in /.../ (e.g., /u_axi_.*_%/) for complex matching
|
|
244
|
-
logic.
|
|
238
|
+
- **Globbing:** Use \* (any characters), ? (single character), or + (one or more to match signals.
|
|
239
|
+
- **Regex:** Wrap the pattern in /.../ (e.g., /u*axi*.\*\_%/) for complex matching logic.
|
|
245
240
|
|
|
246
241
|
```python
|
|
247
242
|
# Simple substitution: Matches 'u_axi_tdata'
|
|
@@ -256,48 +251,55 @@ bus = MyBus.from_pattern(dut, pattern="/u_axi_[0-9]_%/")
|
|
|
256
251
|
|
|
257
252
|
#### Advanced Pattern Options
|
|
258
253
|
|
|
259
|
-
While `from_entity` is strict, `from_pattern` allows for flexibility when the
|
|
260
|
-
RTL structure is non-standard:
|
|
254
|
+
While `from_entity` is strict, `from_pattern` allows for flexibility when the RTL structure is non-standard:
|
|
261
255
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
256
|
+
- **Keyword Overrides:** Pass a signal handle as a keyword argument to skip the pattern search for that specific
|
|
257
|
+
attribute.
|
|
258
|
+
- **Indexing:** Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
|
|
259
|
+
`dut.u_axi_tdata[1]`).
|
|
265
260
|
|
|
266
261
|
```python
|
|
267
262
|
# Connect to index 1 and manually override 'rdy'
|
|
268
263
|
bus = MyBus.from_pattern(dut, pattern="u_axi_%", idx=1, rdy=dut.global_rdy)
|
|
269
264
|
```
|
|
270
265
|
|
|
271
|
-
> [!TIP] To confirm your signals are correctly bound, you can print `print(bus)`
|
|
272
|
-
>
|
|
266
|
+
> [!TIP] To confirm your signals are correctly bound, you can print `print(bus)` or `print(f"{bus=}")` the bus object to
|
|
267
|
+
> inspect the resolved RTL paths.
|
|
273
268
|
|
|
274
|
-
###
|
|
269
|
+
### 4.2 Synchronous Driving (Non-Blocking)
|
|
275
270
|
|
|
276
|
-
When using a modport's clocking block, driving a signal schedules an update for
|
|
277
|
-
|
|
271
|
+
When using a modport's clocking block, driving a signal schedules an update for the next clock edge + output skew. It
|
|
272
|
+
does not block the current coroutine.
|
|
278
273
|
|
|
279
274
|
```python
|
|
280
275
|
# Drive data through the source modport
|
|
281
|
-
bus.source.
|
|
282
|
-
bus.source.
|
|
276
|
+
bus.source.cb.tdata.value = 0xABCD
|
|
277
|
+
bus.source.cb.tvalid.value = 1
|
|
283
278
|
```
|
|
284
279
|
|
|
285
|
-
###
|
|
280
|
+
### 4.3 Synchronous Sampling (Blocking)
|
|
281
|
+
|
|
282
|
+
To read a signal synchronously, you **must** use `await ...capture()`. This ensures the simulation waits for the clock
|
|
283
|
+
edge and the defined input skew before returning the value.
|
|
286
284
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
285
|
+
The `capture()` method accepts two optional arguments:
|
|
286
|
+
|
|
287
|
+
- **`resolver`**: A resolution function (e.g., `cocotb.types.Logic.resolve`) to resolve 4-state logic values ('X', 'Z')
|
|
288
|
+
to a simpler 2-state representation.
|
|
289
|
+
- **`default`**: The value returned if the signal is an **optional signal** that is unconnected.
|
|
290
290
|
|
|
291
291
|
```python
|
|
292
292
|
# Wait until sink is ready
|
|
293
|
-
while await bus.source.
|
|
294
|
-
await bus.source.
|
|
293
|
+
while await bus.source.cb.tready.capture() == 0:
|
|
294
|
+
await bus.source.cb.wait() # Wait for 1 clock cycle
|
|
295
|
+
|
|
296
|
+
# Capture with resolution and a default value for optional signals
|
|
297
|
+
data = await bus.sink.cb.tdata.capture(resolver=Logic.resolve, default=0)
|
|
295
298
|
```
|
|
296
299
|
|
|
297
|
-
###
|
|
300
|
+
### 4.4 Using Interface APIs
|
|
298
301
|
|
|
299
|
-
Methods defined in the interface and imported in the modport can be called
|
|
300
|
-
directly.
|
|
302
|
+
Methods defined in the interface and imported in the modport can be called directly.
|
|
301
303
|
|
|
302
304
|
```python
|
|
303
305
|
# Call the reset task defined by the VIP developer
|
|
@@ -306,26 +308,23 @@ await bus.source.reset()
|
|
|
306
308
|
|
|
307
309
|
---
|
|
308
310
|
|
|
309
|
-
##
|
|
311
|
+
## 5. Technical Summary
|
|
310
312
|
|
|
311
|
-
| SystemVerilog Concept | Framework Implementation
|
|
312
|
-
|
|
|
313
|
-
| `interface`
|
|
314
|
-
| `.*`
|
|
315
|
-
| `.clk(sys_clk)`
|
|
316
|
-
| `modport`
|
|
317
|
-
| `clocking`
|
|
318
|
-
| `cb.sig <= val`
|
|
319
|
-
| `val = cb.sig`
|
|
320
|
-
| `##N`
|
|
313
|
+
| SystemVerilog Concept | Framework Implementation | Behavior |
|
|
314
|
+
| :-------------------- | :---------------------------------- | :------------------------------------- |
|
|
315
|
+
| `interface` | `class MyBus(Interface):` | Structural container. |
|
|
316
|
+
| `.*` | `pattern="%"` | Substitution-based wildcard discovery. |
|
|
317
|
+
| `.clk(sys_clk)` | `clk=dut.sys_clk` | Explicit named mapping override. |
|
|
318
|
+
| `modport` | `@modport` | Role-based grouping (Source/Sink). |
|
|
319
|
+
| `clocking` | `@clocking` | Temporal skews and edge triggers. |
|
|
320
|
+
| `cb.sig <= val` | `cb_name.sig.value = val` | Non-blocking drive (Setter). |
|
|
321
|
+
| `val = cb.sig` | `val = await cb_name.sig.capture()` | Synchronous sample (Coroutine). |
|
|
322
|
+
| `##N` | `await cb_name.wait(N)` | Cycle-based delay. |
|
|
321
323
|
|
|
322
324
|
---
|
|
323
325
|
|
|
324
|
-
##
|
|
326
|
+
## 6. Best Practices
|
|
325
327
|
|
|
326
|
-
1. Define separate clocking blocks for Source and Sink roles to account for
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
user to use the clocking block.
|
|
330
|
-
3. Leverage the `%` wildcard in patterns to avoid manually connecting dozens of
|
|
331
|
-
signals.
|
|
328
|
+
1. Define separate clocking blocks for Source and Sink roles to account for different signal access and physical skews.
|
|
329
|
+
2. Don't give access to synchronous signals in the `modport`, this forces the user to use the clocking block.
|
|
330
|
+
3. Leverage the `%` wildcard in patterns to avoid manually connecting dozens of signals.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Interface Documentation
|
|
2
|
+
|
|
3
|
+
[](https://github.com/RasmusGOlsen/cocotbext-interface/actions)
|
|
4
|
+
[](https://app.codecov.io/gh/RasmusGOlsen/cocotbext-interface/tree/main)
|
|
5
|
+
[](https://pypi.org/project/cocotbext-interface/)
|
|
6
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
7
|
+
[](https://github.com/astral-sh/ruff)
|
|
8
|
+
|
|
9
|
+
This interface package provides a Pythonic implementation of SystemVerilog-style **Interface**, **Modport**, and
|
|
10
|
+
**Clocking Block** for cocotb. It is designed to bridge the gap between hardware-centric verification concepts and the
|
|
11
|
+
flexibility of Python, ensuring race-free simulations and clean, reusable verification IP.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Project Overview
|
|
16
|
+
|
|
17
|
+
In standard cocotb, signal assignments are immediate and reads are subject to simulator delta-cycle races. This package
|
|
18
|
+
introduces a timing-accurate **Clocking Block** mechanism.
|
|
19
|
+
|
|
20
|
+
By mimicking the SystemVerilog `interface` structure, verification engineers can:
|
|
21
|
+
|
|
22
|
+
- **Group Signals:** Define structural signal groups once in a central class.
|
|
23
|
+
- **Restrict Access:** Use **Modports** to define directional access for different VIP components (e.g., Sources, Sinks
|
|
24
|
+
and Monitors).
|
|
25
|
+
- **Enforce Synchronicity:** Use **Clocking Blocks** to ensure signals are driven and sampled at the correct time
|
|
26
|
+
relative to clock edges, supporting both time-based and event-based skews.
|
|
27
|
+
- **Define Functional APIs:** Using Python methods (`def` and `async def`) within the interface to build API tasks for
|
|
28
|
+
implementing interface protocols such as AXI, Avalon, I2C, SPI, etc.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Installation
|
|
33
|
+
|
|
34
|
+
Install the package from PyPI using pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install cocotbext-interface
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Part I: Designing the Interface
|
|
43
|
+
|
|
44
|
+
This section covers how to define the structure, timing, and access rules for a interface protocol.
|
|
45
|
+
|
|
46
|
+
### 3.1 The Interface Class
|
|
47
|
+
|
|
48
|
+
To define a bus, inherit from the Interface class. Signals are now defined as class attributes using type hints, which
|
|
49
|
+
determines whether they are required or optional during RTL binding.
|
|
50
|
+
|
|
51
|
+
#### Defining Signals
|
|
52
|
+
|
|
53
|
+
The framework uses the presence of a default value to distinguish between signal types:
|
|
54
|
+
|
|
55
|
+
**Mandatory Signals**: Define these with a type hint (e.g., `LogicArrayObject`). If no default value is assigned, the
|
|
56
|
+
framework will raise an error if the signal cannot be found in the RTL.
|
|
57
|
+
|
|
58
|
+
**Optional Signals**: Assign None as a default value. This indicates that the signal may or may not exist in the design.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
class MyBus(Interface):
|
|
62
|
+
# Mandatory signal: Must be present in the RTL
|
|
63
|
+
data: LogicArrayObject
|
|
64
|
+
|
|
65
|
+
# Optional signal: Defaults to None if not found
|
|
66
|
+
rdy: LogicObject | None = None
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
These attribute names are used directly by the framework to search for matching signal names within the RTL hierarchy.
|
|
70
|
+
|
|
71
|
+
### 3.2 Defining Clocking Blocks
|
|
72
|
+
|
|
73
|
+
Use the `@clocking` decorator on an inner class. This defines the temporal behavior for synchronous signals.
|
|
74
|
+
|
|
75
|
+
- `clock`: The name of the clock signal in the `signals` list.
|
|
76
|
+
- `edge`: The trigger (e.g., `RisingEdge`, `FallingEdge`, `Edge`).
|
|
77
|
+
- `input`: Input skew (Sample delay). Can be a `Timer` or another `Trigger`.
|
|
78
|
+
- `output`: Output skew (Drive delay).
|
|
79
|
+
|
|
80
|
+
### 3.3 Defining Modports
|
|
81
|
+
|
|
82
|
+
Use the `@modport` decorator to group signals and clocking blocks for specific roles like `source`, `sink` or `monitor`.
|
|
83
|
+
|
|
84
|
+
- `name`: Name of the modport
|
|
85
|
+
- `clocking`: Name of the clocking block to link.
|
|
86
|
+
- `Input`/`Output`/`InOut`: Signals accessible in this modport.
|
|
87
|
+
- `Import`: Method names (APIs) exposed to this modport.
|
|
88
|
+
|
|
89
|
+
### 3.4 Complete Definition Example
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from typing import Callable
|
|
93
|
+
from cocotb.handles import LogicArrayObject, LogicObject
|
|
94
|
+
from cocotb.triggers import RisingEdge, FallingEdge, Timer
|
|
95
|
+
from cocotbext.interface import Interface, modport, clocking, Import, Input, Output
|
|
96
|
+
|
|
97
|
+
class AxiStream(Interface):
|
|
98
|
+
clk: LogicObject
|
|
99
|
+
rst_n: LogicObject
|
|
100
|
+
tdata: LogicArrayObject
|
|
101
|
+
tvalid: LogicObject
|
|
102
|
+
tready: LogicObject
|
|
103
|
+
|
|
104
|
+
async def reset(self):
|
|
105
|
+
self.rst_n.value = 0
|
|
106
|
+
await Timer(10, 'ns')
|
|
107
|
+
self.rst_n.value = 1
|
|
108
|
+
|
|
109
|
+
# Define Source-side timing
|
|
110
|
+
@clocking(clock="clk", edge=RisingEdge, input=Timer(1, 'ns'), output=Timer(2, 'ns'))
|
|
111
|
+
class source_cb:
|
|
112
|
+
tready: Input[LogicObject]
|
|
113
|
+
tdata: Output[LogicArrayObject]
|
|
114
|
+
tvalid: Output[LogicObject]
|
|
115
|
+
|
|
116
|
+
# Define Sink-side timing
|
|
117
|
+
@clocking(clock="clk", edge=RisingEdge, input=Timer(1, 'ns'))
|
|
118
|
+
class sink_cb:
|
|
119
|
+
tdata: Input[LogicArrayObject]
|
|
120
|
+
tvalid: Input[LogicObject]
|
|
121
|
+
tready: Output[LogicObject]
|
|
122
|
+
|
|
123
|
+
@modport(clocking="source_cb")
|
|
124
|
+
class source:
|
|
125
|
+
rst_n: Output[LogicObject]
|
|
126
|
+
reset: Import[Callable]
|
|
127
|
+
|
|
128
|
+
@modport(clocking="sink_cb")
|
|
129
|
+
class sink:
|
|
130
|
+
reset: Import[Callable]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 4. Part II: Using the Interface
|
|
136
|
+
|
|
137
|
+
This section covers how to connect the interface to the DUT and use it in a test.
|
|
138
|
+
|
|
139
|
+
### 4.1 Instantiation & Connection
|
|
140
|
+
|
|
141
|
+
Interfaces are connected to the RTL using named constructors. These methods automatically map your class attributes to
|
|
142
|
+
the corresponding signals in the HDL hierarchy.
|
|
143
|
+
|
|
144
|
+
#### Connection Methods
|
|
145
|
+
|
|
146
|
+
##### 1. Direct Mapping (`from_entity`)
|
|
147
|
+
|
|
148
|
+
Use `from_entity` when the signal names in the RTL match your class attribute names exactly under a specific hierarchy
|
|
149
|
+
level. This is a strict connection method that does not support overrides or pattern substitutions.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# Connects to dut.data and dut.rdy directly
|
|
153
|
+
bus = MyBus.from_entity(dut)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
##### 2. Explicit Assignment (`from_signal`)
|
|
157
|
+
|
|
158
|
+
The `from_signal` constructor allows for manual binding of signal handles to attributes. This is used when signals do
|
|
159
|
+
not follow a pattern or exist in different hierarchies.
|
|
160
|
+
|
|
161
|
+
- **Behavior:** Uses keyword arguments to map handles to class attributes.
|
|
162
|
+
- **Validation:** Still enforces that all mandatory signals defined in the class are provided.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# Manual connection: Explicitly pass handles for each attribute
|
|
166
|
+
bus = MyBus.from_signal(
|
|
167
|
+
tdata = dut.top.data_bus,
|
|
168
|
+
rdy = dut.extra_logic.ready_bit
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
##### 3. Pattern Matching (`from_pattern`)
|
|
173
|
+
|
|
174
|
+
The `from_pattern` method is used when signals follow a specific naming convention. The pattern argument must contain
|
|
175
|
+
the % wildcard, which is substituted with each attribute name defined in your class.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# Replaces % with attribute names: e.g. 'u_axi_tdata'
|
|
179
|
+
bus = MyBus.from_pattern(dut, pattern="u_axi_%")
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
In addition to the % wildcard, `from_pattern` supports flexible discovery:
|
|
183
|
+
|
|
184
|
+
- **Globbing:** Use \* (any characters), ? (single character), or + (one or more to match signals.
|
|
185
|
+
- **Regex:** Wrap the pattern in /.../ (e.g., /u*axi*.\*\_%/) for complex matching logic.
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Simple substitution: Matches 'u_axi_tdata'
|
|
189
|
+
bus = MyBus.from_pattern(dut, pattern="u_axi_%")
|
|
190
|
+
|
|
191
|
+
# Globbing: Matches 'u_axi_0_tdata' or 'u_axi_stage1_tdata'
|
|
192
|
+
bus = MyBus.from_pattern(dut, pattern="u_axi_*_%")
|
|
193
|
+
|
|
194
|
+
# Regex: Matches signals with specific numeric suffixes
|
|
195
|
+
bus = MyBus.from_pattern(dut, pattern="/u_axi_[0-9]_%/")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Advanced Pattern Options
|
|
199
|
+
|
|
200
|
+
While `from_entity` is strict, `from_pattern` allows for flexibility when the RTL structure is non-standard:
|
|
201
|
+
|
|
202
|
+
- **Keyword Overrides:** Pass a signal handle as a keyword argument to skip the pattern search for that specific
|
|
203
|
+
attribute.
|
|
204
|
+
- **Indexing:** Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
|
|
205
|
+
`dut.u_axi_tdata[1]`).
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
# Connect to index 1 and manually override 'rdy'
|
|
209
|
+
bus = MyBus.from_pattern(dut, pattern="u_axi_%", idx=1, rdy=dut.global_rdy)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
> [!TIP] To confirm your signals are correctly bound, you can print `print(bus)` or `print(f"{bus=}")` the bus object to
|
|
213
|
+
> inspect the resolved RTL paths.
|
|
214
|
+
|
|
215
|
+
### 4.2 Synchronous Driving (Non-Blocking)
|
|
216
|
+
|
|
217
|
+
When using a modport's clocking block, driving a signal schedules an update for the next clock edge + output skew. It
|
|
218
|
+
does not block the current coroutine.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Drive data through the source modport
|
|
222
|
+
bus.source.cb.tdata.value = 0xABCD
|
|
223
|
+
bus.source.cb.tvalid.value = 1
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 4.3 Synchronous Sampling (Blocking)
|
|
227
|
+
|
|
228
|
+
To read a signal synchronously, you **must** use `await ...capture()`. This ensures the simulation waits for the clock
|
|
229
|
+
edge and the defined input skew before returning the value.
|
|
230
|
+
|
|
231
|
+
The `capture()` method accepts two optional arguments:
|
|
232
|
+
|
|
233
|
+
- **`resolver`**: A resolution function (e.g., `cocotb.types.Logic.resolve`) to resolve 4-state logic values ('X', 'Z')
|
|
234
|
+
to a simpler 2-state representation.
|
|
235
|
+
- **`default`**: The value returned if the signal is an **optional signal** that is unconnected.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
# Wait until sink is ready
|
|
239
|
+
while await bus.source.cb.tready.capture() == 0:
|
|
240
|
+
await bus.source.cb.wait() # Wait for 1 clock cycle
|
|
241
|
+
|
|
242
|
+
# Capture with resolution and a default value for optional signals
|
|
243
|
+
data = await bus.sink.cb.tdata.capture(resolver=Logic.resolve, default=0)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 4.4 Using Interface APIs
|
|
247
|
+
|
|
248
|
+
Methods defined in the interface and imported in the modport can be called directly.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
# Call the reset task defined by the VIP developer
|
|
252
|
+
await bus.source.reset()
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 5. Technical Summary
|
|
258
|
+
|
|
259
|
+
| SystemVerilog Concept | Framework Implementation | Behavior |
|
|
260
|
+
| :-------------------- | :---------------------------------- | :------------------------------------- |
|
|
261
|
+
| `interface` | `class MyBus(Interface):` | Structural container. |
|
|
262
|
+
| `.*` | `pattern="%"` | Substitution-based wildcard discovery. |
|
|
263
|
+
| `.clk(sys_clk)` | `clk=dut.sys_clk` | Explicit named mapping override. |
|
|
264
|
+
| `modport` | `@modport` | Role-based grouping (Source/Sink). |
|
|
265
|
+
| `clocking` | `@clocking` | Temporal skews and edge triggers. |
|
|
266
|
+
| `cb.sig <= val` | `cb_name.sig.value = val` | Non-blocking drive (Setter). |
|
|
267
|
+
| `val = cb.sig` | `val = await cb_name.sig.capture()` | Synchronous sample (Coroutine). |
|
|
268
|
+
| `##N` | `await cb_name.wait(N)` | Cycle-based delay. |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## 6. Best Practices
|
|
273
|
+
|
|
274
|
+
1. Define separate clocking blocks for Source and Sink roles to account for different signal access and physical skews.
|
|
275
|
+
2. Don't give access to synchronous signals in the `modport`, this forces the user to use the clocking block.
|
|
276
|
+
3. Leverage the `%` wildcard in patterns to avoid manually connecting dozens of signals.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from ._version import __version__
|
|
2
2
|
from .interface import Interface, modport, clocking, Import, InOut, Input, Output
|
|
3
|
-
from .memorymappedinterface import MemoryMappedInterface
|
|
4
|
-
from .streaminterface import StreamInterface
|
|
5
3
|
|
|
6
4
|
__all__ = [
|
|
7
5
|
"__version__",
|
|
@@ -12,6 +10,4 @@ __all__ = [
|
|
|
12
10
|
"Output",
|
|
13
11
|
"modport",
|
|
14
12
|
"clocking",
|
|
15
|
-
"MemoryMappedInterface",
|
|
16
|
-
"StreamInterface"
|
|
17
13
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.4"
|