cocotb 1.9.2__cp311-cp311-win32.whl → 2.0.0b1__cp311-cp311-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.

Files changed (155) hide show
  1. cocotb/{ANSI.py → _ANSI.py} +5 -25
  2. cocotb/__init__.py +76 -332
  3. cocotb/_base_triggers.py +513 -0
  4. cocotb/_bridge.py +187 -0
  5. cocotb/_decorators.py +515 -0
  6. cocotb/_deprecation.py +3 -3
  7. cocotb/_exceptions.py +7 -0
  8. cocotb/_extended_awaitables.py +419 -0
  9. cocotb/_gpi_triggers.py +382 -0
  10. cocotb/_init.py +295 -0
  11. cocotb/_outcomes.py +54 -0
  12. cocotb/_profiling.py +46 -0
  13. cocotb/_py_compat.py +100 -29
  14. cocotb/_scheduler.py +454 -0
  15. cocotb/_test.py +245 -0
  16. cocotb/_test_factory.py +309 -0
  17. cocotb/_test_functions.py +42 -0
  18. cocotb/_typing.py +7 -0
  19. cocotb/_utils.py +296 -0
  20. cocotb/_version.py +3 -7
  21. cocotb/_xunit_reporter.py +66 -0
  22. cocotb/clock.py +271 -108
  23. cocotb/handle.py +1342 -795
  24. cocotb/libs/cocotb.dll +0 -0
  25. cocotb/libs/cocotb.exp +0 -0
  26. cocotb/libs/cocotb.lib +0 -0
  27. cocotb/libs/cocotbfli_modelsim.dll +0 -0
  28. cocotb/libs/cocotbfli_modelsim.exp +0 -0
  29. cocotb/libs/cocotbfli_modelsim.lib +0 -0
  30. cocotb/libs/cocotbutils.dll +0 -0
  31. cocotb/libs/cocotbutils.exp +0 -0
  32. cocotb/libs/cocotbutils.lib +0 -0
  33. cocotb/libs/cocotbvhpi_aldec.dll +0 -0
  34. cocotb/libs/cocotbvhpi_aldec.exp +0 -0
  35. cocotb/libs/cocotbvhpi_aldec.lib +0 -0
  36. cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
  37. cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
  38. cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
  39. cocotb/libs/cocotbvpi_aldec.dll +0 -0
  40. cocotb/libs/cocotbvpi_aldec.exp +0 -0
  41. cocotb/libs/cocotbvpi_aldec.lib +0 -0
  42. cocotb/libs/cocotbvpi_ghdl.dll +0 -0
  43. cocotb/libs/cocotbvpi_ghdl.exp +0 -0
  44. cocotb/libs/cocotbvpi_ghdl.lib +0 -0
  45. cocotb/libs/cocotbvpi_icarus.exp +0 -0
  46. cocotb/libs/cocotbvpi_icarus.lib +0 -0
  47. cocotb/libs/cocotbvpi_icarus.vpl +0 -0
  48. cocotb/libs/cocotbvpi_modelsim.dll +0 -0
  49. cocotb/libs/cocotbvpi_modelsim.exp +0 -0
  50. cocotb/libs/cocotbvpi_modelsim.lib +0 -0
  51. cocotb/libs/embed.dll +0 -0
  52. cocotb/libs/embed.exp +0 -0
  53. cocotb/libs/embed.lib +0 -0
  54. cocotb/libs/gpi.dll +0 -0
  55. cocotb/libs/gpi.exp +0 -0
  56. cocotb/libs/gpi.lib +0 -0
  57. cocotb/libs/gpilog.dll +0 -0
  58. cocotb/libs/gpilog.exp +0 -0
  59. cocotb/libs/gpilog.lib +0 -0
  60. cocotb/libs/pygpilog.dll +0 -0
  61. cocotb/libs/pygpilog.exp +0 -0
  62. cocotb/libs/pygpilog.lib +0 -0
  63. cocotb/{log.py → logging.py} +105 -110
  64. cocotb/queue.py +103 -57
  65. cocotb/regression.py +667 -712
  66. cocotb/result.py +17 -188
  67. cocotb/share/def/aldec.exp +0 -0
  68. cocotb/share/def/aldec.lib +0 -0
  69. cocotb/share/def/ghdl.exp +0 -0
  70. cocotb/share/def/ghdl.lib +0 -0
  71. cocotb/share/def/icarus.exp +0 -0
  72. cocotb/share/def/icarus.lib +0 -0
  73. cocotb/share/def/modelsim.def +1 -0
  74. cocotb/share/def/modelsim.exp +0 -0
  75. cocotb/share/def/modelsim.lib +0 -0
  76. cocotb/share/include/cocotb_utils.h +6 -29
  77. cocotb/share/include/embed.h +5 -28
  78. cocotb/share/include/gpi.h +137 -92
  79. cocotb/share/include/gpi_logging.h +221 -142
  80. cocotb/share/include/py_gpi_logging.h +7 -4
  81. cocotb/share/include/vpi_user_ext.h +4 -26
  82. cocotb/share/lib/verilator/verilator.cpp +59 -54
  83. cocotb/simulator.cp311-win32.exp +0 -0
  84. cocotb/simulator.cp311-win32.lib +0 -0
  85. cocotb/simulator.cp311-win32.pyd +0 -0
  86. cocotb/simulator.pyi +107 -0
  87. cocotb/task.py +434 -212
  88. cocotb/triggers.py +55 -1092
  89. cocotb/types/__init__.py +25 -47
  90. cocotb/types/_abstract_array.py +151 -0
  91. cocotb/types/_array.py +264 -0
  92. cocotb/types/_logic.py +296 -0
  93. cocotb/types/_logic_array.py +834 -0
  94. cocotb/types/{range.py → _range.py} +36 -44
  95. cocotb/types/_resolve.py +76 -0
  96. cocotb/utils.py +119 -587
  97. cocotb-2.0.0b1.dist-info/METADATA +60 -0
  98. cocotb-2.0.0b1.dist-info/RECORD +143 -0
  99. {cocotb-1.9.2.dist-info → cocotb-2.0.0b1.dist-info}/WHEEL +1 -1
  100. cocotb-2.0.0b1.dist-info/entry_points.txt +2 -0
  101. {cocotb-1.9.2.dist-info → cocotb-2.0.0b1.dist-info}/top_level.txt +1 -0
  102. cocotb_tools/__init__.py +0 -0
  103. cocotb_tools/_coverage.py +33 -0
  104. cocotb_tools/_vendor/__init__.py +3 -0
  105. cocotb_tools/check_results.py +65 -0
  106. cocotb_tools/combine_results.py +152 -0
  107. cocotb_tools/config.py +241 -0
  108. {cocotb → cocotb_tools}/ipython_support.py +29 -22
  109. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  110. {cocotb/share → cocotb_tools}/makefiles/Makefile.inc +82 -54
  111. {cocotb/share → cocotb_tools}/makefiles/Makefile.sim +8 -33
  112. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.activehdl +9 -16
  113. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  114. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  115. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.ghdl +13 -42
  116. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  117. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  118. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  119. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  120. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  121. cocotb/share/makefiles/simulators/Makefile.questa → cocotb_tools/makefiles/simulators/Makefile.questa-compat +26 -54
  122. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  123. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.riviera +17 -56
  124. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  125. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.verilator +15 -22
  126. {cocotb/share → cocotb_tools}/makefiles/simulators/Makefile.xcelium +20 -52
  127. cocotb_tools/py.typed +0 -0
  128. {cocotb → cocotb_tools}/runner.py +794 -361
  129. cocotb/_sim_versions.py → cocotb_tools/sim_versions.py +16 -21
  130. pygpi/entry.py +34 -17
  131. pygpi/py.typed +0 -0
  132. cocotb/binary.py +0 -858
  133. cocotb/config.py +0 -289
  134. cocotb/decorators.py +0 -332
  135. cocotb/memdebug.py +0 -35
  136. cocotb/outcomes.py +0 -56
  137. cocotb/scheduler.py +0 -1099
  138. cocotb/share/makefiles/Makefile.deprecations +0 -12
  139. cocotb/share/makefiles/simulators/Makefile.cvc +0 -94
  140. cocotb/share/makefiles/simulators/Makefile.icarus +0 -111
  141. cocotb/share/makefiles/simulators/Makefile.ius +0 -125
  142. cocotb/share/makefiles/simulators/Makefile.modelsim +0 -32
  143. cocotb/share/makefiles/simulators/Makefile.nvc +0 -64
  144. cocotb/share/makefiles/simulators/Makefile.vcs +0 -98
  145. cocotb/types/array.py +0 -309
  146. cocotb/types/logic.py +0 -292
  147. cocotb/types/logic_array.py +0 -298
  148. cocotb/wavedrom.py +0 -199
  149. cocotb/xunit_reporter.py +0 -80
  150. cocotb-1.9.2.dist-info/METADATA +0 -168
  151. cocotb-1.9.2.dist-info/RECORD +0 -121
  152. cocotb-1.9.2.dist-info/entry_points.txt +0 -2
  153. /cocotb/{_vendor/__init__.py → py.typed} +0 -0
  154. {cocotb-1.9.2.dist-info → cocotb-2.0.0b1.dist-info/licenses}/LICENSE +0 -0
  155. {cocotb → cocotb_tools}/_vendor/distutils_version.py +0 -0
cocotb/handle.py CHANGED
@@ -1,48 +1,70 @@
1
- #!/usr/bin/env python
2
-
1
+ # Copyright cocotb contributors
3
2
  # Copyright (c) 2013 Potential Ventures Ltd
4
3
  # Copyright (c) 2013 SolarFlare Communications Inc
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- # * Redistributions of source code must retain the above copyright
10
- # notice, this list of conditions and the following disclaimer.
11
- # * Redistributions in binary form must reproduce the above copyright
12
- # notice, this list of conditions and the following disclaimer in the
13
- # documentation and/or other materials provided with the distribution.
14
- # * Neither the name of Potential Ventures Ltd,
15
- # SolarFlare Communications Inc nor the
16
- # names of its contributors may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
- # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
- # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
23
- # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
- # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
- # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
- # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
- # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
-
30
- # -*- coding: utf-8 -*-
31
-
32
- import ctypes
4
+ # Licensed under the Revised BSD License, see LICENSE for details.
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
+
33
7
  import enum
34
- import warnings
8
+ import logging
9
+ import os
10
+ import re
11
+ from abc import ABC, abstractmethod
35
12
  from functools import lru_cache
36
- from typing import Optional
13
+ from logging import Logger
14
+ from typing import (
15
+ Any,
16
+ Callable,
17
+ Dict,
18
+ Generic,
19
+ Iterable,
20
+ Iterator,
21
+ NoReturn,
22
+ Optional,
23
+ Sequence,
24
+ Tuple,
25
+ Type,
26
+ TypeVar,
27
+ Union,
28
+ cast,
29
+ )
37
30
 
38
31
  import cocotb
39
32
  from cocotb import simulator
40
- from cocotb.binary import BinaryRepresentation, BinaryValue
41
- from cocotb.log import SimLog
42
- from cocotb.types import Logic, LogicArray
43
-
44
- # Only issue a warning for each deprecated attribute access
45
- _deprecation_warned = set()
33
+ from cocotb._base_triggers import Event
34
+ from cocotb._deprecation import deprecated
35
+ from cocotb._gpi_triggers import (
36
+ Edge,
37
+ FallingEdge,
38
+ ReadOnly,
39
+ ReadWrite,
40
+ RisingEdge,
41
+ ValueChange,
42
+ current_gpi_trigger,
43
+ )
44
+ from cocotb._py_compat import cached_property, insertion_ordered_dict
45
+ from cocotb._utils import DocIntEnum
46
+ from cocotb.task import Task
47
+ from cocotb.types import Array, Logic, LogicArray, Range
48
+
49
+ __all__ = (
50
+ "ArrayObject",
51
+ "Deposit",
52
+ "EnumObject",
53
+ "Force",
54
+ "Freeze",
55
+ "GPIDiscovery",
56
+ "HierarchyArrayObject",
57
+ "HierarchyObject",
58
+ "Immediate",
59
+ "IntegerObject",
60
+ "LogicArrayObject",
61
+ "LogicObject",
62
+ "RealObject",
63
+ "Release",
64
+ "SimHandleBase",
65
+ "StringObject",
66
+ "ValueObjectBase",
67
+ )
46
68
 
47
69
 
48
70
  class _Limits(enum.IntEnum):
@@ -52,7 +74,7 @@ class _Limits(enum.IntEnum):
52
74
 
53
75
 
54
76
  @lru_cache(maxsize=None)
55
- def _value_limits(n_bits, limits):
77
+ def _value_limits(n_bits: int, limits: _Limits) -> Tuple[int, int]:
56
78
  """Calculate min/max for given number of bits and limits class"""
57
79
  if limits == _Limits.SIGNED_NBIT:
58
80
  min_val = -(2 ** (n_bits - 1))
@@ -67,58 +89,52 @@ def _value_limits(n_bits, limits):
67
89
  return min_val, max_val
68
90
 
69
91
 
70
- class SimHandleBase:
92
+ class SimHandleBase(ABC):
71
93
  """Base class for all simulation objects.
72
94
 
73
- We maintain a handle which we can use for GPI calls.
95
+ All simulation objects are hashable and equatable by identity.
96
+
97
+ .. code-block:: python
98
+
99
+ a = dut.clk
100
+ b = dut.clk
101
+ assert a == b
102
+
103
+ .. versionchanged:: 2.0
104
+ ``get_definition_name()`` and ``get_definition_file()`` were removed in favor of :meth:`_def_name` and :meth:`_def_file`, respectively.
74
105
  """
75
106
 
