cocotb 1.9.2__cp313-cp313-win32.whl → 2.0.0rc2__cp313-cp313-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.
Potentially problematic release.
This version of cocotb might be problematic. Click here for more details.
- cocotb/_ANSI.py +65 -0
- cocotb/__init__.py +81 -327
- cocotb/_base_triggers.py +515 -0
- cocotb/_bridge.py +186 -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 +385 -0
- cocotb/_init.py +301 -0
- cocotb/_outcomes.py +54 -0
- cocotb/_profiling.py +46 -0
- cocotb/_py_compat.py +114 -29
- 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 +3 -7
- cocotb/_xunit_reporter.py +66 -0
- cocotb/clock.py +353 -108
- cocotb/debug.py +24 -0
- cocotb/handle.py +1370 -793
- 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/logging.py +424 -0
- cocotb/queue.py +103 -57
- cocotb/regression.py +680 -717
- 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 +9 -32
- cocotb/share/include/embed.h +7 -30
- cocotb/share/include/gpi.h +331 -137
- cocotb/share/include/gpi_logging.h +221 -142
- cocotb/share/include/py_gpi_logging.h +8 -5
- cocotb/share/include/vpi_user_ext.h +4 -26
- cocotb/share/lib/verilator/verilator.cpp +80 -67
- cocotb/simtime.py +230 -0
- cocotb/simulator.cp313-win32.exp +0 -0
- cocotb/simulator.cp313-win32.lib +0 -0
- cocotb/simulator.cp313-win32.pyd +0 -0
- cocotb/simulator.pyi +107 -0
- cocotb/task.py +478 -213
- cocotb/triggers.py +55 -1092
- cocotb/types/__init__.py +28 -47
- cocotb/types/_abstract_array.py +151 -0
- cocotb/types/_array.py +295 -0
- cocotb/types/_indexing.py +17 -0
- cocotb/types/_logic.py +333 -0
- cocotb/types/_logic_array.py +868 -0
- cocotb/types/{range.py → _range.py} +47 -48
- cocotb/types/_resolve.py +76 -0
- cocotb/utils.py +58 -646
- cocotb-2.0.0rc2.dist-info/METADATA +60 -0
- cocotb-2.0.0rc2.dist-info/RECORD +146 -0
- {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info}/WHEEL +1 -1
- cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
- {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.dist-info/licenses}/LICENSE +1 -0
- {cocotb-1.9.2.dist-info → cocotb-2.0.0rc2.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 +77 -55
- {cocotb/share → cocotb_tools}/makefiles/Makefile.sim +16 -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_tools/runner.py +1868 -0
- cocotb/_sim_versions.py → cocotb_tools/sim_versions.py +16 -21
- pygpi/entry.py +34 -18
- pygpi/py.typed +0 -0
- cocotb/ANSI.py +0 -92
- cocotb/binary.py +0 -858
- cocotb/config.py +0 -289
- cocotb/decorators.py +0 -332
- cocotb/log.py +0 -303
- cocotb/memdebug.py +0 -35
- cocotb/outcomes.py +0 -56
- cocotb/runner.py +0 -1400
- 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 → cocotb_tools}/_vendor/distutils_version.py +0 -0
cocotb/_test_factory.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import inspect
|
|
7
|
+
import logging
|
|
8
|
+
import warnings
|
|
9
|
+
from itertools import product
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Callable,
|
|
13
|
+
Coroutine,
|
|
14
|
+
Dict,
|
|
15
|
+
Optional,
|
|
16
|
+
Sequence,
|
|
17
|
+
Tuple,
|
|
18
|
+
Type,
|
|
19
|
+
Union,
|
|
20
|
+
cast,
|
|
21
|
+
overload,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from cocotb._base_triggers import Trigger
|
|
25
|
+
from cocotb._decorators import Test
|
|
26
|
+
from cocotb._typing import TimeUnit
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from types import FrameType, FunctionType
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestFactory:
|
|
33
|
+
"""Factory to automatically generate tests.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
test_function: A Callable that returns the test Coroutine.
|
|
37
|
+
Must take *dut* as the first argument.
|
|
38
|
+
*args: Remaining arguments are passed directly to the test function.
|
|
39
|
+
Note that these arguments are not varied. An argument that
|
|
40
|
+
varies with each test must be a keyword argument to the
|
|
41
|
+
test function.
|
|
42
|
+
**kwargs: Remaining keyword arguments are passed directly to the test function.
|
|
43
|
+
Note that these arguments are not varied. An argument that
|
|
44
|
+
varies with each test must be a keyword argument to the
|
|
45
|
+
test function.
|
|
46
|
+
|
|
47
|
+
Assuming we have a common test function that will run a test. This test
|
|
48
|
+
function will take keyword arguments (for example generators for each of
|
|
49
|
+
the input interfaces) and generate tests that call the supplied function.
|
|
50
|
+
|
|
51
|
+
This Factory allows us to generate sets of tests based on the different
|
|
52
|
+
permutations of the possible arguments to the test function.
|
|
53
|
+
|
|
54
|
+
For example, if we have a module that takes backpressure, has two configurable
|
|
55
|
+
features where enabling ``feature_b`` requires ``feature_a`` to be active, and
|
|
56
|
+
need to test against data generation routines ``gen_a`` and ``gen_b``:
|
|
57
|
+
|
|
58
|
+
>>> tf = TestFactory(test_function=run_test)
|
|
59
|
+
>>> tf.add_option(name="data_in", optionlist=[gen_a, gen_b])
|
|
60
|
+
>>> tf.add_option("backpressure", [None, random_backpressure])
|
|
61
|
+
>>> tf.add_option(
|
|
62
|
+
... ("feature_a", "feature_b"), [(False, False), (True, False), (True, True)]
|
|
63
|
+
... )
|
|
64
|
+
>>> tf.generate_tests()
|
|
65
|
+
|
|
66
|
+
We would get the following tests:
|
|
67
|
+
|
|
68
|
+
* ``gen_a`` with no backpressure and both features disabled
|
|
69
|
+
* ``gen_a`` with no backpressure and only ``feature_a`` enabled
|
|
70
|
+
* ``gen_a`` with no backpressure and both features enabled
|
|
71
|
+
* ``gen_a`` with ``random_backpressure`` and both features disabled
|
|
72
|
+
* ``gen_a`` with ``random_backpressure`` and only ``feature_a`` enabled
|
|
73
|
+
* ``gen_a`` with ``random_backpressure`` and both features enabled
|
|
74
|
+
* ``gen_b`` with no backpressure and both features disabled
|
|
75
|
+
* ``gen_b`` with no backpressure and only ``feature_a`` enabled
|
|
76
|
+
* ``gen_b`` with no backpressure and both features enabled
|
|
77
|
+
* ``gen_b`` with ``random_backpressure`` and both features disabled
|
|
78
|
+
* ``gen_b`` with ``random_backpressure`` and only ``feature_a`` enabled
|
|
79
|
+
* ``gen_b`` with ``random_backpressure`` and both features enabled
|
|
80
|
+
|
|
81
|
+
The tests are appended to the calling module for auto-discovery.
|
|
82
|
+
|
|
83
|
+
Tests are simply named ``test_function_N``. The docstring for the test (hence
|
|
84
|
+
the test description) includes the name and description of each generator.
|
|
85
|
+
|
|
86
|
+
.. versionchanged:: 1.5
|
|
87
|
+
Groups of options are now supported
|
|
88
|
+
|
|
89
|
+
.. versionchanged:: 2.0
|
|
90
|
+
You can now pass :func:`cocotb.test` decorator arguments when generating tests.
|
|
91
|
+
|
|
92
|
+
.. deprecated:: 2.0
|
|
93
|
+
Use :func:`cocotb.parametrize` instead.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
test_function: Callable[..., Coroutine[Trigger, None, None]],
|
|
99
|
+
*args: object,
|
|
100
|
+
**kwargs: object,
|
|
101
|
+
) -> None:
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"TestFactory is deprecated, use `@cocotb.parametrize` instead",
|
|
104
|
+
DeprecationWarning,
|
|
105
|
+
stacklevel=2,
|
|
106
|
+
)
|
|
107
|
+
self.test_function = test_function
|
|
108
|
+
self.args = args
|
|
109
|
+
self.kwargs_constant = kwargs
|
|
110
|
+
self.kwargs: Dict[
|
|
111
|
+
Union[str, Sequence[str]],
|
|
112
|
+
Union[Sequence[object], Sequence[Sequence[object]]],
|
|
113
|
+
] = {}
|
|
114
|
+
self._log = logging.getLogger(f"TestFactory({self.test_function.__name__})")
|
|
115
|
+
|
|
116
|
+
@overload
|
|
117
|
+
def add_option(self, name: str, optionlist: Sequence[object]) -> None: ...
|
|
118
|
+
|
|
119
|
+
@overload
|
|
120
|
+
def add_option(
|
|
121
|
+
self, name: Sequence[str], optionlist: Sequence[Sequence[object]]
|
|
122
|
+
) -> None: ...
|
|
123
|
+
|
|
124
|
+
def add_option(
|
|
125
|
+
self,
|
|
126
|
+
name: Union[str, Sequence[str]],
|
|
127
|
+
optionlist: Union[Sequence[object], Sequence[Sequence[object]]],
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Add a named option to the test.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
name:
|
|
133
|
+
An option name, or an iterable of several option names. Passed to test as keyword arguments.
|
|
134
|
+
|
|
135
|
+
optionlist:
|
|
136
|
+
A list of possible options for this test knob.
|
|
137
|
+
If N names were specified, this must be a list of N-tuples or
|
|
138
|
+
lists, where each element specifies a value for its respective
|
|
139
|
+
option.
|
|
140
|
+
|
|
141
|
+
.. versionchanged:: 1.5
|
|
142
|
+
Groups of options are now supported
|
|
143
|
+
"""
|
|
144
|
+
if not isinstance(name, str):
|
|
145
|
+
optionlist = cast("Sequence[Sequence[object]]", optionlist)
|
|
146
|
+
for opt in optionlist:
|
|
147
|
+
if len(name) != len(opt):
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"Mismatch between number of options and number of option values in group"
|
|
150
|
+
)
|
|
151
|
+
self.kwargs[name] = optionlist
|
|
152
|
+
|
|
153
|
+
def generate_tests(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
prefix: Optional[str] = None,
|
|
157
|
+
postfix: Optional[str] = None,
|
|
158
|
+
stacklevel: int = 0,
|
|
159
|
+
name: Optional[str] = None,
|
|
160
|
+
timeout_time: Optional[float] = None,
|
|
161
|
+
timeout_unit: TimeUnit = "step",
|
|
162
|
+
expect_fail: bool = False,
|
|
163
|
+
expect_error: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = (),
|
|
164
|
+
skip: bool = False,
|
|
165
|
+
stage: int = 0,
|
|
166
|
+
) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Generate an exhaustive set of tests using the cartesian product of the
|
|
169
|
+
possible keyword arguments.
|
|
170
|
+
|
|
171
|
+
The generated tests are appended to the namespace of the calling
|
|
172
|
+
module.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
prefix:
|
|
176
|
+
Text string to append to start of ``test_function`` name when naming generated test cases.
|
|
177
|
+
This allows reuse of a single ``test_function`` with multiple :class:`TestFactories <.TestFactory>` without name clashes.
|
|
178
|
+
|
|
179
|
+
.. deprecated:: 2.0
|
|
180
|
+
Use the more flexible ``name`` field instead.
|
|
181
|
+
|
|
182
|
+
postfix:
|
|
183
|
+
Text string to append to end of ``test_function`` name when naming generated test cases.
|
|
184
|
+
This allows reuse of a single ``test_function`` with multiple :class:`TestFactories <.TestFactory>` without name clashes.
|
|
185
|
+
|
|
186
|
+
.. deprecated:: 2.0
|
|
187
|
+
Use the more flexible ``name`` field instead.
|
|
188
|
+
stacklevel:
|
|
189
|
+
Which stack level to add the generated tests to. This can be used to make a custom TestFactory wrapper.
|
|
190
|
+
|
|
191
|
+
name:
|
|
192
|
+
Passed as ``name`` argument to :func:`cocotb.test`.
|
|
193
|
+
|
|
194
|
+
.. versionadded:: 2.0
|
|
195
|
+
|
|
196
|
+
timeout_time:
|
|
197
|
+
Passed as ``timeout_time`` argument to :func:`cocotb.test`.
|
|
198
|
+
|
|
199
|
+
.. versionadded:: 2.0
|
|
200
|
+
|
|
201
|
+
timeout_unit:
|
|
202
|
+
Passed as ``timeout_unit`` argument to :func:`cocotb.test`.
|
|
203
|
+
|
|
204
|
+
.. versionadded:: 2.0
|
|
205
|
+
|
|
206
|
+
expect_fail:
|
|
207
|
+
Passed as ``expect_fail`` argument to :func:`cocotb.test`.
|
|
208
|
+
|
|
209
|
+
.. versionadded:: 2.0
|
|
210
|
+
|
|
211
|
+
expect_error:
|
|
212
|
+
Passed as ``expect_error`` argument to :func:`cocotb.test`.
|
|
213
|
+
|
|
214
|
+
.. versionadded:: 2.0
|
|
215
|
+
|
|
216
|
+
skip:
|
|
217
|
+
Passed as ``skip`` argument to :func:`cocotb.test`.
|
|
218
|
+
|
|
219
|
+
.. versionadded:: 2.0
|
|
220
|
+
|
|
221
|
+
stage:
|
|
222
|
+
Passed as ``stage`` argument to :func:`cocotb.test`.
|
|
223
|
+
|
|
224
|
+
.. versionadded:: 2.0
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
if prefix is not None:
|
|
228
|
+
warnings.warn(
|
|
229
|
+
"``prefix`` argument is deprecated. Use the more flexible ``name`` field instead.",
|
|
230
|
+
DeprecationWarning,
|
|
231
|
+
stacklevel=2,
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
prefix = ""
|
|
235
|
+
|
|
236
|
+
if postfix is not None:
|
|
237
|
+
warnings.warn(
|
|
238
|
+
"``postfix`` argument is deprecated. Use the more flexible ``name`` field instead.",
|
|
239
|
+
DeprecationWarning,
|
|
240
|
+
stacklevel=2,
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
postfix = ""
|
|
244
|
+
|
|
245
|
+
# trust the user puts a reasonable stacklevel in
|
|
246
|
+
glbs = cast("FrameType", inspect.stack()[stacklevel][0].f_back).f_globals
|
|
247
|
+
|
|
248
|
+
test_func_name = self.test_function.__qualname__ if name is None else name
|
|
249
|
+
|
|
250
|
+
for index, testoptions in enumerate(
|
|
251
|
+
dict(zip(self.kwargs, v)) for v in product(*self.kwargs.values())
|
|
252
|
+
):
|
|
253
|
+
name = f"{prefix}{test_func_name}{postfix}_{(index + 1):03d}"
|
|
254
|
+
doc: str = "Automatically generated test\n\n"
|
|
255
|
+
|
|
256
|
+
# preprocess testoptions to split tuples
|
|
257
|
+
testoptions_split: Dict[str, Sequence[object]] = {}
|
|
258
|
+
for optname, optvalue in testoptions.items():
|
|
259
|
+
if isinstance(optname, str):
|
|
260
|
+
optvalue = cast("Sequence[object]", optvalue)
|
|
261
|
+
testoptions_split[optname] = optvalue
|
|
262
|
+
else:
|
|
263
|
+
# previously checked in add_option; ensure nothing has changed
|
|
264
|
+
optvalue = cast("Sequence[Sequence[object]]", optvalue)
|
|
265
|
+
assert len(optname) == len(optvalue)
|
|
266
|
+
for n, v in zip(optname, optvalue):
|
|
267
|
+
testoptions_split[n] = v
|
|
268
|
+
|
|
269
|
+
for optname, optvalue in testoptions_split.items():
|
|
270
|
+
if callable(optvalue):
|
|
271
|
+
optvalue = cast("FunctionType", optvalue)
|
|
272
|
+
if optvalue.__doc__ is None:
|
|
273
|
+
desc = "No docstring supplied"
|
|
274
|
+
else:
|
|
275
|
+
desc = optvalue.__doc__.split("\n")[0]
|
|
276
|
+
doc += f"\t{optname}: {optvalue.__qualname__} ({desc})\n"
|
|
277
|
+
else:
|
|
278
|
+
doc += f"\t{optname}: {optvalue!r}\n"
|
|
279
|
+
|
|
280
|
+
kwargs = self.kwargs_constant.copy()
|
|
281
|
+
kwargs.update(testoptions_split)
|
|
282
|
+
|
|
283
|
+
@functools.wraps(self.test_function)
|
|
284
|
+
async def _my_test(dut: object, kwargs: Dict[str, object] = kwargs) -> None:
|
|
285
|
+
await self.test_function(dut, *self.args, **kwargs)
|
|
286
|
+
|
|
287
|
+
_my_test.__doc__ = doc
|
|
288
|
+
_my_test.__name__ = name
|
|
289
|
+
_my_test.__qualname__ = name
|
|
290
|
+
|
|
291
|
+
if name in glbs:
|
|
292
|
+
self._log.error(
|
|
293
|
+
"Overwriting %s in module %s. "
|
|
294
|
+
"This causes a previously defined testcase not to be run. "
|
|
295
|
+
"Consider using the `name`, `prefix`, or `postfix` arguments to augment the name.",
|
|
296
|
+
name,
|
|
297
|
+
glbs["__name__"],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
test = Test(
|
|
301
|
+
func=_my_test,
|
|
302
|
+
name=name,
|
|
303
|
+
module=glbs["__name__"],
|
|
304
|
+
timeout_time=timeout_time,
|
|
305
|
+
timeout_unit=timeout_unit,
|
|
306
|
+
expect_fail=expect_fail,
|
|
307
|
+
expect_error=expect_error,
|
|
308
|
+
skip=skip,
|
|
309
|
+
stage=stage,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
glbs[test.name] = test
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
"""Collection of functions to control a running test and related exceptions."""
|
|
5
|
+
|
|
6
|
+
from typing import NoReturn, Type, Union
|
|
7
|
+
|
|
8
|
+
Failed: Type[BaseException]
|
|
9
|
+
try:
|
|
10
|
+
import pytest
|
|
11
|
+
except ModuleNotFoundError:
|
|
12
|
+
Failed = AssertionError
|
|
13
|
+
else:
|
|
14
|
+
try:
|
|
15
|
+
with pytest.raises(Exception):
|
|
16
|
+
pass
|
|
17
|
+
except BaseException as _raises_e:
|
|
18
|
+
Failed = type(_raises_e)
|
|
19
|
+
else:
|
|
20
|
+
assert False, "pytest.raises doesn't raise an exception when it fails"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TestSuccess(BaseException):
|
|
24
|
+
"""Implementation of :func:`pass_test`.
|
|
25
|
+
|
|
26
|
+
Users are *not* intended to catch this exception type.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, msg: Union[str, None]) -> None:
|
|
30
|
+
super().__init__(msg)
|
|
31
|
+
self.msg = msg
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pass_test(msg: Union[str, None] = None) -> NoReturn:
|
|
35
|
+
"""Force a test to pass.
|
|
36
|
+
|
|
37
|
+
The test will end and enter termination phase when this is called.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
msg: The message to display when the test passes.
|
|
41
|
+
"""
|
|
42
|
+
raise TestSuccess(msg)
|
cocotb/_typing.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Licensed under the Revised BSD License, see LICENSE for details.
|
|
3
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
from cocotb._py_compat import Literal, TypeAlias
|
|
5
|
+
|
|
6
|
+
RoundMode: TypeAlias = Literal["error", "round", "ceil", "floor"]
|
|
7
|
+
TimeUnit: TypeAlias = Literal["step", "fs", "ps", "ns", "us", "ms", "sec"]
|
cocotb/_utils.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
"""Utilities for implementors."""
|
|
8
|
+
|
|
9
|
+
import traceback
|
|
10
|
+
import types
|
|
11
|
+
from enum import Enum, IntEnum
|
|
12
|
+
from functools import update_wrapper, wraps
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Any,
|
|
17
|
+
Iterable,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
Tuple,
|
|
21
|
+
Type,
|
|
22
|
+
TypeVar,
|
|
23
|
+
Union,
|
|
24
|
+
cast,
|
|
25
|
+
overload,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
ExceptionTuple = Tuple[
|
|
29
|
+
Type[BaseException], BaseException, TracebackType
|
|
30
|
+
] # TypeAlias in Python 3.10
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def remove_traceback_frames(
|
|
35
|
+
tb_or_exc: ExceptionTuple, frame_names: List[str]
|
|
36
|
+
) -> ExceptionTuple: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def remove_traceback_frames(
|
|
41
|
+
tb_or_exc: BaseException, frame_names: List[str]
|
|
42
|
+
) -> BaseException: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def remove_traceback_frames(
|
|
47
|
+
tb_or_exc: TracebackType, frame_names: List[str]
|
|
48
|
+
) -> TracebackType: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def remove_traceback_frames(
|
|
52
|
+
tb_or_exc: Union[ExceptionTuple, BaseException, TracebackType],
|
|
53
|
+
frame_names: List[str],
|
|
54
|
+
) -> Union[ExceptionTuple, BaseException, TracebackType]:
|
|
55
|
+
"""
|
|
56
|
+
Strip leading frames from a traceback
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
tb_or_exc:
|
|
60
|
+
Object to strip frames from. If an exception is passed, creates
|
|
61
|
+
a copy of the exception with a new shorter traceback. If a tuple
|
|
62
|
+
from `sys.exc_info` is passed, returns the same tuple with the
|
|
63
|
+
traceback shortened
|
|
64
|
+
frame_names:
|
|
65
|
+
Names of the frames to strip, which must be present at the top of the Traceback or Exception.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Traceback or Exception passed to the function with the *frame_names* stripped out.
|
|
69
|
+
"""
|
|
70
|
+
# self-invoking overloads
|
|
71
|
+
if isinstance(tb_or_exc, BaseException):
|
|
72
|
+
exc: BaseException = tb_or_exc
|
|
73
|
+
return exc.with_traceback(
|
|
74
|
+
remove_traceback_frames(
|
|
75
|
+
cast("TracebackType", exc.__traceback__), frame_names
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
elif isinstance(tb_or_exc, tuple):
|
|
79
|
+
exc_type, exc_value, exc_tb = tb_or_exc
|
|
80
|
+
exc_tb = remove_traceback_frames(exc_tb, frame_names)
|
|
81
|
+
return exc_type, exc_value, exc_tb
|
|
82
|
+
# base case
|
|
83
|
+
else:
|
|
84
|
+
tb: TracebackType = tb_or_exc
|
|
85
|
+
for frame_name in frame_names:
|
|
86
|
+
# the assert and cast are there assuming the frame_names being removed are correct
|
|
87
|
+
assert tb.tb_frame.f_code.co_name == frame_name
|
|
88
|
+
tb = cast("TracebackType", tb.tb_next)
|
|
89
|
+
return tb
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def walk_coro_stack(
|
|
93
|
+
coro: "types.CoroutineType[Any, Any, Any]",
|
|
94
|
+
) -> Iterable[Tuple[types.FrameType, int]]:
|
|
95
|
+
"""Walk down the coroutine stack, starting at *coro*.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
coro: The :class:`coroutine` object to traverse.
|
|
99
|
+
|
|
100
|
+
Yields:
|
|
101
|
+
Frame and line number of each frame in the coroutine.
|
|
102
|
+
"""
|
|
103
|
+
c: Optional[types.CoroutineType[Any, Any, Any]] = coro
|
|
104
|
+
while c is not None:
|
|
105
|
+
try:
|
|
106
|
+
f = c.cr_frame
|
|
107
|
+
except AttributeError:
|
|
108
|
+
break
|
|
109
|
+
else:
|
|
110
|
+
c = c.cr_await
|
|
111
|
+
if f is not None:
|
|
112
|
+
yield (f, f.f_lineno)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def extract_coro_stack(
|
|
116
|
+
coro: "types.CoroutineType[Any, Any, Any]", limit: Optional[int] = None
|
|
117
|
+
) -> traceback.StackSummary:
|
|
118
|
+
r"""Create a list of pre-processed entries from the coroutine stack.
|
|
119
|
+
|
|
120
|
+
This is based on :func:`traceback.extract_tb`.
|
|
121
|
+
|
|
122
|
+
If *limit* is omitted or ``None``, all entries are extracted.
|
|
123
|
+
The list is a :class:`traceback.StackSummary` object, and
|
|
124
|
+
each entry in the list is a :class:`traceback.FrameSummary` object
|
|
125
|
+
containing attributes ``filename``, ``lineno``, ``name``, and ``line``
|
|
126
|
+
representing the information that is usually printed for a stack
|
|
127
|
+
trace. The line is a string with leading and trailing
|
|
128
|
+
whitespace stripped; if the source is not available it is ``None``.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
coro: The :class:`coroutine` object from which to extract a stack.
|
|
132
|
+
level: The maximum number of frames from *coro*\ s stack to extract.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The stack of *coro*.
|
|
136
|
+
"""
|
|
137
|
+
return traceback.StackSummary.extract(walk_coro_stack(coro), limit=limit)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
EnumT = TypeVar("EnumT", bound=Enum)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class DocEnum(Enum):
|
|
144
|
+
"""Like :class:`enum.Enum`, but allows documenting enum values.
|
|
145
|
+
|
|
146
|
+
Documentation for enum members can be optionally added by setting enum values to a tuple of the intended value and the docstring.
|
|
147
|
+
This adds the provided docstring to the ``__doc__`` field of the enum value.
|
|
148
|
+
|
|
149
|
+
.. code-block:: python
|
|
150
|
+
|
|
151
|
+
class MyEnum(DocEnum):
|
|
152
|
+
\"\"\"Class documentation\"\"\"
|
|
153
|
+
|
|
154
|
+
VALUE1 = 1, "Value documentation"
|
|
155
|
+
VALUE2 = 2 # no documentation
|
|
156
|
+
|
|
157
|
+
Taken from :ref:`this StackOverflow answer <https://stackoverflow.com/questions/50473951/how-can-i-attach-documentation-to-members-of-a-python-enum/50473952#50473952>`
|
|
158
|
+
by :ref:`Eric Wieser <https://stackoverflow.com/users/102441/eric>`,
|
|
159
|
+
as recommended by the ``enum_tools`` documentation.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __new__(cls: Type[EnumT], value: object, doc: Optional[str] = None) -> EnumT:
|
|
163
|
+
# super().__new__() assumes the value is already an enum value
|
|
164
|
+
# so we side step that and create a raw object and fill in _value_
|
|
165
|
+
self = object.__new__(cls)
|
|
166
|
+
self._value_ = value
|
|
167
|
+
if doc is not None:
|
|
168
|
+
self.__doc__ = doc
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
IntEnumT = TypeVar("IntEnumT", bound=IntEnum)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class DocIntEnum(IntEnum):
|
|
176
|
+
"""Like DocEnum but for :class:`IntEnum` enum types."""
|
|
177
|
+
|
|
178
|
+
def __new__(cls: Type[IntEnumT], value: int, doc: Optional[str] = None) -> IntEnumT:
|
|
179
|
+
self = int.__new__(cls, value)
|
|
180
|
+
self._value_ = value
|
|
181
|
+
if doc is not None:
|
|
182
|
+
self.__doc__ = doc
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if TYPE_CHECKING:
|
|
187
|
+
F = TypeVar("F")
|
|
188
|
+
|
|
189
|
+
def cached_method(f: F) -> F: ...
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
|
|
193
|
+
class cached_method:
|
|
194
|
+
def __init__(self, method):
|
|
195
|
+
self._method = method
|
|
196
|
+
update_wrapper(self, method)
|
|
197
|
+
|
|
198
|
+
def __get__(self, instance, objtype=None):
|
|
199
|
+
if instance is None:
|
|
200
|
+
return self
|
|
201
|
+
|
|
202
|
+
cache = {}
|
|
203
|
+
|
|
204
|
+
@wraps(self._method)
|
|
205
|
+
def lookup(*args, **kwargs):
|
|
206
|
+
key = (args, tuple(kwargs.items()))
|
|
207
|
+
try:
|
|
208
|
+
return cache[key]
|
|
209
|
+
except KeyError:
|
|
210
|
+
res = self._method(instance, *args, **kwargs)
|
|
211
|
+
cache[key] = res
|
|
212
|
+
return res
|
|
213
|
+
|
|
214
|
+
lookup.cache = cache
|
|
215
|
+
|
|
216
|
+
setattr(instance, self._method.__name__, lookup)
|
|
217
|
+
return lookup
|
|
218
|
+
|
|
219
|
+
def __call__(self, instance, *args, **kwargs):
|
|
220
|
+
func = getattr(instance, self._method.__name__)
|
|
221
|
+
return func(*args, **kwargs)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
T = TypeVar("T")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if TYPE_CHECKING:
|
|
228
|
+
|
|
229
|
+
def singleton(orig_cls: T) -> T: ...
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
|
|
233
|
+
def singleton(orig_cls):
|
|
234
|
+
"""Class decorator which turns a type into a Singleton type."""
|
|
235
|
+
orig_new = orig_cls.__new__
|
|
236
|
+
orig_init = orig_cls.__init__
|
|
237
|
+
instance = None
|
|
238
|
+
|
|
239
|
+
@wraps(orig_cls.__new__)
|
|
240
|
+
def __new__(cls, *args, **kwargs):
|
|
241
|
+
nonlocal instance
|
|
242
|
+
if instance is None:
|
|
243
|
+
instance = orig_new(cls, *args, **kwargs)
|
|
244
|
+
orig_init(instance, *args, **kwargs)
|
|
245
|
+
return instance
|
|
246
|
+
|
|
247
|
+
@wraps(orig_cls.__init__)
|
|
248
|
+
def __init__(self, *args, **kwargs):
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
orig_cls.__new__ = __new__
|
|
252
|
+
orig_cls.__init__ = __init__
|
|
253
|
+
return orig_cls
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def pointer_str(obj: object) -> str:
|
|
257
|
+
"""Get the memory address of *obj* as used in :meth:`object.__repr__`.
|
|
258
|
+
|
|
259
|
+
This is equivalent to ``sprintf("%p", id(obj))``, but Python does not
|
|
260
|
+
support ``%p``.
|
|
261
|
+
"""
|
|
262
|
+
full_repr = object.__repr__(obj) # gives "<{type} object at {address}>"
|
|
263
|
+
return full_repr.rsplit(" ", 1)[1][:-1]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def safe_divide(a: float, b: float) -> float:
|
|
267
|
+
"""Used when computing time ratios to ensure no exception is raised if either time is 0."""
|
|
268
|
+
try:
|
|
269
|
+
return a / b
|
|
270
|
+
except ZeroDivisionError:
|
|
271
|
+
if a == 0:
|
|
272
|
+
return float("nan")
|
|
273
|
+
else:
|
|
274
|
+
return float("inf")
|
cocotb/_version.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
# Package
|
|
2
|
-
#
|
|
1
|
+
# Package version
|
|
2
|
+
# Generated by setup.py -- do not modify directly
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
# 1) we don't load dependencies by storing it in __init__.py
|
|
6
|
-
# 2) we can import it in setup.py for the same reason
|
|
7
|
-
# 3) we can import it into your module
|
|
8
|
-
__version__ = "1.9.2"
|
|
4
|
+
__version__ = "2.0.0rc2"
|