cocotbext-interface 0.1.3__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.
Files changed (20) hide show
  1. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/PKG-INFO +41 -21
  2. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/README.md +40 -20
  3. cocotbext_interface-0.1.4/cocotbext/interface/_version.py +1 -0
  4. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/interface.py +14 -4
  5. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/PKG-INFO +41 -21
  6. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/tests/test_interface.py +49 -1
  7. cocotbext_interface-0.1.3/cocotbext/interface/_version.py +0 -1
  8. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/LICENSE +0 -0
  9. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/__init__.py +0 -0
  10. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/prototypes/__init__.py +0 -0
  11. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/prototypes/memorymappedinterface.py +0 -0
  12. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/prototypes/streaminterface.py +0 -0
  13. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext/interface/utils.py +0 -0
  14. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/SOURCES.txt +0 -0
  15. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/dependency_links.txt +0 -0
  16. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/requires.txt +0 -0
  17. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/cocotbext_interface.egg-info/top_level.txt +0 -0
  18. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/pyproject.toml +0 -0
  19. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/setup.cfg +0 -0
  20. {cocotbext_interface-0.1.3 → cocotbext_interface-0.1.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cocotbext-interface
3
- Version: 0.1.3
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>
@@ -83,11 +83,21 @@ By mimicking the SystemVerilog `interface` structure, verification engineers can
83
83
 
84
84
  ---
85
85
 
86
- ## 2. Part I: Designing the Interface
86
+ ## 2. Installation
87
+
88
+ Install the package from PyPI using pip:
89
+
90
+ ```bash
91
+ pip install cocotbext-interface
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 3. Part I: Designing the Interface
87
97
 
88
98
  This section covers how to define the structure, timing, and access rules for a interface protocol.
89
99
 
90
- ### 2.1 The Interface Class
100
+ ### 3.1 The Interface Class
91
101
 
92
102
  To define a bus, inherit from the Interface class. Signals are now defined as class attributes using type hints, which
93
103
  determines whether they are required or optional during RTL binding.
@@ -112,7 +122,7 @@ class MyBus(Interface):
112
122
 
113
123
  These attribute names are used directly by the framework to search for matching signal names within the RTL hierarchy.
114
124
 
115
- ### 2.2 Defining Clocking Blocks
125
+ ### 3.2 Defining Clocking Blocks
116
126
 
117
127
  Use the `@clocking` decorator on an inner class. This defines the temporal behavior for synchronous signals.
118
128
 
@@ -121,7 +131,7 @@ Use the `@clocking` decorator on an inner class. This defines the temporal behav
121
131
  - `input`: Input skew (Sample delay). Can be a `Timer` or another `Trigger`.
122
132
  - `output`: Output skew (Drive delay).
123
133
 
124
- ### 2.3 Defining Modports
134
+ ### 3.3 Defining Modports
125
135
 
126
136
  Use the `@modport` decorator to group signals and clocking blocks for specific roles like `source`, `sink` or `monitor`.
127
137
 
@@ -130,12 +140,13 @@ Use the `@modport` decorator to group signals and clocking blocks for specific r
130
140
  - `Input`/`Output`/`InOut`: Signals accessible in this modport.
131
141
  - `Import`: Method names (APIs) exposed to this modport.
132
142
 
133
- ### 2.4 Complete Definition Example
143
+ ### 3.4 Complete Definition Example
134
144
 
135
145
  ```python
146
+ from typing import Callable
136
147
  from cocotb.handles import LogicArrayObject, LogicObject
137
148
  from cocotb.triggers import RisingEdge, FallingEdge, Timer
138
- from interface_framework import Interface, modport, clocking, Import, Input, Output
149
+ from cocotbext.interface import Interface, modport, clocking, Import, Input, Output
139
150
 
140
151
  class AxiStream(Interface):
141
152
  clk: LogicObject
@@ -175,11 +186,11 @@ class AxiStream(Interface):
175
186
 
176
187
  ---
177
188
 
178
- ## 3. Part II: Using the Interface
189
+ ## 4. Part II: Using the Interface
179
190
 
180
191
  This section covers how to connect the interface to the DUT and use it in a test.
181
192
 
182
- ### 3.1 Instantiation & Connection
193
+ ### 4.1 Instantiation & Connection
183
194
 
184
195
  Interfaces are connected to the RTL using named constructors. These methods automatically map your class attributes to
185
196
  the corresponding signals in the HDL hierarchy.
@@ -192,8 +203,7 @@ Use `from_entity` when the signal names in the RTL match your class attribute na
192
203
  level. This is a strict connection method that does not support overrides or pattern substitutions.
193
204
 
194
205
  ```python
195
- # Connects to dut.tdata and
196
- dut.rdy directly
206
+ # Connects to dut.data and dut.rdy directly
197
207
  bus = MyBus.from_entity(dut)
198
208
  ```
199
209
 
@@ -244,7 +254,8 @@ bus = MyBus.from_pattern(dut, pattern="/u_axi_[0-9]_%/")
244
254
  While `from_entity` is strict, `from_pattern` allows for flexibility when the RTL structure is non-standard:
245
255
 
246
256
  - **Keyword Overrides:** Pass a signal handle as a keyword argument to skip the pattern search for that specific
247
- attribute.Indexing: Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
257
+ attribute.
258
+ - **Indexing:** Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
248
259
  `dut.u_axi_tdata[1]`).