76
- # For backwards compatibility we support a mapping of old member names
77
- # which may alias with the simulator hierarchy. In these cases the
78
- # simulator result takes priority, only falling back to the python member
79
- # if there is no colliding object in the elaborated design.
80
- _compat_mapping = {
81
- "log": "_log",
82
- "fullname": "_fullname",
83
- "name": "_name",
84
- }
85
-
86
- def __init__(self, handle, path):
87
- """
88
- .. Constructor. This RST comment works around sphinx-doc/sphinx#6885
107
+ @abstractmethod
108
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
109
+ self._handle = handle
110
+ self._path: str = self._name if path is None else path
111
+ """The path to this handle, or its name if this is the root handle.
89
112
 
90
- Args:
91
- handle (int): The GPI handle to the simulator object.
92
- path (str): Path to this handle, ``None`` if root.
113
+ :meta public:
93
114
  """
94
- self._handle = handle
95
- self._len: Optional[int] = None
96
- """The "length" (the number of elements) of the underlying object. For vectors this is the number of bits."""
97
- self._sub_handles: dict = {}
98
- """Dictionary of this handle's children."""
99
- self._invalid_sub_handles: set = set()
100
- """Python :class:`set` of invalid queries, for caching purposes."""
101
- self._name: str = self._handle.get_name_string()
115
+
116
+ @cached_property
117
+ def _name(self) -> str:
102
118
  """The name of an object.
103
119
 
104
120
  :meta public:
105
121
  """
106
- self._type: str = self._handle.get_type_string()
122
+ return self._handle.get_name_string()
123
+
124
+ @cached_property
125
+ def _type(self) -> str:
107
126
  """The type of an object as a string.
108
127
 
109
128
  :meta public:
110
129
  """
111
- self._fullname: str = self._name + "(%s)" % self._type
112
- """The name of an object with its type appended in parentheses."""
113
- self._path: str = self._name if path is None else path
114
- """The path to this handle, or its name if this is the root handle.
130
+ return self._handle.get_type_string()
115
131
 
116
- :meta public:
117
- """
118
- self._log = SimLog("cocotb.%s" % self._name)
119
- """The logging object."""
120
- self._log.debug("Created")
121
- self._def_name: str = self._handle.get_definition_name()
132
+ @cached_property
133
+ def _log(self) -> Logger:
134
+ return logging.getLogger(f"cocotb.{self._name}")
135
+
136
+ @cached_property
137
+ def _def_name(self) -> str:
122
138
  """The name of a GPI object's definition.
123
139
 
124
140
  This is the value of ``vpiDefName`` for VPI, ``vhpiNameP`` for VHPI,
@@ -127,7 +143,10 @@ class SimHandleBase:
127
143
 
128
144
  :meta public:
129
145
  """
130
- self._def_file: str = self._handle.get_definition_file()
146
+ return self._handle.get_definition_name()
147
+
148
+ @cached_property
149
+ def _def_file(self) -> str:
131
150
  """The name of the file that sources the object's definition.
132
151
 
133
152
  This is the value of ``vpiDefFile`` for VPI, ``vhpiFileNameP`` for VHPI,
@@ -136,43 +155,17 @@ class SimHandleBase:
136
155
 
137
156
  :meta public:
138
157
  """
158
+ return self._handle.get_definition_file()
139
159
 
140
- def get_definition_name(self):
141
- return self._def_name
142
-
143
- def get_definition_file(self):
144
- return self._def_file
145
-
146
- def __hash__(self):
160
+ def __hash__(self) -> int:
147
161
  return hash(self._handle)
148
162
 
149
- def __len__(self):
150
- """Return the "length" (the number of elements) of the underlying object.
151
-
152
- For vectors this is the number of bits.
153
- """
154
- if self._len is None:
155
- self._len = self._handle.get_num_elems()
156
- return self._len
157
-
158
- def __eq__(self, other):
159
- """Compare equality of handles.
160
-
161
- Example usage::
162
-
163
- if clk == dut.clk:
164
- do_something()
165
- """
163
+ def __eq__(self, other: object) -> bool:
166
164
  if not isinstance(other, SimHandleBase):
167
165
  return NotImplemented
168
166
  return self._handle == other._handle
169
167
 
170
- def __ne__(self, other):
171
- if not isinstance(other, SimHandleBase):
172
- return NotImplemented
173
- return self._handle != other._handle
174
-
175
- def __repr__(self):
168
+ def __repr__(self) -> str:
176
169
  desc = self._path
177
170
  defname = self._def_name
178
171
  if defname:
@@ -182,78 +175,101 @@ class SimHandleBase:
182
175
  desc += " (at " + deffile + ")"
183
176
  return type(self).__qualname__ + "(" + desc + ")"
184
177
 
185
- def __str__(self):
186
- return self._path
187
-
188
- def __setattr__(self, name, value):
189
- if name in self._compat_mapping:
190
- if name not in _deprecation_warned:
191
- warnings.warn(
192
- "Use of attribute {!r} is deprecated, use {!r} instead".format(
193
- name, self._compat_mapping[name]
194
- ),
195
- DeprecationWarning,
196
- stacklevel=3,
197
- )
198
- _deprecation_warned.add(name)
199
- return setattr(self, self._compat_mapping[name], value)
200
- else:
201
- return object.__setattr__(self, name, value)
178
+ def __bool__(self) -> NoReturn:
179
+ raise TypeError(
180
+ "This object cannot be cast to bool or used in conditionals. Use `obj is not None` check in conditionals."
181
+ )
182
+
183
+
184
+ class _RangeableObjectMixin(SimHandleBase):
185
+ """Base class for simulation objects that have a range."""
186
+
187
+ @cached_property
188
+ def range(self) -> Range:
189
+ """Return a :class:`~cocotb.types.Range` over the indexes of the array/vector."""
190
+ left, right, direction = self._handle.get_range()
191
+ if direction == simulator.RANGE_NO_DIR:
192
+ raise RuntimeError("Expected range to have a direction but got none!")
193
+ return Range(left, "to" if direction == simulator.RANGE_UP else "downto", right)
194
+
195
+ @property
196
+ def left(self) -> int:
197
+ """Return the leftmost index in the array/vector."""
198
+ return self.range.left
199
+
200
+ @property
201
+ def direction(self) -> str:
202
+ """Return the direction (``"to"``/``"downto"``) of indexes in the array/vector."""
203
+ return self.range.direction
204
+
205
+ @property
206
+ def right(self) -> int:
207
+ """Return the rightmost index in the array/vector."""
208
+ return self.range.right
209
+
210
+ def __len__(self) -> int:
211
+ return len(self.range)
202
212
 
203
- def __getattr__(self, name):
204
- if name in self._compat_mapping:
205
- if name not in _deprecation_warned:
206
- warnings.warn(
207
- "Use of attribute {!r} is deprecated, use {!r} instead".format(
208
- name, self._compat_mapping[name]
209
- ),
210
- DeprecationWarning,
211
- stacklevel=3,
212
- )
213
- _deprecation_warned.add(name)
214
- return getattr(self, self._compat_mapping[name])
215
- else:
216
- return object.__getattribute__(self, name)
217
213
 
214
+ #: Type of keys (name or index) in HierarchyObjectBase.
215
+ KeyType = TypeVar("KeyType")
218
216
 
219
- class RegionObject(SimHandleBase):
220
- """A region object, such as a scope or namespace.
221
217
 
222
- Region objects don't have values, they are effectively scopes or namespaces.
218
+ class GPIDiscovery(DocIntEnum):
219
+ """Simulator object discovery strategy."""
220
+
221
+ AUTO = (0, "Automatic discovery using all registered interfaces.")
222
+ NATIVE = (1, "Native discovery using only the parent's native interface.")
223
+
224
+
225
+ class _HierarchyObjectBase(SimHandleBase, Generic[KeyType]):
226
+ """Base class for hierarchical simulation objects.
227
+
228
+ Hierarchical objects don't have values, they are just scopes/namespaces of other objects.
229
+ This includes array-like hierarchical structures like "generate loops"
230
+ and named hierarchical structures like "generate blocks" or "module"/"entity" instantiations.
231
+
232
+ This base class defines logic to discover, cache, and inspect child objects.
233
+ It provides a :class:`dict`-like interface for doing so.
234
+
235
+ :meth:`_keys`, :meth:`_values`, and :meth:`_items` mimic their :class:`dict` counterparts.
236
+ You can also iterate over an object, which returns child objects, not keys like in :class:`dict`;
237
+ and can check the :func:`len`.
238
+
239
+ See :class:`HierarchyObject` and :class:`HierarchyArrayObject` for examples.
223
240
  """
224
241
 
225
- def __init__(self, handle, path):
226
- SimHandleBase.__init__(self, handle, path)
227
- self._discovered = False # True if this object has already been discovered
228
-
229
- def __iter__(self):
230
- """Iterate over all known objects in this layer of hierarchy."""
231
- if not self._discovered:
232
- self._discover_all()
233
-
234
- for name, handle in self._sub_handles.items():
235
- if isinstance(handle, list):
236
- self._log.debug("Found index list length %d", len(handle))
237
- for subindex, subhdl in enumerate(handle):
238
- if subhdl is None:
239
- self._log.warning(
240
- "Index %d doesn't exist in %s.%s",
241
- subindex,
242
- self._name,
243
- name,
244
- )
245
- continue
246
- self._log.debug(
247
- "Yielding index %d from %s (%s)", subindex, name, type(subhdl)
248
- )
249
- yield subhdl
250
- else:
251
- self._log.debug(
252
- "Yielding %s of type %s (%s)", name, type(handle), handle._path
253
- )
254
- yield handle
242
+ @abstractmethod
243
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
244
+ super().__init__(handle, path)
245
+ self._sub_handles: Dict[KeyType, SimHandleBase] = {}
246
+ self._discovered = False
247
+
248
+ def _keys(self) -> Iterable[KeyType]:
249
+ """Iterate over the keys (name or index) of the child objects.
250
+
251
+ :meta public:
252
+ """
253
+ self._discover_all()
254
+ return self._sub_handles.keys()
255
+
256
+ def _values(self) -> Iterable[SimHandleBase]:
257
+ """Iterate over the child objects.
258
+
259
+ :meta public:
260
+ """
261
+ self._discover_all()
262
+ return self._sub_handles.values()
263
+
264
+ def _items(self) -> Iterable[Tuple[KeyType, SimHandleBase]]:
265
+ """Iterate over ``(key, object)`` tuples of child objects.
266
+
267
+ :meta public:
268
+ """
269
+ self._discover_all()
270
+ return self._sub_handles.items()
255
271
 
256
- def _discover_all(self):
272
+ def _discover_all(self) -> None:
257
273
  """When iterating or performing IPython tab completion, we run through ahead of
258
274
  time and discover all possible children, populating the :any:`_sub_handles`
259
275
  mapping. Hierarchy can't change after elaboration so we only have to
@@ -261,914 +277,1445 @@ class RegionObject(SimHandleBase):
261
277
  """
262
278
  if self._discovered:
263
279
  return
264
- self._log.debug("Discovering all on %s", self._name)
280
+
265
281
  for thing in self._handle.iterate(simulator.OBJECTS):
266
282
  name = thing.get_name_string()
267
- path = self._child_path(name)
268
- try:
269
- hdl = SimHandle(thing, path)
270
- except NotImplementedError as e:
271
- self._log.debug("%s", e)
272
- continue
273
283
 
284
+ # translate HDL name into a consistent key name
274
285
  try:
275
286
  key = self._sub_handle_key(name)
276
287
  except ValueError:
277
- self._log.debug(
288
+ self._log.exception(
278
289
  "Unable to translate handle >%s< to a valid _sub_handle key",
279
- hdl._name,
290
+ name,
280
291
  )
281
292
  continue
282
293
 
294
+ # compute a full path using the key name
295
+ path = self._child_path(key)
296
+
297
+ # attempt to create the child object
298
+ try:
299
+ hdl = _make_sim_object(thing, path)
300
+ except NotImplementedError:
301
+ self._log.exception(
302
+ "Unable to construct a SimHandle object for %s", path
303
+ )
304
+ continue
305
+
306
+ # add to cache
283
307
  self._sub_handles[key] = hdl
284
308
 
285
309
  self._discovered = True
286
310
 
287
- def _child_path(self, name) -> str:
288
- """Return a string of the path of the child :any:`SimHandle` for a given *name*."""
289
- return self._path + "." + name
311
+ def _get(
312
+ self, key: KeyType, discovery_method: GPIDiscovery = GPIDiscovery.AUTO
313
+ ) -> Union[SimHandleBase, None]:
314
+ """Query the simulator for an object with the specified *key*.
290
315
 
291
- def _sub_handle_key(self, name):
292
- """Translate the handle name to a key to use in :any:`_sub_handles` dictionary."""
293
- return name.split(".")[-1]
316
+ Like Python's native dictionary ``get``-function, this returns ``None`` if the object
317
+ is not found instead of raising an :exc:`AttributeError`.
294
318
 
295
- def __dir__(self):
296
- """Permits IPython tab completion to work."""
297
- self._discover_all()
298
- return super().__dir__() + [str(k) for k in self._sub_handles]
319
+ Generally, use the ``handle[child_name]`` syntax instead, unless you have to change the
320
+ *discovery_method* or want to check for optional signals.
299
321
 
322
+ :meta public:
300
323
 
301
- class HierarchyObject(RegionObject):
302
- """Hierarchy objects are namespace/scope objects."""
324
+ Args:
325
+ key: The child object by name.
326
+ discovery_method: Optional selection of discovery strategy. :data:`~cocotb.handle.GPIDiscovery.AUTO` by default.
303
327
 
