cocotb 1.9.2__cp311-cp311-win_amd64.whl → 2.0.0rc2__cp311-cp311-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.cp311-win_amd64.exp +0 -0
  86. cocotb/simulator.cp311-win_amd64.lib +0 -0
  87. cocotb/simulator.cp311-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
cocotb/task.py CHANGED
@@ -3,323 +3,588 @@
3
3
  # SPDX-License-Identifier: BSD-3-Clause
4
4
  import collections.abc
5
5
  import inspect
6
- import os
7
- import typing
8
- import warnings
6
+ import logging
7
+ import traceback
9
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
+ )
10
24
 
11
25
  import cocotb
12
- import cocotb.triggers
13
- from cocotb import outcomes
14
- from cocotb.log import SimLog
15
- from cocotb.result import ReturnValue
16
- from cocotb.utils import extract_coro_stack, lazy_property, remove_traceback_frames
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
- T = typing.TypeVar("T")
19
- Self = typing.TypeVar("Self")
33
+ if TYPE_CHECKING:
34
+ from types import CoroutineType
20
35
 
21
- # Sadly the Python standard logging module is very slow so it's better not to
22
- # make any calls by testing a boolean flag first
23
- _debug = "COCOTB_SCHEDULER_DEBUG" in os.environ
24
36
 
37
+ __all__ = (
38
+ "Join",
39
+ "Task",
40
+ "TaskComplete",
41
+ "bridge",
42
+ "current_task",
43
+ "resume",
44
+ )
25
45
 
26
- class Task(typing.Coroutine[typing.Any, typing.Any, T]):
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]):
27
66
  """Concurrently executing task.
28
67
 
29
68
  This class is not intended for users to directly instantiate.
30
- Use :func:`cocotb.create_task` to create a Task object,
31
- or use :func:`cocotb.start_soon` or :func:`cocotb.start` to
32
- create a Task and schedule it to run.
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.
33
71
 
34
- .. versionchanged:: 1.8.0
72
+ .. versionchanged:: 1.8
35
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.
36
78
  """
37
79
 
38
- _name: str = "Task" # class name of schedulable task
39
80
  _id_count = 0 # used by the scheduler for debug
40
81
 
41
- def __init__(self, inst):
42
-
43
- if isinstance(inst, collections.abc.Coroutine):
44
- self._natively_awaitable = True
45
- elif inspect.isgenerator(inst):
46
- self._natively_awaitable = False
47
- elif inspect.iscoroutinefunction(inst):
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):
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.".format(
56
- inst.__qualname__
57
- )
92
+ f"{inst.__qualname__} is an async generator, not a coroutine. "
93
+ "You likely used the yield keyword instead of await."
58
94
  )
95
+ elif inspect.iscoroutine(inst):
96
+ self._native_coroutine = True
97
+ elif isinstance(inst, collections.abc.Coroutine):
98
+ self._native_coroutine = False
59
99
  else:
60
- raise TypeError(
61
- f"{inst} isn't a valid coroutine! Did you forget to use the yield keyword?"
62
- )
100
+ raise TypeError(f"{inst} isn't a valid coroutine!")
101
+
63
102
  self._coro = inst
64
- self._started = False
65
- self._outcome: outcomes.Outcome = None
66
- self._trigger: typing.Optional[cocotb.triggers.Trigger] = None
67
- self._cancelled: typing.Optional[CancelledError] = None
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()
68
110
 
69
111
  self._task_id = self._id_count
70
112
  type(self)._id_count += 1
71
- self.__name__ = f"{type(self)._name} {self._task_id}"
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__}")
113
+ self._name = f"Task {self._task_id}" if name is None else name
79
114
 
80
115
  @property
81
- def retval(self) -> T:
82
- """Return the result of the Task.
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
83
132
 
84
- If the Task ran to completion, the result is returned.
85
- If the Task failed with an exception, the exception is re-raised.
86
- If the Task is not yet complete, a :exc:`RuntimeError` is raised.
87
133
 
88
- .. deprecated:: 1.7.0
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.
89
148
  """