249
260
 
250
261
  ```python
@@ -255,29 +266,38 @@ bus = MyBus.from_pattern(dut, pattern="u_axi_%", idx=1, rdy=dut.global_rdy)
255
266
  > [!TIP] To confirm your signals are correctly bound, you can print `print(bus)` or `print(f"{bus=}")` the bus object to
256
267
  > inspect the resolved RTL paths.
257
268
 
258
- ### 3.2 Synchronous Driving (Non-Blocking)
269
+ ### 4.2 Synchronous Driving (Non-Blocking)
259
270
 
260
271
  When using a modport's clocking block, driving a signal schedules an update for the next clock edge + output skew. It
261
272
  does not block the current coroutine.
262
273
 
263
274
  ```python
264
275
  # Drive data through the source modport
265
- bus.source.src_cb.tdata.value = 0xABCD
266
- bus.source.src_cb.tvalid.value = 1
276
+ bus.source.cb.tdata.value = 0xABCD
277
+ bus.source.cb.tvalid.value = 1
267
278
  ```
268
279
 
269
- ### 3.3 Synchronous Sampling (Blocking)
280
+ ### 4.3 Synchronous Sampling (Blocking)
270
281
 
271
282
  To read a signal synchronously, you **must** use `await ...capture()`. This ensures the simulation waits for the clock
272
283
  edge and the defined input skew before returning the value.
273
284
 
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
+
274
291
  ```python
275
292
  # Wait until sink is ready
276
- while await bus.source.src_cb.tready.capture() == 0:
277
- await bus.source.src_cb.wait() # Wait for 1 clock cycle
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)
278
298
  ```
279
299
 
280
- ### 3.4 Using Interface APIs
300
+ ### 4.4 Using Interface APIs
281
301
 
282
302
  Methods defined in the interface and imported in the modport can be called directly.
283
303
 
@@ -288,7 +308,7 @@ await bus.source.reset()
288
308
 
289
309
  ---
290
310
 
291
- ## 4. Technical Summary
311
+ ## 5. Technical Summary
292
312
 
293
313
  | SystemVerilog Concept | Framework Implementation | Behavior |
294
314
  | :-------------------- | :---------------------------------- | :------------------------------------- |
@@ -303,7 +323,7 @@ await bus.source.reset()
303
323
 
304
324
  ---
305
325
 
306
- ## 5. Best Practices
326
+ ## 6. Best Practices
307
327
 
308
328
  1. Define separate clocking blocks for Source and Sink roles to account for different signal access and physical skews.
309
329
  2. Don't give access to synchronous signals in the `modport`, this forces the user to use the clocking block.
@@ -29,11 +29,21 @@ By mimicking the SystemVerilog `interface` structure, verification engineers can
29
29
 
30
30
  ---
31
31
 
32
- ## 2. Part I: Designing the Interface
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
33
43
 
34
44
  This section covers how to define the structure, timing, and access rules for a interface protocol.
35
45
 
36
- ### 2.1 The Interface Class
46
+ ### 3.1 The Interface Class
37
47
 
38
48
  To define a bus, inherit from the Interface class. Signals are now defined as class attributes using type hints, which
39
49
  determines whether they are required or optional during RTL binding.
@@ -58,7 +68,7 @@ class MyBus(Interface):
58
68
 
59
69
  These attribute names are used directly by the framework to search for matching signal names within the RTL hierarchy.
60
70
 
61
- ### 2.2 Defining Clocking Blocks
71
+ ### 3.2 Defining Clocking Blocks
62
72
 
63
73
  Use the `@clocking` decorator on an inner class. This defines the temporal behavior for synchronous signals.
64
74
 
@@ -67,7 +77,7 @@ Use the `@clocking` decorator on an inner class. This defines the temporal behav
67
77
  - `input`: Input skew (Sample delay). Can be a `Timer` or another `Trigger`.
68
78
  - `output`: Output skew (Drive delay).
69
79
 
70
- ### 2.3 Defining Modports
80
+ ### 3.3 Defining Modports
71
81
 
72
82
  Use the `@modport` decorator to group signals and clocking blocks for specific roles like `source`, `sink` or `monitor`.
73
83
 
@@ -76,12 +86,13 @@ Use the `@modport` decorator to group signals and clocking blocks for specific r
76
86
  - `Input`/`Output`/`InOut`: Signals accessible in this modport.
77
87
  - `Import`: Method names (APIs) exposed to this modport.
78
88
 
79
- ### 2.4 Complete Definition Example
89
+ ### 3.4 Complete Definition Example
80
90
 
81
91
  ```python
