cocotb 2.0.0rc2__cp310-cp310-macosx_11_0_arm64.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.
- cocotb/_ANSI.py +65 -0
- cocotb/__init__.py +125 -0
- cocotb/_base_triggers.py +515 -0
- cocotb/_bridge.py +186 -0
- cocotb/_decorators.py +515 -0
- cocotb/_deprecation.py +36 -0
- cocotb/_exceptions.py +7 -0
- cocotb/_extended_awaitables.py +419 -0
- cocotb/_gpi_triggers.py +385 -0
- cocotb/_init.py +301 -0
- cocotb/_outcomes.py +54 -0
- cocotb/_profiling.py +46 -0
- cocotb/_py_compat.py +148 -0
- cocotb/_scheduler.py +448 -0
- cocotb/_test.py +248 -0
- cocotb/_test_factory.py +312 -0
- cocotb/_test_functions.py +42 -0
- cocotb/_typing.py +7 -0
- cocotb/_utils.py +274 -0
- cocotb/_version.py +4 -0
- cocotb/_xunit_reporter.py +66 -0
- cocotb/clock.py +419 -0
- cocotb/debug.py +24 -0
- cocotb/handle.py +1752 -0
- cocotb/libs/libcocotb.so +0 -0
- cocotb/libs/libcocotbfli_modelsim.so +0 -0
- cocotb/libs/libcocotbutils.so +0 -0
- cocotb/libs/libcocotbvhpi_aldec.so +0 -0
- cocotb/libs/libcocotbvhpi_ius.so +0 -0
- cocotb/libs/libcocotbvhpi_modelsim.so +0 -0
- cocotb/libs/libcocotbvhpi_nvc.so +0 -0
- cocotb/libs/libcocotbvpi_aldec.so +0 -0
- cocotb/libs/libcocotbvpi_dsim.so +0 -0
- cocotb/libs/libcocotbvpi_ghdl.so +0 -0
- cocotb/libs/libcocotbvpi_icarus.vpl +0 -0
- cocotb/libs/libcocotbvpi_ius.so +0 -0
- cocotb/libs/libcocotbvpi_modelsim.so +0 -0
- cocotb/libs/libcocotbvpi_vcs.so +0 -0
- cocotb/libs/libcocotbvpi_verilator.so +0 -0
- cocotb/libs/libembed.so +0 -0
- cocotb/libs/libgpi.so +0 -0
- cocotb/libs/libgpilog.so +0 -0
- cocotb/libs/libpygpilog.so +0 -0
- cocotb/logging.py +424 -0
- cocotb/py.typed +0 -0
- cocotb/queue.py +225 -0
- cocotb/regression.py +896 -0
- cocotb/result.py +38 -0
- cocotb/share/def/.gitignore +2 -0
- cocotb/share/def/README.md +4 -0
- cocotb/share/def/aldec.def +61 -0
- cocotb/share/def/ghdl.def +43 -0
- cocotb/share/def/icarus.def +43 -0
- cocotb/share/def/modelsim.def +138 -0
- cocotb/share/include/cocotb_utils.h +70 -0
- cocotb/share/include/embed.h +33 -0
- cocotb/share/include/exports.h +20 -0
- cocotb/share/include/gpi.h +459 -0
- cocotb/share/include/gpi_logging.h +291 -0
- cocotb/share/include/py_gpi_logging.h +33 -0
- cocotb/share/include/vhpi_user_ext.h +26 -0
- cocotb/share/include/vpi_user_ext.h +33 -0
- cocotb/share/lib/verilator/verilator.cpp +209 -0
- cocotb/simtime.py +230 -0
- cocotb/simulator.cpython-310-darwin.so +0 -0
- cocotb/simulator.pyi +107 -0
- cocotb/task.py +590 -0
- cocotb/triggers.py +67 -0
- cocotb/types/__init__.py +31 -0
- cocotb/types/_abstract_array.py +151 -0
- cocotb/types/_array.py +295 -0
- cocotb/types/_indexing.py +17 -0
- cocotb/types/_logic.py +333 -0
- cocotb/types/_logic_array.py +868 -0
- cocotb/types/_range.py +197 -0
- cocotb/types/_resolve.py +76 -0
- cocotb/utils.py +110 -0
- cocotb-2.0.0rc2.dist-info/METADATA +60 -0
- cocotb-2.0.0rc2.dist-info/RECORD +115 -0
- cocotb-2.0.0rc2.dist-info/WHEEL +5 -0
- cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
- cocotb-2.0.0rc2.dist-info/licenses/LICENSE +29 -0
- cocotb-2.0.0rc2.dist-info/top_level.txt +23 -0
- cocotb_tools/__init__.py +0 -0
- cocotb_tools/_coverage.py +33 -0
- cocotb_tools/_vendor/__init__.py +3 -0
- cocotb_tools/_vendor/distutils_version.py +346 -0
- cocotb_tools/check_results.py +65 -0
- cocotb_tools/combine_results.py +152 -0
- cocotb_tools/config.py +241 -0
- cocotb_tools/ipython_support.py +99 -0
- cocotb_tools/makefiles/Makefile.deprecations +27 -0
- cocotb_tools/makefiles/Makefile.inc +198 -0
- cocotb_tools/makefiles/Makefile.sim +96 -0
- cocotb_tools/makefiles/simulators/Makefile.activehdl +72 -0
- cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
- cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
- cocotb_tools/makefiles/simulators/Makefile.ghdl +84 -0
- cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
- cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
- cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
- cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
- cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
- cocotb_tools/makefiles/simulators/Makefile.questa-compat +143 -0
- cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
- cocotb_tools/makefiles/simulators/Makefile.riviera +144 -0
- cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
- cocotb_tools/makefiles/simulators/Makefile.verilator +79 -0
- cocotb_tools/makefiles/simulators/Makefile.xcelium +104 -0
- cocotb_tools/py.typed +0 -0
- cocotb_tools/runner.py +1868 -0
- cocotb_tools/sim_versions.py +140 -0
- pygpi/__init__.py +0 -0
- pygpi/entry.py +42 -0
- pygpi/py.typed +0 -0
cocotb/task.py
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
import collections.abc
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import traceback
|
|
8
|
+
from asyncio import CancelledError, InvalidStateError
|
|
9
|
+
from bdb import BdbQuit
|
|
10
|
+
from enum import auto
|
|
11
|
+
from types import SimpleNamespace
|
|
12
|
+
from typing import (
|
|
13
|
+
TYPE_CHECKING,
|
|
14
|
+
Callable,
|
|
15
|
+
Coroutine,
|
|
16
|
+
Generator,
|
|
17
|
+
Generic,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
TypeVar,
|
|
21
|
+
Union,
|
|
22
|
+
cast,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
import cocotb
|
|
26
|
+
from cocotb._base_triggers import Trigger
|
|
27
|
+
from cocotb._bridge import bridge, resume
|
|
28
|
+
from cocotb._deprecation import deprecated
|
|
29
|
+
from cocotb._outcomes import Error, Outcome, Value
|
|
30
|
+
from cocotb._py_compat import Self, cached_property
|
|
31
|
+
from cocotb._utils import DocEnum, extract_coro_stack, remove_traceback_frames
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from types import CoroutineType
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = (
|
|
38
|
+
"Join",
|
|
39
|
+
"Task",
|
|
40
|
+
"TaskComplete",
|
|
41
|
+
"bridge",
|
|
42
|
+
"current_task",
|
|
43
|
+
"resume",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Set __module__ on re-exports
|
|
47
|
+
bridge.__module__ = __name__
|
|
48
|
+
resume.__module__ = __name__
|
|
49
|
+
|
|
50
|
+
#: Task result type
|
|
51
|
+
ResultType = TypeVar("ResultType")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _TaskState(DocEnum):
|
|
55
|
+
"""State of a Task."""
|
|
56
|
+
|
|
57
|
+
UNSTARTED = (auto(), "Task created, but never run and not scheduled")
|
|
58
|
+
SCHEDULED = (auto(), "Task queued to run soon")
|
|
59
|
+
PENDING = (auto(), "Task waiting for Trigger to fire")
|
|
60
|
+
RUNNING = (auto(), "Task is currently running")
|
|
61
|
+
FINISHED = (auto(), "Task has finished with a value or Exception")
|
|
62
|
+
CANCELLED = (auto(), "Task was cancelled before it finished")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Task(Generic[ResultType]):
|
|
66
|
+
"""Concurrently executing task.
|
|
67
|
+
|
|
68
|
+
This class is not intended for users to directly instantiate.
|
|
69
|
+
Use :func:`cocotb.create_task` to create a Task object
|
|
70
|
+
or :func:`cocotb.start_soon` to create a Task and schedule it to run.
|
|
71
|
+
|
|
72
|
+
.. versionchanged:: 1.8
|
|
73
|
+
Moved to the ``cocotb.task`` module.
|
|
74
|
+
|
|
75
|
+
.. versionchanged:: 2.0
|
|
76
|
+
The ``retval``, ``_finished``, and ``__bool__`` methods were removed.
|
|
77
|
+
Use :meth:`result`, :meth:`done`, and :meth:`done` methods instead, respectively.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
_id_count = 0 # used by the scheduler for debug
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self, inst: Coroutine[Trigger, None, ResultType], *, name: Optional[str] = None
|
|
84
|
+
) -> None:
|
|
85
|
+
self._native_coroutine: bool
|
|
86
|
+
if inspect.iscoroutinefunction(inst):
|
|
87
|
+
raise TypeError(
|
|
88
|
+
f"Coroutine function {inst} should be called prior to being scheduled."
|
|
89
|
+
)
|
|
90
|
+
elif inspect.isasyncgen(inst):
|
|
91
|
+
raise TypeError(
|
|
92
|
+
f"{inst.__qualname__} is an async generator, not a coroutine. "
|
|
93
|
+
"You likely used the yield keyword instead of await."
|
|
94
|
+
)
|
|
95
|
+
elif inspect.iscoroutine(inst):
|
|
96
|
+
self._native_coroutine = True
|
|
97
|
+
elif isinstance(inst, collections.abc.Coroutine):
|
|
98
|
+
self._native_coroutine = False
|
|
99
|
+
else:
|
|
100
|
+
raise TypeError(f"{inst} isn't a valid coroutine!")
|
|
101
|
+
|
|
102
|
+
self._coro = inst
|
|
103
|
+
self._state: _TaskState = _TaskState.UNSTARTED
|
|
104
|
+
self._outcome: Union[Outcome[ResultType], None] = None
|
|
105
|
+
self._trigger: Union[Trigger, None] = None
|
|
106
|
+
self._done_callbacks: List[Callable[[Task[ResultType]], None]] = []
|
|
107
|
+
self._cancelled_msg: Union[str, None] = None
|
|
108
|
+
self._must_cancel: bool = False
|
|
109
|
+
self._locals = SimpleNamespace()
|
|
110
|
+
|
|
111
|
+
self._task_id = self._id_count
|
|
112
|
+
type(self)._id_count += 1
|
|
113
|
+
self._name = f"Task {self._task_id}" if name is None else name
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def locals(self) -> SimpleNamespace:
|
|
117
|
+
'''Task-local variables.
|
|
118
|
+
|
|
119
|
+
A modifiable namespace where any user-defined variables can be added.
|
|
120
|
+
Use :func:`~cocotb.task.current_task` to access the current Task's locals.
|
|
121
|
+
|
|
122
|
+
.. code-block:: python
|
|
123
|
+
|
|
124
|
+
def get_rng() -> random.Random:
|
|
125
|
+
"""Get the random number generator for the current Task."""
|
|
126
|
+
try:
|
|
127
|
+
return current_task().locals.rng
|
|
128
|
+
except AttributeError:
|
|
129
|
+
rng = random.Random()
|
|
130
|
+
current_task().locals.rng = rng
|
|
131
|
+
return rng
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Use Task-local RNG to drive a signal
|
|
135
|
+
rng = get_rng()
|
|
136
|
+
for _ in range(10):
|
|
137
|
+
await drive(rng.randint(0, 100))
|
|
138
|
+
|
|
139
|
+
.. versionadded:: 2.0
|
|
140
|
+
'''
|
|
141
|
+
return self._locals
|
|
142
|
+
|
|
143
|
+
def get_name(self) -> str:
|
|
144
|
+
"""Return the name of the :class:`!Task`.
|
|
145
|
+
|
|
146
|
+
If not set using :meth:`set_name` or passed during construction,
|
|
147
|
+
a reasonable default name is generated.
|
|
148
|
+
"""
|
|
149
|
+
return self._name
|
|
150
|
+
|
|
151
|
+
def set_name(self, value: object) -> None:
|
|
152
|
+
"""Set the name of the :class:`!Task`.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
value: Any object which can be converted to a :class:`str` to use as the name.
|
|
156
|
+
"""
|
|
157
|
+
self._name = str(value)
|
|
158
|
+
|
|
159
|
+
@cached_property
|
|
160
|
+
def _cancelled_error(self) -> CancelledError:
|
|
161
|
+
if self._cancelled_msg is None:
|
|
162
|
+
return CancelledError()
|
|
163
|
+
else:
|
|
164
|
+
return CancelledError(self._cancelled_msg)
|
|
165
|
+
|
|
166
|
+
@cached_property
|
|
167
|
+
def _log(self) -> logging.Logger:
|
|
168
|
+
coro_name: str
|
|
169
|
+
if self._native_coroutine:
|
|
170
|
+
coro_name = self._coro.__qualname__
|
|
171
|
+
else:
|
|
172
|
+
coro_name = type(self._coro).__qualname__
|
|
173
|
+
return logging.getLogger(f"cocotb.{self._name}.{coro_name}")
|
|
174
|
+
|
|
175
|
+
def __str__(self) -> str:
|
|
176
|
+
# TODO Do we really need this?
|
|
177
|
+
return f"<{self._name}>"
|
|
178
|
+
|
|
179
|
+
def _get_coro_stack(self) -> traceback.StackSummary:
|
|
180
|
+
"""Get the coroutine callstack of this Task.
|
|
181
|
+
|
|
182
|
+
Assumes :attr:`_coro` is a native Python coroutine object.
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
TypeError: If :attr:`_coro` is not a native Python coroutine object.
|
|
186
|
+
"""
|
|
187
|
+
if not self._native_coroutine:
|
|
188
|
+
raise TypeError(
|
|
189
|
+
"Task._get_coro_stack() can only be called on native Python coroutines."
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
coro_stack = extract_coro_stack(
|
|
193
|
+
cast("CoroutineType[Trigger, None, ResultType]", self._coro)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Remove Trigger.__await__() from the stack, as it's not really useful
|
|
197
|
+
if len(coro_stack) > 0 and coro_stack[-1].name == "__await__":
|
|
198
|
+
coro_stack.pop()
|
|
199
|
+
|
|
200
|
+
return coro_stack
|
|
201
|
+
|
|
202
|
+
def __repr__(self) -> str:
|
|
203
|
+
if self._native_coroutine:
|
|
204
|
+
coro_stack = self._get_coro_stack()
|
|
205
|
+
try:
|
|
206
|
+
coro_name = coro_stack[-1].name
|
|
207
|
+
# coro_stack may be empty if:
|
|
208
|
+
# - exhausted generator
|
|
209
|
+
# - finished coroutine
|
|
210
|
+
except IndexError:
|
|
211
|
+
try:
|
|
212
|
+
coro_name = self._coro.__name__
|
|
213
|
+
except AttributeError:
|
|
214
|
+
coro_name = type(self._coro).__name__
|
|
215
|
+
else:
|
|
216
|
+
coro_name = type(self._coro).__name__
|
|
217
|
+
|
|
218
|
+
if self._state is _TaskState.RUNNING:
|
|
219
|
+
return f"<{self._name} running coro={coro_name}()>"
|
|
220
|
+
elif self._state is _TaskState.FINISHED:
|
|
221
|
+
return f"<{self._name} finished coro={coro_name}() outcome={self._outcome}>"
|
|
222
|
+
elif self._state is _TaskState.PENDING:
|
|
223
|
+
return f"<{self._name} pending coro={coro_name}() trigger={self._trigger}>"
|
|
224
|
+
elif self._state is _TaskState.SCHEDULED:
|
|
225
|
+
return f"<{self._name} scheduled coro={coro_name}()>"
|
|
226
|
+
elif self._state is _TaskState.UNSTARTED:
|
|
227
|
+
return f"<{self._name} created coro={coro_name}()>"
|
|
228
|
+
elif self._state is _TaskState.CANCELLED:
|
|
229
|
+
return f"<{self._name} cancelled coro={coro_name} with={self._cancelled_error} outcome={self._outcome}"
|
|
230
|
+
else:
|
|
231
|
+
raise RuntimeError("Task in unknown state")
|
|
232
|
+
|
|
233
|
+
def _set_outcome(
|
|
234
|
+
self, result: Outcome[ResultType], state: _TaskState = _TaskState.FINISHED
|
|
235
|
+
) -> None:
|
|
236
|
+
self._outcome = result
|
|
237
|
+
self._state = state
|
|
238
|
+
|
|
239
|
+
# Run done callbacks.
|
|
240
|
+
for callback in self._done_callbacks:
|
|
241
|
+
callback(self)
|
|
242
|
+
|
|
243
|
+
# Wake up waiting Tasks.
|
|
244
|
+
cocotb._scheduler_inst._react(self.complete)
|
|
245
|
+
cocotb._scheduler_inst._react(self._join)
|
|
246
|
+
|
|
247
|
+
def _advance(self, exc: Union[BaseException, None]) -> Union[Trigger, None]:
|
|
248
|
+
"""Resume execution of the Task.
|
|
249
|
+
|
|
250
|
+
Runs until the coroutine ends, raises, or yields a Trigger.
|
|
251
|
+
Can optionally throw an Exception into the Task.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
exc: :exc:`BaseException` to throw into the coroutine or nothing.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
The object yielded from the coroutine or ``None`` if coroutine finished.
|
|
258
|
+
"""
|
|
259
|
+
self._state = _TaskState.RUNNING
|
|
260
|
+
|
|
261
|
+
if self._must_cancel:
|
|
262
|
+
exc = self._cancelled_error
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
if exc is None:
|
|
266
|
+
trigger = self._coro.send(None)
|
|
267
|
+
else:
|
|
268
|
+
trigger = self._coro.throw(exc)
|
|
269
|
+
except StopIteration as e:
|
|
270
|
+
outcome = Value(e.value)
|
|
271
|
+
if self._must_cancel:
|
|
272
|
+
self._set_outcome(
|
|
273
|
+
Error(
|
|
274
|
+
RuntimeError(
|
|
275
|
+
"Task was cancelled, but exited normally. Did you forget to re-raise the CancelledError?"
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
self._set_outcome(outcome)
|
|
281
|
+
return None
|
|
282
|
+
except (KeyboardInterrupt, SystemExit, BdbQuit) as e:
|
|
283
|
+
# Allow these to bubble up to the execution root to fail the sim immediately.
|
|
284
|
+
# This follows asyncio's behavior.
|
|
285
|
+
self._set_outcome(Error(remove_traceback_frames(e, ["_advance"])))
|
|
286
|
+
raise
|
|
287
|
+
except CancelledError as e:
|
|
288
|
+
self._set_outcome(
|
|
289
|
+
Error(remove_traceback_frames(e, ["_advance"])), _TaskState.CANCELLED
|
|
290
|
+
)
|
|
291
|
+
return None
|
|
292
|
+
except BaseException as e:
|
|
293
|
+
self._set_outcome(Error(remove_traceback_frames(e, ["_advance"])))
|
|
294
|
+
return None
|
|
295
|
+
else:
|
|
296
|
+
if self._must_cancel:
|
|
297
|
+
self._set_outcome(
|
|
298
|
+
Error(
|
|
299
|
+
RuntimeError(
|
|
300
|
+
"Task was cancelled, but continued running. Did you forget to re-raise the CancelledError?"
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
return None
|
|
305
|
+
else:
|
|
306
|
+
return trigger
|
|
307
|
+
|
|
308
|
+
def _schedule_resume(self, exc: Optional[BaseException] = None) -> None:
|
|
309
|
+
cocotb._scheduler_inst._unschedule(self)
|
|
310
|
+
cocotb._scheduler_inst._schedule_task_internal(self, exc)
|
|
311
|
+
|
|
312
|
+
@deprecated("`task.kill()` is deprecated in favor of `task.cancel()`")
|
|
313
|
+
def kill(self) -> None:
|
|
314
|
+
"""Kill a coroutine."""
|
|
315
|
+
|
|
316
|
+
if self._state in (_TaskState.PENDING, _TaskState.SCHEDULED):
|
|
317
|
+
# Unschedule if scheduled and unprime triggers if pending.
|
|
318
|
+
cocotb._scheduler_inst._unschedule(self)
|
|
319
|
+
elif self._state is _TaskState.UNSTARTED:
|
|
320
|
+
# Don't need to unschedule.
|
|
321
|
+
pass
|
|
322
|
+
elif self._state in (_TaskState.FINISHED, _TaskState.CANCELLED):
|
|
323
|
+
# Do nothing if already done.
|
|
324
|
+
return
|
|
325
|
+
else:
|
|
326
|
+
raise RuntimeError("Can't kill currently running Task")
|
|
327
|
+
|
|
328
|
+
# Close native coroutines if they were never resumed to prevent ResourceWarnings.
|
|
329
|
+
if (
|
|
330
|
+
inspect.iscoroutine(self._coro)
|
|
331
|
+
and inspect.getcoroutinestate(self._coro) == "CORO_CREATED"
|
|
332
|
+
):
|
|
333
|
+
self._coro.close()
|
|
334
|
+
|
|
335
|
+
self._set_outcome(Value(None)) # type: ignore # `kill()` sets the result to None regardless of the ResultType
|
|
336
|
+
|
|
337
|
+
@cached_property
|
|
338
|
+
def complete(self) -> "TaskComplete[ResultType]":
|
|
339
|
+
r"""Trigger which fires when the Task completes.
|
|
340
|
+
|
|
341
|
+
Unlike :meth:`join`, this Trigger does not return the result of the Task when :keyword:`await`\ ed.
|
|
342
|
+
|
|
343
|
+
.. code-block:: python
|
|
344
|
+
|
|
345
|
+
async def coro_inner():
|
|
346
|
+
await Timer(1, unit="ns")
|
|
347
|
+
raise ValueError("Oops")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
task = cocotb.start_soon(coro_inner())
|
|
351
|
+
await task.complete # no exception raised here
|
|
352
|
+
assert task.exception() == ValueError("Oops")
|
|
353
|
+
"""
|
|
354
|
+
return TaskComplete._make(self)
|
|
355
|
+
|
|
356
|
+
@deprecated(
|
|
357
|
+
"Using `task` directly is preferred to `task.join()` in all situations where the latter could be used."
|
|
358
|
+
)
|
|
359
|
+
def join(self) -> "Join[ResultType]":
|
|
360
|
+
r"""Block until the Task completes and return the result.
|
|
361
|
+
|
|
362
|
+
Equivalent to calling :class:`Join(self) <cocotb.task.Join>`.
|
|
363
|
+
|
|
364
|
+
.. code-block:: python
|
|
365
|
+
|
|
366
|
+
async def coro_inner():
|
|
367
|
+
await Timer(1, unit="ns")
|
|
368
|
+
return "Hello world"
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
task = cocotb.start_soon(coro_inner())
|
|
372
|
+
result = await task.join()
|
|
373
|
+
assert result == "Hello world"
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Object that can be :keyword:`await`\ ed or passed into :class:`~cocotb.triggers.First` or :class:`~cocotb.triggers.Combine`;
|
|
377
|
+
the result of which will be the result of the Task.
|
|
378
|
+
|
|
379
|
+
.. deprecated:: 2.0
|
|
380
|
+
Using ``task`` directly is preferred to ``task.join()`` in all situations where the latter could be used.
|
|
381
|
+
"""
|
|
382
|
+
return self._join
|
|
383
|
+
|
|
384
|
+
@cached_property
|
|
385
|
+
def _join(self) -> "Join[ResultType]":
|
|
386
|
+
return Join._make(self)
|
|
387
|
+
|
|
388
|
+
def cancel(self, msg: Optional[str] = None) -> bool:
|
|
389
|
+
"""Cancel a Task's further execution.
|
|
390
|
+
|
|
391
|
+
When a Task is cancelled, a :exc:`asyncio.CancelledError` is thrown into the Task.
|
|
392
|
+
|
|
393
|
+
Returns: ``True`` if the Task was cancelled; ``False`` otherwise.
|
|
394
|
+
"""
|
|
395
|
+
if self._state in {_TaskState.PENDING, _TaskState.SCHEDULED}:
|
|
396
|
+
self._schedule_resume()
|
|
397
|
+
elif self._state in (_TaskState.UNSTARTED, _TaskState.RUNNING):
|
|
398
|
+
# (Re)schedule to throw CancelledError
|
|
399
|
+
cocotb._scheduler_inst._schedule_task_internal(self)
|
|
400
|
+
else:
|
|
401
|
+
# Already finished or cancelled
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
self._cancelled_msg = msg
|
|
405
|
+
self._must_cancel = True
|
|
406
|
+
return True
|
|
407
|
+
|
|
408
|
+
def _cancel_now(self, msg: Optional[str] = None) -> bool:
|
|
409
|
+
"""Like cancel(), but throws CancelledError into the Task and puts it into a "done" state immediately.
|
|
410
|
+
|
|
411
|
+
Not safe to be called from a running Task.
|
|
412
|
+
Only from done callbacks or scheduler or Task internals.
|
|
413
|
+
"""
|
|
414
|
+
if self.done():
|
|
415
|
+
return False
|
|
416
|
+
|
|
417
|
+
self._cancelled_msg = msg
|
|
418
|
+
self._must_cancel = True
|
|
419
|
+
|
|
420
|
+
if self._state is _TaskState.UNSTARTED:
|
|
421
|
+
# Must fail immediately as we can't start a coroutine with an exception.
|
|
422
|
+
self._set_outcome(Error(self._cancelled_error), _TaskState.CANCELLED)
|
|
423
|
+
else:
|
|
424
|
+
# Unprime and unschedule the Task so it's out of the scheduler.
|
|
425
|
+
cocotb._scheduler_inst._unschedule(self)
|
|
426
|
+
# Force CancelledError to be thrown immediately.
|
|
427
|
+
self._advance(None)
|
|
428
|
+
|
|
429
|
+
return True
|
|
430
|
+
|
|
431
|
+
def cancelled(self) -> bool:
|
|
432
|
+
"""Return ``True`` if the Task was cancelled."""
|
|
433
|
+
return self._state is _TaskState.CANCELLED
|
|
434
|
+
|
|
435
|
+
def done(self) -> bool:
|
|
436
|
+
"""Return ``True`` if the Task has finished executing."""
|
|
437
|
+
return self._state in (_TaskState.FINISHED, _TaskState.CANCELLED)
|
|
438
|
+
|
|
439
|
+
def result(self) -> ResultType:
|
|
440
|
+
"""Return the result of the Task.
|
|
441
|
+
|
|
442
|
+
If the Task ran to completion, the result is returned.
|
|
443
|
+
If the Task failed with an exception, the exception is re-raised.
|
|
444
|
+
If the Task was cancelled, the :exc:`asyncio.CancelledError` is re-raised.
|
|
445
|
+
If the coroutine is not yet complete, an :exc:`asyncio.InvalidStateError` is raised.
|
|
446
|
+
"""
|
|
447
|
+
if self._state is _TaskState.CANCELLED:
|
|
448
|
+
raise self._cancelled_error
|
|
449
|
+
elif self._state is _TaskState.FINISHED:
|
|
450
|
+
return cast("Outcome[ResultType]", self._outcome).get()
|
|
451
|
+
else:
|
|
452
|
+
raise InvalidStateError("result is not yet available")
|
|
453
|
+
|
|
454
|
+
def exception(self) -> Optional[BaseException]:
|
|
455
|
+
"""Return the exception of the Task.
|
|
456
|
+
|
|
457
|
+
If the Task ran to completion, ``None`` is returned.
|
|
458
|
+
If the Task failed with an exception, the exception is returned.
|
|
459
|
+
If the Task was cancelled, the :exc:`asyncio.CancelledError` is re-raised.
|
|
460
|
+
If the coroutine is not yet complete, an :exc:`asyncio.InvalidStateError` is raised.
|
|
461
|
+
"""
|
|
462
|
+
if self._state is _TaskState.CANCELLED:
|
|
463
|
+
raise self._cancelled_error
|
|
464
|
+
elif self._state is _TaskState.FINISHED:
|
|
465
|
+
if isinstance(self._outcome, Error):
|
|
466
|
+
return self._outcome.error
|
|
467
|
+
else:
|
|
468
|
+
return None
|
|
469
|
+
else:
|
|
470
|
+
raise InvalidStateError("result is not yet available")
|
|
471
|
+
|
|
472
|
+
def _add_done_callback(
|
|
473
|
+
self, callback: Callable[["Task[ResultType]"], None]
|
|
474
|
+
) -> None:
|
|
475
|
+
"""Add *callback* to the list of callbacks to be run once the Task becomes "done".
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
callback: The callback to run once "done".
|
|
479
|
+
|
|
480
|
+
.. note::
|
|
481
|
+
If the task is already done, calling this function will call the callback immediately.
|
|
482
|
+
"""
|
|
483
|
+
if self.done():
|
|
484
|
+
callback(self)
|
|
485
|
+
self._done_callbacks.append(callback)
|
|
486
|
+
|
|
487
|
+
def __await__(self) -> Generator[Trigger, None, ResultType]:
|
|
488
|
+
if self._state is _TaskState.UNSTARTED:
|
|
489
|
+
cocotb._scheduler_inst._schedule_task_internal(self)
|
|
490
|
+
yield self.complete
|
|
491
|
+
elif not self.done():
|
|
492
|
+
yield self.complete
|
|
493
|
+
return self.result()
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def current_task() -> Task[object]:
|
|
497
|
+
"""Return the currently running Task.
|
|
498
|
+
|
|
499
|
+
Raises:
|
|
500
|
+
RuntimeError: If no Task is running.
|
|
501
|
+
|
|
502
|
+
.. versionadded:: 2.0
|
|
503
|
+
"""
|
|
504
|
+
task = cocotb._scheduler_inst._current_task
|
|
505
|
+
if task is None:
|
|
506
|
+
raise RuntimeError("No Task is currently running")
|
|
507
|
+
return task
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class TaskComplete(Trigger, Generic[ResultType]):
|
|
511
|
+
r"""Fires when a :class:`~cocotb.task.Task` completes.
|
|
512
|
+
|
|
513
|
+
Unlike :class:`~cocotb.task.Join`, this Trigger does not return the result of the Task when :keyword:`await`\ ed.
|
|
514
|
+
See :attr:`.Task.complete` for more information.
|
|
515
|
+
|
|
516
|
+
.. warning::
|
|
517
|
+
This class cannot be instantiated in the normal way.
|
|
518
|
+
You must use :attr:`.Task.complete`.
|
|
519
|
+
|
|
520
|
+
.. versionadded:: 2.0
|
|
521
|
+
"""
|
|
522
|
+
|
|
523
|
+
_task: Task[ResultType]
|
|
524
|
+
|
|
525
|
+
def __new__(cls, task: Task[ResultType]) -> "TaskComplete[ResultType]":
|
|
526
|
+
raise NotImplementedError(
|
|
527
|
+
"TaskComplete cannot be instantiated in this way. Use the `task.complete` attribute."
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def _make(cls, task: Task[ResultType]) -> "Self":
|
|
532
|
+
self = super().__new__(cls)
|
|
533
|
+
super().__init__(self)
|
|
534
|
+
self._task = task
|
|
535
|
+
return self
|
|
536
|
+
|
|
537
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
538
|
+
if self._task.done():
|
|
539
|
+
callback(self)
|
|
540
|
+
else:
|
|
541
|
+
super()._prime(callback)
|
|
542
|
+
|
|
543
|
+
def __repr__(self) -> str:
|
|
544
|
+
return f"{type(self).__qualname__}({self._task!s})"
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def task(self) -> Task[ResultType]:
|
|
548
|
+
"""The :class:`.Task` associated with this completion event."""
|
|
549
|
+
return self._task
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class Join(TaskComplete[ResultType]):
|
|
553
|
+
r"""Fires when a :class:`~cocotb.task.Task` completes and returns the Task's result.
|
|
554
|
+
|
|
555
|
+
Equivalent to calling :meth:`task.join() <cocotb.task.Task.join>`.
|
|
556
|
+
|
|
557
|
+
.. code-block:: python
|
|
558
|
+
|
|
559
|
+
async def coro_inner():
|
|
560
|
+
await Timer(1, unit="ns")
|
|
561
|
+
return "Hello world"
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
task = cocotb.start_soon(coro_inner())
|
|
565
|
+
result = await Join(task)
|
|
566
|
+
assert result == "Hello world"
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
task: The Task upon which to wait for completion.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Object that can be :keyword:`await`\ ed or passed into :class:`~cocotb.triggers.First` or :class:`~cocotb.triggers.Combine`;
|
|
573
|
+
the result of which will be the result of the Task.
|
|
574
|
+
|
|
575
|
+
.. deprecated:: 2.0
|
|
576
|
+
Using ``task`` directly is preferred to ``Join(task)`` in all situations where the latter could be used.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
@deprecated(
|
|
580
|
+
"Using `task` directly is preferred to `Join(task)` in all situations where the latter could be used."
|
|
581
|
+
)
|
|
582
|
+
def __new__(cls, task: Task[ResultType]) -> "Join[ResultType]":
|
|
583
|
+
return task._join
|
|
584
|
+
|
|
585
|
+
def __init__(self, task: Task[ResultType]) -> None:
|
|
586
|
+
pass
|
|
587
|
+
|
|
588
|
+
def __await__(self) -> Generator["Self", None, ResultType]: # type: ignore[override]
|
|
589
|
+
yield self
|
|
590
|
+
return self._task.result()
|
cocotb/triggers.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from cocotb._base_triggers import Event, Lock, NullTrigger, Trigger
|
|
7
|
+
from cocotb._extended_awaitables import (
|
|
8
|
+
ClockCycles,
|
|
9
|
+
Combine,
|
|
10
|
+
First,
|
|
11
|
+
SimTimeoutError,
|
|
12
|
+
Waitable,
|
|
13
|
+
with_timeout,
|
|
14
|
+
)
|
|
15
|
+
from cocotb._gpi_triggers import (
|
|
16
|
+
Edge,
|
|
17
|
+
FallingEdge,
|
|
18
|
+
GPITrigger,
|
|
19
|
+
NextTimeStep,
|
|
20
|
+
ReadOnly,
|
|
21
|
+
ReadWrite,
|
|
22
|
+
RisingEdge,
|
|
23
|
+
Timer,
|
|
24
|
+
ValueChange,
|
|
25
|
+
current_gpi_trigger,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = (
|
|
29
|
+
"ClockCycles",
|
|
30
|
+
"Combine",
|
|
31
|
+
"Edge",
|
|
32
|
+
"Event",
|
|
33
|
+
"FallingEdge",
|
|
34
|
+
"First",
|
|
35
|
+
"GPITrigger",
|
|
36
|
+
"Lock",
|
|
37
|
+
"NextTimeStep",
|
|
38
|
+
"NullTrigger",
|
|
39
|
+
"ReadOnly",
|
|
40
|
+
"ReadWrite",
|
|
41
|
+
"RisingEdge",
|
|
42
|
+
"SimTimeoutError",
|
|
43
|
+
"Timer",
|
|
44
|
+
"Trigger",
|
|
45
|
+
"ValueChange",
|
|
46
|
+
"Waitable",
|
|
47
|
+
"current_gpi_trigger",
|
|
48
|
+
"with_timeout",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Set __module__ on re-exports
|
|
52
|
+
for name in __all__:
|
|
53
|
+
obj = globals()[name]
|
|
54
|
+
obj.__module__ = __name__
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __getattr__(name: str) -> object:
|
|
58
|
+
if name == "Join":
|
|
59
|
+
warnings.warn(
|
|
60
|
+
"Join has been moved to `cocotb.task`.",
|
|
61
|
+
DeprecationWarning,
|
|
62
|
+
stacklevel=2,
|
|
63
|
+
)
|
|
64
|
+
from cocotb.task import Join # noqa: PLC0415
|
|
65
|
+
|
|
66
|
+
return Join
|
|
67
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
cocotb/types/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
from ._abstract_array import AbstractArray, AbstractMutableArray
|
|
5
|
+
from ._array import Array
|
|
6
|
+
from ._indexing import IndexingChangedWarning
|
|
7
|
+
from ._logic import Bit, Logic
|
|
8
|
+
from ._logic_array import LogicArray
|
|
9
|
+
from ._range import Range
|
|
10
|
+
|
|
11
|
+
# isort: split
|
|
12
|
+
# These are imports for doctests in the submodules. Since we fix up the `__module__`
|
|
13
|
+
# attribute, `--doctest-modules` thinks this is the module the types were defined in
|
|
14
|
+
# and will evaluate this module first before running tests.
|
|
15
|
+
from typing import Tuple # noqa: F401
|
|
16
|
+
|
|
17
|
+
__all__ = (
|
|
18
|
+
"AbstractArray",
|
|
19
|
+
"AbstractMutableArray",
|
|
20
|
+
"Array",
|
|
21
|
+
"Bit",
|
|
22
|
+
"IndexingChangedWarning",
|
|
23
|
+
"Logic",
|
|
24
|
+
"LogicArray",
|
|
25
|
+
"Range",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Set __module__ on re-exports
|
|
29
|
+
for name in __all__:
|
|
30
|
+
obj = globals()[name]
|
|
31
|
+
obj.__module__ = __name__
|