304
- def __get_sub_handle_by_name(self, name):
328
+ Returns:
329
+ The child object, or ``None`` if not found.
330
+ """
331
+ # try to use cached value
305
332
  try:
306
- return self._sub_handles[name]
333
+ return self._sub_handles[key]
307
334
  except KeyError:
308
335
  pass
309
336
 
310
- # Cache to avoid a call to the simulator if we already know the name is
311
- # invalid. Unclear if we care, but we had this before.
312
- if name in self._invalid_sub_handles:
337
+ # try to get value from GPI
338
+ new_handle = self._get_handle_by_key(key, discovery_method)
339
+ if new_handle is None:
313
340
  return None
314
341
 
315
- new_handle = self._handle.get_handle_by_name(name)
342
+ # if successful, construct and cache
343
+ sub_handle = _make_sim_object(new_handle, self._child_path(key))
344
+ self._sub_handles[key] = sub_handle
316
345
 
317
- if not new_handle:
318
- self._invalid_sub_handles.add(name)
319
- return None
320
-
321
- sub_handle = SimHandle(new_handle, self._child_path(name))
322
- self._sub_handles[name] = sub_handle
323
346
  return sub_handle
324
347
 
325
- def __setattr__(self, name, value):
326
- """Provide transparent access to signals via the hierarchy.
348
+ @abstractmethod
349
+ def _get_handle_by_key(
350
+ self, key: KeyType, discovery_method: GPIDiscovery
351
+ ) -> Union[simulator.gpi_sim_hdl, None]:
352
+ """Get child object by key from the simulator.
327
353
 
328
- Slightly hacky version of operator overloading in Python.
354
+ Args:
355
+ key: The key of the child object.
356
+ discovery_method: How to discover the object using the GPI.
329
357
 
330
- Raise an :exc:`AttributeError` if users attempt to create new members which
331
- don't exist in the design.
358
+ Returns:
359
+ A raw simulator handle for the child object at the given key, or ``None``.
332
360
  """
333
361
 
334
- # private attributes pass through directly
335
- if name.startswith("_"):
336
- return SimHandleBase.__setattr__(self, name, value)
337
-
338
- # then try handles
339
- sub = self.__get_sub_handle_by_name(name)
340
- if sub is not None:
341
- warnings.warn(
342
- "Setting values on handles using the ``dut.handle = value`` syntax is deprecated. "
343
- "Instead use the ``handle.value = value`` syntax",
344
- DeprecationWarning,
345
- stacklevel=2,
346
- )
347
- sub.value = value
348
- return
362
+ @abstractmethod
363
+ def _child_path(self, key: KeyType) -> str:
364
+ """Compute the path string of a child object at the given key.
349
365
 
350
- # compat behavior
351
- if name in self._compat_mapping:
352
- return SimHandleBase.__setattr__(self, name, value)
366
+ Args:
367
+ key: The key of the child object.
353
368
 
354
- raise AttributeError(f"{self._name} contains no object named {name}")
369
+ Returns:
370
+ A path string of the child object at the a given key.
371
+ """
372
+
373
+ @abstractmethod
374
+ def _sub_handle_key(self, name: str) -> KeyType:
375
+ """Translate a discovered child object name into a key.
376
+
377
+ Args:
378
+ name: The GPI name of the child object.
379
+
380
+ Returns:
381
+ A unique key for the child object.
355
382
 
356
- def __getattr__(self, name):
357
- """Query the simulator for an object with the specified name
358
- and cache the result to build a tree of objects.
383
+ Raises:
384
+ ValueError: if unable to translate handle to a valid _sub_handle key.
359
385
  """
360
- if name.startswith("_"):
361
- return SimHandleBase.__getattr__(self, name)
362
386
 
363
- handle = self.__get_sub_handle_by_name(name)
364
- if handle is not None:
365
- return handle
387
+ def __iter__(self) -> Iterator[SimHandleBase]:
388
+ return iter(self._values())
389
+
390
+ def __len__(self) -> int:
391
+ self._discover_all()
392
+ return len(self._sub_handles)
393
+
394
+ def __dir__(self) -> Iterable[str]:
395
+ """Permits IPython tab completion and debuggers to work."""
396
+ self._discover_all()
397
+ return set(super().__dir__()) | {str(k) for k in self._keys()}
398
+
399
+
400
+ class HierarchyObject(_HierarchyObjectBase[str]):
401
+ r"""A simulation object that is a name-indexed collection of hierarchical simulation objects.
402
+
403
+ Inherits from :class:`SimHandleBase`.
404
+
405
+ This class is used for named hierarchical structures, such as "generate blocks" or "module"/"entity" instantiations.
406
+
407
+ Children under this structure are found by using the name of the child with either the attribute syntax or index syntax.
408
+ For example, if in your :envvar:`COCOTB_TOPLEVEL` entity/module you have a signal/net named ``count``, you could do either of the following.
366
409
 
367
- if name in self._compat_mapping:
368
- return SimHandleBase.__getattr__(self, name)
410
+ .. code-block:: python
411
+
412
+ dut.count # attribute syntax
413
+ dut["count"] # index syntax
414
+
415
+ Attribute syntax is usually shorter and easier to read, and is more common.
416
+ However, it has limitations:
417
+
418
+ - the name cannot start with a number
419
+ - the name cannot start with a ``_`` character
420
+ - the name can only contain ASCII letters, numbers, and the ``_`` character
421
+
422
+ Any possible name of an object is supported with the index syntax,
423
+ but it can be more verbose.
424
+
425
+ Accessing a non-existent child with attribute syntax results in an :class:`AttributeError`,
426
+ and accessing a non-existent child with index syntax results in a :class:`KeyError`.
427
+
428
+ .. note::
429
+ If you need to access signals/nets that start with ``_``,
430
+ or use escaped identifier (Verilog) or extended identifier (VHDL) characters,
431
+ you have to use the index syntax.
432
+ Accessing escaped/extended identifiers requires enclosing the name
433
+ with leading and trailing double backslashes (``\\``).
434
+
435
+ .. code-block:: python
436
+
437
+ dut["_underscore_signal"]
438
+ dut["\\%extended !ID\\"]
439
+
440
+ Iteration yields all child objects in no particular order.
441
+ The :func:`len` function can be used to find the number of children.
442
+
443
+ .. code-block:: python
444
+
445
+ # discover all children in 'some_module'
446
+ total = 0
447
+ for handle in dut.some_module:
448
+ cocotb.log("Found %r", handle._path)
449
+ total += 1
450
+
451
+ # make sure we found them all
452
+ assert len(dut.some_module) == total
453
+ """
454
+
455
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
456
+ super().__init__(handle, path)
457
+
458
+ def __setattr__(self, name: str, value: object) -> None:
459
+ # private attributes pass through directly
460
+ if name.startswith("_"):
461
+ return object.__setattr__(self, name, value)
369
462
 
370
463
  raise AttributeError(f"{self._name} contains no object named {name}")
371
464
 
372
- def _id(self, name, extended: bool = True):
373
- """Query the simulator for an object with the specified *name*,
374
- and cache the result to build a tree of objects.
465
+ def __getattr__(self, name: str) -> SimHandleBase:
466
+ if name.startswith("_"):
467
+ return object.__getattribute__(self, name)
468
+
469
+ handle = self._get(name)
470
+ if handle is None:
471
+ raise AttributeError(f"{self._path} contains no child object named {name}")
472
+ return handle
473
+
474
+ def __getitem__(self, key: str) -> SimHandleBase:
475
+ handle = self._get(key)
476
+ if handle is None:
477
+ raise KeyError(f"{self._path} contains no child object named {key}")
478
+ return handle
479
+
480
+ @deprecated(
481
+ "Use `handle[child_name]` syntax instead. If extended identifiers are needed simply add a '\\' character before and after the name."
482
+ )
483
+ def _id(self, name: str, extended: bool = True) -> SimHandleBase:
484
+ """Query the simulator for an object with the specified *name*.
375
485
 
376
486
  If *extended* is ``True``, run the query only for VHDL extended identifiers.
377
487
  For Verilog, only ``extended=False`` is supported.
378
488
 
379
489
  :meta public:
490
+
491
+ Args:
492
+ name: The child object by name.
493
+ extended: If ``True``, treat the *name* as an extended identifier.
494
+
495
+ Returns:
496
+ The child object.
497
+
498
+ Raises:
499
+ AttributeError: If the child object is not found.
500
+
501
+ .. deprecated:: 2.0
502
+ Use ``handle[child_name]`` syntax instead.
503
+ If extended identifiers are needed simply add a ``\\`` character before and after the name.
504
+
380
505
  """
381
506
  if extended:
382
507
  name = "\\" + name + "\\"
383
508
 
384
- handle = self.__get_sub_handle_by_name(name)
385
- if handle is not None:
386
- return handle
509
+ handle = self._get(name)
510
+ if handle is None:
511
+ raise AttributeError(f"{self._path} contains no child object named {name}")
512
+ return handle
387
513
 
388
- raise AttributeError(f"{self._name} contains no object named {name}")
514
+ def _child_path(self, key: str) -> str:
515
+ delimiter = "::" if self._type == "GPI_PACKAGE" else "."
516
+ return f"{self._path}{delimiter}{key}"
517
+
518
+ def _sub_handle_key(self, name: str) -> str:
519
+ return name
520
+
521
+ def _get_handle_by_key(
522
+ self, key: str, discovery_method: GPIDiscovery
523
+ ) -> Union[simulator.gpi_sim_hdl, None]:
524
+ return self._handle.get_handle_by_name(key, discovery_method)
525
+
526
+
527
+ class HierarchyArrayObject(_HierarchyObjectBase[int], _RangeableObjectMixin):
528
+ """A simulation object that is an array of hierarchical simulation objects.
529
+
530
+ Inherits from :class:`SimHandleBase`.
531
+
532
+ This class is used for array-like hierarchical structures like "generate loops".
533
+
534
+ Children of this object are found by supplying a numerical index using index syntax.
535
+ For example, if you have a design with a generate loop ``gen_pipe_stages`` from the range ``0`` to ``7``:
389
536
 
537
+ .. code-block:: python
390
538
 
391
- class HierarchyArrayObject(RegionObject):
392
- """Hierarchy Arrays are containers of Hierarchy Objects."""
539
+ block_0 = dut.gen_pipe_stages[0]
540
+ block_7 = dut.gen_pipe_stages[7]
393
541
 
394
- def _sub_handle_key(self, name):
395
- """Translate the handle name to a key to use in :any:`_sub_handles` dictionary."""
542
+ Accessing a non-existent child results in an :class:`IndexError`.
543
+
544
+ Iteration yields all child objects in order.
545
+
546
+ .. code-block:: python
547
+
548
+ # set all 'reg's in each pipe stage to 0
549
+ for pipe_stage in dut.gen_pipe_stages:
550
+ pipe_stage.reg.value = 0
551
+
552
+ Use the :meth:`range` property if you want to iterate over the indexes.
553
+ The :func:`len` function can be used to find the number of elements.
554
+
555
+ .. code-block:: python
556
+
557
+ # set all 'reg's in each pipe stage to 0
558
+ for idx in dut.gen_pipe_stages.range:
559
+ dut.gen_pipe_stages[idx].reg.value = 0
560
+
561
+ # make sure we have all the pipe stages
562
+ assert len(dut.gen_pipe_stage) == len(dut.gen_pipe_stages.range)
563
+ """
564
+
565
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
566
+ super().__init__(handle, path)
567
+
568
+ def _sub_handle_key(self, name: str) -> int:
396
569
  # This is slightly hacky, but we need to extract the index from the name
397
570
  # See also GEN_IDX_SEP_* in VhpiImpl.h for the VHPI separators.
398
571
  #
399
572
  # FLI and VHPI: _name(X) where X is the index
400
573
  # VHPI(ALDEC): _name__X where X is the index
401
574
  # VPI: _name[X] where X is the index
402
- import re
403
-
404
- result = re.match(rf"{self._name}__(?P<index>\d+)$", name)
575
+ result = re.match(rf"{re.escape(self._name)}__(?P<index>\d+)$", name)
405
576
  if not result:
406
- result = re.match(rf"{self._name}\((?P<index>\d+)\)$", name, re.IGNORECASE)
577
+ result = re.match(
578
+ rf"{re.escape(self._name)}\((?P<index>\d+)\)$", name, re.IGNORECASE
579
+ )
407
580
  if not result:
408
- result = re.match(rf"{self._name}\[(?P<index>\d+)\]$", name)
581
+ result = re.match(rf"{re.escape(self._name)}\[(?P<index>\d+)\]$", name)
409
582
 
410
583
  if result:
411
584
  return int(result.group("index"))
412
585
  else:
413
586
  raise ValueError(f"Unable to match an index pattern: {name}")
414
587
 
415
- def __len__(self):
416
- """Return the "length" of the generate block."""
417
- if self._len is None:
418
- if not self._discovered:
419
- self._discover_all()
588
+ def _child_path(self, key: int) -> str:
589
+ return f"{self._path}[{key}]"
420
590
 
421
- self._len = len(self._sub_handles)
422
- return self._len
591
+ def _get_handle_by_key(
592
+ self, key: int, discovery_method: GPIDiscovery
593
+ ) -> Union[simulator.gpi_sim_hdl, None]:
594
+ if discovery_method is not GPIDiscovery.AUTO:
595
+ raise NotImplementedError(
596
+ f"Only GPIDiscovery.AUTO is supported for {type(self).__qualname__} right now"
597
+ )
598
+ return self._handle.get_handle_by_index(key)
423
599
 