92
+ from typing import Callable
82
93
  from cocotb.handles import LogicArrayObject, LogicObject
83
94
  from cocotb.triggers import RisingEdge, FallingEdge, Timer
84
- from interface_framework import Interface, modport, clocking, Import, Input, Output
95
+ from cocotbext.interface import Interface, modport, clocking, Import, Input, Output
85
96
 
86
97
  class AxiStream(Interface):
87
98
  clk: LogicObject
@@ -121,11 +132,11 @@ class AxiStream(Interface):
121
132
 
122
133
  ---
123
134
 
124
- ## 3. Part II: Using the Interface
135
+ ## 4. Part II: Using the Interface
125
136
 
126
137
  This section covers how to connect the interface to the DUT and use it in a test.
127
138
 
128
- ### 3.1 Instantiation & Connection
139
+ ### 4.1 Instantiation & Connection
129
140
 
130
141
  Interfaces are connected to the RTL using named constructors. These methods automatically map your class attributes to
131
142
  the corresponding signals in the HDL hierarchy.
@@ -138,8 +149,7 @@ Use `from_entity` when the signal names in the RTL match your class attribute na
138
149
  level. This is a strict connection method that does not support overrides or pattern substitutions.
139
150
 
140
151
  ```python
141
- # Connects to dut.tdata and
142
- dut.rdy directly
152
+ # Connects to dut.data and dut.rdy directly
143
153
  bus = MyBus.from_entity(dut)
144
154
  ```
145
155
 
@@ -190,7 +200,8 @@ bus = MyBus.from_pattern(dut, pattern="/u_axi_[0-9]_%/")
190
200
  While `from_entity` is strict, `from_pattern` allows for flexibility when the RTL structure is non-standard:
191
201
 
192
202
  - **Keyword Overrides:** Pass a signal handle as a keyword argument to skip the pattern search for that specific
193
- attribute.Indexing: Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
203
+ attribute.
204
+ - **Indexing:** Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
194
205
  `dut.u_axi_tdata[1]`).
195
206
 
196
207
  ```python
@@ -201,29 +212,38 @@ bus = MyBus.from_pattern(dut, pattern="u_axi_%", idx=1, rdy=dut.global_rdy)
201
212
  > [!TIP] To confirm your signals are correctly bound, you can print `print(bus)` or `print(f"{bus=}")` the bus object to
202
213
  > inspect the resolved RTL paths.
203
214
 
204
- ### 3.2 Synchronous Driving (Non-Blocking)
215
+ ### 4.2 Synchronous Driving (Non-Blocking)
205
216
 
206
217
  When using a modport's clocking block, driving a signal schedules an update for the next clock edge + output skew. It
207
218
  does not block the current coroutine.
208
219
 
209
220
  ```python
210
221
  # Drive data through the source modport
211
- bus.source.src_cb.tdata.value = 0xABCD
212
- bus.source.src_cb.tvalid.value = 1
222
+ bus.source.cb.tdata.value = 0xABCD
223
+ bus.source.cb.tvalid.value = 1
213
224
  ```
214
225
 
215
- ### 3.3 Synchronous Sampling (Blocking)
226
+ ### 4.3 Synchronous Sampling (Blocking)
216
227
 
217
228
  To read a signal synchronously, you **must** use `await ...capture()`. This ensures the simulation waits for the clock
218
229
  edge and the defined input skew before returning the value.
219
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
+
220
237
  ```python
221
238
  # Wait until sink is ready
