cocotb 1.9.2__cp310-cp310-win_amd64.whl → 2.0.0rc2__cp310-cp310-win_amd64.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.

Potentially problematic release.


This version of cocotb might be problematic. Click here for more details.

Files changed (161) hide show
  1. cocotb/_ANSI.py +65 -0
  2. cocotb/__init__.py +81 -327
  3. cocotb/_base_triggers.py +515 -0
  4. cocotb/_bridge.py +186 -0
  5. cocotb/_decorators.py +515 -0
  6. cocotb/_deprecation.py +3 -3
  7. cocotb/_exceptions.py +7 -0
  8. cocotb/_extended_awaitables.py +419 -0
  9. cocotb/_gpi_triggers.py +385 -0
  10. cocotb/_init.py +301 -0
  11. cocotb/_outcomes.py +54 -0
  12. cocotb/_profiling.py +46 -0
  13. cocotb/_py_compat.py +114 -29
  14. cocotb/_scheduler.py +448 -0
  15. cocotb/_test.py +248 -0
  16. cocotb/_test_factory.py +312 -0
  17. cocotb/_test_functions.py +42 -0
  18. cocotb/_typing.py +7 -0
  19. cocotb/_utils.py +274 -0
  20. cocotb/_version.py +3 -7
  21. cocotb/_xunit_reporter.py +66 -0
  22. cocotb/clock.py +353 -108
  23. cocotb/debug.py +24 -0
  24. cocotb/handle.py +1370 -793
  25. cocotb/libs/cocotb.dll +0 -0
  26. cocotb/libs/cocotb.exp +0 -0
  27. cocotb/libs/cocotb.lib +0 -0
  28. cocotb/libs/cocotbfli_modelsim.dll +0 -0
  29. cocotb/libs/cocotbfli_modelsim.exp +0 -0
  30. cocotb/libs/cocotbfli_modelsim.lib +0 -0
  31. cocotb/libs/cocotbutils.dll +0 -0
  32. cocotb/libs/cocotbutils.exp +0 -0
  33. cocotb/libs/cocotbutils.lib +0 -0
  34. cocotb/libs/cocotbvhpi_aldec.dll +0 -0
  35. cocotb/libs/cocotbvhpi_aldec.exp +0 -0
  36. cocotb/libs/cocotbvhpi_aldec.lib +0 -0
  37. cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
  38. cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
  39. cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
  40. cocotb/libs/cocotbvpi_aldec.dll +0 -0
  41. cocotb/libs/cocotbvpi_aldec.exp +0 -0
  42. cocotb/libs/cocotbvpi_aldec.lib +0 -0
  43. cocotb/libs/cocotbvpi_ghdl.dll +0 -0
  44. cocotb/libs/cocotbvpi_ghdl.exp +0 -0
  45. cocotb/libs/cocotbvpi_ghdl.lib +0 -0
  46. cocotb/libs/cocotbvpi_icarus.exp +0 -0
  47. cocotb/libs/cocotbvpi_icarus.lib +0 -0
  48. cocotb/libs/cocotbvpi_icarus.vpl +0 -0
  49. cocotb/libs/cocotbvpi_modelsim.dll +0 -0
  50. cocotb/libs/cocotbvpi_modelsim.exp +0 -0
  51. cocotb/libs/cocotbvpi_modelsim.lib +0 -0
  52. cocotb/libs/embed.dll +0 -0
  53. cocotb/libs/embed.exp +0 -0
  54. cocotb/libs/embed.lib +0 -0
  55. cocotb/libs/gpi.dll +0 -0
  56. cocotb/libs/gpi.exp +0 -0
  57. cocotb/libs/gpi.lib +0 -0
  58. cocotb/libs/gpilog.dll +0 -0
  59. cocotb/libs/gpilog.exp +0 -0
  60. cocotb/libs/gpilog.lib +0 -0
  61. cocotb/libs/pygpilog.dll +0 -0
  62. cocotb/libs/pygpilog.exp +0 -0
  63. cocotb/libs/pygpilog.lib +0 -0
  64. cocotb/logging.py +424 -0
  65. cocotb/queue.py +103 -57
  66. cocotb/regression.py +680 -717
  67. cocotb/result.py +17 -188
  68. cocotb/share/def/aldec.exp +0 -0
  69. cocotb/share/def/aldec.lib +0 -0
  70. cocotb/share/def/ghdl.exp +0 -0
  71. cocotb/share/def/ghdl.lib +0 -0
  72. cocotb/share/def/icarus.exp +0 -0
  73. cocotb/share/def/icarus.lib +0 -0
  74. cocotb/share/def/modelsim.def +1 -0
  75. cocotb/share/def/modelsim.exp +0 -0
  76. cocotb/share/def/modelsim.lib +0 -0
  77. cocotb/share/include/cocotb_utils.h +9 -32
  78. cocotb/share/include/embed.h +7 -30
  79. cocotb/share/include/gpi.h +331 -137
  80. cocotb/share/include/gpi_logging.h +221 -142
  81. cocotb/share/include/py_gpi_logging.h +8 -5
  82. cocotb/share/include/vpi_user_ext.h +4 -26
  83. cocotb/share/lib/verilator/verilator.cpp +80 -67
  84. cocotb/simtime.py +230 -0
  85. cocotb/simulator.cp310-win_amd64.exp +0 -0
  86. cocotb/simulator.cp310-win_amd64.lib +0 -0
  87. cocotb/simulator.cp310-win_amd64.pyd +0 -0
  88. cocotb/simulator.pyi +107 -0
  89. cocotb/task.py +478 -213
  90. cocotb/triggers.py +55 -1092
  91. cocotb/types/__init__.py +28 -47
  92. cocotb/types/_abstract_array.py +151 -0
  93. cocotb/types/_array.py +295 -0
  94. cocotb/types/_indexing.py +17 -0
  95. cocotb/types/_logic.py +333 -0
  96. cocotb/types/_logic_array.py +868 -0
  97. cocotb/types/{range.py → _range.py} +47 -48
  98. cocotb/types/_resolve.py +76 -0
  99. cocotb/utils.py +58 -646
  100. cocotb-2.0.0rc2.dist-info/METADATA +60 -0
  101. cocotb-2.0.0rc2.dist-info/RECORD +146 -0
  102. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info}/WHEEL +1 -1
  103. cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
  104. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info/licenses}/LICENSE +1 -0
  105. {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info}/top_level.txt +1 -0
  106. cocotb_tools/__init__.py +0 -0
  107. cocotb_tools/_coverage.py +33 -0
  108. cocotb_tools/_vendor/__init__.py +3 -0
  109. cocotb_tools/check_results.py +65 -0
  110. cocotb_tools/combine_results.py +152 -0
  111. cocotb_tools/config.py +241 -0
  112. {cocotb → cocotb_tools}/ipython_support.py +29 -22
  113. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  114. {cocotb/share → cocotb_tools}/makefiles/Makefile.inc +77 -55
  115. {cocotb/share → cocotb_tools}/makefiles/Makefile.sim +16 -33
  116. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.activehdl +9 -16
  117. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  118. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  119. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.ghdl +13 -42
  120. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  121. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  122. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  123. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  124. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  125. cocotb/share/makefiles/simulators/Makefile.questa → cocotb_tools/makefiles/simulators/Makefile.questa-compat +26 -54
  126. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  127. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.riviera +17 -56
  128. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  129. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.verilator +15 -22
  130. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.xcelium +20 -52
  131. cocotb_tools/py.typed +0 -0
  132. cocotb_tools/runner.py +1868 -0
  133. cocotb/_sim_versions.py → cocotb_tools/sim_versions.py +16 -21
  134. pygpi/entry.py +34 -18
  135. pygpi/py.typed +0 -0
  136. cocotb/ANSI.py +0 -92
  137. cocotb/binary.py +0 -858
  138. cocotb/config.py +0 -289
  139. cocotb/decorators.py +0 -332
  140. cocotb/log.py +0 -303
  141. cocotb/memdebug.py +0 -35
  142. cocotb/outcomes.py +0 -56
  143. cocotb/runner.py +0 -1400
  144. cocotb/scheduler.py +0 -1099
  145. cocotb/share/makefiles/Makefile.deprecations +0 -12
  146. cocotb/share/makefiles/simulators/Makefile.cvc +0 -94
  147. cocotb/share/makefiles/simulators/Makefile.icarus +0 -111
  148. cocotb/share/makefiles/simulators/Makefile.ius +0 -125
  149. cocotb/share/makefiles/simulators/Makefile.modelsim +0 -32
  150. cocotb/share/makefiles/simulators/Makefile.nvc +0 -64
  151. cocotb/share/makefiles/simulators/Makefile.vcs +0 -98
  152. cocotb/types/array.py +0 -309
  153. cocotb/types/logic.py +0 -292
  154. cocotb/types/logic_array.py +0 -298
  155. cocotb/wavedrom.py +0 -199
  156. cocotb/xunit_reporter.py +0 -80
  157. cocotb-1.9.2.dist-info/METADATA +0 -168
  158. cocotb-1.9.2.dist-info/RECORD +0 -121
  159. cocotb-1.9.2.dist-info/entry_points.txt +0 -2
  160. /cocotb/{_vendor/__init__.py → py.typed} +0 -0
  161. {cocotb → cocotb_tools}/_vendor/distutils_version.py +0 -0