424
- def __getitem__(self, index):
425
- if isinstance(index, slice):
426
- raise IndexError("Slice indexing is not supported")
427
- if index in self._sub_handles:
428
- return self._sub_handles[index]
429
- new_handle = self._handle.get_handle_by_index(index)
430
- if not new_handle:
431
- raise IndexError("%s contains no object at index %d" % (self._name, index))
432
- path = self._path + "[" + str(index) + "]"
433
- self._sub_handles[index] = SimHandle(new_handle, path)
434
- return self._sub_handles[index]
600
+ def __getitem__(self, key: int) -> SimHandleBase:
601
+ if isinstance(key, slice):
602
+ raise TypeError("Slice indexing is not supported")
603
+
604
+ handle = self._get(key)
605
+ if handle is None:
606
+ raise IndexError(f"{self._path} contains no child object at index {key}")
607
+ return handle
608
+
609
+ # ideally `__len__` could be implemented in terms of `range`, but `range` doesn't work universally.
610
+
611
+ def __iter__(self) -> Iterator[SimHandleBase]:
612
+ # must use `sorted(self._keys())` instead of the range because `range` doesn't work universally.
613
+ for i in sorted(self._keys()):
614
+ yield self[i]
615
+
616
+
617
+ class _GPISetAction(enum.IntEnum):
618
+ DEPOSIT = 0
619
+ FORCE = 1
620
+ RELEASE = 2
621
+ NO_DELAY = 3
435
622
 
436
- def _child_path(self, name):
437
- """Return a string of the path of the child :any:`SimHandle` for a given name."""
438
- index = self._sub_handle_key(name)
439
- return self._path + "[" + str(index) + "]"
440
623
 
441
- def __setitem__(self, index, value):
442
- raise TypeError("Not permissible to set %s at index %d" % (self._name, index))
624
+ _ValueT = TypeVar("_ValueT")
443
625
 
444
626
 
445
- class _AssignmentResult:
627
+ class Deposit(Generic[_ValueT]):
628
+ r""":term:`Inertially deposits <inertial deposit>` the given value on a simulator object.
629
+
630
+ If another :term:`deposit` comes after this deposit, the newer deposit overwrites the old value.
631
+ If an HDL process is :term:`driving` the signal/net/register where a deposit from cocotb is made,
632
+ the deposited value will be overwritten at the end of the next delta cycle,
633
+ essentially causing a single delta cycle "glitch" in the waveform.
634
+
635
+ .. note::
636
+ VHDL applies writes according to their definition.
637
+ ``signal`` writes are set inertially, regardless of using this class;
638
+ while ``variable`` writes are set immediately, regardless of using this class.
446
639
  """
447
- An object that exists solely to provide an error message if the caller
448
- is not aware of cocotb's meaning of ``<=``.
640
+
641
+ def __init__(self, value: _ValueT) -> None:
642
+ self.value = value
643
+
644
+
645
+ class Force(Generic[_ValueT]):
646
+ r""":term:`Force <force>` the given value on a simulator object immediately.
647
+
648
+ Further :term:`deposits <deposit>` from cocotb or :term:`drives <driving>` from HDL processes
649
+ do not cause the value to change until the handle is :term:`released <release>` by cocotb or HDL code.
650
+ Further :term:`forces <force>` will overwrite the value and leave the value forced.
651
+
652
+ .. note::
653
+ VHDL applies writes according to their definition.
654
+ ``signal`` writes are set inertially, regardless of using this class;
655
+ while ``variable`` writes are set immediately, regardless of using this class.
656
+
657
+ .. note::
658
+ Verilog Forces are always immediate.
659
+ This also means that if there are multiple cocotb Tasks or multiple ``always`` blocks writing to the same object,
660
+ the resulting value is non-deterministic.
661
+
662
+ .. note::
663
+ Issuing a Force and Release in the same evaluation cycle in VHDL will result in the Force "winning".
449
664
  """
450
665
 
451
- def __init__(self, signal, value):
452
- self._signal = signal
453
- self._value = value
666
+ def __init__(self, value: _ValueT) -> None:
667
+ self.value = value
454
668
 
455
- def __bool__(self):
456
- raise TypeError(
457
- "Attempted to use `{0._signal!r} <= {0._value!r}` (a cocotb "
458
- "delayed write) as if it were a numeric comparison. To perform "
459
- "comparison, use `{0._signal!r}.value <= {0._value!r}` instead.".format(
460
- self
461
- )
462
- )
463
669
 
670
+ class Freeze:
671
+ r""":term:`Force <force>` the simulator object with its current value.
464
672
 
465
- class NonHierarchyObject(SimHandleBase):
466
- """Common base class for all non-hierarchy objects."""
673
+ Useful if you have done a :term:`deposit` and later decide to lock the value from changing.
674
+ Does not change the current value of the simulation object.
675
+ See :class:`Force` for information on behavior after this write completes.
467
676
 
468
- def __iter__(self):
469
- return iter(())
677
+ .. note::
678
+ VHDL applies writes according to their definition.
679
+ ``signal`` writes are set inertially, regardless of using this class;
680
+ while ``variable`` writes are set immediately, regardless of using this class.
470
681
 
471
- @property
472
- def value(self):
473
- """The value of this simulation object.
682
+ .. note::
683
+ Verilog Forces are always immediate.
684
+ This also means that if there are multiple cocotb Tasks or multiple ``always`` blocks writing to the same object,
685
+ the resulting value is non-deterministic.
474
686
 
475
- .. note::
476
- When setting this property, the value is stored by the :class:`~cocotb.scheduler.Scheduler`
477
- and all stored values are written at the same time at the end of the current simulator time step.
687
+ .. note::
688
+ Issuing a Force and Release in the same evaluation cycle in VHDL will result in the Force "winning".
689
+ """
478
690
 
479
- Use :meth:`setimmediatevalue` to set the value immediately.
480
- """
481
- raise TypeError(
482
- "Not permissible to get values of object {} of type {}".format(
483
- self._name, type(self)
484
- )
485
- )
486
691
 
487
- @value.setter
488
- def value(self, value):
489
- self._set_value(value, cocotb.scheduler._schedule_write)
692
+ class Release:
693
+ """:term:`Release <release>` a :term:`forced <force>` simulation object.
490
694
 
491
- def setimmediatevalue(self, value):
492
- """Assign a value to this simulation object immediately."""
695
+ Does not change the current value of the simulation object.
696
+ See :class:`Deposit` for information on behavior after this write completes.
493
697
 
494
- def _call_now(handle, f, *args):
495
- f(*args)
698
+ .. note::
699
+ VHDL applies writes according to their definition.
700
+ ``signal`` writes are set inertially, regardless of using this class,
701
+ while ``variable`` writes are set immediately, regardless of using this class.
496
702
 
497
- self._set_value(value, _call_now)
703
+ .. note::
704
+ Verilog Releases are always immediate.
705
+ This also means that if there are multiple cocotb Tasks or multiple ``always`` blocks writing to the same object,
706
+ the resulting value is non-deterministic.
498
707
 
499
- def _set_value(self, value, call_sim):
500
- """This should be overriden in subclasses.
708
+ .. note::
709
+ Issuing a Force and Release in the same evaluation cycle in VHDL will result in the Force "winning".
501
710
 
502
- This is used to implement both the setter for :attr:`value`, and the
503
- :meth:`setimmediatevalue` method.
711
+ .. note::
712
+ Releasing a ``reg`` or `logic` in Verilog will leave the current value.
713
+ Releasing a ``wire`` in Verilog will cause the value to be recomputed from the wire's drivers current values.
714
+ Releasing a ``signal`` in VHDL will cause the value to be recomputed from the signal's drivers current value.
715
+ Unconnected ``in`` ports and unconnected internal signals have no drivers and their value after Release will be ``U`` in VHDL and ``X`` in Verilog.
716
+ """
504
717
 
505
- ``call_sim(handle, f, *args)`` should be used to schedule simulator writes,
506
- rather than performing them directly as ``f(*args)``.
507
- """
508
- raise TypeError(
509
- "Not permissible to set values on object {} of type {}".format(
510
- self._name, type(self)
511
- )
512
- )
513
718
 
514
- def __le__(self, value):
515
- """Overload less-than-or-equal-to operator to provide an HDL-like shortcut.
719
+ class Immediate(Generic[_ValueT]):
720
+ """:term:`Deposit <no-delay deposit>` a value on a simulator object without delay.
516
721
 
517
- Example:
518
- >>> module.signal <= 2
519
- """
520
- warnings.warn(
521
- "Setting values on handles using the ``handle <= value`` syntax is deprecated. "
522
- "Instead use the ``handle.value = value`` syntax",
523
- DeprecationWarning,
524
- stacklevel=2,
525
- )
722
+ The value of the signal will be changed immediately
723
+ and should be able to be read back immediately following the write.
724
+ Otherwise, behaves like :class:`Deposit`.
725
+
726
+ .. note::
727
+ VHDL applies writes according to their definition.
728
+ ``signal`` writes are set inertially, regardless of using this class;
729
+ while ``variable`` writes are set immediately, regardless of using this class.
730
+
731
+ .. note::
732
+ In Verilog, because these writes are immediate,
733
+ if there are multiple cocotb Tasks or multiple ``always`` blocks writing to the same object,
734
+ the resulting value is non-deterministic.
735
+ """
736
+
737
+ def __init__(self, value: _ValueT) -> None:
526
738
  self.value = value
527
- return _AssignmentResult(self, value)
528
739
 
529
- def __eq__(self, other):
530
- """Equality comparator for non-hierarchy objects
531
740
 
532
- If ``other`` is not a :class:`SimHandleBase` instance the comparison
533
- uses the comparison method of the ``other`` object against our
534
- ``.value``.
535
- """
536
- if isinstance(other, SimHandleBase):
537
- return SimHandleBase.__eq__(self, other)
538
- return self.value == other
741
+ _trust_inertial = bool(int(os.environ.get("COCOTB_TRUST_INERTIAL_WRITES", "0")))
742
+
743
+ # A dictionary of pending (write_func, args), keyed by handle.
744
+ # Writes are applied oldest to newest (least recently used).
745
+ # Only the last scheduled write to a particular handle in a timestep is performed.
746
+ _write_calls: "dict[ValueObjectBase[Any, Any], Tuple[Callable[[int, Any], None], _GPISetAction, Any]]" = insertion_ordered_dict()
747
+
748
+ _write_task: Union[Task[None], None] = None
749
+
750
+ _writes_pending = Event()
751
+
752
+
753
+ async def _do_writes() -> None:
754
+ """An internal task that schedules a ReadWrite to force writes to occur."""
755
+ while True:
756
+ await _writes_pending.wait()
757
+ await ReadWrite()
758
+
759
+
760
+ def _start_write_scheduler() -> None:
761
+ global _write_task
762
+ if _write_task is None:
763
+ _write_task = Task(_do_writes())
764
+ cocotb._scheduler_inst._schedule_task(_write_task)
765
+
766
+
767
+ def _stop_write_scheduler() -> None:
768
+ global _write_task
769
+ if _write_task is not None:
770
+ _write_task.cancel()
771
+ _write_task = None
772
+ _write_calls.clear()
773
+ _writes_pending.clear()
774
+
775
+
776
+ def _apply_scheduled_writes() -> None:
777
+ for func, action, value in _write_calls.values():
778
+ func(action.value, value)
779
+ _write_calls.clear()
780
+ _writes_pending.clear()
781
+
782
+
783
+ if _trust_inertial:
784
+
785
+ def _schedule_write(
786
+ handle: "ValueObjectBase[Any, Any]",
787
+ write_func: Callable[[int, _ValueT], None],
788
+ action: _GPISetAction,
789
+ value: _ValueT,
790
+ ) -> None:
791
+ # Trust the simulator and just write.
792
+ write_func(action.value, value)
793
+ else:
794
+
795
+ def _schedule_write(
796
+ handle: "ValueObjectBase[Any, Any]",
797
+ write_func: Callable[[int, _ValueT], None],
798
+ action: _GPISetAction,
799
+ value: _ValueT,
800
+ ) -> None:
801
+ if isinstance(current_gpi_trigger(), ReadWrite):
802
+ # If we are already in the ReadWrite phase, apply writes immediately as an optimization.
803
+ write_func(action.value, value)
804
+ elif action == _GPISetAction.DEPOSIT:
805
+ # Queue write for the beginning of the next ReadWrite phase because we can't trust the simulator. =(
806
+ if handle in _write_calls:
807
+ del _write_calls[handle]
808
+ _write_calls[handle] = (write_func, action, value)
809
+ _writes_pending.set()
810
+ else:
811
+ # If we are writing anything that isn't an inertial write, it must be applied immediately.
812
+ write_func(action.value, value)
813
+
814
+
815
+ #: Type returned by the :attr:`~ValueObjectBase.value` getter and returned by the :meth:`~ValueObjectBase.get` method.
816
+ ValueGetT = TypeVar("ValueGetT")
539
817
 
540
- def __ne__(self, other):
541
- if isinstance(other, SimHandleBase):
542
- return SimHandleBase.__ne__(self, other)
543
- return self.value != other
544
818
 
545
- # Re-define hash because we defined __eq__
546
- def __hash__(self):
547
- return SimHandleBase.__hash__(self)
819
+ #: Type accepted by the :attr:`~ValueObjectBase.value` setter and the :meth:`~ValueObjectBase.set` and :meth:`~ValueObjectBase.setimmediatevalue` methods.
820
+ ValueSetT = TypeVar("ValueSetT")
548
821
 
549
822
 
550
- class ConstantObject(NonHierarchyObject):
551
- """An object which has a value that can be read, but not set.
823
+ class ValueObjectBase(SimHandleBase, Generic[ValueGetT, ValueSetT]):
824
+ """Abstract base class for simulation objects that have a value.
552
825
 
