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/_gpi_triggers.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
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
|
+
"""A collection of triggers which a testbench can :keyword:`await`."""
|
|
8
|
+
|
|
9
|
+
import warnings
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from fractions import Fraction
|
|
12
|
+
from typing import (
|
|
13
|
+
TYPE_CHECKING,
|
|
14
|
+
Any,
|
|
15
|
+
Callable,
|
|
16
|
+
ClassVar,
|
|
17
|
+
Generic,
|
|
18
|
+
Optional,
|
|
19
|
+
TypeVar,
|
|
20
|
+
Union,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
import cocotb
|
|
24
|
+
import cocotb.handle
|
|
25
|
+
from cocotb import simulator
|
|
26
|
+
from cocotb._base_triggers import Trigger
|
|
27
|
+
from cocotb._deprecation import deprecated
|
|
28
|
+
from cocotb._typing import RoundMode, TimeUnit
|
|
29
|
+
from cocotb._utils import pointer_str, singleton
|
|
30
|
+
from cocotb.utils import get_sim_steps, get_time_from_sim_steps
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from cocotb._py_compat import Self
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class GPITrigger(Trigger):
|
|
37
|
+
"""A trigger for a simulation event."""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
super().__init__()
|
|
41
|
+
self._cbhdl: Optional[simulator.gpi_cb_hdl] = None
|
|
42
|
+
|
|
43
|
+
def _unprime(self) -> None:
|
|
44
|
+
"""Disable a primed trigger, can be re-primed."""
|
|
45
|
+
if self._cbhdl is not None:
|
|
46
|
+
self._cbhdl.deregister()
|
|
47
|
+
return super()._unprime()
|
|
48
|
+
|
|
49
|
+
def _cleanup(self) -> None:
|
|
50
|
+
self._cbhdl = None
|
|
51
|
+
return super()._cleanup()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Timer(GPITrigger):
|
|
55
|
+
r"""Fire after the specified simulation time period has elapsed.
|
|
56
|
+
|
|
57
|
+
This trigger will *always* consume some simulation time
|
|
58
|
+
and will return control to the :keyword:`await`\ ing task at the beginning of the time step.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
time: The time value.
|
|
62
|
+
|
|
63
|
+
.. versionchanged:: 1.5
|
|
64
|
+
Previously this argument was misleadingly called `time_ps`.
|
|
65
|
+
|
|
66
|
+
unit: The unit of the time value.
|
|
67
|
+
|
|
68
|
+
One of ``'step'``, ``'fs'``, ``'ps'``, ``'ns'``, ``'us'``, ``'ms'``, ``'sec'``.
|
|
69
|
+
When *unit* is ``'step'``,
|
|
70
|
+
the timestep is determined by the simulator (see :make:var:`COCOTB_HDL_TIMEPRECISION`).
|
|
71
|
+
|
|
72
|
+
.. versionchanged:: 2.0
|
|
73
|
+
Renamed from ``units``.
|
|
74
|
+
|
|
75
|
+
round_mode:
|
|
76
|
+
|
|
77
|
+
String specifying how to handle time values that sit between time steps
|
|
78
|
+
(one of ``'error'``, ``'round'``, ``'ceil'``, ``'floor'``, ``None``).
|
|
79
|
+
A ``None`` argument is converted to the current value of :attr:`.Timer.round_mode`.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If a non-positive value is passed for Timer setup.
|
|
83
|
+
|
|
84
|
+
Usage:
|
|
85
|
+
>>> await Timer(100, unit="ps")
|
|
86
|
+
|
|
87
|
+
The time can also be a ``float``:
|
|
88
|
+
|
|
89
|
+
>>> await Timer(100e-9, unit="sec")
|
|
90
|
+
|
|
91
|
+
which is particularly convenient when working with frequencies:
|
|
92
|
+
|
|
93
|
+
>>> freq = 10e6 # 10 MHz
|
|
94
|
+
>>> await Timer(1 / freq, unit="sec")
|
|
95
|
+
|
|
96
|
+
Other built-in exact numeric types can be used too:
|
|
97
|
+
|
|
98
|
+
>>> from fractions import Fraction
|
|
99
|
+
>>> await Timer(Fraction(1, 10), unit="ns")
|
|
100
|
+
|
|
101
|
+
>>> from decimal import Decimal
|
|
102
|
+
>>> await Timer(Decimal("100e-9"), unit="sec")
|
|
103
|
+
|
|
104
|
+
These are most useful when using computed durations while
|
|
105
|
+
avoiding floating point inaccuracies.
|
|
106
|
+
|
|
107
|
+
.. versionchanged:: 1.5
|
|
108
|
+
Raise an exception when Timer uses a negative value as it is undefined behavior.
|
|
109
|
+
Warn for 0 as this will cause erratic behavior in some simulators as well.
|
|
110
|
+
|
|
111
|
+
.. versionchanged:: 1.5
|
|
112
|
+
Support ``'step'`` as the *unit* argument to mean "simulator time step".
|
|
113
|
+
|
|
114
|
+
.. versionchanged:: 1.6
|
|
115
|
+
Support rounding modes.
|
|
116
|
+
|
|
117
|
+
.. versionremoved:: 2.0
|
|
118
|
+
Passing ``None`` as the *unit* argument was removed, use ``'step'`` instead.
|
|
119
|
+
|
|
120
|
+
.. versionremoved:: 2.0
|
|
121
|
+
The ``time_ps`` parameter was removed, use the ``time`` parameter instead.
|
|
122
|
+
|
|
123
|
+
.. versionchanged:: 2.0
|
|
124
|
+
Passing ``0`` as the *time* argument now raises a :exc:`ValueError`.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
round_mode: ClassVar[RoundMode] = "error"
|
|
128
|
+
"""The default rounding mode."""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
time: Union[float, Fraction, Decimal],
|
|
133
|
+
unit: TimeUnit = "step",
|
|
134
|
+
*,
|
|
135
|
+
round_mode: Optional[RoundMode] = None,
|
|
136
|
+
units: None = None,
|
|
137
|
+
) -> None:
|
|
138
|
+
super().__init__()
|
|
139
|
+
if time <= 0:
|
|
140
|
+
raise ValueError("Timer argument time must be positive")
|
|
141
|
+
if units is not None:
|
|
142
|
+
warnings.warn(
|
|
143
|
+
"The 'units' argument has been renamed to 'unit'.",
|
|
144
|
+
DeprecationWarning,
|
|
145
|
+
stacklevel=2,
|
|
146
|
+
)
|
|
147
|
+
unit = units
|
|
148
|
+
if round_mode is None:
|
|
149
|
+
round_mode = type(self).round_mode
|
|
150
|
+
self._sim_steps = get_sim_steps(time, unit, round_mode=round_mode)
|
|
151
|
+
# If we round to 0, we fix it up to 1 step as rounding is imprecise,
|
|
152
|
+
# and Timer(0) is invalid.
|
|
153
|
+
if self._sim_steps == 0:
|
|
154
|
+
self._sim_steps = 1
|
|
155
|
+
|
|
156
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
157
|
+
"""Register for a timed callback."""
|
|
158
|
+
if self._cbhdl is None:
|
|
159
|
+
self._cbhdl = simulator.register_timed_callback(
|
|
160
|
+
self._sim_steps, callback, self
|
|
161
|
+
)
|
|
162
|
+
if self._cbhdl is None:
|
|
163
|
+
raise RuntimeError(f"Unable set up {self!s} Trigger")
|
|
164
|
+
super()._prime(callback)
|
|
165
|
+
|
|
166
|
+
def __repr__(self) -> str:
|
|
167
|
+
return "<{} of {:1.2f}ps at {}>".format(
|
|
168
|
+
type(self).__qualname__,
|
|
169
|
+
get_time_from_sim_steps(self._sim_steps, unit="ps"),
|
|
170
|
+
pointer_str(self),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@singleton
|
|
175
|
+
class ReadOnly(GPITrigger):
|
|
176
|
+
"""Fires when the current simulation timestep moves to the read-only phase.
|
|
177
|
+
|
|
178
|
+
The read-only phase is entered when the current timestep no longer has any further delta steps.
|
|
179
|
+
This will be a point where all the signal values are stable as there are no more RTL events scheduled for the timestep.
|
|
180
|
+
The simulator will not allow scheduling of more events in this timestep.
|
|
181
|
+
Useful for monitors which need to wait for all processes to execute (both RTL and cocotb) to ensure sampled signal values are final.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
185
|
+
if isinstance(current_gpi_trigger(), ReadOnly):
|
|
186
|
+
raise RuntimeError(
|
|
187
|
+
"Attempted illegal transition: awaiting ReadOnly in ReadOnly phase"
|
|
188
|
+
)
|
|
189
|
+
if self._cbhdl is None:
|
|
190
|
+
self._cbhdl = simulator.register_readonly_callback(callback, self)
|
|
191
|
+
if self._cbhdl is None:
|
|
192
|
+
raise RuntimeError(f"Unable set up {self!s} Trigger")
|
|
193
|
+
super()._prime(callback)
|
|
194
|
+
|
|
195
|
+
def __repr__(self) -> str:
|
|
196
|
+
return f"{type(self).__qualname__}()"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@singleton
|
|
200
|
+
class ReadWrite(GPITrigger):
|
|
201
|
+
"""Fires when the read-write simulation phase is reached."""
|
|
202
|
+
|
|
203
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
204
|
+
if isinstance(current_gpi_trigger(), ReadOnly):
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
"Attempted illegal transition: awaiting ReadWrite in ReadOnly phase"
|
|
207
|
+
)
|
|
208
|
+
if self._cbhdl is None:
|
|
209
|
+
self._cbhdl = simulator.register_rwsynch_callback(callback, self)
|
|
210
|
+
if self._cbhdl is None:
|
|
211
|
+
raise RuntimeError(f"Unable set up {self!s} Trigger")
|
|
212
|
+
super()._prime(callback)
|
|
213
|
+
|
|
214
|
+
def __repr__(self) -> str:
|
|
215
|
+
return f"{type(self).__qualname__}()"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@singleton
|
|
219
|
+
class NextTimeStep(GPITrigger):
|
|
220
|
+
"""Fires when the next time step is started."""
|
|
221
|
+
|
|
222
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
223
|
+
if self._cbhdl is None:
|
|
224
|
+
self._cbhdl = simulator.register_nextstep_callback(callback, self)
|
|
225
|
+
if self._cbhdl is None:
|
|
226
|
+
raise RuntimeError(f"Unable set up {self!s} Trigger")
|
|
227
|
+
super()._prime(callback)
|
|
228
|
+
|
|
229
|
+
def __repr__(self) -> str:
|
|
230
|
+
return f"{type(self).__qualname__}()"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
_SignalType = TypeVar("_SignalType", bound="cocotb.handle.ValueObjectBase[Any, Any]")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class _EdgeBase(GPITrigger, Generic[_SignalType]):
|
|
237
|
+
"""Internal base class that fires on a given edge of a signal."""
|
|
238
|
+
|
|
239
|
+
_edge_type: ClassVar[int]
|
|
240
|
+
signal: _SignalType
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def _make(cls, signal: _SignalType) -> "Self":
|
|
244
|
+
self = GPITrigger.__new__(cls)
|
|
245
|
+
GPITrigger.__init__(self)
|
|
246
|
+
self.signal = signal
|
|
247
|
+
return self
|
|
248
|
+
|
|
249
|
+
def __init__(self, _: _SignalType) -> None:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
def _prime(self, callback: Callable[["Self"], None]) -> None:
|
|
253
|
+
if self._cbhdl is None:
|
|
254
|
+
self._cbhdl = simulator.register_value_change_callback(
|
|
255
|
+
self.signal._handle, callback, type(self)._edge_type, self
|
|
256
|
+
)
|
|
257
|
+
if self._cbhdl is None:
|
|
258
|
+
raise RuntimeError(f"Unable set up {self!s} Trigger")
|
|
259
|
+
super()._prime(callback)
|
|
260
|
+
|
|
261
|
+
def __repr__(self) -> str:
|
|
262
|
+
return f"{type(self).__qualname__}({self.signal!r})"
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class RisingEdge(_EdgeBase["cocotb.handle.LogicObject"]):
|
|
266
|
+
"""Fires on the rising edge of *signal*, on a transition to ``1``.
|
|
267
|
+
|
|
268
|
+
Only valid for scalar ``logic`` or ``bit``-typed signals.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
signal: The signal upon which to wait for a rising edge.
|
|
272
|
+
|
|
273
|
+
Raises:
|
|
274
|
+
TypeError: If *signal* is not a 1-bit ``logic`` or ``bit``-typed object.
|
|
275
|
+
|
|
276
|
+
.. note::
|
|
277
|
+
Prefer :attr:`await signal.rising_edge <cocotb.handle.LogicObject.rising_edge>` to ``await RisingEdge(signal)``.
|
|
278
|
+
|
|
279
|
+
.. warning::
|
|
280
|
+
On many simulators this will trigger on transitions from non-``0``/``1`` value to ``1``,
|
|
281
|
+
not just from ``0`` to ``1`` like the ``rising_edge`` function in VHDL.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
_edge_type = simulator.RISING
|
|
285
|
+
|
|
286
|
+
def __new__(cls, signal: "cocotb.handle.LogicObject") -> "RisingEdge":
|
|
287
|
+
if not (isinstance(signal, cocotb.handle.LogicObject)):
|
|
288
|
+
raise TypeError(
|
|
289
|
+
f"{cls.__qualname__} requires a scalar LogicObject. Got {signal!r} of type {type(signal).__qualname__}"
|
|
290
|
+
)
|
|
291
|
+
return signal.rising_edge
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FallingEdge(_EdgeBase["cocotb.handle.LogicObject"]):
|
|
295
|
+
"""Fires on the falling edge of *signal*, on a transition to ``0``.
|
|
296
|
+
|
|
297
|
+
Only valid for scalar ``logic`` or ``bit``-typed signals.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
signal: The signal upon which to wait for a rising edge.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
TypeError: If *signal* is not a 1-bit ``logic`` or ``bit``-typed object.
|
|
304
|
+
|
|
305
|
+
.. note::
|
|
306
|
+
Prefer :attr:`await signal.falling_edge <cocotb.handle.LogicObject.falling_edge>` to ``await FallingEdge(signal)``.
|
|
307
|
+
|
|
308
|
+
.. warning::
|
|
309
|
+
On many simulators this will trigger on transitions from non-``0``/``1`` value to ``0``,
|
|
310
|
+
not just from ``1`` to ``0`` like the ``falling_edge`` function in VHDL.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
_edge_type = simulator.FALLING
|
|
314
|
+
|
|
315
|
+
def __new__(cls, signal: "cocotb.handle.LogicObject") -> "FallingEdge":
|
|
316
|
+
if not (isinstance(signal, cocotb.handle.LogicObject)):
|
|
317
|
+
raise TypeError(
|
|
318
|
+
f"{cls.__qualname__} requires a scalar LogicObject. Got {signal!r} of type {type(signal).__qualname__}"
|
|
319
|
+
)
|
|
320
|
+
return signal.falling_edge
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class ValueChange(_EdgeBase["cocotb.handle._NonIndexableValueObjectBase[Any, Any]"]):
|
|
324
|
+
"""Fires on any value change of *signal*.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
signal: The signal upon which to wait for a value change.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
TypeError: If the signal is not an object which can change value.
|
|
331
|
+
|
|
332
|
+
.. note::
|
|
333
|
+
Prefer :attr:`await signal.value_change <cocotb.handle.NonArrayValueObject.value_change>` to ``await ValueChange(signal)``.
|
|
334
|
+
|
|
335
|
+
.. versionadded:: 2.0
|
|
336
|
+
"""
|
|
337
|
+
|
|
338
|
+
_edge_type = simulator.VALUE_CHANGE
|
|
339
|
+
|
|
340
|
+
def __new__(
|
|
341
|
+
cls, signal: "cocotb.handle._NonIndexableValueObjectBase[Any, Any]"
|
|
342
|
+
) -> "ValueChange":
|
|
343
|
+
if not isinstance(signal, cocotb.handle._NonIndexableValueObjectBase):
|
|
344
|
+
raise TypeError(
|
|
345
|
+
f"{cls.__qualname__} requires a simulation object derived from ValueObjectBase. "
|
|
346
|
+
f"Got {signal!r} of type {type(signal).__qualname__}"
|
|
347
|
+
)
|
|
348
|
+
return signal.value_change
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class Edge(ValueChange):
|
|
352
|
+
"""Fires on any value change of *signal*.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
signal: The signal upon which to wait for a value change.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
TypeError: If the signal is not an object which can change value.
|
|
359
|
+
|
|
360
|
+
.. deprecated:: 2.0
|
|
361
|
+
|
|
362
|
+
Use :attr:`signal.value_change <cocotb.handle.NonArrayValueObject.value_change>` instead.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
@deprecated("Use `signal.value_change` instead.")
|
|
366
|
+
def __new__(
|
|
367
|
+
cls, signal: "cocotb.handle._NonIndexableValueObjectBase[Any, Any]"
|
|
368
|
+
) -> "Edge":
|
|
369
|
+
if not isinstance(signal, cocotb.handle._NonIndexableValueObjectBase):
|
|
370
|
+
raise TypeError(
|
|
371
|
+
f"{cls.__qualname__} requires a simulation object derived from ValueObjectBase. "
|
|
372
|
+
f"Got {signal!r} of type {type(signal).__qualname__}"
|
|
373
|
+
)
|
|
374
|
+
return signal._edge
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# The initializer is a lie, but a useful one. Perhaps one day this can be something like `StartupTrigger`.`
|
|
378
|
+
_current_gpi_trigger = Timer(1, "step") # type: Union[None, GPITrigger]
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def current_gpi_trigger() -> GPITrigger:
|
|
382
|
+
"""Return the last GPITrigger that fired."""
|
|
383
|
+
if _current_gpi_trigger is None:
|
|
384
|
+
raise RuntimeError("No GPI trigger has fired.")
|
|
385
|
+
return _current_gpi_trigger
|
cocotb/_init.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
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
|
+
import ast
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import random
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import warnings
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import SimpleNamespace
|
|
15
|
+
from typing import Callable, List, cast
|
|
16
|
+
|
|
17
|
+
import cocotb
|
|
18
|
+
import cocotb._profiling
|
|
19
|
+
import cocotb.handle
|
|
20
|
+
import cocotb.logging
|
|
21
|
+
import cocotb.simtime
|
|
22
|
+
import cocotb.simulator
|
|
23
|
+
from cocotb._scheduler import Scheduler
|
|
24
|
+
from cocotb.regression import RegressionManager, RegressionMode
|
|
25
|
+
|
|
26
|
+
log: logging.Logger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _setup_logging() -> None:
|
|
30
|
+
cocotb.log = logging.getLogger("test")
|
|
31
|
+
cocotb.log.setLevel(logging.INFO)
|
|
32
|
+
|
|
33
|
+
global log
|
|
34
|
+
log = logging.getLogger("cocotb")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_shutdown_callbacks: List[Callable[[], None]] = []
|
|
38
|
+
"""List of callbacks to be called when cocotb shuts down."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _register_shutdown_callback(cb: Callable[[], None]) -> None:
|
|
42
|
+
"""Register a callback to be called when cocotb shuts down."""
|
|
43
|
+
_shutdown_callbacks.append(cb)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _shutdown_testbench() -> None:
|
|
47
|
+
"""Call all registered shutdown callbacks."""
|
|
48
|
+
while _shutdown_callbacks:
|
|
49
|
+
cb = _shutdown_callbacks.pop(0)
|
|
50
|
+
cb()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def init_package_from_simulation(argv: List[str]) -> None:
|
|
54
|
+
"""Initialize the cocotb package from a simulation context."""
|
|
55
|
+
|
|
56
|
+
# register a callback to be called if the simulation fails
|
|
57
|
+
cocotb.simulator.set_sim_event_callback(_sim_event)
|
|
58
|
+
|
|
59
|
+
cocotb.is_simulation = True
|
|
60
|
+
|
|
61
|
+
cocotb.argv = argv
|
|
62
|
+
|
|
63
|
+
# sys.path normally includes "" (the current directory), but does not appear to when python is embedded.
|
|
64
|
+
# Add it back because users expect to be able to import files in their test directory.
|
|
65
|
+
sys.path.insert(0, "")
|
|
66
|
+
|
|
67
|
+
cocotb.logging._init()
|
|
68
|
+
_setup_logging()
|
|
69
|
+
|
|
70
|
+
# From https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners
|
|
71
|
+
# If the user doesn't want to see these, they can always change the global
|
|
72
|
+
# warning settings in their test module.
|
|
73
|
+
if not sys.warnoptions:
|
|
74
|
+
warnings.simplefilter("default")
|
|
75
|
+
|
|
76
|
+
cocotb.SIM_NAME = cocotb.simulator.get_simulator_product().strip()
|
|
77
|
+
cocotb.SIM_VERSION = cocotb.simulator.get_simulator_version().strip()
|
|
78
|
+
|
|
79
|
+
log.info("Running on %s version %s", cocotb.SIM_NAME, cocotb.SIM_VERSION)
|
|
80
|
+
|
|
81
|
+
cocotb._profiling.initialize()
|
|
82
|
+
_register_shutdown_callback(cocotb._profiling.finalize)
|
|
83
|
+
|
|
84
|
+
_process_plusargs()
|
|
85
|
+
_process_packages()
|
|
86
|
+
_setup_random_seed()
|
|
87
|
+
_setup_root_handle()
|
|
88
|
+
_start_user_coverage()
|
|
89
|
+
|
|
90
|
+
cocotb.simtime._init()
|
|
91
|
+
|
|
92
|
+
log.info(
|
|
93
|
+
"Initialized cocotb v%s from %s",
|
|
94
|
+
cocotb.__version__,
|
|
95
|
+
Path(__file__).parent.absolute(),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def run_regression(_: object) -> None:
|
|
100
|
+
"""Setup and run a regression."""
|
|
101
|
+
|
|
102
|
+
_setup_regression_manager()
|
|
103
|
+
|
|
104
|
+
# setup global scheduler system
|
|
105
|
+
cocotb._scheduler_inst = Scheduler()
|
|
106
|
+
|
|
107
|
+
# start Regression Manager
|
|
108
|
+
log.info("Running tests")
|
|
109
|
+
cocotb._regression_manager.start_regression()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _sim_event(msg: str) -> None:
|
|
113
|
+
"""Function that can be called externally to signal an event."""
|
|
114
|
+
# We simply return here as the simulator will exit
|
|
115
|
+
# so no cleanup is needed
|
|
116
|
+
if hasattr(cocotb, "_regression_manager"):
|
|
117
|
+
cocotb._regression_manager._fail_simulation(msg)
|
|
118
|
+
else:
|
|
119
|
+
log.error(msg)
|
|
120
|
+
_shutdown_testbench()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _process_plusargs() -> None:
|
|
124
|
+
cocotb.plusargs = {}
|
|
125
|
+
|
|
126
|
+
for option in cocotb.argv:
|
|
127
|
+
if option.startswith("+"):
|
|
128
|
+
if option.find("=") != -1:
|
|
129
|
+
(name, value) = option[1:].split("=", 1)
|
|
130
|
+
cocotb.plusargs[name] = value
|
|
131
|
+
else:
|
|
132
|
+
cocotb.plusargs[option[1:]] = True
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _process_packages() -> None:
|
|
136
|
+
pkg_dict = {}
|
|
137
|
+
|
|
138
|
+
from cocotb import simulator # noqa: PLC0415
|
|
139
|
+
|
|
140
|
+
pkgs = simulator.package_iterate()
|
|
141
|
+
if pkgs is None:
|
|
142
|
+
cocotb.packages = SimpleNamespace()
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
for pkg in pkgs:
|
|
146
|
+
handle = cast(
|
|
147
|
+
"cocotb.handle.HierarchyObject", cocotb.handle._make_sim_object(pkg)
|
|
148
|
+
)
|
|
149
|
+
name = handle._name
|
|
150
|
+
|
|
151
|
+
# Icarus doesn't support named access to package objects:
|
|
152
|
+
# https://github.com/steveicarus/iverilog/issues/1038
|
|
153
|
+
# so we cannot lazily create handles
|
|
154
|
+
if cocotb.SIM_NAME == "Icarus Verilog":
|
|
155
|
+
handle._discover_all()
|
|
156
|
+
pkg_dict[name] = handle
|
|
157
|
+
|
|
158
|
+
cocotb.packages = SimpleNamespace(**pkg_dict)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _start_user_coverage() -> None:
|
|
162
|
+
coverage_envvar = os.getenv("COCOTB_USER_COVERAGE")
|
|
163
|
+
if coverage_envvar is None:
|
|
164
|
+
coverage_envvar = os.getenv("COVERAGE")
|
|
165
|
+
if coverage_envvar is not None:
|
|
166
|
+
warnings.warn(
|
|
167
|
+
"COVERAGE is deprecated in favor of COCOTB_USER_COVERAGE",
|
|
168
|
+
DeprecationWarning,
|
|
169
|
+
stacklevel=2,
|
|
170
|
+
)
|
|
171
|
+
if coverage_envvar:
|
|
172
|
+
try:
|
|
173
|
+
import coverage # noqa: PLC0415
|
|
174
|
+
except ImportError:
|
|
175
|
+
raise RuntimeError(
|
|
176
|
+
"Coverage collection requested but coverage module not available. Install it using `pip install coverage`."
|
|
177
|
+
) from None
|
|
178
|
+
else:
|
|
179
|
+
config_filepath = os.getenv("COVERAGE_RCFILE")
|
|
180
|
+
if config_filepath is None:
|
|
181
|
+
# Exclude cocotb itself from coverage collection.
|
|
182
|
+
log.info(
|
|
183
|
+
"Collecting coverage of user code. No coverage config file supplied via COVERAGE_RCFILE."
|
|
184
|
+
)
|
|
185
|
+
cocotb_package_dir = Path(__file__).parent.absolute()
|
|
186
|
+
user_coverage = coverage.coverage(
|
|
187
|
+
branch=True, omit=[f"{cocotb_package_dir}/*"]
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
log.info(
|
|
191
|
+
"Collecting coverage of user code. Coverage config file supplied."
|
|
192
|
+
)
|
|
193
|
+
# Allow the config file to handle all configuration
|
|
194
|
+
user_coverage = coverage.coverage(config_file=config_filepath)
|
|
195
|
+
user_coverage.start()
|
|
196
|
+
|
|
197
|
+
def stop_user_coverage() -> None:
|
|
198
|
+
user_coverage.stop()
|
|
199
|
+
log.debug("Writing user coverage data")
|
|
200
|
+
user_coverage.save()
|
|
201
|
+
|
|
202
|
+
_register_shutdown_callback(stop_user_coverage)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _setup_random_seed() -> None:
|
|
206
|
+
seed_envvar = os.getenv("COCOTB_RANDOM_SEED")
|
|
207
|
+
if seed_envvar is None:
|
|
208
|
+
seed_envvar = os.getenv("RANDOM_SEED")
|
|
209
|
+
if seed_envvar is not None:
|
|
210
|
+
warnings.warn(
|
|
211
|
+
"RANDOM_SEED is deprecated in favor of COCOTB_RANDOM_SEED",
|
|
212
|
+
DeprecationWarning,
|
|
213
|
+
)
|
|
214
|
+
if seed_envvar is None:
|
|
215
|
+
if "ntb_random_seed" in cocotb.plusargs:
|
|
216
|
+
warnings.warn(
|
|
217
|
+
"Passing +ntb_random_seed will not be used to seed Python's random number generator in the future. "
|
|
218
|
+
"Ensure you also set `COCOTB_RANDOM_SEED`.",
|
|
219
|
+
FutureWarning,
|
|
220
|
+
)
|
|
221
|
+
plusarg_seed = cocotb.plusargs["ntb_random_seed"]
|
|
222
|
+
if not isinstance(plusarg_seed, str):
|
|
223
|
+
raise TypeError("ntb_random_seed plusarg is not a valid seed value.")
|
|
224
|
+
seed = ast.literal_eval(plusarg_seed)
|
|
225
|
+
if not isinstance(seed, int):
|
|
226
|
+
raise TypeError("ntb_random_seed plusargs is not a valid seed value.")
|
|
227
|
+
cocotb.RANDOM_SEED = seed
|
|
228
|
+
elif "seed" in cocotb.plusargs:
|
|
229
|
+
warnings.warn(
|
|
230
|
+
"Passing +seed will not be used to seed Python's random number generator in the future. "
|
|
231
|
+
"Ensure you also set `COCOTB_RANDOM_SEED`.",
|
|
232
|
+
FutureWarning,
|
|
233
|
+
)
|
|
234
|
+
plusarg_seed = cocotb.plusargs["seed"]
|
|
235
|
+
if not isinstance(plusarg_seed, str):
|
|
236
|
+
raise TypeError("seed plusarg is not a valid seed value.")
|
|
237
|
+
seed = ast.literal_eval(plusarg_seed)
|
|
238
|
+
if not isinstance(seed, int):
|
|
239
|
+
raise TypeError("seed plusargs is not a valid seed value.")
|
|
240
|
+
cocotb.RANDOM_SEED = seed
|
|
241
|
+
else:
|
|
242
|
+
cocotb.RANDOM_SEED = int(time.time())
|
|
243
|
+
log.info("Seeding Python random module with %d", cocotb.RANDOM_SEED)
|
|
244
|
+
else:
|
|
245
|
+
cocotb.RANDOM_SEED = ast.literal_eval(seed_envvar)
|
|
246
|
+
log.info(
|
|
247
|
+
"Seeding Python random module with supplied seed %d", cocotb.RANDOM_SEED
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
random.seed(cocotb.RANDOM_SEED)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _setup_root_handle() -> None:
|
|
254
|
+
root_name = os.getenv("COCOTB_TOPLEVEL")
|
|
255
|
+
if root_name is not None:
|
|
256
|
+
root_name = root_name.strip()
|
|
257
|
+
if root_name == "":
|
|
258
|
+
root_name = None
|
|
259
|
+
elif "." in root_name:
|
|
260
|
+
# Skip any library component of the toplevel
|
|
261
|
+
root_name = root_name.split(".", 1)[1]
|
|
262
|
+
|
|
263
|
+
from cocotb import simulator # noqa: PLC0415
|
|
264
|
+
|
|
265
|
+
handle = simulator.get_root_handle(root_name)
|
|
266
|
+
if not handle:
|
|
267
|
+
raise RuntimeError(f"Can not find root handle {root_name!r}")
|
|
268
|
+
|
|
269
|
+
cocotb.top = cocotb.handle._make_sim_object(handle)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _setup_regression_manager() -> None:
|
|
273
|
+
cocotb._regression_manager = RegressionManager()
|
|
274
|
+
|
|
275
|
+
# discover tests
|
|
276
|
+
module_str = os.getenv("COCOTB_TEST_MODULES", "")
|
|
277
|
+
if not module_str:
|
|
278
|
+
raise RuntimeError(
|
|
279
|
+
"Environment variable COCOTB_TEST_MODULES, which defines the module(s) to execute, is not defined or empty."
|
|
280
|
+
)
|
|
281
|
+
modules = [s.strip() for s in module_str.split(",") if s.strip()]
|
|
282
|
+
cocotb._regression_manager.setup_pytest_assertion_rewriting()
|
|
283
|
+
cocotb._regression_manager.discover_tests(*modules)
|
|
284
|
+
|
|
285
|
+
# filter tests
|
|
286
|
+
testcase_str = os.getenv("COCOTB_TESTCASE", "").strip()
|
|
287
|
+
test_filter_str = os.getenv("COCOTB_TEST_FILTER", "").strip()
|
|
288
|
+
if testcase_str and test_filter_str:
|
|
289
|
+
raise RuntimeError("Specify only one of COCOTB_TESTCASE or COCOTB_TEST_FILTER")
|
|
290
|
+
elif testcase_str:
|
|
291
|
+
warnings.warn(
|
|
292
|
+
"COCOTB_TESTCASE is deprecated in favor of COCOTB_TEST_FILTER",
|
|
293
|
+
DeprecationWarning,
|
|
294
|
+
stacklevel=2,
|
|
295
|
+
)
|
|
296
|
+
filters = [f"{s.strip()}$" for s in testcase_str.split(",") if s.strip()]
|
|
297
|
+
cocotb._regression_manager.add_filters(*filters)
|
|
298
|
+
cocotb._regression_manager.set_mode(RegressionMode.TESTCASE)
|
|
299
|
+
elif test_filter_str:
|
|
300
|
+
cocotb._regression_manager.add_filters(test_filter_str)
|
|
301
|
+
cocotb._regression_manager.set_mode(RegressionMode.TESTCASE)
|