90
- warnings.warn(
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()
149
+ return self._name
99
150
 
100
- @property
101
- def _finished(self) -> bool:
102
- """``True`` if the Task is finished executing.
151
+ def set_name(self, value: object) -> None:
152
+ """Set the name of the :class:`!Task`.
103
153
 
104
- .. deprecated:: 1.7.0
154
+ Args:
155
+ value: Any object which can be converted to a :class:`str` to use as the name.
105
156
  """
106
- warnings.warn(
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
157
+ self._name = str(value)
113
158
 
114
- def __iter__(self: Self) -> Self:
115
- # for use in "yield from" statements
116
- return self
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}")
117
174
 
118
175
  def __str__(self) -> str:
119
- return f"<{self.__name__}>"
176
+ # TODO Do we really need this?
177
+ return f"<{self._name}>"
120
178
 
121
- def _get_coro_stack(self) -> typing.Any:
122
- """Get the coroutine callstack of this Task."""
123
- coro_stack = extract_coro_stack(self._coro)
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
+ )
124
195
 
125
196
  # Remove Trigger.__await__() from the stack, as it's not really useful
126
- if self._natively_awaitable and len(coro_stack):
127
- if coro_stack[-1].name == "__await__":
128
- coro_stack.pop()
197
+ if len(coro_stack) > 0 and coro_stack[-1].name == "__await__":
198
+ coro_stack.pop()
129
199
 
130
200
  return coro_stack
131
201
 
132
202
  def __repr__(self) -> str:
133
- coro_stack = self._get_coro_stack()
134
-
135
- if cocotb.scheduler._current_task is self:
136
- fmt = "<{name} running coro={coro}()>"
137
- elif self.done():
138
- fmt = "<{name} finished coro={coro}() outcome={outcome}>"
139
- elif self._trigger is not None:
140
- fmt = "<{name} pending coro={coro}() trigger={trigger}>"
141
- elif not self._started:
142
- fmt = "<{name} created coro={coro}()>"
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}"
143
230
  else:
144
- fmt = "<{name} adding coro={coro}()>"
231
+ raise RuntimeError("Task in unknown state")
145
232
 
146
- try:
147
- coro_name = coro_stack[-1].name
148
- # coro_stack may be empty if:
149
- # - exhausted generator
150
- # - finished coroutine
151
- except IndexError:
152
- coro_name = self._coro.__name__
153
-
154
- repr_string = fmt.format(
155
- name=self.__name__,
156
- coro=coro_name,
157
- trigger=self._trigger,
158
- outcome=self._outcome,
159
- )
160
- return repr_string
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)
161
246
 
162
- def _advance(self, outcome: outcomes.Outcome) -> typing.Any:
163
- """Advance to the next yield in this coroutine.
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.
164
252
 
165
253
  Args:
166
- outcome: The :any:`outcomes.Outcome` object to resume with.
254
+ exc: :exc:`BaseException` to throw into the coroutine or nothing.
167
255
 
168
256
  Returns:
169
- The object yielded from the coroutine or None if coroutine finished
170
-
257
+ The object yielded from the coroutine or ``None`` if coroutine finished.
171
258
  """
259
+ self._state = _TaskState.RUNNING
260
+
261
+ if self._must_cancel:
262
+ exc = self._cancelled_error
263
+
172
264
  try:
173
- self._started = True
174
- return outcome.send(self._coro)
175
- except ReturnValue as e:
176
- self._outcome = outcomes.Value(e.retval)
265
+ if exc is None:
266
+ trigger = self._coro.send(None)
267
+ else:
268
+ trigger = self._coro.throw(exc)
177
269
  except StopIteration as e:
178
- self._outcome = outcomes.Value(e.value)
179
- except BaseException as e:
180
- self._outcome = outcomes.Error(
181
- remove_traceback_frames(e, ["_advance", "send"])
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
182
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
183
307
 
184
- def send(self, value: typing.Any) -> typing.Any:
185
- return self._coro.send(value)
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()
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)
192
311
 
312
+ @deprecated("`task.kill()` is deprecated in favor of `task.cancel()`")
193
313
  def kill(self) -> None:
194
314
  """Kill a coroutine."""
195
- if self._outcome is not None:
196
- # already finished, nothing to kill
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.
197
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()
198
334
 
199
- if _debug:
200
- self.log.debug("kill() called on coroutine")
201
- # todo: probably better to throw an exception for anyone waiting on the coroutine
202
- self._outcome = outcomes.Value(None)
203
- cocotb.scheduler._unschedule(self)
335
+ self._set_outcome(Value(None)) # type: ignore # `kill()` sets the result to None regardless of the ResultType
204
336
 
205
- def join(self) -> "cocotb.triggers.Join":
206
- """Return a trigger that will fire when the wrapped coroutine exits."""
207
- return cocotb.triggers.Join(self)
337
+ @cached_property
338
+ def complete(self) -> "TaskComplete[ResultType]":
339
+ r"""Trigger which fires when the Task completes.
208
340
 
209
- def has_started(self) -> bool:
210
- """Return ``True`` if the Task has started executing."""
211
- return self._started
341
+ Unlike :meth:`join`, this Trigger does not return the result of the Task when :keyword:`await`\ ed.
212
342
 
213
- def cancel(self, msg: typing.Optional[str] = None) -> None:
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:
214
389
  """Cancel a Task's further execution.
215
390
 
216
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.
217
394
  """
218
- self._cancelled = CancelledError(msg)
219
- warnings.warn(
220
- "Calling this method will cause a CancelledError to be thrown in the "
221
- "Task sometime in the future.",
222
- FutureWarning,
223
- stacklevel=2,
224
- )
225
- self.kill()
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
226
430
 
227
431
  def cancelled(self) -> bool:
228
432
  """Return ``True`` if the Task was cancelled."""
229
- return self._cancelled is not None
433
+ return self._state is _TaskState.CANCELLED
230
434
 
231
435
  def done(self) -> bool:
232
436
  """Return ``True`` if the Task has finished executing."""
233
- return self._outcome is not None or self.cancelled()
437
+ return self._state in (_TaskState.FINISHED, _TaskState.CANCELLED)
234
438
 
235
- def result(self) -> T:
439
+ def result(self) -> ResultType:
236
440
  """Return the result of the Task.
237
441
 
238
442
  If the Task ran to completion, the result is returned.
239
443
  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, a :exc:`asyncio.InvalidStateError` is 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.
242
446
  """
243
- if not self.done():
244
- raise InvalidStateError("result is not yet available")
245
- elif self.cancelled():
246
- raise self._cancelled
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()
247
451
  else:
248
- return self._outcome.get()
452
+ raise InvalidStateError("result is not yet available")
249
453
 
250
- def exception(self) -> typing.Optional[BaseException]:
454
+ def exception(self) -> Optional[BaseException]:
251
455
  """Return the exception of the Task.
252
456
 
253
457
  If the Task ran to completion, ``None`` is returned.
254
458
  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, a :exc:`asyncio.InvalidStateError` is raised.
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.
257
461
  """
258
- if not self.done():
259
- raise InvalidStateError("result is not yet available")
260
- elif self.cancelled():
261
- raise self._cancelled
262
- elif isinstance(self._outcome, outcomes.Error):
263
- return self._outcome.error
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
264
469
  else:
265
- return None
470
+ raise InvalidStateError("result is not yet available")
266
471
 
267
- def __bool__(self) -> bool:
268
- """``True`` if Task is not done.
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".
269
479
 
270
- .. deprecated:: 1.7.0
480
+ .. note::
481
+ If the task is already done, calling this function will call the callback immediately.
271
482
  """
272
- warnings.warn(
273
- "Deprecated in favor of the done() method. "
274
- "Replace with `not task.done()`.",
275
- DeprecationWarning,
276
- stacklevel=2,
277
- )
278
- return not self.done()
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()
279
494
 
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
495
 
288
- # Hand the coroutine back to the scheduler trampoline.
289
- return (yield self)
496
+ def current_task() -> Task[object]:
497
+ """Return the currently running Task.
290
498
 
499
+ Raises:
500
+ RuntimeError: If no Task is running.
291
501
 
292
- class _RunningCoroutine(Task[T]):
502
+ .. versionadded:: 2.0
293
503
  """
294
- The result of calling a :any:`cocotb.coroutine` decorated coroutine.
504
+ task = cocotb._scheduler_inst._current_task
505
+ if task is None:
506
+ raise RuntimeError("No Task is currently running")
507
+ return task
295
508
 
296
- All this class does is provide some extra attributes.
297
509
 
298
- .. versionchanged:: 1.8.0
299
- Moved to the ``cocotb.task`` module.
300
- """
510
+ class TaskComplete(Trigger, Generic[ResultType]):
511
+ r"""Fires when a :class:`~cocotb.task.Task` completes.
301
512
 
302
- def __init__(self, inst, parent):
303
- super().__init__(inst)
304
- self._parent = parent
305
- self.__doc__ = parent._func.__doc__
306
- self.module = parent._func.__module__
307
- self.funcname = parent._func.__name__
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.
308
515
 
516
+ .. warning::
517
+ This class cannot be instantiated in the normal way.
518
+ You must use :attr:`.Task.complete`.
309
519
 
310
- class _RunningTest(_RunningCoroutine[T]):
520
+ .. versionadded:: 2.0
311
521
  """
312
- The result of calling a :class:`cocotb.test` decorated object.
313
522
 
314
- All this class does is change ``__name__`` to show "Test" instead of "Task".
523
+ _task: Task[ResultType]
315
524
 
316
- .. versionchanged:: 1.8.0
317
- Moved to the ``cocotb.task`` module.
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.
318
577
  """
319
578
 
320
- _name: str = "Test"
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
321
587
 
322
- def __init__(self, inst, parent):
323
- super().__init__(inst, parent)
324
- self.__name__ = f"{type(self)._name} {self.funcname}"
325
- self.__qualname__ = self.__name__
588
+ def __await__(self) -> Generator["Self", None, ResultType]: # type: ignore[override]
589
+ yield self
590
+ return self._task.result()