553
- The value is cached in the class since it is fixed at elaboration
554
- time and won't change within a simulation.
826
+ Inherits from :class:`SimHandleBase`.
555
827
  """
556
828
 
557
- def __init__(self, handle, path, handle_type):
829
+ @property
830
+ def value(self) -> ValueGetT:
831
+ """Get or set the value of the simulation object.
832
+
833
+ :getter: Return the current value of the simulation object.
834
+
835
+ :setter:
836
+ Set the value of the simulation object.
837
+
838
+ See :class:`Deposit`, :class:`Force`, :class:`Freeze`, :class:`Release`, and :class:`Immediate`
839
+ for additional actions that can be taken when setting a value.
840
+ These are used like so:
841
+
842
+ .. code-block:: python
843
+
844
+ dut.handle.value = 1 # default Deposit action
845
+ dut.handle.value = Deposit(2)
846
+ dut.handle.value = Force(3)
847
+ dut.handle.value = Freeze()
848
+ dut.handle.value = Release()
849
+ dut.handle.value = Immediate(4)
558
850
  """
851
+ return self.get()
852
+
853
+ @value.setter
854
+ def value(self, value: ValueSetT) -> None:
855
+ self.set(value)
856
+
857
+ @abstractmethod
858
+ def get(self) -> ValueGetT:
859
+ """Return the current value of the simulation object."""
860
+
861
+ def set(
862
+ self,
863
+ value: Union[
864
+ ValueSetT,
865
+ Deposit[ValueSetT],
866
+ Force[ValueSetT],
867
+ Freeze,
868
+ Release,
869
+ Immediate[ValueSetT],
870
+ ],
871
+ ) -> None:
872
+ """Set the value of the simulation object.
873
+
874
+ See :class:`Deposit`, :class:`Force`, :class:`Freeze`, :class:`Release`, and :class:`Immediate`
875
+ for additional actions that can be taken when setting a value.
876
+
559
877
  Args:
560
- handle (int): The GPI handle to the simulator object.
561
- path (str): Path to this handle, ``None`` if root.
562
- handle_type: The type of the handle
563
- (``simulator.INTEGER``, ``simulator.ENUM``,
564
- ``simulator.REAL``, ``simulator.STRING``).
878
+ value: The value to set the simulation object to. This may include type conversion.
879
+ action: How to set the value. See :class:`Action` for more details.
880
+
881
+ Raises:
882
+ TypeError: If the *value* is of a type that cannot be converted to a simulation value,
883
+ or if the simulation object is immutable.
884
+ ValueError: If the *value* is of the correct type, but the value fails to convert.
565
885
  """
566
- NonHierarchyObject.__init__(self, handle, path)
567
- if handle_type in [simulator.INTEGER, simulator.ENUM]:
568
- self._value = self._handle.get_signal_val_long()
569
- elif handle_type == simulator.REAL:
570
- self._value = self._handle.get_signal_val_real()
571
- elif handle_type == simulator.STRING:
572
- self._value = self._handle.get_signal_val_str()
886
+ if isinstance(current_gpi_trigger(), ReadOnly):
887
+ raise RuntimeError("Attempting settings a value during the ReadOnly phase.")
888
+ if self.is_const:
889
+ raise TypeError("Attempted setting an immutable object")
890
+ if isinstance(value, Deposit):
891
+ self._set_value(value.value, _GPISetAction.DEPOSIT)
892
+ elif isinstance(value, Force):
893
+ self._set_value(value.value, _GPISetAction.FORCE)
894
+ elif isinstance(value, Freeze):
895
+ # We assume that ValueSetT >= ValueGetT
896
+ self._set_value(cast("ValueSetT", self.get()), _GPISetAction.FORCE)
897
+ elif isinstance(value, Release):
898
+ # We assume that ValueSetT >= ValueGetT
899
+ self._set_value(cast("ValueSetT", self.get()), _GPISetAction.RELEASE)
900
+ elif isinstance(value, Immediate):
901
+ self._set_value(value.value, _GPISetAction.NO_DELAY)
573
902
  else:
574
- val = self._handle.get_signal_val_binstr()
575
- self._value = BinaryValue(n_bits=len(val))
576
- try:
577
- self._value.binstr = val
578
- except Exception:
579
- self._value = val
903
+ self._set_value(value, _GPISetAction.DEPOSIT)
904
+
905
+ @deprecated(
906
+ "Use `handle.set(Immediate(...))` or `handle.value = Immediate(...)` instead."
907
+ )
908
+ def setimmediatevalue(
909
+ self,
910
+ value: Union[
911
+ ValueSetT,
912
+ Deposit[ValueSetT],
913
+ Force[ValueSetT],
914
+ Freeze,
915
+ Release,
916
+ Immediate[ValueSetT],
917
+ ],
918
+ ) -> None:
919
+ r"""Set the value of the simulation object immediately.
920
+
921
+ See :class:`Deposit`, :class:`Force`, :class:`Freeze`, :class:`Release`, and :class:`Immediate`
922
+ for additional actions that can be taken when setting a value.
923
+
924
+ Passing :class:`Deposit`\ s and unwrapped values is equivalent to passing an :class:`Immediate` to :meth:`set`.
925
+
926
+ .. deprecated:: 2.0
927
+ "Use `handle.set(Immediate(...))` or `handle.value = Immediate(...)` instead.
928
+ """
929
+ if isinstance(value, Deposit):
930
+ value = Immediate(value.value)
931
+ elif not isinstance(value, (Force, Freeze, Release, Immediate)):
932
+ value = Immediate(value)
933
+ self.set(value)
934
+
935
+ @cached_property
936
+ def is_const(self) -> bool:
937
+ """``True`` if the simulator object is immutable, e.g. a Verilog parameter or VHDL constant or generic."""
938
+ return self._handle.get_const()
939
+
940
+ @abstractmethod
941
+ def _set_value(
942
+ self,
943
+ value: ValueSetT,
944
+ action: _GPISetAction,
945
+ ) -> None:
946
+ """Schedule a write of the given value to a simulator object.
947
+
948
+ Conversion from multiple Python types into a type understood by the simulator is expected.
949
+ This is used to implement the :attr:`value` property setter, :meth:`setimmediatevalue`, and :meth:`set`.
950
+ Implementations can assume that handle isn't :meth:`const <is_const>`
951
+ and the Scheduler is not in the :data:`ReadOnly <cocotb.SimPhase.READ_ONLY>` phase.
580
952
 
581
- def __int__(self):
582
- return int(self.value)
953
+ Args:
954
+ value: A value used to set the handle.
955
+ action: Whether to deposit, force, or release the value on the handle.
956
+ """
583
957
 
584
- def __float__(self):
585
- return float(self.value)
586
958
 
587
- @NonHierarchyObject.value.getter
588
- def value(self):
589
- """The value of this simulation object."""
590
- return self._value
959
+ #: Type of value of each element in an :class:`ArrayObject`.
960
+ ElemValueT = TypeVar("ElemValueT")
591
961
 
592
- def __str__(self):
593
- if isinstance(self.value, bytes):
594
- StringObject._emit_str_warning(self)
595
- return self.value.decode("ascii")
596
- else:
597
- ModifiableObject._emit_str_warning(self)
598
- return str(self.value)
962
+ #: Subtype of :class:`ValueObjectBase` returned when iterating or indexing a :class:`ArrayObject`.
963
+ ChildObjectT = TypeVar("ChildObjectT", bound=ValueObjectBase[Any, Any])
599
964
 
600
965
 
601
- class NonHierarchyIndexableObject(NonHierarchyObject):
602
- """A non-hierarchy indexable object.
966
+ class ArrayObject(
967
+ ValueObjectBase[Array[ElemValueT], Union[Array[ElemValueT], Sequence[ElemValueT]]],
968
+ _RangeableObjectMixin,
969
+ Generic[ElemValueT, ChildObjectT],
970
+ ):
971
+ """A simulation object that is an array of value-having simulation objects.
603
972
 
604
- Getting and setting the current value of an array is done
605
- by iterating through sub-handles in left-to-right order.
973
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
606
974
 
607
- Given an HDL array ``arr``:
975
+ With Verilog simulation objects, unpacked vectors are mapped to this type.
976
+ Packed vectors are typically mapped to :class:`LogicArrayObject`.
608
977
 
609
- +--------------+---------------------+--------------------------------------------------------------+
610
- | Verilog | VHDL | ``arr.value`` is equivalent to |
611
- +==============+=====================+==============================================================+
612
- | ``arr[4:7]`` | ``arr(4 to 7)`` | ``[arr[4].value, arr[5].value, arr[6].value, arr[7].value]`` |
613
- +--------------+---------------------+--------------------------------------------------------------+
614
- | ``arr[7:4]`` | ``arr(7 downto 4)`` | ``[arr[7].value, arr[6].value, arr[5].value, arr[4].value]`` |
615
- +--------------+---------------------+--------------------------------------------------------------+
978
+ With VHDL simulation objects, all arrayed objects that aren't ``std_(u)logic``,
979
+ ``sfixed``, ``ufixed``, ``unsigned``, ``signed``, and ``string`` are mapped to this type.
616
980
 
617
- When setting the signal as in ``arr.value = ...``, the same index equivalence as noted in the table holds.
981
+ These objects can be iterated over to yield child objects:
618
982
 
619
- .. warning::
620
- Assigning a value to a sub-handle:
983
+ .. code-block:: python
621
984
 
622
- - **Wrong**: ``dut.some_array.value[0] = 1`` (gets value as a list then updates index 0)
623
- - **Correct**: ``dut.some_array[0].value = 1``
624
- """
985
+ for child in dut.array_object:
986
+ print(child._path)
625
987
 
626
- def __init__(self, handle, path):
627
- NonHierarchyObject.__init__(self, handle, path)
628
- self._range = self._handle.get_range()
629
-
630
- def __setitem__(self, index, value):
631
- """Provide transparent assignment to indexed array handles."""
632
- warnings.warn(
633
- "Setting values on handles using the ``dut.handle[i] = value`` syntax is deprecated. "
634
- "Instead use the ``handle[i].value = value`` syntax",
635
- DeprecationWarning,
636
- stacklevel=2,
637
- )
638
- self[index].value = value
988
+ A particular child can be retrieved using its index:
639
989
 
640
- def __getitem__(self, index):
641
- if isinstance(index, slice):
642
- raise IndexError("Slice indexing is not supported")
643
- if self._range is None:
644
- raise IndexError(
645
- "%s is not indexable. Unable to get object at index %d"
646
- % (self._fullname, index)
647
- )
648
- if index in self._sub_handles:
649
- return self._sub_handles[index]
650
- new_handle = self._handle.get_handle_by_index(index)
651
- if not new_handle:
652
- raise IndexError(
653
- "%s contains no object at index %d" % (self._fullname, index)
654
- )
655
- path = self._path + "[" + str(index) + "]"
656
- self._sub_handles[index] = SimHandle(new_handle, path)
657
- return self._sub_handles[index]
990
+ .. code-block:: python
658
991
 
659
- def __iter__(self):
660
- if self._range is None:
661
- return
992
+ child = dut.array_object[0]
662
993
 
663
- self._log.debug("Iterating with range [%d:%d]", self._range[0], self._range[1])
664
- for i in self._range_iter(self._range[0], self._range[1]):
665
- try:
666
- result = self[i]
667
- yield result
668
- except IndexError:
669
- continue
994
+ # reversed iteration over children
995
+ for child_idx in reversed(dut.array_object.range):
996
+ dut.array_object[child_idx]
997
+ """
670
998
 
671
- def _range_iter(self, left, right):
672
- if left > right:
673
- while left >= right:
674
- yield left
675
- left = left - 1
676
- else:
677
- while left <= right:
678
- yield left
679
- left = left + 1
999
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1000
+ super().__init__(handle, path)
1001
+ self._sub_handles: Dict[int, ChildObjectT] = {}
680
1002
 
681
- @NonHierarchyObject.value.getter
682
- def value(self) -> list:
683
- # Don't use self.__iter__, because it has an unwanted `except IndexError`
684
- return [self[i].value for i in self._range_iter(self._range[0], self._range[1])]
1003
+ def get(self) -> Array[ElemValueT]:
1004
+ """Return the current value as an :class:`~cocotb.types.Array`.
685
1005
 
686
- def _set_value(self, value, call_sim):
687
- """Assign value from a list of same length to an array in left-to-right order.
688
- Index 0 of the list maps to the left-most index in the array.
1006
+ Given the HDL array ``arr``, getting the value is equivalent to:
689
1007
 
690
- See the docstring for this class.
1008
+ +--------------+---------------------+--------------------------------------------------------------------------------------------------+
1009
+ | Verilog | VHDL | ``arr.get()`` is equivalent to |
1010
+ +==============+=====================+==================================================================================================+
1011
+ | ``arr[4:7]`` | ``arr(4 to 7)`` | ``Array([arr[4].value, arr[5].value, arr[6].value, arr[7].value], range=Range(4, 'to', 7))`` |
1012
+ +--------------+---------------------+--------------------------------------------------------------------------------------------------+
1013
+ | ``arr[7:4]`` | ``arr(7 downto 4)`` | ``Array([arr[7].value, arr[6].value, arr[5].value, arr[4].value], range=Range(7, 'downto', 4))`` |
1014
+ +--------------+---------------------+--------------------------------------------------------------------------------------------------+
691
1015
  """