@@ -0,0 +1,515 @@
1
+ # Copyright cocotb contributors
2
+ # Copyright (c) 2013 Potential Ventures Ltd
3
+ # Copyright (c) 2013 SolarFlare Communications Inc
4
+ # Licensed under the Revised BSD License, see LICENSE for details.
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
+
7
+ """A collection of triggers which a testbench can :keyword:`await`."""
8
+
9
+ import logging
10
+ import warnings
11
+ from typing import (
12
+ AsyncContextManager,
13
+ Callable,
14
+ Generator,
15
+ List,
16
+ Optional,
17
+ Union,
18
+ )
19
+
20
+ from cocotb._deprecation import deprecated
21
+ from cocotb._py_compat import Self, cached_property
22
+ from cocotb._utils import pointer_str
23
+
24
+
25
+ class Trigger:
26
+ """A future event that a Task can wait upon."""
27
+
28
+ def __init__(self) -> None:
29
+ self._primed = False
30
+
31
+ @cached_property
32
+ def _log(self) -> logging.Logger:
33
+ return logging.getLogger(f"cocotb.{type(self).__qualname__}.0x{id(self):x}")
34
+
35
+ def _prime(self, callback: Callable[["Self"], None]) -> None:
36
+ """Set a callback to be invoked when the trigger fires.
37
+
38
+ The callback will be invoked with a single argument, `self`.
39
+
40
+ Sub-classes must override this, but should end by calling the base class
41
+ method.
42
+
43
+ .. warning::
44
+ Do not call this directly within a :term:`task`. It is intended to be used
45
+ only by the scheduler.
46
+ """
47
+ # Set _primed so the trigger can test if it's already been primed and behave appropriately.
48
+ self._primed = True
49
+
50
+ def _unprime(self) -> None:
51
+ """Remove the callback, and perform cleanup if necessary.
52
+
53
+ After being un-primed, a Trigger may be re-primed again in the future.
54
+ Calling `_unprime` multiple times is allowed, subsequent calls should be
55
+ a no-op.
56
+
57
+ Sub-classes may override this, but should end by calling the base class
58
+ method.
59
+
60
+ .. warning::
61
+ Do not call this directly within a :term:`task`. It is intended to be used
62
+ only by the scheduler.
63
+ """
64
+ self._cleanup()
65
+
66
+ def _cleanup(self) -> None:
67
+ # Clear _primed so this Trigger can be re-primed.
68
+ self._primed = False
69
+
70
+ def __await__(self) -> Generator["Self", None, "Self"]:
71
+ yield self
72
+ return self
73
+
74
+
75
+ class _Event(Trigger):
76
+ """Unique instance used by the Event object.
77
+
78
+ One created for each attempt to wait on the event so that the scheduler
79
+ can maintain a unique mapping of triggers to tasks.
80
+ """
81
+
82
+ _callback: Callable[["_Event"], None]
83
+
84
+ def __init__(self, parent: "Event") -> None:
85
+ super().__init__()
86
+ self._parent = parent
87
+
88
+ def _prime(self, callback: Callable[["_Event"], None]) -> None:
89
+ if self._primed:
90
+ return
91
+ if self._parent.is_set():
92
+ # If the event is already set, we need to call the callback
93
+ # immediately, so we don't need to wait for the scheduler.
94
+ callback(self)
95
+ return
96
+ self._callback = callback
97
+ return super()._prime(callback)
98
+
99
+ def _unprime(self) -> None:
100
+ if not self._primed:
101
+ return
102
+ return super()._unprime()
103
+
104
+ def _set(self) -> None:
105
+ if self._primed:
106
+ self._callback(self)
107
+
108
+ def __repr__(self) -> str:
109
+ return f"<{self._parent!r}.wait() at {pointer_str(self)}>"
110
+
111
+
112
+ class Event:
113
+ r"""A way to signal an event across :class:`~cocotb.task.Task`\ s.
114
+
115
+ :keyword:`await`\ ing the result of :meth:`wait()` will block the :keyword:`await`\ ing :class:`~cocotb.task.Task`
116
+ until :meth:`set` is called.
117
+
118
+ Args:
119
+ name: Name for the Event.
120
+
121
+ Usage:
122
+ .. code-block:: python
123
+
124
+ e = Event()
125
+
126
+
127
+ async def task1():
128
+ await e.wait()
129
+ print("resuming!")
130
+
131
+
132
+ cocotb.start_soon(task1())
133
+ # do stuff
134
+ e.set()
135
+ await NullTrigger() # allows task1 to execute
136
+ # resuming!
137
+
138
+ .. versionremoved:: 2.0
139
+
140
+ Removed the undocumented *data* attribute and argument to :meth:`set`,
141
+ and the *name* attribute and argument to the constructor.
142
+ """
143
+
144
+ def __init__(self, name: Optional[str] = None) -> None:
145
+ self._event: _Event = _Event(self)
146
+ self._name: Union[str, None] = None
147
+ if name is not None:
148
+ warnings.warn(
149
+ "The 'name' argument will be removed in a future release.",
150
+ DeprecationWarning,
151
+ stacklevel=2,
152
+ )
153
+ self.name = name
154
+ self._fired: bool = False
155
+ self._data: object = None
156
+
157
+ @property
158
+ @deprecated("The 'name' field will be removed in a future release.")
159
+ def name(self) -> Union[str, None]:
160
+ """Name of the Event.
161
+
162
+ .. deprecated:: 2.0
163
+ The *name* field will be removed in a future release.
164
+ """
165
+ return self._name
166
+
167
+ @name.setter
168
+ @deprecated("The 'name' field will be removed in a future release.")
169
+ def name(self, new_name: Union[str, None]) -> None:
170
+ self._name = new_name
171
+
172
+ @property
173
+ @deprecated("The data field will be removed in a future release.")
174
+ def data(self) -> object:
175
+ """The data associated with the Event.
176
+
177
+ .. deprecated:: 2.0
178
+ The data field will be removed in a future release.
179
+ Use a separate variable to store the data instead.
180
+ """
181
+ return self._data
182
+
183
+ @data.setter
184
+ @deprecated("The data field will be removed in a future release.")
185
+ def data(self, new_data: object) -> None:
186
+ self._data = new_data
187
+
188
+ def set(self, data: Optional[object] = None) -> None:
189
+ """Set the Event and unblock all Tasks blocked on this Event."""
190
+ self._fired = True
191
+ if data is not None:
192
+ warnings.warn(
193
+ "The data field will be removed in a future release.",
194
+ DeprecationWarning,
195
+ stacklevel=2,
196
+ )
197
+ self._data = data
198
+ self._event._set()
199
+
200
+ def wait(self) -> Trigger:
201
+ """Block the current Task until the Event is set.
202
+
203
+ If the event has already been set, the trigger will fire immediately.
204
+
205
+ To set the Event call :meth:`set`.
206
+ To reset the Event (and enable the use of :meth:`wait` again),
207
+ call :meth:`clear`.
208
+ """
209
+ return self._event
210
+
211
+ def clear(self) -> None:
212
+ """Clear this event that has been set.
213
+
214
+ Subsequent calls to :meth:`~cocotb.triggers.Event.wait` will block until
215
+ :meth:`~cocotb.triggers.Event.set` is called again.
216
+ """
217
+ self._fired = False
218
+
219
+ def is_set(self) -> bool:
220
+ """Return ``True`` if event has been set."""
221
+ return self._fired
222
+
223
+ def __repr__(self) -> str:
224
+ if self._name is None:
225
+ fmt = "<{0} at {2}>"
226
+ else:
227
+ fmt = "<{0} for {1} at {2}>"
228
+ return fmt.format(type(self).__qualname__, self._name, pointer_str(self))
229
+
230
+
231
+ class _InternalEvent(Trigger):
232
+ """Event used internally for triggers that need cross-:class:`~cocotb.task.Task` synchronization.
233
+
234
+ This Event can only be waited on once, by a single :class:`~cocotb.task.Task`.
235
+
236
+ Provides transparent :func`repr` pass-through to the :class:`Trigger` using this event,
237
+ providing a better debugging experience.
238
+ """
239
+
240
+ def __init__(self, parent: object) -> None:
241
+ super().__init__()
242
+ self._parent = parent
243
+ self._callback: Optional[Callable[[_InternalEvent], None]] = None
244
+ self.fired: bool = False
245
+
246
+ def _prime(self, callback: Callable[["_InternalEvent"], None]) -> None:
247
+ if self._primed:
248
+ raise RuntimeError("This Trigger may only be awaited once")
249
+ self._callback = callback
250
+ super()._prime(callback)
251
+ if self.fired:
252
+ self._callback(self)
253
+
254
+ def _cleanup(self) -> None:
255
+ # Don't clear _primed so a second call to _prime() fails.
256
+ pass
257
+
258
+ def set(self) -> None:
259
+ """Wake up coroutine blocked on this event."""
260
+ self.fired = True
261
+
262
+ if self._callback is not None:
263
+ self._callback(self)
264
+
265
+ def is_set(self) -> bool:
266
+ """Return true if event has been set."""
267
+ return self.fired
268
+
269
+ def __await__(
270
+ self,
271
+ ) -> Generator["Self", None, "Self"]:
272
+ if self._primed:
273
+ raise RuntimeError("Only one Task may await this Trigger")
274
+ yield self
275
+ return self
276
+
277
+ def __repr__(self) -> str:
278
+ return repr(self._parent)
279
+
280
+
281
+ class _Lock(Trigger):
282
+ """Unique instance used by the Lock object.
283
+
284
+ One created for each attempt to acquire the Lock so that the scheduler
285
+ can maintain a unique mapping of triggers to tasks.
286
+ """
287
+
288
+ def __init__(self, parent: "Lock") -> None:
289
+ super().__init__()
290
+ self._parent = parent
291
+
292
+ def _prime(self, callback: Callable[["Self"], None]) -> None:
293
+ if self._primed:
294
+ raise RuntimeError(
295
+ "Lock.acquire() result can only be used by one task at a time"
296
+ )
297
+ self._callback = callback
298
+ self._parent._prime_lock(self)
299
+ return super()._prime(callback)
300
+
301
+ def _unprime(self) -> None:
302
+ if not self._primed:
303
+ return
304
+ self._parent._unprime_lock(self)
305
+ return super()._unprime()
306
+
307
+ def __repr__(self) -> str:
308
+ return f"<{self._parent!r}.acquire() at {pointer_str(self)}>"
309
+
310
+
311
+ class Lock(AsyncContextManager[None]):
312
+ """A mutual exclusion lock.
313
+
314
+ Guarantees fair scheduling.
315
+ Lock acquisition is given in order of attempted lock acquisition.
316
+
317
+ Usage:
318
+ By directly calling :meth:`acquire` and :meth:`release`.
319
+
320
+ .. code-block:: python
321
+
322
+ lock = Lock()
323
+ ...
324
+ await lock.acquire()
325
+ try:
326
+ # do some stuff
327
+ ...
328
+ finally:
329
+ lock.release()
330
+
331
+ Or...
332
+
333
+ .. code-block:: python
334
+
335
+ async with Lock():
336
+ # do some stuff
337
+ ...
338
+
339
+ .. versionchanged:: 1.4
340
+
341
+ The lock can be used as an asynchronous context manager in an
342
+ :keyword:`async with` statement
343
+ """
344
+
345
+ def __init__(self, name: Optional[str] = None) -> None:
346
+ self._pending_primed: List[_Lock] = []
347
+ self._name: Union[str, None] = None
348
+ if name is not None:
349
+ warnings.warn(
350
+ "The 'name' argument will be removed in a future release.",
351
+ DeprecationWarning,
352
+ stacklevel=2,
353
+ )
354
+ self._name = name
355
+ self._locked: bool = False
356
+
357
+ @property
358
+ @deprecated("The 'name' field will be removed in a future release.")
359
+ def name(self) -> Union[str, None]:
360
+ """Name of the Lock.
361
+
362
+ .. deprecated:: 2.0
363
+ The *name* field will be removed in a future release.
364
+ """
365
+ return self._name
366
+
367
+ @name.setter
368
+ @deprecated("The 'name' field will be removed in a future release.")
369
+ def name(self, new_name: Union[str, None]) -> None:
370
+ self._name = new_name
371
+
372
+ def locked(self) -> bool:
373
+ """Return ``True`` if the lock has been acquired.
374
+
375
+ .. versionchanged:: 2.0
376
+ This is now a method to match :meth:`asyncio.Lock.locked`, rather than an attribute.
377
+ """
378
+ return self._locked
379
+
380
+ def _acquire_and_fire(self, lock: _Lock) -> None:
381
+ self._locked = True
382
+ lock._callback(lock)
383
+
384
+ def _prime_lock(self, lock: _Lock) -> None:
385
+ if not self._locked:
386
+ self._acquire_and_fire(lock)
387
+ else:
388
+ self._pending_primed.append(lock)
389
+
390
+ def _unprime_lock(self, lock: _Lock) -> None:
391
+ if lock in self._pending_primed:
392
+ self._pending_primed.remove(lock)
393
+
394
+ def acquire(self) -> Trigger:
395
+ """Produce a trigger which fires when the lock is acquired."""
396
+ trig = _Lock(self)
397
+ return trig
398
+
399
+ def release(self) -> None:
400
+ """Release the lock."""
401
+ if not self._locked:
402
+ raise RuntimeError(f"Attempt to release an unacquired Lock {self!s}")
403
+
404
+ self._locked = False
405
+
406
+ # nobody waiting for this lock
407
+ if not self._pending_primed:
408
+ return
409
+
410
+ lock = self._pending_primed.pop(0)
411
+ self._acquire_and_fire(lock)
412
+
413
+ def __repr__(self) -> str:
414
+ if self._name is None:
415
+ fmt = "<{0} [{2} waiting] at {3}>"
416
+ else:
417
+ fmt = "<{0} for {1} [{2} waiting] at {3}>"
418
+ return fmt.format(
419
+ type(self).__qualname__,
420
+ self._name,
421
+ len(self._pending_primed),
422
+ pointer_str(self),
423
+ )
424
+
425
+ async def __aenter__(self) -> None:
426
+ await self.acquire()
427
+
428
+ async def __aexit__(self, *args: object) -> None:
429
+ self.release()
430
+
431
+
432
+ class NullTrigger(Trigger):
433
+ """Trigger that fires immediately.
434
+
435
+ Mostly useful when building or using higher-order functions which need to take or return Triggers.
436
+
437
+ The scheduling order of the Task awaiting this Trigger with respect to any other Task is not deterministic
438
+ and should generally not be relied upon.
439
+ Instead of using this Trigger to push the Task until "after" another Task has run,
440
+ use other synchronization techniques, such as using an :class:`.Event`.
441
+
442
+ **Do not** do this:
443
+
444
+ .. code-block:: python
445
+ :class: removed
446
+
447
+ transaction_data = None
448
+
449
+
450
+ def monitor(dut):
451
+ while dut.valid.value != 1 and dut.ready.value != 1:
452
+ await RisingEdge(dut.clk)
453
+ transaction_data = dut.data.value
454
+
455
+
456
+ def use_transaction(dut):
457
+ while True:
458
+ await RisingEdge(dut.clk)
459
+ # We need the NullTrigger here because both Tasks react to RisingEdge,
460
+ # but there's no guarantee about which Task is run first,
461
+ # so we need to force this one to run "later" using NullTrigger.
462
+ await NullTrigger()
463
+ if transaction_data is not None:
464
+ process(transaction_data)
465
+
466
+
467
+ use_task = cocotb.start_soon(use_transaction(cocotb.top))
468
+ monitor_task = cocotb.start_soon(monitor(cocotb.top))
469
+
470
+ Instead use an :class:`!Event` to explicitly synchronize the two Tasks, like so:
471
+
472
+ .. code-block:: python
473
+ :class: new
474
+
475
+ transaction_data = None
476
+ transaction_event = Event()
477
+
478
+
479
+ def monitor(dut):
480
+ while dut.valid.value != 1 and dut.ready.value != 1:
481
+ await RisingEdge(dut.clk)
482
+ transaction_data = dut.data.value
483
+ transaction_event.set()
484
+
485
+
486
+ def use_transaction(dut):
487
+ # Now we don't need the NullTrigger.
488
+ # This Task will wake up *strictly* after `monitor_task` sets the transaction.
489
+ await transaction_event.wait()
490
+ process(transaction_data)
491
+
492
+
493
+ use_task = cocotb.start_soon(use_transaction(cocotb.top))
494
+ monitor_task = cocotb.start_soon(monitor(cocotb.top))
495
+
496
+ .. versionremoved:: 2.0
497
+ The *outcome* parameter was removed. There is no alternative.
498
+ """
499
+
500
+ def __init__(self, name: Optional[str] = None) -> None:
501
+ super().__init__()
502
+ self.name = name
503
+
504
+ def _prime(self, callback: Callable[["Self"], None]) -> None:
505
+ if self._primed:
506
+ return
507
+ callback(self)
508
+ return super()._prime(callback)
509
+
510
+ def __repr__(self) -> str:
511
+ if self.name is None:
512
+ fmt = "<{0} at {2}>"
513
+ else:
514
+ fmt = "<{0} for {1} at {2}>"
515
+ return fmt.format(type(self).__qualname__, self.name, pointer_str(self))
cocotb/_bridge.py ADDED
@@ -0,0 +1,186 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ import functools
5
+ import logging
6
+ import threading
7
+ from enum import IntEnum
8
+ from typing import (
9
+ TYPE_CHECKING,
10
+ Callable,
11
+ Coroutine,
12
+ Generic,
13
+ TypeVar,
14
+ Union,
15
+ )
16
+
17
+ import cocotb
18
+ from cocotb import debug
19
+ from cocotb._base_triggers import Event, Trigger
20
+ from cocotb._exceptions import InternalError
21
+ from cocotb._py_compat import ParamSpec
22
+
23
+ if TYPE_CHECKING:
24
+ from cocotb._outcomes import Outcome
25
+
26
+ P = ParamSpec("P")
27
+
28
+ Result = TypeVar("Result")
29
+
30
+
31
+ def resume(
32
+ func: "Callable[P, Coroutine[Trigger, None, Result]]",
33
+ ) -> "Callable[P, Result]":
34
+ """Converts a coroutine function into a blocking function.
35
+
36
+ This allows a :term:`coroutine function` that awaits cocotb triggers to be
37
+ called from a :term:`blocking function` converted by :func:`.bridge`.
38
+ This completes the bridge through non-:keyword:`async` code.
39
+
40
+ When a converted coroutine function is called the current function blocks until the
41
+ converted function exits.
42
+
43
+ Results of the converted function are returned from the function call.
44
+
45
+ Args:
46
+ func: The :term:`coroutine function` to convert into a :term:`blocking function`.
47
+
48
+ Returns:
49
+ *func* as a :term:`blocking function`.
50
+
51
+ Raises:
52
+ RuntimeError:
53
+ If the function that is returned is subsequently called from a
54
+ thread that was not started with :class:`.bridge`.
55
+
56
+ .. versionchanged:: 2.0
57
+ Renamed from ``function``.
58
+ No longer implemented as a type.
59
+ The ``log`` attribute is no longer available.
60
+ """
61
+
62
+ @functools.wraps(func)
63
+ def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> Result:
64
+ return cocotb._scheduler_inst._queue_function(func(*args, **kwargs))
65
+
66
+ return wrapper
67
+
68
+
69
+ def bridge(
70
+ func: "Callable[P, Result]",
71
+ ) -> "Callable[P, Coroutine[Trigger, None, Result]]":
72
+ r"""Converts a blocking function into a coroutine function.
73
+
74
+ This function converts a :term:`blocking function` into a :term:`coroutine function`
75
+ with the expectation that the function being converted is intended to call a
76
+ :func:`.resume` converted function. This creates a bridge through
77
+ non-:keyword:`async` code for code wanting to eventually :keyword:`await` on cocotb
78
+ triggers.
79
+
80
+ When a converted function call is used in an :keyword:`await` statement, the current
81
+ Task blocks until the converted function finishes.
82
+
83
+ Results of the converted function are returned from the :keyword:`await` expression.
84
+
85
+ .. note::
86
+ Bridge threads *must* either finish or block on a :func:`.resume`
87
+ converted function before control is given back to the simulator.
88
+ This is done to prevent any code from executing in parallel with the simulation.
89
+
90
+ Args:
91
+ func: The :term:`blocking function` to convert into a :term:`coroutine function`.
92
+
93
+ Returns:
94
+ *func* as a :term:`coroutine function`.
95
+
96
+ .. versionchanged:: 2.0
97
+ Renamed from ``external``.
98
+ No longer implemented as a type.
99
+ The ``log`` attribute is no longer available.
100
+ """
101
+
102
+ @functools.wraps(func)
103
+ def wrapper(
104
+ *args: "P.args", **kwargs: "P.kwargs"
105
+ ) -> Coroutine[Trigger, None, Result]:
106
+ return cocotb._scheduler_inst._run_in_executor(func, *args, **kwargs)
107
+
108
+ return wrapper
109
+
110
+
111
+ class external_state(IntEnum):
112
+ INIT = 0
113
+ RUNNING = 1
114
+ PAUSED = 2
115
+ EXITED = 3
116
+
117
+
118
+ class external_waiter(Generic[Result]):
119
+ def __init__(self) -> None:
120
+ self._outcome: Union[Outcome[Result], None] = None
121
+ self.thread: threading.Thread
122
+ self.event = Event()
123
+ self.state = external_state.INIT
124
+ self.cond = threading.Condition()
125
+ self._log = logging.getLogger(f"cocotb.bridge.0x{id(self):x}")
126
+
127
+ @property
128
+ def result(self) -> Result:
129
+ if self._outcome is None:
130
+ raise InternalError("Got result of external before it finished")
131
+ return self._outcome.get()
132
+
133
+ def _propagate_state(self, new_state: external_state) -> None:
134
+ with self.cond:
135
+ if debug.debug:
136
+ self._log.debug(
137
+ f"Changing state from {self.state} -> {new_state} from {threading.current_thread()}"
138
+ )
139
+ self.state = new_state
140
+ self.cond.notify()
141
+
142
+ def thread_done(self) -> None:
143
+ if debug.debug:
144
+ self._log.debug(f"Thread finished from {threading.current_thread()}")
145
+ self._propagate_state(external_state.EXITED)
146
+
147
+ def thread_suspend(self) -> None:
148
+ self._propagate_state(external_state.PAUSED)
149
+
150
+ def thread_start(self) -> None:
151
+ if self.state > external_state.INIT:
152
+ return
153
+
154
+ if not self.thread.is_alive():
155
+ self._propagate_state(external_state.RUNNING)
156
+ self.thread.start()
157
+
158
+ def thread_resume(self) -> None:
159
+ self._propagate_state(external_state.RUNNING)
160
+
161
+ def thread_wait(self) -> external_state:
162
+ if debug.debug:
163
+ self._log.debug(
164
+ f"Waiting for the condition lock {threading.current_thread()}"
165
+ )
166
+
167
+ with self.cond:
168
+ while self.state == external_state.RUNNING:
169
+ self.cond.wait()
170
+
171
+ if debug.debug:
172
+ if self.state == external_state.EXITED:
173
+ self._log.debug(
174
+ f"Thread {self.thread} has exited from {threading.current_thread()}"
175
+ )
176
+ elif self.state == external_state.PAUSED:
177
+ self._log.debug(
178
+ f"Thread {self.thread} has called yield from {threading.current_thread()}"
179
+ )
180
+
181
+ if self.state == external_state.INIT:
182
+ raise Exception(
183
+ f"Thread {self.thread} state was not allowed from {threading.current_thread()}"
184
+ )
185
+
186
+ return self.state