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