692
- if type(value) is not list:
693
- raise TypeError(
694
- "Assigning non-list value to object {} of type {}".format(
695
- self._name, type(self)
696
- )
697
- )
698
- if len(value) != len(self):
699
- raise ValueError(
700
- "Assigning list of length %d to object %s of length %d"
701
- % (len(value), self._name, len(self))
702
- )
703
- for val_idx, self_idx in enumerate(
704
- self._range_iter(self._range[0], self._range[1])
705
- ):
706
- self[self_idx]._set_value(value[val_idx], call_sim)
1016
+ return Array._from_handle([self[i].value for i in self.range], self.range)
1017
+
1018
+ def set(
1019
+ self,
1020
+ value: Union[
1021
+ Union[Array[ElemValueT], Sequence[ElemValueT]],
1022
+ Deposit[Union[Array[ElemValueT], Sequence[ElemValueT]]],
1023
+ Force[Union[Array[ElemValueT], Sequence[ElemValueT]]],
1024
+ Freeze,
1025
+ Release,
1026
+ Immediate[Union[Array[ElemValueT], Sequence[ElemValueT]]],
1027
+ ],
1028
+ ) -> None:
1029
+ """Set the value using an :class:`.Array`-like value.
1030
+
1031
+ The simulation object is set, element-by-element, left-to-right, using the corresponding element of *value*.
1032
+ The indexes of *value* and the simulation object are not taken into account, only position.
1033
+
1034
+ .. warning::
1035
+ Assigning a value to a sub-handle:
1036
+
1037
+ - **Wrong**: ``dut.some_array.value[0] = 1`` (gets value as an Array, updates index 0, then throws it away)
1038
+ - **Correct**: ``dut.some_array[0].value = 1``
707
1039
 
1040
+ Args:
1041
+ value: The value to set the signal to. This may include type conversion.
708
1042
 
709
- class NonConstantObject(NonHierarchyIndexableObject):
710
- """A non-constant object"""
1043
+ Raises:
1044
+ TypeError: If *value* is of a type that can't be assigned to the simulation object.
711
1045
 
712
- # FIXME: what is the difference to ModifiableObject? Explain in docstring.
1046
+ .. warning::
1047
+ Exceptions from array element :meth:`.ValueObjectBase.set` calls will be propagated up,
1048
+ so the actual set of exceptions possible is greater than this list.
1049
+ """
1050
+ super().set(value)
713
1051
 
714
- def drivers(self):
715
- """An iterator for gathering all drivers for a signal.
1052
+ def _set_value(
1053
+ self,
1054
+ value: Union[Array[ElemValueT], Sequence[ElemValueT]],
1055
+ action: _GPISetAction,
1056
+ ) -> None:
1057
+ if len(value) != len(self):
1058
+ raise ValueError(
1059
+ f"Assigning list of length {len(value)} to object {self._name} of length {len(self)}"
1060
+ )
1061
+ for elem, self_idx in zip(value, self.range):
1062
+ self[self_idx]._set_value(elem, action)
716
1063
 
717
- This is currently only available for VPI.
718
- Also, only a few simulators implement this.
719
- """
720
- return self._handle.iterate(simulator.DRIVERS)
1064
+ def __getitem__(self, index: int) -> ChildObjectT:
1065
+ if isinstance(index, slice):
1066
+ raise TypeError("Slicing is not supported")
1067
+ if index in self._sub_handles:
1068
+ return self._sub_handles[index]
1069
+ new_handle = self._handle.get_handle_by_index(index)
1070
+ if not new_handle:
1071
+ raise IndexError(f"{self._path} contains no object at index {index}")
1072
+ path = self._path + "[" + str(index) + "]"
1073
+ self._sub_handles[index] = cast(
1074
+ "ChildObjectT", _make_sim_object(new_handle, path)
1075
+ )
1076
+ return self._sub_handles[index]
721
1077
 
722
- def loads(self):
723
- """An iterator for gathering all loads on a signal.
1078
+ def __iter__(self) -> Iterable[ChildObjectT]:
1079
+ for i in self.range:
1080
+ yield self[i]
724
1081
 
725
- This is currently only available for VPI.
726
- Also, only a few simulators implement this.
727
- """
728
- return self._handle.iterate(simulator.LOADS)
729
1082
 
1083
+ class _NonIndexableValueObjectBase(ValueObjectBase[ValueGetT, ValueSetT]):
1084
+ """ValueObject that is treated as a single object in the GPI.
730
1085
 
731
- class _SetAction:
732
- """Base class representing the type of action used while write-accessing a handle."""
1086
+ NonArrayValueObjects support :meth:`value_change` triggers.
1087
+ """
733
1088
 
734
- pass
1089
+ @cached_property
1090
+ def value_change(self) -> ValueChange:
1091
+ """A trigger which fires whenever the value changes."""
1092
+ if self.is_const:
1093
+ raise TypeError("Can't get ValueChange on immutable signal.")
1094
+ return ValueChange._make(self)
735
1095
 
1096
+ @cached_property
1097
+ def _edge(self) -> Edge:
1098
+ if self.is_const:
1099
+ raise TypeError("Can't get Edge on immutable signal.")
1100
+ return Edge._make(self)
736
1101
 
737
- class _SetValueAction(_SetAction):
738
- __slots__ = ("value",)
739
- """Base class representing the type of action used while write-accessing a handle with a value."""
740
1102
 
741
- def __init__(self, value):
742
- self.value = value
1103
+ class LogicObject(_NonIndexableValueObjectBase[Logic, Union[Logic, int, str]]):
1104
+ """A scalar logic simulation object.
743
1105
 
1106
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
744
1107
 
745
- class Deposit(_SetValueAction):
746
- """Action used for placing a value into a given handle."""
1108
+ Verilog data types that map to this object:
747
1109
 
748
- def _as_gpi_args_for(self, hdl):
749
- return self.value, 0 # GPI_DEPOSIT
1110
+ * ``logic``
1111
+ * ``bit``
750
1112
 
1113
+ VHDL types that map to this object:
751
1114
 
752
- class Force(_SetValueAction):
753
- """Action used to force a handle to a given value until a release is applied."""
1115
+ * ``std_logic``
1116
+ * ``std_ulogic``
1117
+ * ``bit``
1118
+ """
754
1119
 
755
- def _as_gpi_args_for(self, hdl):
756
- return self.value, 1 # GPI_FORCE
1120
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1121
+ super().__init__(handle, path)
757
1122
 
1123
+ def _set_value(
1124
+ self,
1125
+ value: Union[Logic, int, str],
1126
+ action: _GPISetAction,
1127
+ ) -> None:
1128
+ value_: str
1129
+ if isinstance(value, (int, str)):
1130
+ value_ = str(Logic(value))
758
1131
 
759
- class Freeze(_SetAction):
760
- """Action used to make a handle keep its current value until a release is used."""
1132
+ elif isinstance(value, LogicArray):
1133
+ if len(value) != 1:
1134
+ raise ValueError(
1135
+ f"Cannot assign value of length {len(value)} to handle of length 1"
1136
+ )
1137
+ value_ = str(value)
761
1138
 
762
- def _as_gpi_args_for(self, hdl):
763
- return hdl.value, 1 # GPI_FORCE
1139
+ elif isinstance(value, Logic):
1140
+ value_ = str(value)
764
1141
 
1142
+ else:
1143
+ raise TypeError(
1144
+ f"Unsupported type for value assignment: {type(value)} ({value!r})"
1145
+ )
765
1146
 
766
- class Release(_SetAction):
767
- """Action used to stop the effects of a previously applied force/freeze action."""
1147
+ _schedule_write(self, self._handle.set_signal_val_binstr, action, value_)
768
1148
 
769
- def _as_gpi_args_for(self, hdl):
770
- return 0, 2 # GPI_RELEASE
1149
+ def get(self) -> Logic:
1150
+ """Return the current value of the simulation object as a :class:`.Logic`."""
1151
+ binstr = self._handle.get_signal_val_binstr()
1152
+ return Logic(binstr)
1153
+
1154
+ def set(
1155
+ self,
1156
+ value: Union[
1157
+ Union[Logic, int, str],
1158
+ Deposit[Union[Logic, int, str]],
1159
+ Force[Union[Logic, int, str]],
1160
+ Freeze,
1161
+ Release,
1162
+ Immediate[Union[Logic, int, str]],
1163
+ ],
1164
+ ) -> None:
1165
+ """Set the value of the simulation object using a :class:`.Logic`-like value.
771
1166
 
1167
+ Args:
1168
+ value: The value to set the simulation object to.
772
1169
 
773
- class ModifiableObject(NonConstantObject):
774
- """Base class for simulator objects whose values can be modified."""
1170
+ Raises:
1171
+ TypeError: If *value* is of a type that can't be assigned to the simulation object, or readily converted into a type that can.
1172
+ ValueError: If *value* would not fit in the bounds of the simulation object.
1173
+ """
1174
+ super().set(value)
1175
+
1176
+ @cached_property
1177
+ def rising_edge(self) -> RisingEdge:
1178
+ """A trigger which fires whenever the value changes to a ``1``."""
1179
+ if self.is_const:
1180
+ raise TypeError("Can't get RisingEdge on immutable signal")
1181
+ return RisingEdge._make(self)
1182
+
1183
+ @cached_property
1184
+ def falling_edge(self) -> FallingEdge:
1185
+ """A trigger which fires whenever the value changes to a ``0``."""
1186
+ if self.is_const:
1187
+ raise TypeError("Can't get FallingEdge on immutable signal")
1188
+ return FallingEdge._make(self)
1189
+
1190
+ @deprecated(
1191
+ '`len(logic_scalar_handle)` has been deprecated. A scalar handle\'s "length" is always 1.'
1192
+ )
1193
+ def __len__(self) -> int:
1194
+ return 1
1195
+
1196
+ @deprecated(
1197
+ "`int(handle)` casts have been deprecated. Use `int(handle.value)` instead."
1198
+ )
1199
+ def __int__(self) -> int:
1200
+ return int(self.value)
775
1201
 
776
- def _set_value(self, value, call_sim):
777
- """Set the value of the underlying simulation object to *value*.
1202
+ @deprecated(
1203
+ "`str(handle)` casts have been deprecated. Use `str(handle.value)` instead."
1204
+ )
1205
+ def __str__(self) -> str:
1206
+ return str(self.value)
778
1207
 
779
- This operation will fail unless the handle refers to a modifiable
780
- object, e.g. net, signal or variable.
781
1208
 
782
- We determine the library call to make based on the type of the value
783
- because assigning integers less than 32 bits is faster.
1209
+ class LogicArrayObject(
1210
+ _NonIndexableValueObjectBase[LogicArray, Union[LogicArray, Logic, int, str]],
1211
+ _RangeableObjectMixin,
1212
+ ):
1213
+ """A logic array simulation object.
784
1214
 
785
- Args:
786
- value (cocotb.binary.BinaryValue, int):
787
- The value to drive onto the simulator object.
1215
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
788
1216
 
789
- Raises:
790
- TypeError: If target has an unsupported type for value assignment.
1217
+ Verilog types that map to this object:
791
1218
 
792
- OverflowError: If int value is out of the range that can be represented
793
- by the target. -2**(Nbits - 1) <= value <= 2**Nbits - 1
1219
+ * packed any-dimensional vectors of ``logic`` or ``bit``
1220
+ * packed any-dimensional vectors of packed structures
794
1221
 
795
- .. deprecated:: 1.5
796
- :class:`ctypes.Structure` objects are no longer accepted as values for assignment.
797
- Convert the struct object to a :class:`~cocotb.binary.BinaryValue` before assignment using
798
- ``BinaryValue(value=bytes(struct_obj), n_bits=len(signal))`` instead.
1222
+ VHDL types that map to this object:
799
1223
 
800
- .. deprecated:: 1.5
801
- :class:`dict` objects are no longer accepted as values for assignment.
802
- Convert the dictionary to an integer before assignment using
803
- ``sum(v << (d['bits'] * i) for i, v in enumerate(d['values']))``.
804
- """
805
- value, set_action = self._check_for_set_action(value)
1224
+ * ``std_logic_vector`` and ``std_ulogic_vector``
1225
+ * ``unsigned``
1226
+ * ``signed``
1227
+ * ``ufixed``
1228
+ * ``sfixed``
1229
+ * ``float``
1230
+ """
806
1231
 
1232
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1233
+ super().__init__(handle, path)
1234
+
1235
+ def _set_value(
1236
+ self,
1237
+ value: Union[LogicArray, Logic, int, str],
1238
+ action: _GPISetAction,
1239
+ ) -> None:
1240
+ value_: str
807
1241
  if isinstance(value, int):
808
1242
  min_val, max_val = _value_limits(len(self), _Limits.VECTOR_NBIT)
809
1243
  if min_val <= value <= max_val:
810
1244
  if len(self) <= 32:
811
- call_sim(self, self._handle.set_signal_val_int, set_action, value)
1245
+ _schedule_write(
1246
+ self, self._handle.set_signal_val_int, action, value
1247
+ )
812
1248
  return
813
1249
 
1250
+ # LogicArray used for checking
814
1251
  if value < 0:
815
- value = BinaryValue(
816
- value=value,
817
- n_bits=len(self),
818
- bigEndian=False,
819
- binaryRepresentation=BinaryRepresentation.TWOS_COMPLEMENT,
1252
+ value_ = str(
1253
+ LogicArray.from_signed(
1254
+ value,
1255
+ Range(len(self) - 1, "downto", 0),
1256
+ )
820
1257
  )
821
1258
  else:
822
- value = BinaryValue(
823
- value=value,
824
- n_bits=len(self),
825
- bigEndian=False,
826
- binaryRepresentation=BinaryRepresentation.UNSIGNED,
1259
+ value_ = str(
1260
+ LogicArray.from_unsigned(
1261
+ value,
1262
+ Range(len(self) - 1, "downto", 0),
1263
+ )
827
1264
  )
828
1265
  else:
829
- raise OverflowError(
830
- "Int value ({!r}) out of range for assignment of {!r}-bit signal ({!r})".format(
831
- value, len(self), self._name
832
- )
833
- )
834
-
835
- if isinstance(value, ctypes.Structure):
836
- warnings.warn(
837
- "`ctypes.Structure` values are no longer accepted for value assignment. "
838
- "Use `BinaryValue(value=bytes(struct_obj), n_bits=len(signal))` instead",
839
- DeprecationWarning,
840
- stacklevel=3,
841
- )
842
- value = BinaryValue(value=cocotb.utils.pack(value), n_bits=len(self))
843
-
844
- elif isinstance(value, dict):
845
- warnings.warn(
846
- "dict values are no longer accepted for value assignment. "
847
- "Use `sum(v << (d['bits'] * i) for i, v in enumerate(d['values']))` "
848
- "to convert the dict to an int before assignment.",
849
- DeprecationWarning,
850
- stacklevel=3,
851
- )
852
- # We're given a dictionary with a list of values and a bit size...
853
- num = 0
854
- vallist = list(value["values"])
855
- vallist.reverse()
856
- if len(vallist) * value["bits"] != len(self):
857
- raise TypeError(
858
- "Unable to set with array length %d of %d bit entries = %d total, target is only %d bits long"
859
- % (
860
- len(value["values"]),
861
- value["bits"],
862
- len(value["values"]) * value["bits"],
863
- len(self),
864
- )
1266
+ raise ValueError(
1267
+ f"Int value ({value!r}) out of range for assignment of {len(self)!r}-bit signal ({self._name!r})"
865
1268
  )
866
1269
 
867
- for val in vallist:
868
- num = (num << value["bits"]) + val
869
- value = BinaryValue(value=num, n_bits=len(self), bigEndian=False)
1270
+ elif isinstance(value, str):
1271
+ # LogicArray used for checking
1272
+ value_ = str(LogicArray(value, self.range))
870
1273
 
871
1274
  elif isinstance(value, LogicArray):
872
1275
  if len(self) != len(value):
873
1276
  raise ValueError(
874
1277
  f"cannot assign value of length {len(value)} to handle of length {len(self)}"
875
1278
  )
876
- value = value.to_BinaryValue()
1279
+ value_ = str(value)
877
1280
 
878
1281
  elif isinstance(value, Logic):
879
1282
  if len(self) != 1:
880
1283
  raise ValueError(
881
1284
  f"cannot assign value of length 1 to handle of length {len(self)}"
882
1285
  )
883
- value = BinaryValue(str(value))
884
-
885
- elif isinstance(value, BinaryValue):
886
- if len(value) != len(self):
887
- raise ValueError(
888
- f"cannot assign value of length {len(value)} to handle of length {len(self)}"
889
- )
1286
+ value_ = str(value)
890
1287
 
891
1288
  else:
892
1289
  raise TypeError(
893
- "Unsupported type for value assignment: {} ({!r})".format(
894
- type(value), value
895
- )
1290
+ f"Unsupported type for value assignment: {type(value)} ({value!r})"
896
1291
  )
897
1292
 
898
- call_sim(self, self._handle.set_signal_val_binstr, set_action, value.binstr)
899
-
900
- def _check_for_set_action(self, value):
901
- if not isinstance(value, _SetAction):
902
- return value, 0 # GPI_DEPOSIT
903
- return value._as_gpi_args_for(self)
1293
+ _schedule_write(self, self._handle.set_signal_val_binstr, action, value_)
904
1294
 
905
- @NonConstantObject.value.getter
906
- def value(self) -> BinaryValue:
1295
+ def get(self) -> LogicArray:
1296
+ """Return the current value of the simulation object as a :class:`.LogicArray`."""
907
1297
  binstr = self._handle.get_signal_val_binstr()
908
- # Skip BinaryValue.assign() as we know we are using a binstr
909
- result = BinaryValue(n_bits=len(binstr))
910
- # Skip the permitted characters check as we trust the simulator
911
- result._set_trusted_binstr(binstr)
912
- return result
1298
+ return LogicArray._from_handle(binstr)
1299
+
1300
+ def set(
1301
+ self,
1302
+ value: Union[
1303
+ Union[LogicArray, Logic, int, str],
1304
+ Deposit[Union[LogicArray, Logic, int, str]],
1305
+ Force[Union[LogicArray, Logic, int, str]],
1306
+ Freeze,
1307
+ Release,
1308
+ Immediate[Union[LogicArray, Logic, int, str]],
1309
+ ],
1310
+ ) -> None:
1311
+ """Set the value of the simulation object using a :class:`.LogicArray`-like value.
913
1312
 
914
- def __int__(self):
915
- return int(self.value)
1313
+ Args:
1314
+ value: The value to set the simulation object to.
916
1315
 
917
- def _emit_str_warning(self):
918
- warnings.warn(
919
- "`str({t})` is deprecated, and in future will return `{t}._path`. "
920
- "To get a string representation of the value, use `str({t}.value)`.".format(
921
- t=type(self).__qualname__
922
- ),
923
- FutureWarning,
924
- stacklevel=3,
925
- )
1316
+ Raises:
1317
+ TypeError: If *value* is of a type that can't be assigned to the simulation object, or readily converted into a type that can.
1318
+ ValueError: If *value* would not fit in the bounds of the simulation object.
1319
+
1320
+ .. versionchanged:: 2.0
1321
+ Using :class:`ctypes.Structure` objects to set values was removed.
1322
+ Convert the struct object to a :class:`~cocotb.types.LogicArray` before assignment using
1323
+ ``LogicArray("".join(format(int(byte), "08b") for byte in bytes(struct_obj)))`` instead.
1324
+
1325
+ .. versionchanged:: 2.0
1326
+ Using :class:`dict` objects to set values was removed.
1327
+ Convert the dictionary to an integer before assignment using
1328
+ ``sum(v << (d['bits'] * i) for i, v in enumerate(d['values']))`` instead.
1329
+
1330
+ .. versionchanged:: 2.0
1331
+ Supplying too large of an :class:`int` value results in raising a :exc:`ValueError` instead of an :exc:`OverflowError`.
1332
+ """
1333
+ super().set(value)
926
1334
 
927
- def __str__(self):
928
- self._emit_str_warning()
1335
+ @deprecated(
1336
+ "`int(handle)` casts have been deprecated. Use `int(handle.value)` instead."
1337
+ )
1338
+ def __int__(self) -> int:
1339
+ return int(self.value)
1340
+
1341
+ @deprecated(
1342
+ "`str(handle)` casts have been deprecated. Use `str(handle.value)` instead."
1343
+ )
1344
+ def __str__(self) -> str:
929
1345
  return str(self.value)
930
1346
 
1347
+ def __len__(self) -> int:
1348
+ # can't use `range` to get length because `range` is for outer-most dimension only
1349
+ # and this object needs to support multi-dimensional packed arrays.
1350
+ return self._len
931
1351
 
932
- class RealObject(ModifiableObject):
933
- """Specific object handle for Real signals and variables."""
1352
+ @cached_property
1353
+ def _len(self) -> int:
1354
+ return self._handle.get_num_elems()
934
1355
 
935
- def _set_value(self, value, call_sim):
936
- """Set the value of the underlying simulation object to value.
1356
+ def __getitem__(self, _: object) -> NoReturn:
1357
+ raise TypeError(
1358
+ "Packed objects, either arrays or structs, cannot be indexed.\n"
1359
+ "Try instead reading the whole value and slicing: `t = handle.value; t[0:3]`.\n"
1360
+ "If you need to use an element in an Edge Trigger, consider making the array or struct unpacked.\n"
1361
+ "Alternatively, use `ValueChange` on the whole object and check the bit(s) you care about for changes afterwards."
1362
+ )
937
1363
 
938
- This operation will fail unless the handle refers to a modifiable
939
- object, e.g. net, signal or variable.
940
1364
 
941
- Args:
942
- value (float): The value to drive onto the simulator object.
1365
+ class RealObject(_NonIndexableValueObjectBase[float, float]):
1366
+ """A floating point simulation object.
943
1367
 
944
- Raises:
945
- TypeError: If target has an unsupported type for
946
- real value assignment.
947
- """
948
- value, set_action = self._check_for_set_action(value)
1368
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
949
1369
 
950
- try:
951
- value = float(value)
952
- except ValueError:
1370
+ This type is used when a ``real`` object in VHDL or ``float`` object in Verilog is seen.
1371
+ """
1372
+
1373
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1374
+ super().__init__(handle, path)
1375
+
1376
+ def _set_value(
1377
+ self,
1378
+ value: float,
1379
+ action: _GPISetAction,
1380
+ ) -> None:
1381
+ if not isinstance(value, (float, int)):
953
1382
  raise TypeError(
954
- "Unsupported type for real value assignment: {} ({!r})".format(
955
- type(value), value
956
- )
1383
+ f"Unsupported type for real value assignment: {type(value)} ({value!r})"
957
1384
  )
958
1385
 
959
- call_sim(self, self._handle.set_signal_val_real, set_action, value)
1386
+ _schedule_write(self, self._handle.set_signal_val_real, action, value)
960
1387
 
961
- @ModifiableObject.value.getter
962
- def value(self) -> float:
1388
+ def get(self) -> float:
1389
+ """Return the current value of the simulation object as a :class:`float`."""
963
1390
  return self._handle.get_signal_val_real()
964
1391
 
965
- def __float__(self):
966
- return float(self.value)
1392
+ def set(
1393
+ self,
1394
+ value: Union[
1395
+ float,
1396
+ Deposit[float],
1397
+ Force[float],
1398
+ Freeze,
1399
+ Release,
1400
+ Immediate[float],
1401
+ ],
1402
+ ) -> None:
1403
+ """Set the value of the simulation object using a :class:`float` value.
967
1404
 
1405
+ Args:
1406
+ value: The value to set the simulation object to.
968
1407
 
969
- class EnumObject(ModifiableObject):
970
- """Specific object handle for enumeration signals and variables."""
1408
+ Raises:
1409
+ TypeError: If *value* is any type other than :class:`float`.
1410
+ """
1411
+ super().set(value)
971
1412
 
972
- def _set_value(self, value, call_sim):
973
- """Set the value of the underlying simulation object to *value*.
1413
+ @deprecated(
1414
+ "`float(handle)` casts have been deprecated. Use `float(handle.value)` instead."
1415
+ )
1416
+ def __float__(self) -> float:
1417
+ return self.value
974
1418
 
975
- This operation will fail unless the handle refers to a modifiable
976
- object, e.g. net, signal or variable.
977
1419
 
978
- Args:
979
- value (int): The value to drive onto the simulator object.
1420
+ class EnumObject(_NonIndexableValueObjectBase[int, int]):
1421
+ """An enumeration simulation object.
980
1422
 
981
- Raises:
982
- TypeError: If target has an unsupported type for
983
- integer value assignment.
984
- """
985
- value, set_action = self._check_for_set_action(value)
1423
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
986
1424
 
987
- if isinstance(value, BinaryValue):
988
- value = int(value)
989
- elif not isinstance(value, int):
1425
+ This type is used when an enumerated-type simulation object is seen that aren't a "logic" or similar type.
1426
+ The value of this object is represented with an :class:`int`.
1427
+
1428
+ For VHDL objects, the value being represented is the enumeration value at the integer index into the original ``type`` declaration,
1429
+ as if it were a 1-based array.
1430
+
1431
+ For Verilog objects, enumerations are little more than named integer values.
1432
+ There may be many enumeration values that a given :class:`int` value represents.
1433
+ """
1434
+
1435
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1436
+ super().__init__(handle, path)
1437
+
1438
+ def _set_value(
1439
+ self,
1440
+ value: int,
1441
+ action: _GPISetAction,
1442
+ ) -> None:
1443
+ if not isinstance(value, int):
990
1444
  raise TypeError(
991
- "Unsupported type for enum value assignment: {} ({!r})".format(
992
- type(value), value
993
- )
1445
+ f"Unsupported type for enum value assignment: {type(value)} ({value!r})"
994
1446
  )
995
1447
 
996
1448
  min_val, max_val = _value_limits(32, _Limits.UNSIGNED_NBIT)
997
1449
  if min_val <= value <= max_val:
998
- call_sim(self, self._handle.set_signal_val_int, set_action, value)
1450
+ _schedule_write(self, self._handle.set_signal_val_int, action, value)
999
1451
  else:
1000
- raise OverflowError(
1001
- "Int value ({!r}) out of range for assignment of enum signal ({!r})".format(
1002
- value, self._name
1003
- )
1452
+ raise ValueError(
1453
+ f"Int value ({value!r}) out of range for assignment of enum signal ({self._name!r})"
1004
1454
  )
1005
1455
 
1006
- @ModifiableObject.value.getter
1007
- def value(self) -> int:
1008
- return self._handle.get_signal_val_long()
1009
-
1456
+ def get(self) -> int:
1457
+ """Return the current value of the simulation object as an :class:`int`.
1010
1458
 
1011
- class IntegerObject(ModifiableObject):
1012
- """Specific object handle for integer and enumeration signals and variables."""
1013
-
1014
- def _set_value(self, value, call_sim):
1015
- """Set the value of the underlying simulation object to *value*.
1459
+ See :class:`EnumObject` for details on what :class:`int` values correspond to which enumeration values.
1460
+ """
1461
+ return self._handle.get_signal_val_long()
1016
1462
 
1017
- This operation will fail unless the handle refers to a modifiable
1018
- object, e.g. net, signal or variable.
1463
+ def set(
1464
+ self,
1465
+ value: Union[
1466
+ int,
1467
+ Deposit[int],
1468
+ Force[int],
1469
+ Freeze,
1470
+ Release,
1471
+ Immediate[int],
1472
+ ],
1473
+ ) -> None:
1474
+ """Set the value of the simulation object using an :class:`int`.
1475
+
1476
+ See :class:`EnumObject` for details on what :class:`int` values correspond to which enumeration values.
1019
1477
 
1020
1478
  Args:
1021
- value (int): The value to drive onto the simulator object.
1479
+ value: The value to set the simulation object to.
1022
1480
 
1023
1481
  Raises:
1024
- TypeError: If target has an unsupported type for
1025
- integer value assignment.
1482
+ TypeError: If *value* is any type other than :class:`int`.
1483
+ ValueError: If *value* would not fit in a 32-bit signed integer.
1026
1484
 
1027
- OverflowError: If value is out of range for assignment
1028
- of 32-bit IntegerObject.
1485
+ .. versionchanged:: 2.0
1486
+ Supplying too large of a value results in raising a :exc:`ValueError` instead of an :exc:`OverflowError`.
1029
1487
  """
1030
- value, set_action = self._check_for_set_action(value)
1488
+ super().set(value)
1489
+
1490
+ @deprecated(
1491
+ "`int(handle)` casts have been deprecated. Use `int(handle.value)` instead."
1492
+ )
1493
+ def __int__(self) -> int:
1494
+ return int(self.value)
1495
+
1496
+
1497
+ class IntegerObject(_NonIndexableValueObjectBase[int, int]):
1498
+ """An integer simulation object.
1499
+
1500
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
1501
+
1502
+ Verilog types that map to this object:
1031
1503
 
1032
- if isinstance(value, BinaryValue):
1033
- value = int(value)
1034
- elif not isinstance(value, int):
1504
+ * ``byte``
1505
+ * ``shortint``
1506
+ * ``int``
1507
+ * ``longint``
1508
+
1509
+ This type should not be used for the 4-state integer types ``integer`` and ``time``.
1510
+
1511
+ VHDL types that map to this object:
1512
+
1513
+ * ``integer``
1514
+ * ``natural``
1515
+ * ``positive``
1516
+
1517
+ Objects that use this type are assumed to be two's complement 32-bit integers with 2-state (``0`` and ``1``) bits.
1518
+ """
1519
+
1520
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1521
+ super().__init__(handle, path)
1522
+
1523
+ def _set_value(
1524
+ self,
1525
+ value: int,
1526
+ action: _GPISetAction,
1527
+ ) -> None:
1528
+ if not isinstance(value, int):
1035
1529
  raise TypeError(
1036
- "Unsupported type for integer value assignment: {} ({!r})".format(
1037
- type(value), value
1038
- )
1530
+ f"Unsupported type for integer value assignment: {type(value)} ({value!r})"
1039
1531
  )
1040
1532
 
1041
1533
  min_val, max_val = _value_limits(32, _Limits.SIGNED_NBIT)
1042
1534
  if min_val <= value <= max_val:
1043
- call_sim(self, self._handle.set_signal_val_int, set_action, value)
1535
+ _schedule_write(self, self._handle.set_signal_val_int, action, value)
1044
1536
  else:
1045
- raise OverflowError(
1046
- "Int value ({!r}) out of range for assignment of integer signal ({!r})".format(
1047
- value, self._name
1048
- )
1537
+ raise ValueError(
1538
+ f"Int value ({value!r}) out of range for assignment of integer signal ({self._name!r})"
1049
1539
  )
1050
1540
 
1051
- @ModifiableObject.value.getter
1052
- def value(self) -> int:
1541
+ def get(self) -> int:
1542
+ """Return the current value of the simulation object as an :class:`int`."""
1053
1543
  return self._handle.get_signal_val_long()
1054
1544
 
1545
+ def set(
1546
+ self,
1547
+ value: Union[
1548
+ int,
1549
+ Deposit[int],
1550
+ Force[int],
1551
+ Freeze,
1552
+ Release,
1553
+ Immediate[int],
1554
+ ],
1555
+ ) -> None:
1556
+ """Set the the value of the simulation object using an :class:`int` value.
1557
+
1558
+ Args:
1559
+ value: The value to set the simulation object to.
1560
+
1561
+ Raises:
1562
+ TypeError: If *value* is any type other than :class:`int`.
1563
+ ValueError: If *value* would not fit in a 32-bit signed integer.
1055
1564
 
1056
- class StringObject(ModifiableObject):
1057
- """Specific object handle for String variables."""
1565
+ .. versionchanged:: 2.0
1566
+ Supplying too large of a value results in raising a :exc:`ValueError` instead of an :exc:`OverflowError`.
1567
+ """
1568
+ super().set(value)
1058
1569
 
1059
- def _set_value(self, value, call_sim):
1060
- """Set the value of the underlying simulation object to *value*.
1570
+ @deprecated(
1571
+ "`int(handle)` casts have been deprecated. Use `int(handle.value)` instead."
1572
+ )
1573
+ def __int__(self) -> int:
1574
+ return self.value
1061
1575
 
1062
- This operation will fail unless the handle refers to a modifiable
1063
- object, e.g. net, signal or variable.
1064
1576
 
1065
- Args:
1066
- value (bytes): The value to drive onto the simulator object.
1577
+ class StringObject(
1578
+ _NonIndexableValueObjectBase[bytes, bytes],
1579
+ _RangeableObjectMixin,
1580
+ ):
1581
+ """A string simulation object.
1067
1582
 
1068
- Raises:
1069
- TypeError: If target has an unsupported type for
1070
- string value assignment.
1583
+ Inherits from :class:`SimHandleBase` and :class:`ValueObjectBase`.
1071
1584
 
1072
- .. versionchanged:: 1.4
1073
- Takes :class:`bytes` instead of :class:`str`.
1074
- Users are now expected to choose an encoding when using these objects.
1075
- As a convenience, when assigning :class:`str` values, ASCII encoding will be used as a safe default.
1585
+ This type is used when a ``string`` (VHDL or Verilog) simulation object is seen.
1586
+ """
1076
1587
 
1077
- """
1078
- value, set_action = self._check_for_set_action(value)
1079
-
1080
- if isinstance(value, str):
1081
- warnings.warn(
1082
- "Handles on string objects will soon not accept `str` objects. "
1083
- "Please use a bytes object by encoding the string as you see fit. "
1084
- "`str.encode('ascii')` is typically sufficient.",
1085
- DeprecationWarning,
1086
- stacklevel=2,
1087
- )
1088
- value = value.encode("ascii") # may throw UnicodeEncodeError
1588
+ def __init__(self, handle: simulator.gpi_sim_hdl, path: Optional[str]) -> None:
1589
+ super().__init__(handle, path)
1089
1590
 
1090
- if not isinstance(value, bytes):
1591
+ def _set_value(
1592
+ self,
1593
+ value: bytes,
1594
+ action: _GPISetAction,
1595
+ ) -> None:
1596
+ if not isinstance(value, (bytes, bytearray)):
1091
1597
  raise TypeError(
1092
- "Unsupported type for string value assignment: {} ({!r})".format(
1093
- type(value), value
1094
- )
1598
+ f"Unsupported type for string value assignment: {type(value)} ({value!r})"
1095
1599
  )
1600
+ _schedule_write(self, self._handle.set_signal_val_str, action, value)
1096
1601
 
1097
- call_sim(self, self._handle.set_signal_val_str, set_action, value)
1098
-
1099
- @ModifiableObject.value.getter
1100
- def value(self) -> bytes:
1602
+ def get(self) -> bytes:
1603
+ """Return the current value of the simulation object as a :class:`bytes`."""
1101
1604
  return self._handle.get_signal_val_str()
1102
1605
 
1103
- def _emit_str_warning(self):
1104
- warnings.warn(
1105
- "`str({t})` is deprecated, and in future will return `{t}._path`. "
1106
- "To access the `bytes` value of this handle, use `{t}.value`.".format(
1107
- t=type(self).__qualname__
1108
- ),
1109
- FutureWarning,
1110
- stacklevel=3,
1111
- )
1606
+ def set(
1607
+ self,
1608
+ value: Union[
1609
+ bytes,
1610
+ Deposit[bytes],
1611
+ Force[bytes],
1612
+ Freeze,
1613
+ Release,
1614
+ Immediate[bytes],
1615
+ ],
1616
+ ) -> None:
1617
+ """Set the value of the simulation object with a :class:`bytes` or :class:`bytearray` value.
1618
+
1619
+ When *value*'s length is less than the simulation object's,
1620
+ the value is padded with NUL (``'\0'``) characters up to the appropriate length.
1621
+ When the value's length is greater than the simulation object's,
1622
+ the value is truncated without a NUL terminator to the appropriate length,
1623
+ without warning.
1624
+
1625
+ Strings in both Verilog and VHDL are byte arrays without any particular encoding.
1626
+ Encoding must be done to turn Python strings into byte arrays.
1627
+ Because `there are many encodings <https://docs.python.org/3/library/codecs.html#standard-encodings>`_,
1628
+ this step is left up to the user.
1629
+
1630
+ An example of how encoding and decoding could be accomplished using an ASCII string.
1631
+
1632
+ .. code-block:: python
1633
+
1634
+ # lowercase a string
1635
+ value = dut.string_handle.value.decode("ascii")
1636
+ value = value.lower()
1637
+ dut.string_handle.value = value.encode("ascii")
1112
1638
 
1113
- def __str__(self):
1114
- self._emit_str_warning()
1115
- return self.value.decode("ascii")
1639
+ Args:
1640
+ value: The value to set the simulation object to.
1116
1641
 
1642
+ Raises:
1643
+ TypeError: If *value* is any type other than :class:`bytes`.
1644
+
1645
+ .. versionchanged:: 1.4
1646
+ Takes :class:`bytes` instead of :class:`str`.
1647
+ Users are now expected to choose an encoding when using these objects.
1648
+ """
1649
+ super().set(value)
1117
1650
 
1118
- _handle2obj = {}
1651
+ @deprecated(
1652
+ '`str(handle)` casts have been deprecated. Use `handle.value.decode("ascii")` instead.'
1653
+ )
1654
+ def __str__(self) -> str:
1655
+ return self.value.decode("ascii")
1119
1656
 
1120
1657
 
1121
- def SimHandle(handle, path=None):
1658
+ _ConcreteHandleTypes = Union[
1659
+ HierarchyObject,
1660
+ HierarchyArrayObject,
1661
+ LogicObject,
1662
+ LogicArrayObject,
1663
+ ArrayObject[Any, ValueObjectBase[Any, Any]],
1664
+ RealObject,
1665
+ IntegerObject,
1666
+ EnumObject,
1667
+ StringObject,
1668
+ ]
1669
+
1670
+
1671
+ _handle2obj: Dict[
1672
+ simulator.gpi_sim_hdl,
1673
+ _ConcreteHandleTypes,
1674
+ ] = {}
1675
+
1676
+ _type2cls: Dict[int, Type[_ConcreteHandleTypes]] = {
1677
+ simulator.MODULE: HierarchyObject,
1678
+ simulator.STRUCTURE: HierarchyObject,
1679
+ simulator.PACKED_STRUCTURE: LogicArrayObject,
1680
+ simulator.LOGIC: LogicObject,
1681
+ simulator.LOGIC_ARRAY: LogicArrayObject,
1682
+ simulator.NETARRAY: ArrayObject[Any, ValueObjectBase[Any, Any]],
1683
+ simulator.REAL: RealObject,
1684
+ simulator.INTEGER: IntegerObject,
1685
+ simulator.ENUM: EnumObject,
1686
+ simulator.STRING: StringObject,
1687
+ simulator.GENARRAY: HierarchyArrayObject,
1688
+ simulator.PACKAGE: HierarchyObject,
1689
+ }
1690
+
1691
+
1692
+ def _make_sim_object(
1693
+ handle: simulator.gpi_sim_hdl, path: Optional[str] = None
1694
+ ) -> SimHandleBase:
1122
1695
  """Factory function to create the correct type of `SimHandle` object.
1123
1696
 
1124
1697
  Args:
1125
- handle (int): The GPI handle to the simulator object.
1126
- path (str): Path to this handle, ``None`` if root.
1698
+ handle: The GPI handle to the simulator object.
1699
+ path: Path to this handle.
1127
1700
 
1128
1701
  Returns:
1129
- The `SimHandle` object.
1702
+ An appropriate :class:`SimHandleBase` object.
1130
1703
 
1131
1704
  Raises:
1132
1705
  NotImplementedError: If no matching object for GPI type could be found.
1133
1706
  """
1134
- _type2cls = {
1135
- simulator.MODULE: HierarchyObject,
1136
- simulator.STRUCTURE: HierarchyObject,
1137
- simulator.REG: ModifiableObject,
1138
- simulator.NET: ModifiableObject,
1139
- simulator.NETARRAY: NonHierarchyIndexableObject,
1140
- simulator.REAL: RealObject,
1141
- simulator.INTEGER: IntegerObject,
1142
- simulator.ENUM: EnumObject,
1143
- simulator.STRING: StringObject,
1144
- simulator.GENARRAY: HierarchyArrayObject,
1145
- }
1146
1707
 
1147
1708
  # Enforce singletons since it's possible to retrieve handles avoiding
1148
1709
  # the hierarchy by getting driver/load information
1149
- global _handle2obj
1150
1710
  try:
1151
1711
  return _handle2obj[handle]
1152
1712
  except KeyError:
1153
1713
  pass
1154
1714
 
1155
1715
  t = handle.get_type()
1156
-
1157
- # Special case for constants
1158
- if handle.get_const() and t not in [
1159
- simulator.MODULE,
1160
- simulator.STRUCTURE,
1161
- simulator.NETARRAY,
1162
- simulator.GENARRAY,
1163
- ]:
1164
- obj = ConstantObject(handle, path, t)
1165
- _handle2obj[handle] = obj
1166
- return obj
1167
-
1168
1716
  if t not in _type2cls:
1169
1717
  raise NotImplementedError(
1170
- "Couldn't find a matching object for GPI type %s(%d) (path=%s)"
1171
- % (handle.get_type_string(), t, path)
1718
+ f"Couldn't find a matching object for GPI type {handle.get_type_string()}({t}) (path={path})"
1172
1719
  )
1173
1720
  obj = _type2cls[t](handle, path)
1174
1721
  _handle2obj[handle] = obj