222
- while await bus.source.src_cb.tready.capture() == 0:
223
- await bus.source.src_cb.wait() # Wait for 1 clock cycle
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)
224
244
  ```
225
245
 
226
- ### 3.4 Using Interface APIs
246
+ ### 4.4 Using Interface APIs
227
247
 
228
248
  Methods defined in the interface and imported in the modport can be called directly.
229
249
 
@@ -234,7 +254,7 @@ await bus.source.reset()
234
254
 
235
255
  ---
236
256
 
237
- ## 4. Technical Summary
257
+ ## 5. Technical Summary
238
258
 
239
259
  | SystemVerilog Concept | Framework Implementation | Behavior |
240
260
  | :-------------------- | :---------------------------------- | :------------------------------------- |
@@ -249,7 +269,7 @@ await bus.source.reset()
249
269
 
250
270
  ---
251
271
 
252
- ## 5. Best Practices
272
+ ## 6. Best Practices
253
273
 
254
274
  1. Define separate clocking blocks for Source and Sink roles to account for different signal access and physical skews.
255
275
  2. Don't give access to synchronous signals in the `modport`, this forces the user to use the clocking block.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -112,6 +112,7 @@ class ClockedSignal:
112
112
  else:
113
113
  logger.warning(f"{self._handle} is not resolvable")
114
114
  return value
115
+ return default
115
116
 
116
117
  def __getattr__(self, name):
117
118
  """Forward other cocotb handle methods (like ._name, etc)"""
@@ -204,12 +205,21 @@ class ModportView:
204
205
  origin = getattr(_type, "__origin__", None)
205
206
 
206
207
  if origin is Import:
207
- # 1. Map Method from parent Interface
208
- if hasattr(parent, name):
209
- setattr(self, name, getattr(parent, name))
208
+ # Resolve class methods before instance attributes, which may
209
+ # contain an HDL signal with the same name as the imported method.
210
+ imported = inspect.getattr_static(type(parent), name, None)
211
+ if imported is not None:
212
+ descriptor_get = getattr(imported, "__get__", None)
213
+ if descriptor_get is not None:
214
+ imported = descriptor_get(parent, type(parent))
215
+ else:
216
+ imported = getattr(parent, name, None)
217
+
218
+ if callable(imported):
219
+ setattr(self, name, imported)
210
220
  else:
211
221
  raise AttributeError(
212
- f"Modport '{mp_cls.__name__}' expected imported method '{name}', "
222
+ f"Modport '{mp_cls.__name__}' expected imported callable '{name}', "
213
223
  f"but it was not found in {parent.__class__.__name__}"
214
224
  )
215
225
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cocotbext-interface
3
- Version: 0.1.3
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>
@@ -83,11 +83,21 @@ By mimicking the SystemVerilog `interface` structure, verification engineers can
83
83
 
84
84
  ---
85
85
 
86
- ## 2. Part I: Designing the Interface
86
+ ## 2. Installation
87
+
88
+ Install the package from PyPI using pip:
89
+
90
+ ```bash
91
+ pip install cocotbext-interface
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 3. Part I: Designing the Interface
87
97
 
88
98
  This section covers how to define the structure, timing, and access rules for a interface protocol.
89
99
 
90
- ### 2.1 The Interface Class
100
+ ### 3.1 The Interface Class
91
101
 
92
102
  To define a bus, inherit from the Interface class. Signals are now defined as class attributes using type hints, which
93
103
  determines whether they are required or optional during RTL binding.
@@ -112,7 +122,7 @@ class MyBus(Interface):
112
122
 
113
123
  These attribute names are used directly by the framework to search for matching signal names within the RTL hierarchy.
114
124
 
115
- ### 2.2 Defining Clocking Blocks
125
+ ### 3.2 Defining Clocking Blocks
116
126
 
117
127
  Use the `@clocking` decorator on an inner class. This defines the temporal behavior for synchronous signals.
118
128
 
@@ -121,7 +131,7 @@ Use the `@clocking` decorator on an inner class. This defines the temporal behav
121
131
  - `input`: Input skew (Sample delay). Can be a `Timer` or another `Trigger`.
122
132
  - `output`: Output skew (Drive delay).
123
133
 
124
- ### 2.3 Defining Modports
134
+ ### 3.3 Defining Modports
125
135
 
126
136
  Use the `@modport` decorator to group signals and clocking blocks for specific roles like `source`, `sink` or `monitor`.
127
137
 
@@ -130,12 +140,13 @@ Use the `@modport` decorator to group signals and clocking blocks for specific r
130
140
  - `Input`/`Output`/`InOut`: Signals accessible in this modport.
