cocotb 2.0.1__cp38-cp38-win32.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.
- cocotb/_ANSI.py +65 -0
- cocotb/__init__.py +127 -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 +150 -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 +103 -0
- cocotb/clock.py +419 -0
- cocotb/debug.py +24 -0
- cocotb/handle.py +1752 -0
- 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/cocotbvhpi_nvc.dll +0 -0
- cocotb/libs/cocotbvhpi_nvc.exp +0 -0
- cocotb/libs/cocotbvhpi_nvc.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/logging.py +417 -0
- cocotb/py.typed +0 -0
- cocotb/queue.py +235 -0
- cocotb/regression.py +900 -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/aldec.exp +0 -0
- cocotb/share/def/aldec.lib +0 -0
- cocotb/share/def/ghdl.def +43 -0
- cocotb/share/def/ghdl.exp +0 -0
- cocotb/share/def/ghdl.lib +0 -0
- cocotb/share/def/icarus.def +43 -0
- cocotb/share/def/icarus.exp +0 -0
- cocotb/share/def/icarus.lib +0 -0
- cocotb/share/def/modelsim.def +138 -0
- cocotb/share/def/modelsim.exp +0 -0
- cocotb/share/def/modelsim.lib +0 -0
- cocotb/share/def/nvcvhpi.def +18 -0
- cocotb/share/def/nvcvhpi.exp +0 -0
- cocotb/share/def/nvcvhpi.lib +0 -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 +238 -0
- cocotb/simulator.cp38-win32.exp +0 -0
- cocotb/simulator.cp38-win32.lib +0 -0
- cocotb/simulator.cp38-win32.pyd +0 -0
- cocotb/simulator.cp38-win32.pyd.2.config +8 -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 +297 -0
- cocotb/types/_indexing.py +17 -0
- cocotb/types/_logic.py +333 -0
- cocotb/types/_logic_array.py +884 -0
- cocotb/types/_range.py +197 -0
- cocotb/types/_resolve.py +76 -0
- cocotb/utils.py +110 -0
- cocotb-2.0.1.dist-info/LICENSE +29 -0
- cocotb-2.0.1.dist-info/METADATA +44 -0
- cocotb-2.0.1.dist-info/RECORD +152 -0
- cocotb-2.0.1.dist-info/WHEEL +5 -0
- cocotb-2.0.1.dist-info/entry_points.txt +2 -0
- cocotb-2.0.1.dist-info/top_level.txt +18 -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 +242 -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 +2001 -0
- cocotb_tools/sim_versions.py +140 -0
- pygpi/__init__.py +0 -0
- pygpi/entry.py +42 -0
- pygpi/py.typed +0 -0
cocotb/_bridge.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
import functools
|
|
5
|
+
import logging
|
|
6
|
+
import threading
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Callable,
|
|
11
|
+
Coroutine,
|
|
12
|
+
Generic,
|
|
13
|
+
TypeVar,
|
|
14
|
+
Union,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
import cocotb
|
|
18
|
+
from cocotb import debug
|
|
19
|
+
from cocotb._base_triggers import Event, Trigger
|
|
20
|
+
from cocotb._exceptions import InternalError
|
|
21
|
+
from cocotb._py_compat import ParamSpec
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from cocotb._outcomes import Outcome
|
|
25
|
+
|
|
26
|
+
P = ParamSpec("P")
|
|
27
|
+
|
|
28
|
+
Result = TypeVar("Result")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resume(
|
|
32
|
+
func: "Callable[P, Coroutine[Trigger, None, Result]]",
|
|
33
|
+
) -> "Callable[P, Result]":
|
|
34
|
+
"""Converts a coroutine function into a blocking function.
|
|
35
|
+
|
|
36
|
+
This allows a :term:`coroutine function` that awaits cocotb triggers to be
|
|
37
|
+
called from a :term:`blocking function` converted by :func:`.bridge`.
|
|
38
|
+
This completes the bridge through non-:keyword:`async` code.
|
|
39
|
+
|
|
40
|
+
When a converted coroutine function is called the current function blocks until the
|
|
41
|
+
converted function exits.
|
|
42
|
+
|
|
43
|
+
Results of the converted function are returned from the function call.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
func: The :term:`coroutine function` to convert into a :term:`blocking function`.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
*func* as a :term:`blocking function`.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
RuntimeError:
|
|
53
|
+
If the function that is returned is subsequently called from a
|
|
54
|
+
thread that was not started with :class:`.bridge`.
|
|
55
|
+
|
|
56
|
+
.. versionchanged:: 2.0
|
|
57
|
+
Renamed from ``function``.
|
|
58
|
+
No longer implemented as a type.
|
|
59
|
+
The ``log`` attribute is no longer available.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
@functools.wraps(func)
|
|
63
|
+
def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> Result:
|
|
64
|
+
return cocotb._scheduler_inst._queue_function(func(*args, **kwargs))
|
|
65
|
+
|
|
66
|
+
return wrapper
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def bridge(
|
|
70
|
+
func: "Callable[P, Result]",
|
|
71
|
+
) -> "Callable[P, Coroutine[Trigger, None, Result]]":
|
|
72
|
+
r"""Converts a blocking function into a coroutine function.
|
|
73
|
+
|
|
74
|
+
This function converts a :term:`blocking function` into a :term:`coroutine function`
|
|
75
|
+
with the expectation that the function being converted is intended to call a
|
|
76
|
+
:func:`.resume` converted function. This creates a bridge through
|
|
77
|
+
non-:keyword:`async` code for code wanting to eventually :keyword:`await` on cocotb
|
|
78
|
+
triggers.
|
|
79
|
+
|
|
80
|
+
When a converted function call is used in an :keyword:`await` statement, the current
|
|
81
|
+
Task blocks until the converted function finishes.
|
|
82
|
+
|
|
83
|
+
Results of the converted function are returned from the :keyword:`await` expression.
|
|
84
|
+
|
|
85
|
+
.. note::
|
|
86
|
+
Bridge threads *must* either finish or block on a :func:`.resume`
|
|
87
|
+
converted function before control is given back to the simulator.
|
|
88
|
+
This is done to prevent any code from executing in parallel with the simulation.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
func: The :term:`blocking function` to convert into a :term:`coroutine function`.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
*func* as a :term:`coroutine function`.
|
|
95
|
+
|
|
96
|
+
.. versionchanged:: 2.0
|
|
97
|
+
Renamed from ``external``.
|
|
98
|
+
No longer implemented as a type.
|
|
99
|
+
The ``log`` attribute is no longer available.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
@functools.wraps(func)
|
|
103
|
+
def wrapper(
|
|
104
|
+
*args: "P.args", **kwargs: "P.kwargs"
|
|
105
|
+
) -> Coroutine[Trigger, None, Result]:
|
|
106
|
+
return cocotb._scheduler_inst._run_in_executor(func, *args, **kwargs)
|
|
107
|
+
|
|
108
|
+
return wrapper
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class external_state(IntEnum):
|
|
112
|
+
INIT = 0
|
|
113
|
+
RUNNING = 1
|
|
114
|
+
PAUSED = 2
|
|
115
|
+
EXITED = 3
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class external_waiter(Generic[Result]):
|
|
119
|
+
def __init__(self) -> None:
|
|
120
|
+
self._outcome: Union[Outcome[Result], None] = None
|
|
121
|
+
self.thread: threading.Thread
|
|
122
|
+
self.event = Event()
|
|
123
|
+
self.state = external_state.INIT
|
|
124
|
+
self.cond = threading.Condition()
|
|
125
|
+
self._log = logging.getLogger(f"cocotb.bridge.0x{id(self):x}")
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def result(self) -> Result:
|
|
129
|
+
if self._outcome is None:
|
|
130
|
+
raise InternalError("Got result of external before it finished")
|
|
131
|
+
return self._outcome.get()
|
|
132
|
+
|
|
133
|
+
def _propagate_state(self, new_state: external_state) -> None:
|
|
134
|
+
with self.cond:
|
|
135
|
+
if debug.debug:
|
|
136
|
+
self._log.debug(
|
|
137
|
+
f"Changing state from {self.state} -> {new_state} from {threading.current_thread()}"
|
|
138
|
+
)
|
|
139
|
+
self.state = new_state
|
|
140
|
+
self.cond.notify()
|
|
141
|
+
|
|
142
|
+
def thread_done(self) -> None:
|
|
143
|
+
if debug.debug:
|
|
144
|
+
self._log.debug(f"Thread finished from {threading.current_thread()}")
|
|
145
|
+
self._propagate_state(external_state.EXITED)
|
|
146
|
+
|
|
147
|
+
def thread_suspend(self) -> None:
|
|
148
|
+
self._propagate_state(external_state.PAUSED)
|
|
149
|
+
|
|
150
|
+
def thread_start(self) -> None:
|
|
151
|
+
if self.state > external_state.INIT:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if not self.thread.is_alive():
|
|
155
|
+
self._propagate_state(external_state.RUNNING)
|
|
156
|
+
self.thread.start()
|
|
157
|
+
|
|
158
|
+
def thread_resume(self) -> None:
|
|
159
|
+
self._propagate_state(external_state.RUNNING)
|
|
160
|
+
|
|
161
|
+
def thread_wait(self) -> external_state:
|
|
162
|
+
if debug.debug:
|
|
163
|
+
self._log.debug(
|
|
164
|
+
f"Waiting for the condition lock {threading.current_thread()}"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
with self.cond:
|
|
168
|
+
while self.state == external_state.RUNNING:
|
|
169
|
+
self.cond.wait()
|
|
170
|
+
|
|
171
|
+
if debug.debug:
|
|
172
|
+
if self.state == external_state.EXITED:
|
|
173
|
+
self._log.debug(
|
|
174
|
+
f"Thread {self.thread} has exited from {threading.current_thread()}"
|
|
175
|
+
)
|
|
176
|
+
elif self.state == external_state.PAUSED:
|
|
177
|
+
self._log.debug(
|
|
178
|
+
f"Thread {self.thread} has called yield from {threading.current_thread()}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if self.state == external_state.INIT:
|
|
182
|
+
raise Exception(
|
|
183
|
+
f"Thread {self.thread} state was not allowed from {threading.current_thread()}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return self.state
|
cocotb/_decorators.py
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Copyright (c) 2013 Potential Ventures Ltd
|
|
3
|
+
# Copyright (c) 2013 SolarFlare Communications Inc
|
|
4
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
5
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import inspect
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from itertools import product
|
|
11
|
+
from typing import (
|
|
12
|
+
Callable,
|
|
13
|
+
Coroutine,
|
|
14
|
+
Dict,
|
|
15
|
+
Iterable,
|
|
16
|
+
List,
|
|
17
|
+
Optional,
|
|
18
|
+
Sequence,
|
|
19
|
+
Tuple,
|
|
20
|
+
Type,
|
|
21
|
+
Union,
|
|
22
|
+
cast,
|
|
23
|
+
overload,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from cocotb._base_triggers import Trigger
|
|
27
|
+
from cocotb._py_compat import Protocol, TypeAlias
|
|
28
|
+
from cocotb._typing import TimeUnit
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Test:
|
|
32
|
+
"""A cocotb test in a regression.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
func:
|
|
36
|
+
The test function object.
|
|
37
|
+
|
|
38
|
+
name:
|
|
39
|
+
The name of the test function.
|
|
40
|
+
Defaults to ``func.__qualname__`` (the dotted path to the test function in the module).
|
|
41
|
+
|
|
42
|
+
module:
|
|
43
|
+
The name of the module containing the test function.
|
|
44
|
+
Defaults to ``func.__module__`` (the name of the module containing the test function).
|
|
45
|
+
|
|
46
|
+
doc:
|
|
47
|
+
The docstring for the test.
|
|
48
|
+
Defaults to ``func.__doc__`` (the docstring of the test function).
|
|
49
|
+
|
|
50
|
+
timeout_time:
|
|
51
|
+
Simulation time duration before the test is forced to fail with a :exc:`~cocotb.triggers.SimTimeoutError`.
|
|
52
|
+
|
|
53
|
+
timeout_unit:
|
|
54
|
+
Unit of ``timeout_time``, accepts any unit that :class:`~cocotb.triggers.Timer` does.
|
|
55
|
+
|
|
56
|
+
expect_fail:
|
|
57
|
+
If ``True`` and the test fails a functional check via an :keyword:`assert` statement, :func:`pytest.raises`,
|
|
58
|
+
:func:`pytest.warns`, or :func:`pytest.deprecated_call`, the test is considered to have passed.
|
|
59
|
+
If ``True`` and the test passes successfully, the test is considered to have failed.
|
|
60
|
+
|
|
61
|
+
expect_error:
|
|
62
|
+
Mark the result as a pass only if one of the given exception types is raised in the test.
|
|
63
|
+
|
|
64
|
+
skip:
|
|
65
|
+
Don't execute this test as part of the regression.
|
|
66
|
+
The test can still be run manually by setting :envvar:`COCOTB_TESTCASE`.
|
|
67
|
+
|
|
68
|
+
stage:
|
|
69
|
+
Order tests logically into stages.
|
|
70
|
+
Tests from earlier stages are run before tests from later stages.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
*,
|
|
76
|
+
func: Callable[..., Coroutine[Trigger, None, None]],
|
|
77
|
+
name: Optional[str] = None,
|
|
78
|
+
module: Optional[str] = None,
|
|
79
|
+
doc: Optional[str] = None,
|
|
80
|
+
timeout_time: Optional[float] = None,
|
|
81
|
+
timeout_unit: TimeUnit = "step",
|
|
82
|
+
expect_fail: bool = False,
|
|
83
|
+
expect_error: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = (),
|
|
84
|
+
skip: bool = False,
|
|
85
|
+
stage: int = 0,
|
|
86
|
+
) -> None:
|
|
87
|
+
self.func: Callable[..., Coroutine[Trigger, None, None]] = func
|
|
88
|
+
self.timeout_time = timeout_time
|
|
89
|
+
self.timeout_unit = timeout_unit
|
|
90
|
+
self.expect_fail = expect_fail
|
|
91
|
+
if isinstance(expect_error, type):
|
|
92
|
+
expect_error = (expect_error,)
|
|
93
|
+
self.expect_error = expect_error
|
|
94
|
+
self.skip = skip
|
|
95
|
+
self.stage = stage
|
|
96
|
+
self.name = self.func.__qualname__ if name is None else name
|
|
97
|
+
self.module = self.func.__module__ if module is None else module
|
|
98
|
+
self.doc = self.func.__doc__ if doc is None else doc
|
|
99
|
+
if self.doc is not None:
|
|
100
|
+
# cleanup docstring using `trim` function from PEP257
|
|
101
|
+
self.doc = inspect.cleandoc(self.doc)
|
|
102
|
+
self.fullname = f"{self.module}.{self.name}"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
TestFuncType: TypeAlias = Callable[..., Coroutine[Trigger, None, None]]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Parameterized:
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
test_function: TestFuncType,
|
|
112
|
+
options: List[
|
|
113
|
+
Union[
|
|
114
|
+
Tuple[str, Sequence[object]],
|
|
115
|
+
Tuple[Sequence[str], Sequence[Sequence[object]]],
|
|
116
|
+
]
|
|
117
|
+
],
|
|
118
|
+
) -> None:
|
|
119
|
+
self.test_template = Test(func=test_function)
|
|
120
|
+
self.options = options
|
|
121
|
+
# we are assuming the input checking is done in parametrize()
|
|
122
|
+
|
|
123
|
+
self._option_reprs: Dict[str, List[str]] = {}
|
|
124
|
+
|
|
125
|
+
for name, values in options:
|
|
126
|
+
if isinstance(name, str):
|
|
127
|
+
self._option_reprs[name] = _reprs(values)
|
|
128
|
+
else:
|
|
129
|
+
# transform to Dict[name, values]
|
|
130
|
+
transformed: Dict[str, List[object]] = {}
|
|
131
|
+
for nam_idx, nam in enumerate(name):
|
|
132
|
+
transformed[nam] = []
|
|
133
|
+
for value_array in cast("Sequence[Sequence[object]]", values):
|
|
134
|
+
value = value_array[nam_idx]
|
|
135
|
+
transformed[nam].append(value)
|
|
136
|
+
for n, vs in transformed.items():
|
|
137
|
+
self._option_reprs[n] = _reprs(vs)
|
|
138
|
+
|
|
139
|
+
def generate_tests(self) -> Iterable[Test]:
|
|
140
|
+
test_func = self.test_template.func
|
|
141
|
+
test_func_name = self.test_template.name
|
|
142
|
+
|
|
143
|
+
# this value is a list of ranges of the same length as each set of values in self.options for passing to itertools.product
|
|
144
|
+
option_indexes = [range(len(option[1])) for option in self.options]
|
|
145
|
+
|
|
146
|
+
# go through the cartesian product of all values of all options
|
|
147
|
+
for selected_options in product(*option_indexes):
|
|
148
|
+
test_kwargs: Dict[str, object] = {}
|
|
149
|
+
test_name_pieces: List[str] = [test_func_name]
|
|
150
|
+
for option_idx, select_idx in enumerate(selected_options):
|
|
151
|
+
option_name, option_values = self.options[option_idx]
|
|
152
|
+
selected_value = option_values[select_idx]
|
|
153
|
+
|
|
154
|
+
if isinstance(option_name, str):
|
|
155
|
+
# single params per option
|
|
156
|
+
selected_value = cast("Sequence[object]", selected_value)
|
|
157
|
+
test_kwargs[option_name] = selected_value
|
|
158
|
+
test_name_pieces.append(
|
|
159
|
+
f"/{option_name}={self._option_reprs[option_name][select_idx]}"
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
# multiple params per option
|
|
163
|
+
selected_value = cast("Sequence[object]", selected_value)
|
|
164
|
+
for n, v in zip(option_name, selected_value):
|
|
165
|
+
test_kwargs[n] = v
|
|
166
|
+
test_name_pieces.append(
|
|
167
|
+
f"/{n}={self._option_reprs[n][select_idx]}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
parametrized_test_name = "".join(test_name_pieces)
|
|
171
|
+
|
|
172
|
+
# create wrapper function to bind kwargs
|
|
173
|
+
@functools.wraps(test_func)
|
|
174
|
+
async def _my_test(
|
|
175
|
+
dut: object, kwargs: Dict[str, object] = test_kwargs
|
|
176
|
+
) -> None:
|
|
177
|
+
await test_func(dut, **kwargs)
|
|
178
|
+
|
|
179
|
+
yield Test(
|
|
180
|
+
func=_my_test,
|
|
181
|
+
name=parametrized_test_name,
|
|
182
|
+
timeout_time=self.test_template.timeout_time,
|
|
183
|
+
timeout_unit=self.test_template.timeout_unit,
|
|
184
|
+
expect_fail=self.test_template.expect_fail,
|
|
185
|
+
expect_error=self.test_template.expect_error,
|
|
186
|
+
skip=self.test_template.skip,
|
|
187
|
+
stage=self.test_template.stage,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _reprs(values: Sequence[object]) -> List[str]:
|
|
192
|
+
result: List[str] = []
|
|
193
|
+
for value in values:
|
|
194
|
+
value_repr = _repr(value)
|
|
195
|
+
if value_repr is None:
|
|
196
|
+
# non-representable value in option, so default to index strings and give up
|
|
197
|
+
return [str(i) for i in range(len(values))]
|
|
198
|
+
else:
|
|
199
|
+
result.append(value_repr)
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _repr(v: object) -> Optional[str]:
|
|
204
|
+
if isinstance(v, Enum):
|
|
205
|
+
return v.name
|
|
206
|
+
elif isinstance(v, str):
|
|
207
|
+
if len(v) <= 10 and v.isidentifier():
|
|
208
|
+
return v
|
|
209
|
+
else:
|
|
210
|
+
return None
|
|
211
|
+
elif isinstance(v, (int, float, bool, type(None))):
|
|
212
|
+
return repr(v)
|
|
213
|
+
elif isinstance(v, type):
|
|
214
|
+
return v.__qualname__
|
|
215
|
+
elif hasattr(v, "__qualname__"):
|
|
216
|
+
return v.__qualname__
|
|
217
|
+
else:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TestDecoratorType(Protocol):
|
|
222
|
+
# TODO use position only argument for *obj* so we aren't tied to that name.
|
|
223
|
+
@overload
|
|
224
|
+
def __call__(self, obj: TestFuncType) -> Test: ...
|
|
225
|
+
@overload
|
|
226
|
+
def __call__(self, obj: Parameterized) -> Parameterized: ...
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@overload
|
|
230
|
+
def test(obj: TestFuncType) -> Test: ...
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@overload
|
|
234
|
+
def test(obj: Parameterized) -> Parameterized: ...
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@overload
|
|
238
|
+
def test(
|
|
239
|
+
*,
|
|
240
|
+
timeout_time: Optional[float] = None,
|
|
241
|
+
timeout_unit: TimeUnit = "step",
|
|
242
|
+
expect_fail: bool = False,
|
|
243
|
+
expect_error: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = (),
|
|
244
|
+
skip: bool = False,
|
|
245
|
+
stage: int = 0,
|
|
246
|
+
name: Optional[str] = None,
|
|
247
|
+
) -> TestDecoratorType: ...
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test(
|
|
251
|
+
obj: Optional[Union[TestFuncType, Parameterized]] = None,
|
|
252
|
+
*,
|
|
253
|
+
timeout_time: Optional[float] = None,
|
|
254
|
+
timeout_unit: TimeUnit = "step",
|
|
255
|
+
expect_fail: bool = False,
|
|
256
|
+
expect_error: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = (),
|
|
257
|
+
skip: bool = False,
|
|
258
|
+
stage: int = 0,
|
|
259
|
+
name: Optional[str] = None,
|
|
260
|
+
) -> Union[
|
|
261
|
+
Test,
|
|
262
|
+
Parameterized,
|
|
263
|
+
TestDecoratorType,
|
|
264
|
+
]:
|
|
265
|
+
r"""
|
|
266
|
+
Decorator to register a Callable which returns a Coroutine as a test.
|
|
267
|
+
|
|
268
|
+
The test decorator provides a test timeout, and allows us to mark tests as skipped or expecting errors or failures.
|
|
269
|
+
Tests are evaluated in the order they are defined in a test module.
|
|
270
|
+
|
|
271
|
+
Usage:
|
|
272
|
+
.. code-block:: python
|
|
273
|
+
|
|
274
|
+
@cocotb.test(timeout_time=10, timeout_unit="ms")
|
|
275
|
+
async def test_thing(dut): ...
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
timeout_time:
|
|
279
|
+
Simulation time duration before timeout occurs.
|
|
280
|
+
|
|
281
|
+
.. versionadded:: 1.3
|
|
282
|
+
|
|
283
|
+
.. note::
|
|
284
|
+
Test timeout is intended for protection against deadlock.
|
|
285
|
+
Users should use :class:`~cocotb.triggers.with_timeout` if they require a
|
|
286
|
+
more general-purpose timeout mechanism.
|
|
287
|
+
|
|
288
|
+
timeout_unit:
|
|
289
|
+
Units of timeout_time, accepts any units that :class:`~cocotb.triggers.Timer` does.
|
|
290
|
+
|
|
291
|
+
.. versionadded:: 1.3
|
|
292
|
+
|
|
293
|
+
.. versionchanged:: 2.0
|
|
294
|
+
Passing ``None`` as the *timeout_unit* argument was removed, use ``'step'`` instead.
|
|
295
|
+
|
|
296
|
+
expect_fail:
|
|
297
|
+
If ``True`` and the test fails a functional check via an :keyword:`assert` statement, :class:`pytest.raises`,
|
|
298
|
+
:class:`pytest.warns`, or :class:`pytest.deprecated_call` the test is considered to have passed.
|
|
299
|
+
If ``True`` and the test passes successfully, the test is considered to have failed.
|
|
300
|
+
|
|
301
|
+
expect_error:
|
|
302
|
+
Mark the result as a pass only if one of the exception types is raised in the test.
|
|
303
|
+
This is primarily for cocotb internal regression use for when a simulator error is expected.
|
|
304
|
+
|
|
305
|
+
Users are encouraged to use the following idiom instead::
|
|
306
|
+
|
|
307
|
+
@cocotb.test()
|
|
308
|
+
async def my_test(dut):
|
|
309
|
+
try:
|
|
310
|
+
await thing_that_should_fail()
|
|
311
|
+
except ExceptionIExpect:
|
|
312
|
+
pass
|
|
313
|
+
else:
|
|
314
|
+
assert False, "Exception did not occur"
|
|
315
|
+
|
|
316
|
+
.. versionchanged:: 1.3
|
|
317
|
+
Specific exception types can be expected
|
|
318
|
+
|
|
319
|
+
.. versionchanged:: 2.0
|
|
320
|
+
Passing a :class:`bool` value was removed.
|
|
321
|
+
Pass a specific :class:`Exception` or a tuple of Exceptions instead.
|
|
322
|
+
|
|
323
|
+
skip:
|
|
324
|
+
Don't execute this test as part of the regression. Test can still be run
|
|
325
|
+
manually by setting :make:var:`COCOTB_TESTCASE`.
|
|
326
|
+
|
|
327
|
+
stage:
|
|
328
|
+
Order tests logically into stages, where multiple tests can share a stage.
|
|
329
|
+
Defaults to 0.
|
|
330
|
+
|
|
331
|
+
name:
|
|
332
|
+
Override the default name of the test.
|
|
333
|
+
The default test name is the :meth:`__qualname__` of the decorated test function.
|
|
334
|
+
|
|
335
|
+
.. versionadded:: 2.0
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
The test function to which the decorator is applied.
|
|
339
|
+
|
|
340
|
+
.. note::
|
|
341
|
+
|
|
342
|
+
To extend the test decorator, use the following template to create a new
|
|
343
|
+
``cocotb.test``\-like wrapper.
|
|
344
|
+
|
|
345
|
+
.. code-block:: python
|
|
346
|
+
|
|
347
|
+
import functools
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_extender(**decorator_kwargs):
|
|
351
|
+
def decorator(obj):
|
|
352
|
+
@cocotb.test(**decorator_kwargs)
|
|
353
|
+
@functools.wraps(obj)
|
|
354
|
+
async def test(dut, **test_kwargs):
|
|
355
|
+
# your code here
|
|
356
|
+
...
|
|
357
|
+
|
|
358
|
+
return obj
|
|
359
|
+
|
|
360
|
+
return decorator
|
|
361
|
+
|
|
362
|
+
.. versionchanged:: 2.0
|
|
363
|
+
Support using decorator on test function without supplying parameters first.
|
|
364
|
+
|
|
365
|
+
Assumes all default values for the test parameters.
|
|
366
|
+
|
|
367
|
+
.. code-block:: python
|
|
368
|
+
|
|
369
|
+
@cocotb.test
|
|
370
|
+
async def test_thing(dut): ...
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
.. versionchanged:: 2.0
|
|
374
|
+
Decorated tests now return the decorated object.
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
if obj is not None:
|
|
378
|
+
if isinstance(obj, Parameterized):
|
|
379
|
+
return obj
|
|
380
|
+
else:
|
|
381
|
+
return Test(func=obj)
|
|
382
|
+
|
|
383
|
+
@overload
|
|
384
|
+
def wrapper(obj: TestFuncType) -> Test: ...
|
|
385
|
+
|
|
386
|
+
@overload
|
|
387
|
+
def wrapper(obj: Parameterized) -> Parameterized: ...
|
|
388
|
+
|
|
389
|
+
def wrapper(obj: Union[TestFuncType, Parameterized]) -> Union[Test, Parameterized]:
|
|
390
|
+
if isinstance(obj, Parameterized):
|
|
391
|
+
obj.test_template = Test(
|
|
392
|
+
func=obj.test_template.func,
|
|
393
|
+
name=name,
|
|
394
|
+
timeout_time=timeout_time,
|
|
395
|
+
timeout_unit=timeout_unit,
|
|
396
|
+
expect_fail=expect_fail,
|
|
397
|
+
expect_error=expect_error,
|
|
398
|
+
skip=skip,
|
|
399
|
+
stage=stage,
|
|
400
|
+
)
|
|
401
|
+
return obj
|
|
402
|
+
else:
|
|
403
|
+
return Test(
|
|
404
|
+
func=obj,
|
|
405
|
+
name=name,
|
|
406
|
+
timeout_time=timeout_time,
|
|
407
|
+
timeout_unit=timeout_unit,
|
|
408
|
+
expect_fail=expect_fail,
|
|
409
|
+
expect_error=expect_error,
|
|
410
|
+
skip=skip,
|
|
411
|
+
stage=stage,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
return wrapper
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def parametrize(
|
|
418
|
+
*options_by_tuple: Union[
|
|
419
|
+
Tuple[str, Sequence[object]], Tuple[Sequence[str], Sequence[Sequence[object]]]
|
|
420
|
+
],
|
|
421
|
+
**options_by_name: Sequence[object],
|
|
422
|
+
) -> Callable[[TestFuncType], Parameterized]:
|
|
423
|
+
"""Decorator to generate parametrized tests from a single test function.
|
|
424
|
+
|
|
425
|
+
Decorates a test function with named test parameters.
|
|
426
|
+
The call to ``parametrize`` should include the name of each test parameter and the possible values each parameter can hold.
|
|
427
|
+
This will generate a test for each of the Cartesian products of the parameters and their values.
|
|
428
|
+
|
|
429
|
+
.. code-block:: python
|
|
430
|
+
|
|
431
|
+
@cocotb.test(
|
|
432
|
+
skip=False,
|
|
433
|
+
)
|
|
434
|
+
@cocotb.parametrize(
|
|
435
|
+
arg1=[0, 1],
|
|
436
|
+
arg2=["a", "b"],
|
|
437
|
+
)
|
|
438
|
+
async def my_test(arg1: int, arg2: str) -> None: ...
|
|
439
|
+
|
|
440
|
+
The above is equivalent to the following.
|
|
441
|
+
|
|
442
|
+
.. code-block:: python
|
|
443
|
+
|
|
444
|
+
@cocotb.test(skip=False)
|
|
445
|
+
async def my_test_0_a() -> None:
|
|
446
|
+
arg1, arg2 = 0, "a"
|
|
447
|
+
...
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@cocotb.test(skip=False)
|
|
451
|
+
async def my_test_0_b() -> None:
|
|
452
|
+
arg1, arg2 = 0, "b"
|
|
453
|
+
...
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@cocotb.test(skip=False)
|
|
457
|
+
async def my_test_1_a() -> None:
|
|
458
|
+
arg1, arg2 = 1, "a"
|
|
459
|
+
...
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@cocotb.test(skip=False)
|
|
463
|
+
async def my_test_1_b() -> None:
|
|
464
|
+
arg1, arg2 = 1, "b"
|
|
465
|
+
...
|
|
466
|
+
|
|
467
|
+
Options can also be specified in much the same way that :meth:`TestFactory.add_option <cocotb.regression.TestFactory.add_option>` can,
|
|
468
|
+
either by supplying tuples of the parameter name to values,
|
|
469
|
+
or a sequence of variable names and a sequence of values.
|
|
470
|
+
|
|
471
|
+
.. code-block:: python
|
|
472
|
+
|
|
473
|
+
@cocotb.parametrize(
|
|
474
|
+
("arg1", [0, 1]),
|
|
475
|
+
(("arg2", "arg3"), [(1, 2), (3, 4)]),
|
|
476
|
+
)
|
|
477
|
+
async def my_test_2(arg1: int, arg2: int, arg3: int) -> None: ...
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
options_by_tuple:
|
|
481
|
+
Tuple of parameter name to sequence of values for that parameter,
|
|
482
|
+
or tuple of sequence of parameter names to sequence of sequences of values for that pack of parameters.
|
|
483
|
+
|
|
484
|
+
options_by_name:
|
|
485
|
+
Mapping of parameter name to sequence of values for that parameter.
|
|
486
|
+
|
|
487
|
+
.. versionadded:: 2.0
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
# check good inputs
|
|
491
|
+
for i, option_by_tuple in enumerate(options_by_tuple):
|
|
492
|
+
if len(option_by_tuple) != 2:
|
|
493
|
+
raise ValueError(
|
|
494
|
+
f"Invalid option tuple {i}, expected exactly two fields `(name, values)`"
|
|
495
|
+
)
|
|
496
|
+
name, values = option_by_tuple
|
|
497
|
+
if not isinstance(name, str):
|
|
498
|
+
for n in name:
|
|
499
|
+
if not n.isidentifier():
|
|
500
|
+
raise ValueError("Option names must be valid Python identifiers")
|
|
501
|
+
values = cast("Sequence[Sequence[object]]", values)
|
|
502
|
+
for value in values:
|
|
503
|
+
if len(name) != len(value):
|
|
504
|
+
raise ValueError(
|
|
505
|
+
f"Invalid option tuple {i}, mismatching number of parameters ({name}) and values ({value})"
|
|
506
|
+
)
|
|
507
|
+
elif not name.isidentifier():
|
|
508
|
+
raise ValueError("Option names must be valid Python identifiers")
|
|
509
|
+
|
|
510
|
+
options = [*options_by_tuple, *options_by_name.items()]
|
|
511
|
+
|
|
512
|
+
def wrapper(f: TestFuncType) -> Parameterized:
|
|
513
|
+
return Parameterized(f, options)
|
|
514
|
+
|
|
515
|
+
return wrapper
|