131
141
  - `Import`: Method names (APIs) exposed to this modport.
132
142
 
133
- ### 2.4 Complete Definition Example
143
+ ### 3.4 Complete Definition Example
134
144
 
135
145
  ```python
146
+ from typing import Callable
136
147
  from cocotb.handles import LogicArrayObject, LogicObject
137
148
  from cocotb.triggers import RisingEdge, FallingEdge, Timer
138
- from interface_framework import Interface, modport, clocking, Import, Input, Output
149
+ from cocotbext.interface import Interface, modport, clocking, Import, Input, Output
139
150
 
140
151
  class AxiStream(Interface):
141
152
  clk: LogicObject
@@ -175,11 +186,11 @@ class AxiStream(Interface):
175
186
 
176
187
  ---
177
188
 
178
- ## 3. Part II: Using the Interface
189
+ ## 4. Part II: Using the Interface
179
190
 
180
191
  This section covers how to connect the interface to the DUT and use it in a test.
181
192
 
182
- ### 3.1 Instantiation & Connection
193
+ ### 4.1 Instantiation & Connection
183
194
 
184
195
  Interfaces are connected to the RTL using named constructors. These methods automatically map your class attributes to
185
196
  the corresponding signals in the HDL hierarchy.
@@ -192,8 +203,7 @@ Use `from_entity` when the signal names in the RTL match your class attribute na
192
203
  level. This is a strict connection method that does not support overrides or pattern substitutions.
193
204
 
194
205
  ```python
195
- # Connects to dut.tdata and
196
- dut.rdy directly
206
+ # Connects to dut.data and dut.rdy directly
197
207
  bus = MyBus.from_entity(dut)
198
208
  ```
199
209
 
@@ -244,7 +254,8 @@ bus = MyBus.from_pattern(dut, pattern="/u_axi_[0-9]_%/")
244
254
  While `from_entity` is strict, `from_pattern` allows for flexibility when the RTL structure is non-standard:
245
255
 
246
256
  - **Keyword Overrides:** Pass a signal handle as a keyword argument to skip the pattern search for that specific
247
- attribute.Indexing: Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
257
+ attribute.
258
+ - **Indexing:** Use the `idx` argument to index into all signals discovered via the pattern (e.g.,
248
259
  `dut.u_axi_tdata[1]`).
249
260
 
250
261
  ```python
@@ -255,29 +266,38 @@ bus = MyBus.from_pattern(dut, pattern="u_axi_%", idx=1, rdy=dut.global_rdy)
255
266
  > [!TIP] To confirm your signals are correctly bound, you can print `print(bus)` or `print(f"{bus=}")` the bus object to
256
267
  > inspect the resolved RTL paths.
257
268
 
258
- ### 3.2 Synchronous Driving (Non-Blocking)
269
+ ### 4.2 Synchronous Driving (Non-Blocking)
259
270
 
260
271
  When using a modport's clocking block, driving a signal schedules an update for the next clock edge + output skew. It
261
272
  does not block the current coroutine.
262
273
 
263
274
  ```python
264
275
  # Drive data through the source modport
265
- bus.source.src_cb.tdata.value = 0xABCD
266
- bus.source.src_cb.tvalid.value = 1
276
+ bus.source.cb.tdata.value = 0xABCD
277
+ bus.source.cb.tvalid.value = 1
267
278
  ```
268
279
 
269
- ### 3.3 Synchronous Sampling (Blocking)
280
+ ### 4.3 Synchronous Sampling (Blocking)
270
281
 
271
282
  To read a signal synchronously, you **must** use `await ...capture()`. This ensures the simulation waits for the clock
272
283
  edge and the defined input skew before returning the value.
273
284
 
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
+
274
291
  ```python
275
292
  # Wait until sink is ready
276
- while await bus.source.src_cb.tready.capture() == 0:
277
- await bus.source.src_cb.wait() # Wait for 1 clock cycle
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)
278
298
  ```
279
299
 
280
- ### 3.4 Using Interface APIs
300
+ ### 4.4 Using Interface APIs
281
301
 
282
302
  Methods defined in the interface and imported in the modport can be called directly.
283
303
 
@@ -288,7 +308,7 @@ await bus.source.reset()
288
308
 
289
309
  ---
290
310
 
291
- ## 4. Technical Summary
311
+ ## 5. Technical Summary
292
312
 
293
313
  | SystemVerilog Concept | Framework Implementation | Behavior |
294
314
  | :-------------------- | :---------------------------------- | :------------------------------------- |
@@ -303,7 +323,7 @@ await bus.source.reset()
303
323
 
304
324
  ---
305
325
 
306
- ## 5. Best Practices
326
+ ## 6. Best Practices
307
327
 
308
328
  1. Define separate clocking blocks for Source and Sink roles to account for different signal access and physical skews.
309
329
  2. Don't give access to synchronous signals in the `modport`, this forces the user to use the clocking block.
@@ -32,7 +32,7 @@ from cocotb.triggers import RisingEdge, FallingEdge
32
32
 
33
33
  @pytest.fixture(autouse=True)
34
34
  def mock_cocotb_scheduler(monkeypatch):
35
- """Auto-patch cocotb.start_soon to avoid scheduler errors in tests.
35
+ """Auto-patch cocotb.start_soon and ReadOnlyManager to avoid scheduler errors in tests.
36
36
 
37
37
  Properly closes coroutines to avoid RuntimeWarning about unawaited coroutines.
38
38
  """
@@ -42,7 +42,11 @@ def mock_cocotb_scheduler(monkeypatch):
42
42
  if inspect.iscoroutine(coro):
43
43
  coro.close()
44
44
 
45
+ async def mock_wait():
46
+ pass
47
+
45
48
  monkeypatch.setattr("cocotb.start_soon", mock_start_soon)
49
+ monkeypatch.setattr("cocotbext.interface.interface.ReadOnlyManager.wait", mock_wait)
46
50
 
47
51
 
48
52
  # ============================================================================
@@ -121,6 +125,7 @@ def mock_hierarchy():
121
125
  hierarchy.add_signal("clk")
122
126
  hierarchy.add_signal("rst")
123
127
  hierarchy.add_signal("data") # Generic data signal
128
+ hierarchy.add_signal("write")
124
129
  hierarchy.add_signal("data_in")
125
130
  hierarchy.add_signal("data_out")
126
131
  hierarchy.add_signal("valid")
@@ -162,6 +167,24 @@ class InterfaceWithModport(Interface):
162
167
  pass
163
168
 
164
169
 
170
+ class InterfaceWithCollidingImport(Interface):
171
+ """Test a modport callable sharing its name with an HDL signal."""
172
+
173
+ clk: Output[object]
174
+ write: Input[object]
175
+
176
+ @clocking(clock="clk")
177
+ class cb:
178
+ write: Input[object]
179
+
180
+ @modport(clocking="cb")
181
+ class master:
182
+ write: Import[Callable]
183
+
184
+ def write(self) -> str:
185
+ return "method"
186
+
187
+
165
188
  class InterfaceWithClocking(Interface):
166
189
  """Test interface with clocking block."""
167
190
 
@@ -385,6 +408,13 @@ class TestModports:
385
408
  assert hasattr(iface, "send")
386
409
  assert hasattr(master, "send")
387
410
 
411
+ def test_modport_import_callable_wins_signal_name_collision(self, mock_hierarchy):
412
+ iface = InterfaceWithCollidingImport.from_entity(mock_hierarchy)
413
+
414
+ assert callable(iface.master.write)
415
+ assert iface.master.write() == "method"
416
+ assert iface.cb.write._handle is mock_hierarchy._children["write"]
417
+
388
418
  def test_modport_dont_contains_callable(self, mock_hierarchy):
389
419
  """Test that modport contains the mapped callable."""
390
420
  iface = InterfaceWithModport.from_entity(mock_hierarchy)
@@ -577,6 +607,24 @@ class TestClockedSignal:
577
607
 
578
608
  asyncio.run(inner())
579
609
 
610
+ def test_clocked_signal_capture_default(self, mock_hierarchy):
611
+ """Test capturing with a default value for unconnected signals."""
612
+
613
+ async def inner():
614
+ clk = mock_hierarchy._children["clk"]
615
+ # Signal with no handle
616
+ cs = ClockedSignal(None, clk, RisingEdge, is_input=True)
617
+
618
+ # Capture should return the default value
619
+ val = await cs.capture(default=123)
620
+ assert val == 123
621
+
622
+ # Capture without default should return None
623
+ val_none = await cs.capture()
624
+ assert val_none is None
625
+
626
+ asyncio.run(inner())
627
+
580
628
 
581
629
  # ============================================================================
582
630
  # Tests: Modport + Clocking Integration
@@ -1 +0,0 @@
1
- __version__ = "0.1.3"