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
@@ -9,7 +9,8 @@
9
9
  # TODO: support timescale on all simulators
10
10
  # TODO: support custom dependencies
11
11
 
12
- import abc
12
+ import logging
13
+ import multiprocessing
13
14
  import os
14
15
  import re
15
16
  import shlex
@@ -18,41 +19,86 @@ import subprocess
18
19
  import sys
19
20
  import tempfile
20
21
  import warnings
22
+ from abc import ABC, abstractmethod
21
23
  from contextlib import suppress
22
24
  from pathlib import Path
23
- from typing import Dict, List, Mapping, Optional, Sequence, TextIO, Tuple, Type, Union
24
- from xml.etree import ElementTree as ET
25
+ from typing import (
26
+ Dict,
27
+ Iterable,
28
+ List,
29
+ Mapping,
30
+ Optional,
31
+ Sequence,
32
+ TextIO,
33
+ Tuple,
34
+ Type,
35
+ Union,
36
+ )
25
37
 
26
38
  import find_libpython
27
39
 
28
- import cocotb.config
40
+ import cocotb_tools.config
41
+ from cocotb_tools.check_results import get_results
42
+ from cocotb_tools.sim_versions import NvcVersion
29
43
 
30
- PathLike = Union["os.PathLike[str]", str]
31
- Command = List[str]
32
- Timescale = Tuple[str, str]
44
+ PathLike = Union["os.PathLike[str]", str] # TODO use TypeAlias in Python 3.10
45
+ "A path that can be passed to :class:`pathlib.Path` or :func:`open`"
33
46
 
34
- warnings.warn(
35
- "Python runners and associated APIs are an experimental feature and subject to change.",
36
- UserWarning,
37
- stacklevel=2,
38
- )
47
+ _Command = List[str]
39
48
 
40
49
  _magic_re = re.compile(r"([\\{}])")
41
50
  _space_re = re.compile(r"([\s])", re.ASCII)
42
51
 
43
52
 
44
- def as_tcl_value(value: str) -> str:
53
+ MAX_PARALLEL_BUILD_JOBS: int = 4
54
+ """The maximum number of parallel build threads in calls to :meth:`.Runner.build`.
55
+
56
+ If the number of CPU cores is less than this value, it uses the CPU core count.
57
+ Set this variable to globally change the number of parallel build jobs.
58
+ """
59
+
60
+
61
+ def _get_max_parallel_build_jobs() -> int:
62
+ return min(MAX_PARALLEL_BUILD_JOBS, multiprocessing.cpu_count())
63
+
64
+
65
+ def _as_tcl_value(value: str) -> str:
45
66
  # add '\' before special characters and spaces
46
67
  value = _magic_re.sub(r"\\\1", value)
47
68
  value = value.replace("\n", r"\n")
48
69
  value = _space_re.sub(r"\\\1", value)
49
- if value[0] == '"':
70
+ if value[:1] == '"':
50
71
  value = "\\" + value
51
72
 
52
73
  return value
53
74
 
54
75
 
55
- def shlex_join(split_command):
76
+ _sv_escapes = {
77
+ "\n": "\\n",
78
+ "\t": "\\t",
79
+ "\\": "\\\\",
80
+ '"': '\\"',
81
+ "\v": "\\v",
82
+ "\f": "\\f",
83
+ "\xff": "\\xFF",
84
+ }
85
+ for i in range(32):
86
+ if chr(i) not in _sv_escapes:
87
+ _sv_escapes[chr(i)] = f"\\x{i:02x}"
88
+
89
+ _sv_escape_translate_table = str.maketrans(_sv_escapes)
90
+
91
+
92
+ def _as_sv_literal(value: str) -> str:
93
+ if isinstance(value, (int, float)):
94
+ return str(value)
95
+ elif isinstance(value, str):
96
+ return '"' + value.translate(_sv_escape_translate_table) + '"'
97
+ else:
98
+ raise TypeError("Can't serialize this type as an SV literal")
99
+
100
+
101
+ def _shlex_join(split_command: Iterable[str]) -> str:
56
102
  """
57
103
  Return a shell-escaped string from *split_command*
58
104
  This is here more for compatibility purposes
@@ -61,19 +107,17 @@ def shlex_join(split_command):
61
107
 
62
108
 
63
109
  class VHDL(str):
64
- """Tags source files and build arguments to :meth:`~cocotb.runner.Simulator.build` as VHDL-specific."""
110
+ """Tags source files and build arguments to :meth:`Runner.build() <cocotb_tools.runner.Runner.build>` as VHDL-specific."""
65
111
 
66
112
 
67
113
  class Verilog(str):
68
- """Tags source files and build arguments to :meth:`~cocotb.runner.Simulator.build` as Verilog-specific."""
114
+ """Tags source files and build arguments to :meth:`Runner.build() <cocotb_tools.runner.Runner.build>` as Verilog-specific."""
69
115
 
70
116
 
71
- class Simulator(abc.ABC):
72
-
117
+ class Runner(ABC):
73
118
  supported_gpi_interfaces: Dict[str, List[str]] = {}
74
119
 
75
120
  def __init__(self) -> None:
76
-
77
121
  self._simulator_in_path()
78
122
 
79
123
  self.env: Dict[str, str] = {}
@@ -82,7 +126,10 @@ class Simulator(abc.ABC):
82
126
  self.build_dir: Path = get_abs_path("sim_build")
83
127
  self.parameters: Mapping[str, object] = {}
84
128
 
85
- @abc.abstractmethod
129
+ self.log = logging.getLogger(type(self).__qualname__)
130
+ self.log.setLevel(logging.INFO)
131
+
132
+ @abstractmethod
86
133
  def _simulator_in_path(self) -> None:
87
134
  """Raise exception if the simulator executable does not exist in :envvar:`PATH`.
88
135
 
@@ -90,8 +137,6 @@ class Simulator(abc.ABC):
90
137
  SystemExit: Simulator executable does not exist in :envvar:`PATH`.
91
138
  """
92
139
 
93
- raise NotImplementedError()
94
-
95
140
  def _check_hdl_toplevel_lang(self, hdl_toplevel_lang: Optional[str]) -> str:
96
141
  """Return *hdl_toplevel_lang* if supported by simulator, raise exception otherwise.
97
142
 
@@ -120,12 +165,12 @@ class Simulator(abc.ABC):
120
165
  else:
121
166
  lang = hdl_toplevel_lang
122
167
 
123
- if lang in self.supported_gpi_interfaces.keys():
168
+ if lang in self.supported_gpi_interfaces:
124
169
  return lang
125
170
  else:
126
171
  raise ValueError(
127
172
  f"{type(self).__qualname__}: hdl_toplevel_lang {hdl_toplevel_lang!r} is not "
128
- f"in supported list: {', '.join(self.supported_gpi_interfaces.keys())}"
173
+ f"in supported list: {', '.join(self.supported_gpi_interfaces)}"
129
174
  )
130
175
 
131
176
  def _set_env(self) -> None:
@@ -142,23 +187,28 @@ class Simulator(abc.ABC):
142
187
  )
143
188
  self.env["LIBPYTHON_LOC"] = libpython_path
144
189
 
145
- self.env["PATH"] += os.pathsep + cocotb.config.libs_dir
190
+ self.env["PATH"] += os.pathsep + str(cocotb_tools.config.libs_dir)
146
191
  self.env["PYTHONPATH"] = os.pathsep.join(sys.path)
147
- self.env["PYTHONHOME"] = sys.prefix
148
- self.env["TOPLEVEL"] = self.sim_hdl_toplevel
149
- self.env["MODULE"] = self.test_module
192
+ self.env["PYGPI_PYTHON_BIN"] = sys.executable
193
+ self.env["COCOTB_TOPLEVEL"] = self.sim_hdl_toplevel
194
+ self.env["COCOTB_TEST_MODULES"] = self.test_module
195
+ self.env["TOPLEVEL_LANG"] = self.hdl_toplevel_lang
150
196
 
151
- @abc.abstractmethod
152
- def _build_command(self) -> Sequence[Command]:
197
+ @abstractmethod
198
+ def _build_command(self) -> Sequence[_Command]:
153
199
  """Return command to build the HDL sources."""
154
200
 
155
- raise NotImplementedError()
156
-
157
- @abc.abstractmethod
158
- def _test_command(self) -> Sequence[Command]:
201
+ @abstractmethod
202
+ def _test_command(self) -> Sequence[_Command]:
159
203
  """Return command to run a test."""
160
204
 
161
- raise NotImplementedError()
205
+ def _use_external_viewer(self) -> bool:
206
+ """Return if an external viewer should be called after simulation when ``gui=True``."""
207
+ return False
208
+
209
+ def _waves_file(self) -> Optional[str]:
210
+ """Return file name of the generated waveform file for use with external viewer."""
211
+ return None
162
212
 
163
213
  def build(
164
214
  self,
@@ -175,8 +225,8 @@ class Simulator(abc.ABC):
175
225
  build_dir: PathLike = "sim_build",
176
226
  clean: bool = False,
177
227
  verbose: bool = False,
178
- timescale: Optional[Timescale] = None,
179
- waves: Optional[bool] = None,
228
+ timescale: Optional[Tuple[str, str]] = None,
229
+ waves: bool = False,
180
230
  log_file: Optional[PathLike] = None,
181
231
  ) -> None:
182
232
  """Build the HDL sources.
@@ -199,7 +249,7 @@ class Simulator(abc.ABC):
199
249
  +----------+------------------------------------+
200
250
 
201
251
 
202
- .. code-block:: python3
252
+ .. code-block:: python
203
253
 
204
254
  runner.build(
205
255
  sources=[
@@ -224,25 +274,41 @@ class Simulator(abc.ABC):
224
274
  hdl_toplevel: The name of the HDL toplevel module.
225
275
  always: Always run the build step.
226
276
  build_dir: Directory to run the build step in.
227
- clean: Delete build_dir before building
277
+ clean: Delete *build_dir* before building.
228
278
  verbose: Enable verbose messages.
229
279
  timescale: Tuple containing time unit and time precision for simulation.
230
- waves: Record signal traces.
280
+ waves: Record signal traces. Overridden by the ``WAVES`` environment variable.
231
281
  log_file: File to write the build log to.
282
+
283
+ .. deprecated:: 2.0
284
+
285
+ Uses of the *verilog_sources* and *vhdl_sources* parameters should be replaced with the language-agnostic *sources* argument.
232
286
  """
233
287
 
234
288
  self.clean: bool = clean
235
289
  self.build_dir = get_abs_path(build_dir)
236
290
  if self.clean:
237
291
  self.rm_build_folder(self.build_dir)
238
- os.makedirs(self.build_dir, exist_ok=True)
292
+ self.build_dir.mkdir(parents=True, exist_ok=True)
239
293
 
240
294
  # note: to avoid mutating argument defaults, we ensure that no value
241
295
  # is written without a copy. This is much more concise and leads to
242
296
  # a better docstring than using `None` as a default in the parameters
243
297
  # list.
244
298
  self.hdl_library: str = hdl_library
299
+ if verilog_sources:
300
+ warnings.warn(
301
+ "Simulator.build *verilog_sources* parameter is deprecated. Use the language-agnostic *sources* parameter instead.",
302
+ DeprecationWarning,
303
+ stacklevel=2,
304
+ )
245
305
  self.verilog_sources: List[Path] = get_abs_paths(verilog_sources)
306
+ if vhdl_sources:
307
+ warnings.warn(
308
+ "Simulator.build *vhdl_sources* parameter is deprecated. Use the language-agnostic *sources* parameter instead.",
309
+ DeprecationWarning,
310
+ stacklevel=2,
311
+ )
246
312
  self.vhdl_sources: List[Path] = get_abs_paths(vhdl_sources)
247
313
  self.sources: List[Path] = get_abs_paths(sources)
248
314
  self.includes: List[Path] = get_abs_paths(includes)
@@ -252,15 +318,14 @@ class Simulator(abc.ABC):
252
318
  self.always: bool = always
253
319
  self.hdl_toplevel: Optional[str] = hdl_toplevel
254
320
  self.verbose: bool = verbose
255
- self.timescale: Optional[Timescale] = timescale
321
+ self.timescale: Optional[Tuple[str, str]] = timescale
256
322
  self.log_file: Optional[PathLike] = log_file
257
323
 
258
- self.waves = bool(waves)
324
+ self.waves = bool(os.getenv("WAVES", waves))
259
325
 
260
- for e in os.environ:
261
- self.env[e] = os.environ[e]
326
+ self.env.update(os.environ)
262
327
 
263
- cmds: Sequence[Command] = self._build_command()
328
+ cmds: Sequence[_Command] = self._build_command()
264
329
  self._execute(cmds, cwd=self.build_dir)
265
330
 
266
331
  def test(
@@ -272,19 +337,21 @@ class Simulator(abc.ABC):
272
337
  gpi_interfaces: Optional[List[str]] = None,
273
338
  testcase: Optional[Union[str, Sequence[str]]] = None,
274
339
  seed: Optional[Union[str, int]] = None,
340
+ elab_args: Sequence[str] = [],
275
341
  test_args: Sequence[str] = [],
276
342
  plusargs: Sequence[str] = [],
277
343
  extra_env: Mapping[str, str] = {},
278
- waves: Optional[bool] = None,
279
- gui: Optional[bool] = None,
280
- parameters: Mapping[str, object] = None,
344
+ waves: bool = False,
345
+ gui: bool = False,
346
+ parameters: Optional[Mapping[str, object]] = None,
281
347
  build_dir: Optional[PathLike] = None,
282
348
  test_dir: Optional[PathLike] = None,
283
349
  results_xml: Optional[str] = None,
284
- pre_cmd: List[str] = [],
350
+ pre_cmd: Optional[List[str]] = None,
285
351
  verbose: bool = False,
286
- timescale: Optional[Timescale] = None,
352
+ timescale: Optional[Tuple[str, str]] = None,
287
353
  log_file: Optional[PathLike] = None,
354
+ test_filter: Optional[str] = None,
288
355
  ) -> Path:
289
356
  """Run the tests.
290
357
 
@@ -299,11 +366,12 @@ class Simulator(abc.ABC):
299
366
  If not set, run all testcases found in *test_module*.
300
367
  Can be a comma-separated list.
301
368
  seed: A specific random seed to use.
302
- test_args: Extra arguments for the simulator.
369
+ elab_args: A list of elaboration arguments for the simulator.
370
+ test_args: A list of extra arguments for the simulator.
303
371
  plusargs: 'plusargs' to set for the simulator.
304
372
  extra_env: Extra environment variables to set.
305
- waves: Record signal traces.
306
- gui: Run with simulator GUI.
373
+ waves: Record signal traces. Overridden by the ``WAVES`` environment variable.
374
+ gui: Run with simulator GUI. Overridden by the ``GUI`` environment variable.
307
375
  parameters: Verilog parameters or VHDL generics.
308
376
  build_dir: Directory the build step has been run in.
309
377
  test_dir: Directory to run the tests in.
@@ -313,8 +381,11 @@ class Simulator(abc.ABC):
313
381
  This argument should not be set when run with ``pytest``.
314
382
  verbose: Enable verbose messages.
315
383
  pre_cmd: Commands to run before simulation begins.
384
+ Typically Tcl commands for simulators that support them.
316
385
  timescale: Tuple containing time unit and time precision for simulation.
317
386
  log_file: File to write the test log to.
387
+ test_filter: Regular expression which matches test names.
388
+ Only matched tests are run if this argument if given.
318
389
 
319
390
  Returns:
320
391
  The absolute location of the results XML file which can be
@@ -333,7 +404,7 @@ class Simulator(abc.ABC):
333
404
  self.test_dir = self.build_dir
334
405
  else:
335
406
  self.test_dir = get_abs_path(test_dir)
336
- os.makedirs(self.test_dir, exist_ok=True)
407
+ self.test_dir.mkdir(parents=True, exist_ok=True)
337
408
 
338
409
  if isinstance(test_module, str):
339
410
  self.test_module = test_module
@@ -356,84 +427,134 @@ class Simulator(abc.ABC):
356
427
 
357
428
  self.pre_cmd = pre_cmd
358
429
 
430
+ self.elab_args = list(elab_args)
359
431
  self.test_args = list(test_args)
360
432
  self.plusargs = list(plusargs)
361
433
  self.env = dict(extra_env)
362
434
 
363
435
  if testcase is not None:
364
436
  if isinstance(testcase, str):
365
- self.env["TESTCASE"] = testcase
437
+ self.env["COCOTB_TESTCASE"] = testcase
366
438
  else:
367
- self.env["TESTCASE"] = ",".join(testcase)
439
+ self.env["COCOTB_TESTCASE"] = ",".join(testcase)
440
+
441
+ if test_filter is not None:
442
+ self.env["COCOTB_TEST_FILTER"] = test_filter
368
443
 
369
444
  if seed is not None:
370
- self.env["RANDOM_SEED"] = str(seed)
445
+ self.env["COCOTB_RANDOM_SEED"] = str(seed)
371
446
 
372
447
  self.log_file = log_file
373
- self.waves = bool(waves)
374
- self.gui = bool(gui)
375
- self.timescale: Optional[Timescale] = timescale
448
+ self.waves = bool(os.getenv("WAVES", waves))
449
+ self.gui = bool(os.getenv("GUI", gui))
450
+ self.timescale = timescale
376
451
 
377
452
  if verbose is not None:
378
453
  self.verbose = verbose
379
454
 
380
- # When using pytest, use test name as result file name
455
+ # Pytest test name is used by the next couple sections.
381
456
  pytest_current_test = os.getenv("PYTEST_CURRENT_TEST", None)
382
- test_dir_path = Path(self.test_dir)
383
- self.current_test_name = "test"
384
- if results_xml is not None:
385
- # PYTEST_CURRENT_TEST only allowed when results_xml is not set
386
- assert not pytest_current_test
387
- results_xml_path = Path(results_xml)
388
- if results_xml_path.is_absolute():
389
- results_xml_file = results_xml_path
390
- else:
391
- results_xml_file = test_dir_path / results_xml_path
392
- elif pytest_current_test is not None:
457
+
458
+ if pytest_current_test is not None:
393
459
  self.current_test_name = pytest_current_test.split(":")[-1].split(" ")[0]
394
- results_xml_file = test_dir_path / f"{self.current_test_name}.{results_xml}"
395
460
  else:
396
- results_xml_file = test_dir_path / "results.xml"
461
+ self.current_test_name = "test"
462
+
463
+ results_xml_path: Union[None, Path] = (
464
+ Path(results_xml) if results_xml is not None else None
465
+ )
466
+
467
+ # result.xml filename precedence:
468
+ # 1. absolute path
469
+ # 2. pytest test name
470
+ # 3. relative path
471
+ # 4. default name
472
+ if results_xml_path is not None and results_xml_path.is_absolute():
473
+ results_xml_file = results_xml_path
474
+ elif pytest_current_test is not None:
475
+ if results_xml_path is not None:
476
+ raise NotImplementedError(
477
+ "Relative result_xml paths aren't supported when using pytest"
478
+ )
479
+ results_xml_file = self.test_dir / f"{self.current_test_name}.result.xml"
480
+ elif results_xml_path is not None:
481
+ results_xml_file = self.test_dir / results_xml_path
482
+ else:
483
+ results_xml_file = self.test_dir / "results.xml"
397
484
 
398
485
  with suppress(OSError):
399
- os.remove(results_xml_file)
486
+ results_xml_file.unlink()
400
487
 
401
488
  # transport the settings to cocotb via environment variables
402
489
  self._set_env()
403
490
  self.env["COCOTB_RESULTS_FILE"] = str(results_xml_file)
404
491
 
405
- cmds: Sequence[Command] = self._test_command()
406
- self._execute(cmds, cwd=self.test_dir)
492
+ cmds: Sequence[_Command] = self._test_command()
493
+ simulator_exit_code: int = 0
494
+ try:
495
+ self._execute(cmds, cwd=self.test_dir)
496
+ except subprocess.CalledProcessError as e:
497
+ # It is possible for the simulator to fail but still leave results.
498
+ self.log.error("Simulation failed: %d", e.returncode)
499
+ simulator_exit_code = e.returncode
407
500
 
408
501
  # Only when running under pytest, check the results file here,
409
502
  # potentially raising an exception with failing testcases,
410
503
  # otherwise return the results file for later analysis.
411
504
  if pytest_current_test:
412
- check_results_file(results_xml_file)
505
+ try:
506
+ (num_tests, num_failed) = get_results(results_xml_file)
507
+ except RuntimeError as e:
508
+ self.log.error("%s", e.args[0])
509
+ sys.exit(simulator_exit_code)
510
+ else:
511
+ if num_failed:
512
+ self.log.error(
513
+ "ERROR: Failed %d of %d tests.", num_failed, num_tests
514
+ )
515
+ sys.exit(1 if simulator_exit_code == 0 else simulator_exit_code)
413
516
 
414
- print(f"INFO: Results file: {results_xml_file}")
517
+ if simulator_exit_code != 0:
518
+ sys.exit(simulator_exit_code)
519
+
520
+ if pytest_current_test and self._use_external_viewer() and self.gui:
521
+ viewer = os.getenv("COCOTB_WAVEFORM_VIEWER")
522
+ if viewer is not None:
523
+ viewer_path = shutil.which(viewer)
524
+ if viewer_path is None:
525
+ raise ValueError(f"Cannot find {viewer} in the system path")
526
+ else:
527
+ viewer_path = shutil.which("surfer")
528
+ if viewer_path is None:
529
+ viewer_path = shutil.which("gtkwave")
530
+ if viewer_path is None:
531
+ raise SystemError(
532
+ "Cannot find any viewer (surfer or gtkwave) in the system path"
533
+ )
534
+
535
+ subprocess.run(
536
+ [f"{viewer_path} {self._waves_file()}"],
537
+ cwd=self.test_dir,
538
+ check=True,
539
+ shell=True,
540
+ )
541
+
542
+ self.log.info("Results file: %s", results_xml_file)
415
543
  return results_xml_file
416
544
 
417
- @staticmethod
418
- def _get_include_options(self, includes: Sequence[PathLike]) -> Command:
545
+ @abstractmethod
546
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
419
547
  """Return simulator-specific formatted option strings with *includes* directories."""
420
548
 
421
- raise NotImplementedError()
422
-
423
- @staticmethod
424
- def _get_define_options(self, defines: Mapping[str, object]) -> Command:
549
+ @abstractmethod
550
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
425
551
  """Return simulator-specific formatted option strings with *defines* macros."""
426
552
 
427
- raise NotImplementedError()
428
-
429
- @abc.abstractmethod
430
- def _get_parameter_options(self, parameters: Mapping[str, object]) -> Command:
553
+ @abstractmethod
554
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
431
555
  """Return simulator-specific formatted option strings with *parameters*/generics."""
432
556
 
433
- raise NotImplementedError()
434
-
435
- def _execute(self, cmds: Sequence[Command], cwd: PathLike) -> None:
436
-
557
+ def _execute(self, cmds: Sequence[_Command], cwd: PathLike) -> None:
437
558
  __tracebackhide__ = True # Hide the traceback when using PyTest.
438
559
 
439
560
  if self.log_file is None:
@@ -443,77 +564,27 @@ class Simulator(abc.ABC):
443
564
  self._execute_cmds(cmds, cwd, f)
444
565
 
445
566
  def _execute_cmds(
446
- self, cmds: Sequence[Command], cwd: PathLike, stdout: Optional[TextIO] = None
567
+ self, cmds: Sequence[_Command], cwd: PathLike, stdout: Optional[TextIO] = None
447
568
  ) -> None:
448
569
  __tracebackhide__ = True # Hide the traceback when using PyTest.
449
570
 
450
571
  for cmd in cmds:
451
- print(f"INFO: Running command {shlex_join(cmd)} in directory {cwd}")
572
+ self.log.info("Running command %s in directory %s", _shlex_join(cmd), cwd)
452
573
 
453
574
  # TODO: create a thread to handle stderr and log as error?
454
575
  # TODO: log forwarding
455
576
 
456
577
  stderr = None if stdout is None else subprocess.STDOUT
457
- process = subprocess.run(
458
- cmd, cwd=cwd, env=self.env, stdout=stdout, stderr=stderr
578
+ subprocess.run(
579
+ cmd, cwd=cwd, env=self.env, check=True, stdout=stdout, stderr=stderr
459
580
  )
460
581
 
461
- if process.returncode != 0:
462
- raise SystemExit(
463
- f"Process {process.args[0]!r} terminated with error {process.returncode}"
464
- )
465
-
466
- def rm_build_folder(self, build_dir: Path):
467
- if os.path.isdir(build_dir):
468
- print("Removing:", build_dir)
582
+ def rm_build_folder(self, build_dir: Path) -> None:
583
+ if build_dir.is_dir():
584
+ self.log.info("Removing: %s", build_dir)
469
585
  shutil.rmtree(build_dir, ignore_errors=True)
470
586
 
471
587
 
472
- def get_results(results_xml_file: Path) -> Tuple[int, int]:
473
- """Return number of tests and fails in *results_xml_file*.
474
-
475
- Returns:
476
- Tuple of number of tests and number of fails.
477
-
478
- Raises:
479
- SystemExit: *results_xml_file* is non-existent.
480
- """
481
-
482
- __tracebackhide__ = True # Hide the traceback when using PyTest.
483
-
484
- if not results_xml_file.is_file():
485
- raise SystemExit(
486
- f"ERROR: Simulation terminated abnormally. Results file {results_xml_file} not found."
487
- )
488
-
489
- num_tests = 0
490
- num_failed = 0
491
-
492
- tree = ET.parse(results_xml_file)
493
- for ts in tree.iter("testsuite"):
494
- for tc in ts.iter("testcase"):
495
- num_tests += 1
496
- for _ in tc.iter("failure"):
497
- num_failed += 1
498
-
499
- return (num_tests, num_failed)
500
-
501
-
502
- def check_results_file(results_xml_file: Path) -> None:
503
- """Raise exception if *results_xml_file* does not exist or contains failed tests.
504
-
505
- Raises:
506
- SystemExit: *results_xml_file* is non-existent or contains fails.
507
- """
508
-
509
- __tracebackhide__ = True # Hide the traceback when using PyTest.
510
-
511
- (num_tests, num_failed) = get_results(results_xml_file)
512
-
513
- if num_failed:
514
- raise SystemExit(f"ERROR: Failed {num_failed} of {num_tests} tests.")
515
-
516
-
517
588
  def outdated(output: Path, dependencies: Sequence[Path]) -> bool:
518
589
  """Return ``True`` if any source files in *dependencies* are newer than the *output* directory.
519
590
 
@@ -529,8 +600,7 @@ def outdated(output: Path, dependencies: Sequence[Path]) -> bool:
529
600
  dep_mtime = 0.0
530
601
  for dependency in dependencies:
531
602
  mtime = dependency.stat().st_mtime
532
- if mtime > dep_mtime:
533
- dep_mtime = mtime
603
+ dep_mtime = max(mtime, dep_mtime)
534
604
 
535
605
  return dep_mtime > output_mtime
536
606
 
@@ -559,6 +629,10 @@ _verilog_extensions_s = ", ".join(f"`{c}`" for c in _verilog_extensions)
559
629
 
560
630
 
561
631
  class UnknownFileExtension(ValueError):
632
+ """Raised when a source file type cannot be determined from the file extension.
633
+
634
+ See :meth:`Runner.build() <cocotb_tools.runner.Runner.build>` for details on supported standard file extensions and the use of :class:`VHDL` and :class:`Verilog` for specifying file type."""
635
+
562
636
  def __init__(self, source: PathLike) -> None:
563
637
  super().__init__(
564
638
  f"Can't determine if {source} is a VHDL or Verilog file. "
@@ -571,44 +645,52 @@ def is_vhdl_source(source: PathLike) -> bool:
571
645
  if isinstance(source, VHDL):
572
646
  return True
573
647
  source_as_path = Path(source)
574
- if source_as_path.suffix in _vhdl_extensions:
575
- return True
576
- return False
648
+ return source_as_path.suffix in _vhdl_extensions
577
649
 
578
650
 
579
651
  def is_verilog_source(source: PathLike) -> bool:
580
652
  if isinstance(source, Verilog):
581
653
  return True
582
654
  source_as_path = Path(source)
583
- if source_as_path.suffix in _verilog_extensions:
584
- return True
585
- return False
655
+ return source_as_path.suffix in _verilog_extensions
586
656
 
587
657
 
588
- class Icarus(Simulator):
658
+ class Icarus(Runner):
659
+ """Implementation of :class:`Runner` for Icarus.
660
+
661
+ * ``hdl_toplevel`` argument to :meth:`.build` is *required*.
662
+ * ``waves=True`` *must* be given to :meth:`.build` if either ``waves`` or ``gui`` are to be used during :meth:`.test`.
663
+ * ``timescale`` argument to :meth:`.build` must be given to support dumping the command file.
664
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
665
+ """
666
+
589
667
  supported_gpi_interfaces = {"verilog": ["vpi"]}
590
668
 
591
- @staticmethod
592
- def _simulator_in_path() -> None:
669
+ def _simulator_in_path(self) -> None:
593
670
  if shutil.which("iverilog") is None:
594
671
  raise SystemExit("ERROR: iverilog executable not found!")
595
672
 
596
- @staticmethod
597
- def _get_include_options(includes: Sequence[PathLike]) -> Command:
673
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
598
674
  return [f"-I{include}" for include in includes]
599
675
 
600
- @staticmethod
601
- def _get_define_options(defines: Mapping[str, object]) -> Command:
602
- return [f"-D{name}={value}" for name, value in defines.items()]
676
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
677
+ return [f"-D{name}={_as_sv_literal(value)}" for name, value in defines.items()]
603
678
 
604
- def _get_parameter_options(self, parameters: Mapping[str, object]) -> Command:
679
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
605
680
  assert self.hdl_toplevel is not None
606
681
  return [
607
682
  f"-P{self.hdl_toplevel}.{name}={value}"
608
683
  for name, value in parameters.items()
609
684
  ]
610
685
 
686
+ def _use_external_viewer(self) -> bool:
687
+ return True
688
+
689
+ def _waves_file(self) -> Optional[str]:
690
+ return f"{self.hdl_toplevel}.fst"
691
+
611
692
  def _create_cmd_file(self) -> None:
693
+ assert self.timescale is not None
612
694
  with open(self.cmds_file, "w") as f:
613
695
  f.write("+timescale+{}/{}\n".format(*self.timescale))
614
696
 
@@ -617,7 +699,14 @@ class Icarus(Simulator):
617
699
  with open(self.iverilog_dump_file, "w") as f:
618
700
  f.write("module cocotb_iverilog_dump();\n")
619
701
  f.write("initial begin\n")
620
- f.write(f' $dumpfile("{dumpfile_path}");\n')
702
+ f.write(" string dumpfile_path;")
703
+ f.write(
704
+ ' if ($value$plusargs("dumpfile_path=%s", dumpfile_path)) begin\n'
705
+ )
706
+ f.write(" $dumpfile(dumpfile_path);\n")
707
+ f.write(" end else begin\n")
708
+ f.write(f' $dumpfile("{dumpfile_path}");\n')
709
+ f.write(" end\n")
621
710
  f.write(f" $dumpvars(0, {self.hdl_toplevel});\n")
622
711
  f.write("end\n")
623
712
  f.write("endmodule\n")
@@ -634,28 +723,33 @@ class Icarus(Simulator):
634
723
  def cmds_file(self) -> Path:
635
724
  return self.build_dir / "cmds.f"
636
725
 
637
- def _test_command(self) -> List[Command]:
726
+ def _test_command(self) -> List[_Command]:
638
727
  plusargs = self.plusargs
639
- if self.waves:
728
+ if self.waves or self.gui:
640
729
  plusargs += ["-fst"]
730
+ else:
731
+ # Disable waveform output
732
+ plusargs += ["-none"]
641
733
 
642
- if self.pre_cmd:
643
- print("WARNING: pre_cmd is not implemented for Icarus Verilog.")
734
+ if self.pre_cmd is not None:
735
+ raise ValueError("WARNING: pre_cmd is not implemented for Icarus Verilog.")
644
736
 
645
737
  return [
646
738
  [
647
739
  "vvp",
648
740
  "-M",
649
- cocotb.config.libs_dir,
741
+ str(cocotb_tools.config.libs_dir),
650
742
  "-m",
651
- cocotb.config.lib_name("vpi", "icarus"),
743
+ cocotb_tools.config.lib_name("vpi", "icarus"),
744
+ *self.test_args,
745
+ str(self.sim_file),
746
+ *plusargs,
652
747
  ]
653
- + self.test_args
654
- + [str(self.sim_file)]
655
- + plusargs
656
748
  ]
657
749
 
658
- def _build_command(self) -> List[Command]:
750
+ def _build_command(self) -> List[_Command]:
751
+ assert self.hdl_toplevel is not None
752
+
659
753
  for source in self.sources:
660
754
  if not is_verilog_source(source):
661
755
  raise ValueError(
@@ -663,8 +757,8 @@ class Icarus(Simulator):
663
757
  )
664
758
  for arg in self.build_args:
665
759
  if type(arg) not in (str, Verilog):
666
- print(
667
- f"WARNING: {type(self).__qualname__} only supports Verilog. build_args {arg!r} will not be applied."
760
+ raise ValueError(
761
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} cannot be applied."
668
762
  )
669
763
 
670
764
  build_args = list(self.build_args)
@@ -676,7 +770,7 @@ class Icarus(Simulator):
676
770
  self._create_cmd_file()
677
771
  build_args += ["-f", str(self.cmds_file)]
678
772
 
679
- cmds = []
773
+ cmds: list[_Command] = []
680
774
  sources = [
681
775
  source for source in self.sources if is_verilog_source(source)
682
776
  ] + self.verilog_sources
@@ -686,8 +780,6 @@ class Icarus(Simulator):
686
780
  "iverilog",
687
781
  "-o",
688
782
  str(self.sim_file),
689
- "-D",
690
- "COCOTB_SIM=1",
691
783
  "-s",
692
784
  self.hdl_toplevel,
693
785
  "-g2012",
@@ -705,39 +797,38 @@ class Icarus(Simulator):
705
797
  ]
706
798
 
707
799
  else:
708
- print("WARNING: Skipping compilation of", self.sim_file)
800
+ self.log.warning("Skipping compilation of %s", self.sim_file)
709
801
 
710
802
  return cmds
711
803
 
712
804
 
713
- class Questa(Simulator):
805
+ class Questa(Runner):
806
+ """Implementation of :class:`Runner` for Questa.
807
+
808
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
809
+ """
810
+
714
811
  supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["fli", "vhpi"]}
715
812
 
716
- @staticmethod
717
- def _simulator_in_path() -> None:
813
+ def _simulator_in_path(self) -> None:
718
814
  if shutil.which("vsim") is None:
719
815
  raise SystemExit("ERROR: vsim executable not found!")
720
816
 
721
- @staticmethod
722
- def _get_include_options(includes: Sequence[PathLike]) -> Command:
723
- return [f"+incdir+{as_tcl_value(str(include))}" for include in includes]
817
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
818
+ return [f"+incdir+{_as_tcl_value(str(include))}" for include in includes]
724
819
 
725
- @staticmethod
726
- def _get_define_options(defines: Mapping[str, object]) -> Command:
820
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
727
821
  return [
728
- f"+define+{as_tcl_value(name)}={as_tcl_value(str(value))}"
729
- for name, value in defines.items()
822
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
730
823
  ]
731
824
 
732
- @staticmethod
733
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
825
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
734
826
  return [f"-g{name}={value}" for name, value in parameters.items()]
735
827
 
736
- def _build_command(self) -> List[Command]:
737
-
828
+ def _build_command(self) -> List[_Command]:
738
829
  cmds = []
739
830
 
740
- cmds.append(["vlib", as_tcl_value(self.hdl_library)])
831
+ cmds.append(["vlib", _as_tcl_value(self.hdl_library)])
741
832
  for source in self.sources:
742
833
  if is_vhdl_source(source):
743
834
  cmds.append(self._build_vhdl_command(source))
@@ -752,32 +843,33 @@ class Questa(Simulator):
752
843
 
753
844
  return cmds
754
845
 
755
- def _build_vhdl_command(self, source: PathLike) -> Command:
846
+ def _build_vhdl_command(self, source: PathLike) -> _Command:
756
847
  return (
757
848
  ["vcom"]
758
- + ["-work", as_tcl_value(self.hdl_library)]
759
- + [as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)]
760
- + [as_tcl_value(str(source))]
849
+ + ["-work", _as_tcl_value(self.hdl_library)]
850
+ + [_as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)]
851
+ + [_as_tcl_value(str(source))]
761
852
  )
762
853
 
763
- def _build_verilog_command(self, source: PathLike) -> Command:
854
+ def _build_verilog_command(self, source: PathLike) -> _Command:
764
855
  return (
765
856
  ["vlog"]
766
857
  + ([] if self.always else ["-incr"])
767
- + ["-work", as_tcl_value(self.hdl_library)]
858
+ + ["-work", _as_tcl_value(self.hdl_library)]
768
859
  + ["-sv"]
769
860
  + self._get_define_options(self.defines)
770
861
  + self._get_include_options(self.includes)
771
- + [as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)]
772
- + [as_tcl_value(str(source))]
862
+ + [_as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)]
863
+ + [_as_tcl_value(str(source))]
773
864
  )
774
865
 
775
- def _test_command(self) -> List[Command]:
776
-
866
+ def _test_command(self) -> List[_Command]:
777
867
  cmds = []
778
868
 
779
- if self.pre_cmd:
780
- self.pre_cmd = ["-do"] + self.pre_cmd
869
+ if self.pre_cmd is not None:
870
+ pre_cmd = ["-do", *self.pre_cmd]
871
+ else:
872
+ pre_cmd = []
781
873
 
782
874
  do_script = ""
783
875
  if self.waves:
@@ -791,19 +883,25 @@ class Questa(Simulator):
791
883
  lib_opts = [
792
884
  "-foreign",
793
885
  "cocotb_init "
794
- + as_tcl_value(cocotb.config.lib_name_path("fli", "questa")),
886
+ + _as_tcl_value(
887
+ cocotb_tools.config.lib_name_path("fli", "questa").as_posix()
888
+ ),
795
889
  ]
796
890
  elif gpi_if_entry == "vhpi":
797
891
  lib_opts = ["-voptargs=-access=rw+/."]
798
892
  lib_opts += [
799
893
  "-foreign",
800
894
  "vhpi_startup_routines_bootstrap "
801
- + as_tcl_value(cocotb.config.lib_name_path("vhpi", "questa")),
895
+ + _as_tcl_value(
896
+ cocotb_tools.config.lib_name_path("vhpi", "questa").as_posix()
897
+ ),
802
898
  ]
803
899
  else:
804
900
  lib_opts = [
805
901
  "-pli",
806
- as_tcl_value(cocotb.config.lib_name_path("vpi", "questa")),
902
+ _as_tcl_value(
903
+ cocotb_tools.config.lib_name_path("vpi", "questa").as_posix()
904
+ ),
807
905
  ]
808
906
 
809
907
  cmds.append(
@@ -811,34 +909,42 @@ class Questa(Simulator):
811
909
  + ["-gui" if self.gui else "-c"]
812
910
  + ["-onfinish", "stop" if self.gui else "exit"]
813
911
  + lib_opts
814
- + [as_tcl_value(v) for v in self.test_args]
815
- + [as_tcl_value(v) for v in self._get_parameter_options(self.parameters)]
816
- + [as_tcl_value(f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}")]
817
- + [as_tcl_value(v) for v in self.plusargs]
818
- + self.pre_cmd
912
+ + [_as_tcl_value(v) for v in self.test_args]
913
+ + [_as_tcl_value(v) for v in self._get_parameter_options(self.parameters)]
914
+ + [_as_tcl_value(f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}")]
915
+ + [_as_tcl_value(v) for v in self.plusargs]
916
+ + pre_cmd
819
917
  + ["-do", do_script]
820
918
  )
821
919
 
822
920
  gpi_extra_list = []
823
921
  for gpi_if in self.gpi_interfaces[1:]:
824
- gpi_if_lib_path = cocotb.config.lib_name_path(gpi_if, "questa")
825
- if Path(gpi_if_lib_path).is_file():
922
+ gpi_if_lib_path = cocotb_tools.config.lib_name_path(gpi_if, "questa")
923
+ if gpi_if_lib_path.is_file():
826
924
  gpi_extra_list.append(
827
- cocotb.config.lib_name_path(gpi_if, "questa")
828
- + f":cocotb{gpi_if}_entry_point"
925
+ gpi_if_lib_path.as_posix() + f":cocotb{gpi_if}_entry_point"
829
926
  )
830
927
  else:
831
- print("WARNING: {gpi_if_lib_path} library not found.")
928
+ raise RuntimeError(f"{gpi_if_lib_path} library not found.")
832
929
  self.env["GPI_EXTRA"] = ",".join(gpi_extra_list)
833
930
 
834
931
  return cmds
835
932
 
836
933
 
837
- class Ghdl(Simulator):
934
+ class Ghdl(Runner):
935
+ """Implementation of :class:`Runner` for GHDL.
936
+
937
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
938
+ """
939
+
838
940
  supported_gpi_interfaces = {"vhdl": ["vpi"]}
839
941
 
840
- @staticmethod
841
- def _simulator_in_path() -> None:
942
+ def _set_env(self) -> None:
943
+ super()._set_env()
944
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
945
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
946
+
947
+ def _simulator_in_path(self) -> None:
842
948
  if shutil.which("ghdl") is None:
843
949
  raise SystemExit("ERROR: ghdl executable not found!")
844
950
 
@@ -853,11 +959,22 @@ class Ghdl(Simulator):
853
959
  )
854
960
  return "mcode" in result.stdout
855
961
 
856
- @staticmethod
857
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
962
+ def _use_external_viewer(self) -> bool:
963
+ return True
964
+
965
+ def _waves_file(self) -> Optional[str]:
966
+ return f"{self.hdl_toplevel}.ghw"
967
+
968
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
969
+ raise RuntimeError
970
+
971
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
972
+ raise RuntimeError
973
+
974
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
858
975
  return [f"-g{name}={value}" for name, value in parameters.items()]
859
976
 
860
- def _build_command(self) -> List[Command]:
977
+ def _build_command(self) -> List[_Command]:
861
978
  for source in self.sources:
862
979
  if not is_vhdl_source(source):
863
980
  raise ValueError(
@@ -865,8 +982,8 @@ class Ghdl(Simulator):
865
982
  )
866
983
  for arg in self.build_args:
867
984
  if type(arg) not in (str, VHDL):
868
- print(
869
- f"WARNING: {type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
985
+ raise ValueError(
986
+ f"{type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
870
987
  )
871
988
 
872
989
  cmds = [
@@ -879,17 +996,20 @@ class Ghdl(Simulator):
879
996
 
880
997
  if self.hdl_toplevel is not None:
881
998
  cmds += [
882
- ["ghdl", "-m"]
883
- + [f"--work={self.hdl_library}"]
884
- + self.build_args
885
- + [self.hdl_toplevel]
999
+ [
1000
+ "ghdl",
1001
+ "-m",
1002
+ f"--work={self.hdl_library}",
1003
+ *self.build_args,
1004
+ self.hdl_toplevel,
1005
+ ]
886
1006
  ]
887
1007
 
888
1008
  return cmds
889
1009
 
890
- def _test_command(self) -> List[Command]:
891
- if self.pre_cmd:
892
- print("WARNING: pre_cmd is not implemented for GHDL.")
1010
+ def _test_command(self) -> List[_Command]:
1011
+ if self.pre_cmd is not None:
1012
+ raise RuntimeError("pre_cmd is not implemented for GHDL.")
893
1013
 
894
1014
  ghdl_run_args = self.test_args
895
1015
 
@@ -925,27 +1045,64 @@ class Ghdl(Simulator):
925
1045
  + [f"--work={self.hdl_toplevel_library}"]
926
1046
  + ghdl_run_args
927
1047
  + [self.sim_hdl_toplevel]
928
- + ["--vpi=" + cocotb.config.lib_name_path("vpi", "ghdl")]
1048
+ + ["--vpi=" + cocotb_tools.config.lib_name_path("vpi", "ghdl").as_posix()]
929
1049
  + self.plusargs
930
1050
  + self._get_parameter_options(self.parameters)
1051
+ + ([f"--wave={self._waves_file()}"] if self.waves or self.gui else [])
931
1052
  ]
932
1053
 
933
1054
  return cmds
934
1055
 
935
1056
 
936
- class Nvc(Simulator):
1057
+ class Nvc(Runner):
1058
+ """Implementation of :class:`Runner` for NVC.
1059
+
1060
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1061
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1062
+ """
1063
+
937
1064
  supported_gpi_interfaces = {"vhdl": ["vhpi"]}
938
1065
 
939
- @staticmethod
940
- def _simulator_in_path() -> None:
1066
+ def __init__(self) -> None:
1067
+ super().__init__()
1068
+
1069
+ version_str = subprocess.run(
1070
+ ["nvc", "--version"],
1071
+ check=True,
1072
+ text=True,
1073
+ stdout=subprocess.PIPE,
1074
+ ).stdout
1075
+ version = NvcVersion.from_commandline(version_str)
1076
+ if version > NvcVersion("1.16"):
1077
+ self._preserve_case = ["--preserve-case"]
1078
+ else:
1079
+ self._preserve_case = []
1080
+
1081
+ def _set_env(self) -> None:
1082
+ super()._set_env()
1083
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
1084
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
1085
+
1086
+ def _simulator_in_path(self) -> None:
941
1087
  if shutil.which("nvc") is None:
942
1088
  raise SystemExit("ERROR: nvc executable not found!")
943
1089
 
944
- @staticmethod
945
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
1090
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1091
+ raise RuntimeError
1092
+
1093
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1094
+ raise RuntimeError
1095
+
1096
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
946
1097
  return [f"-g{name}={value}" for name, value in parameters.items()]
947
1098
 
948
- def _build_command(self) -> List[Command]:
1099
+ def _use_external_viewer(self) -> bool:
1100
+ return True
1101
+
1102
+ def _waves_file(self) -> Optional[str]:
1103
+ return f"{self.hdl_toplevel}.fst"
1104
+
1105
+ def _build_command(self) -> List[_Command]:
949
1106
  for source in self.sources:
950
1107
  if not is_vhdl_source(source):
951
1108
  raise ValueError(
@@ -953,65 +1110,97 @@ class Nvc(Simulator):
953
1110
  )
954
1111
  for arg in self.build_args:
955
1112
  if type(arg) not in (str, VHDL):
956
- print(
957
- f"WARNING: {type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
1113
+ raise ValueError(
1114
+ f"{type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
958
1115
  )
959
1116
 
960
1117
  cmds = [
961
- ["nvc", f"--work={self.hdl_library}"]
1118
+ [
1119
+ "nvc",
1120
+ f"--work={self.hdl_library}",
1121
+ "-L",
1122
+ str(get_abs_path(self.build_dir)),
1123
+ ]
962
1124
  + [arg for arg in self.build_args if type(arg) in (str, VHDL)]
963
1125
  + ["-a"]
964
1126
  + [str(source) for source in self.sources if is_vhdl_source(source)]
965
1127
  + [str(source) for source in self.vhdl_sources]
1128
+ + self._preserve_case
966
1129
  ]
967
1130
 
968
1131
  return cmds
969
1132
 
970
- def _test_command(self) -> List[Command]:
1133
+ def _test_command(self) -> List[_Command]:
1134
+ work_library = str(get_abs_path(self.build_dir / self.hdl_toplevel_library))
971
1135
  cmds = [
972
- ["nvc", f"--work={self.hdl_toplevel_library}"]
1136
+ [
1137
+ "nvc",
1138
+ f"--work={self.hdl_toplevel_library}:{work_library}",
1139
+ "-L",
1140
+ str(get_abs_path(self.build_dir)),
1141
+ ]
973
1142
  + self.build_args
974
1143
  + ["-e", self.sim_hdl_toplevel, "--no-save", "--jit"]
1144
+ + self.elab_args
975
1145
  + self._get_parameter_options(self.parameters)
976
1146
  + ["-r"]
977
1147
  + self.test_args
978
- + ["--load=" + cocotb.config.lib_name_path("vhpi", "nvc")]
1148
+ + ["--load=" + cocotb_tools.config.lib_name_path("vhpi", "nvc").as_posix()]
979
1149
  + self.plusargs
1150
+ + ([f"--wave={self._waves_file()}"] if self.waves or self.gui else [])
980
1151
  ]
981
1152
 
982
1153
  return cmds
983
1154
 
984
1155
 
985
- class Riviera(Simulator):
1156
+ class Riviera(Runner):
1157
+ """Implementation of :class:`Runner` for Riviera-PRO.
1158
+
1159
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1160
+ * Does not support the ``gui`` argument to :meth:`.test`.
1161
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1162
+ """
1163
+
986
1164
  supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
987
1165
 
988
- @staticmethod
989
- def _simulator_in_path() -> None:
1166
+ def _simulator_in_path(self) -> None:
990
1167
  if shutil.which("vsimsa") is None:
991
1168
  raise SystemExit("ERROR: vsimsa executable not found!")
992
1169
 
993
- @staticmethod
994
- def _get_include_options(includes: Sequence[PathLike]) -> Command:
995
- return [f"+incdir+{as_tcl_value(str(include))}" for include in includes]
1170
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1171
+ return [f"+incdir+{_as_tcl_value(str(include))}" for include in includes]
996
1172
 
997
- @staticmethod
998
- def _get_define_options(defines: Mapping[str, object]) -> Command:
1173
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
999
1174
  return [
1000
- f"+define+{as_tcl_value(name)}={as_tcl_value(str(value))}"
1175
+ f"+define+{name}={self._as_define_value(value)}"
1001
1176
  for name, value in defines.items()
1002
1177
  ]
1003
1178
 
1004
- @staticmethod
1005
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
1179
+ def _as_define_value(self, value: object) -> str:
1180
+ if isinstance(value, int):
1181
+ return str(value)
1182
+ elif isinstance(value, str):
1183
+ for char in value:
1184
+ if ord(char) < 32 or ord(char) >= 255 or char in '\\"':
1185
+ # Control characters are generally not supported.
1186
+ # Not sure if there's any way to escape quotes or backslashes.
1187
+ raise ValueError(
1188
+ f"Character {char!r} not supported in define value"
1189
+ )
1190
+ return '\\"\\\\"' + value + '\\\\"\\"'
1191
+ else:
1192
+ raise TypeError("Can't serialize this type as an SV literal")
1193
+
1194
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1006
1195
  return [f"-g{name}={value}" for name, value in parameters.items()]
1007
1196
 
1008
- def _build_command(self) -> List[Command]:
1197
+ def _build_command(self) -> List[_Command]:
1009
1198
  do_script: List[str] = ["onerror {\n quit -code 1 \n}"]
1010
1199
 
1011
1200
  out_file = self.build_dir / self.hdl_library / f"{self.hdl_library}.lib"
1012
1201
 
1013
1202
  if outdated(out_file, self.verilog_sources + self.vhdl_sources) or self.always:
1014
- do_script.append(f"alib {as_tcl_value(self.hdl_library)}")
1203
+ do_script.append(f"alib {_as_tcl_value(self.hdl_library)}")
1015
1204
 
1016
1205
  for source in self.sources:
1017
1206
  if is_verilog_source(source):
@@ -1032,74 +1221,81 @@ class Riviera(Simulator):
1032
1221
  # behavior.
1033
1222
  do_script.append("exit")
1034
1223
 
1035
- do_file = tempfile.NamedTemporaryFile(delete=False)
1036
- do_file.write("\n".join(do_script).encode())
1037
- do_file.close()
1224
+ with tempfile.NamedTemporaryFile(delete=False) as do_file:
1225
+ do_file.write("\n".join(do_script).encode())
1038
1226
 
1039
- return [["vsimsa"] + ["-do"] + ["do"] + [do_file.name]]
1227
+ return [["vsimsa", "-do", "do", do_file.name]]
1040
1228
 
1041
1229
  def _build_vhdl_source(self, source: PathLike) -> str:
1042
1230
  return "acom -work {RTL_LIBRARY} {EXTRA_ARGS} {VHDL_SOURCES}".format(
1043
- RTL_LIBRARY=as_tcl_value(self.hdl_library),
1044
- VHDL_SOURCES=as_tcl_value(str(source)),
1231
+ RTL_LIBRARY=_as_tcl_value(self.hdl_library),
1232
+ VHDL_SOURCES=_as_tcl_value(str(source)),
1045
1233
  EXTRA_ARGS=" ".join(
1046
- as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)
1234
+ _as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)
1047
1235
  ),
1048
1236
  )
1049
1237
 
1050
1238
  def _build_verilog_source(self, source: PathLike) -> str:
1051
1239
  return "alog -work {RTL_LIBRARY} -pli {EXT_NAME} -sv {DEFINES} {INCDIR} {EXTRA_ARGS} {VERILOG_SOURCES}".format(
1052
- RTL_LIBRARY=as_tcl_value(self.hdl_library),
1053
- EXT_NAME=as_tcl_value(cocotb.config.lib_name_path("vpi", "riviera")),
1054
- VERILOG_SOURCES=as_tcl_value(str(source)),
1240
+ RTL_LIBRARY=_as_tcl_value(self.hdl_library),
1241
+ EXT_NAME=_as_tcl_value(
1242
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1243
+ ),
1244
+ VERILOG_SOURCES=_as_tcl_value(str(source)),
1055
1245
  DEFINES=" ".join(self._get_define_options(self.defines)),
1056
1246
  INCDIR=" ".join(self._get_include_options(self.includes)),
1057
1247
  EXTRA_ARGS=" ".join(
1058
- as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)
1248
+ _as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)
1059
1249
  ),
1060
1250
  )
1061
1251
 
1062
- def _test_command(self) -> List[Command]:
1063
- if self.pre_cmd:
1064
- print("WARNING: pre_cmd is not implemented for Riviera.")
1252
+ def _test_command(self) -> List[_Command]:
1253
+ if self.pre_cmd is not None:
1254
+ raise RuntimeError("pre_cmd is not implemented for Riviera.")
1065
1255
 
1066
1256
  do_script: str = "\nonerror {\n quit -code 1 \n} \n"
1067
1257
 
1068
1258
  if self.hdl_toplevel_lang == "vhdl":
1069
1259
  do_script += "asim +access +w_nets -interceptcoutput -loadvhpi {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS}\n".format(
1070
- TOPLEVEL=as_tcl_value(
1260
+ TOPLEVEL=_as_tcl_value(
1071
1261
  f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
1072
1262
  ),
1073
- EXT_NAME=as_tcl_value(cocotb.config.lib_name_path("vhpi", "riviera")),
1263
+ EXT_NAME=_as_tcl_value(
1264
+ cocotb_tools.config.lib_name_path("vhpi", "riviera").as_posix()
1265
+ + ":vhpi_startup_routines_bootstrap"
1266
+ ),
1074
1267
  EXTRA_ARGS=" ".join(
1075
- as_tcl_value(v)
1268
+ _as_tcl_value(v)
1076
1269
  for v in (
1077
1270
  self.test_args + self._get_parameter_options(self.parameters)
1078
1271
  )
1079
1272
  ),
1080
- PLUSARGS=" ".join(as_tcl_value(v) for v in self.plusargs),
1273
+ PLUSARGS=" ".join(_as_tcl_value(v) for v in self.plusargs),
1081
1274
  )
1082
1275
 
1083
1276
  self.env["GPI_EXTRA"] = (
1084
- cocotb.config.lib_name_path("vpi", "riviera") + ":cocotbvpi_entry_point"
1277
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1278
+ + ":cocotbvpi_entry_point"
1085
1279
  )
1086
1280
  else:
1087
1281
  do_script += "asim +access +w_nets -interceptcoutput -pli {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS} \n".format(
1088
- TOPLEVEL=as_tcl_value(
1282
+ TOPLEVEL=_as_tcl_value(
1089
1283
  f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
1090
1284
  ),
1091
- EXT_NAME=as_tcl_value(cocotb.config.lib_name_path("vpi", "riviera")),
1285
+ EXT_NAME=_as_tcl_value(
1286
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1287
+ ),
1092
1288
  EXTRA_ARGS=" ".join(
1093
- as_tcl_value(v)
1289
+ _as_tcl_value(v)
1094
1290
  for v in (
1095
1291
  self.test_args + self._get_parameter_options(self.parameters)
1096
1292
  )
1097
1293
  ),
1098
- PLUSARGS=" ".join(as_tcl_value(v) for v in self.plusargs),
1294
+ PLUSARGS=" ".join(_as_tcl_value(v) for v in self.plusargs),
1099
1295
  )
1100
1296
 
1101
1297
  self.env["GPI_EXTRA"] = (
1102
- cocotb.config.lib_name_path("vhpi", "riviera")
1298
+ cocotb_tools.config.lib_name_path("vhpi", "riviera").as_posix()
1103
1299
  + ":cocotbvhpi_entry_point"
1104
1300
  )
1105
1301
 
@@ -1108,39 +1304,52 @@ class Riviera(Simulator):
1108
1304
 
1109
1305
  do_script += "run -all \nexit"
1110
1306
 
1111
- do_file = tempfile.NamedTemporaryFile(delete=False)
1112
- do_file.write(do_script.encode())
1113
- do_file.close()
1307
+ with tempfile.NamedTemporaryFile(delete=False) as do_file:
1308
+ do_file.write(do_script.encode())
1309
+
1310
+ return [["vsimsa", "-do", "do", do_file.name]]
1114
1311
 
1115
- return [["vsimsa"] + ["-do"] + ["do"] + [do_file.name]]
1116
1312
 
1313
+ class Verilator(Runner):
1314
+ """Implementation of :class:`Runner` for Verilator.
1315
+
1316
+ * ``waves=True`` *must* be given to :meth:`.build` if either ``waves`` or ``gui`` are to be used during :meth:`.test`.
1317
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1318
+ """
1117
1319
 
1118
- class Verilator(Simulator):
1119
1320
  supported_gpi_interfaces = {"verilog": ["vpi"]}
1120
1321
 
1322
+ def _set_env(self) -> None:
1323
+ super()._set_env()
1324
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
1325
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
1326
+
1121
1327
  def _simulator_in_path(self) -> None:
1122
1328
  # the verilator binary is only needed for building
1123
1329
  return
1124
1330
 
1331
+ def _use_external_viewer(self) -> bool:
1332
+ return True
1333
+
1334
+ def _waves_file(self) -> Optional[str]:
1335
+ return "dump.vcd"
1336
+
1125
1337
  def _simulator_in_path_build_only(self) -> None:
1126
1338
  executable = shutil.which("verilator")
1127
1339
  if executable is None:
1128
1340
  raise SystemExit("ERROR: verilator executable not found!")
1129
1341
  self.executable: str = executable
1130
1342
 
1131
- @staticmethod
1132
- def _get_include_options(includes: Sequence[PathLike]) -> Command:
1343
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1133
1344
  return [f"-I{include}" for include in includes]
1134
1345
 
1135
- @staticmethod
1136
- def _get_define_options(defines: Mapping[str, object]) -> Command:
1137
- return [f"-D{name}={value}" for name, value in defines.items()]
1346
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1347
+ return [f"-D{name}={_as_sv_literal(value)}" for name, value in defines.items()]
1138
1348
 
1139
- @staticmethod
1140
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
1349
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1141
1350
  return [f"-G{name}={value}" for name, value in parameters.items()]
1142
1351
 
1143
- def _build_command(self) -> List[Command]:
1352
+ def _build_command(self) -> List[_Command]:
1144
1353
  self._simulator_in_path_build_only()
1145
1354
 
1146
1355
  for source in self.sources:
@@ -1150,8 +1359,8 @@ class Verilator(Simulator):
1150
1359
  )
1151
1360
  for arg in self.build_args:
1152
1361
  if type(arg) not in (str, Verilog):
1153
- print(
1154
- f"WARNING: {type(self).__qualname__} only supports Verilog. build_args {arg!r} will not be applied."
1362
+ raise ValueError(
1363
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} will not be applied."
1155
1364
  )
1156
1365
 
1157
1366
  if self.hdl_toplevel is None:
@@ -1163,11 +1372,7 @@ class Verilator(Simulator):
1163
1372
  # TODO: support "--always"
1164
1373
 
1165
1374
  verilator_cpp = str(
1166
- Path(cocotb.__file__).parent
1167
- / "share"
1168
- / "lib"
1169
- / "verilator"
1170
- / "verilator.cpp"
1375
+ cocotb_tools.config.share_dir / "lib" / "verilator" / "verilator.cpp"
1171
1376
  )
1172
1377
 
1173
1378
  cmds = []
@@ -1179,7 +1384,6 @@ class Verilator(Simulator):
1179
1384
  "--exe",
1180
1385
  "-Mdir",
1181
1386
  str(self.build_dir),
1182
- "-DCOCOTB_SIM=1",
1183
1387
  "--top-module",
1184
1388
  self.hdl_toplevel,
1185
1389
  "--vpi",
@@ -1189,12 +1393,15 @@ class Verilator(Simulator):
1189
1393
  "-o",
1190
1394
  self.hdl_toplevel,
1191
1395
  "-LDFLAGS",
1192
- "-Wl,-rpath,{LIB_DIR} -L{LIB_DIR} -lcocotbvpi_verilator".format(
1193
- LIB_DIR=cocotb.config.libs_dir
1194
- ),
1396
+ f"-Wl,-rpath,{cocotb_tools.config.libs_dir} -L{cocotb_tools.config.libs_dir} -lcocotbvpi_verilator",
1195
1397
  ]
1196
1398
  + (["--trace"] if self.waves else [])
1197
1399
  + [arg for arg in self.build_args if type(arg) in (str, Verilog)]
1400
+ + (
1401
+ ["--timescale", "{}/{}".format(*self.timescale)]
1402
+ if self.timescale is not None
1403
+ else []
1404
+ )
1198
1405
  + self._get_define_options(self.defines)
1199
1406
  + self._get_include_options(self.includes)
1200
1407
  + self._get_parameter_options(self.parameters)
@@ -1206,6 +1413,8 @@ class Verilator(Simulator):
1206
1413
  cmds.append(
1207
1414
  [
1208
1415
  "make",
1416
+ "-j",
1417
+ f"{_get_max_parallel_build_jobs()}",
1209
1418
  "-C",
1210
1419
  str(self.build_dir),
1211
1420
  "-f",
@@ -1216,40 +1425,60 @@ class Verilator(Simulator):
1216
1425
 
1217
1426
  return cmds
1218
1427
 
1219
- def _test_command(self) -> List[Command]:
1220
- if self.pre_cmd:
1221
- print("WARNING: pre_cmd is not implemented for Verilator.")
1428
+ def _test_command(self) -> List[_Command]:
1429
+ if self.pre_cmd is not None:
1430
+ raise RuntimeError("pre_cmd is not implemented for Verilator.")
1222
1431
 
1223
1432
  out_file = self.build_dir / self.sim_hdl_toplevel
1224
1433
  return [
1225
1434
  [str(out_file)]
1226
- + (["--trace"] if self.waves else [])
1435
+ + (["--trace"] if self.waves or self.gui else [])
1227
1436
  + self.test_args
1228
1437
  + self.plusargs
1229
1438
  ]
1230
1439
 
1231
1440
 
1232
- class Xcelium(Simulator):
1441
+ class Xcelium(Runner):
1442
+ """Implementation of :class:`Runner` for Xcelium.
1443
+
1444
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1445
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1446
+ """
1447
+
1233
1448
  supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
1234
1449
 
1235
- @staticmethod
1236
- def _simulator_in_path() -> None:
1450
+ def _simulator_in_path(self) -> None:
1237
1451
  if shutil.which("xrun") is None:
1238
1452
  raise SystemExit("ERROR: xrun executable not found!")
1239
1453
 
1240
- @staticmethod
1241
- def _get_include_options(includes: Sequence[PathLike]) -> Command:
1454
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1242
1455
  return [f"-incdir {include}" for include in includes]
1243
1456
 
1244
- @staticmethod
1245
- def _get_define_options(defines: Mapping[str, object]) -> Command:
1246
- return [f"-define {name}={value}" for name, value in defines.items()]
1457
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1458
+ return [
1459
+ f"-define {name}={self._as_define_value(value)}"
1460
+ for name, value in defines.items()
1461
+ ]
1462
+
1463
+ def _as_define_value(self, value: object) -> str:
1464
+ if isinstance(value, int):
1465
+ return str(value)
1466
+ elif isinstance(value, str):
1467
+ for char in value:
1468
+ if ord(char) < 32 or ord(char) >= 255 or char == '"':
1469
+ # Control characters are generally not supported.
1470
+ # Not sure if there's any way to escape quotes.
1471
+ raise ValueError(
1472
+ f"Character {char!r} not supported in define value"
1473
+ )
1474
+ return '"\\"' + value.replace("\\", "\\\\") + '\\""'
1475
+ else:
1476
+ raise TypeError("Can't serialize this type as an SV literal")
1247
1477
 
1248
- @staticmethod
1249
- def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
1478
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1250
1479
  return [f'-gpg "{name} => {value}"' for name, value in parameters.items()]
1251
1480
 
1252
- def _build_command(self) -> List[Command]:
1481
+ def _build_command(self) -> List[_Command]:
1253
1482
  self.env["CDS_AUTO_64BIT"] = "all"
1254
1483
 
1255
1484
  assert self.hdl_toplevel, "A HDL toplevel is required in all Xcelium compiles."
@@ -1270,9 +1499,10 @@ class Xcelium(Simulator):
1270
1499
  verbosity_opts += ["-plinowarn"]
1271
1500
 
1272
1501
  vhpi_opts = []
1273
- # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1274
- # following define is set.
1275
- vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1502
+ if self.vhdl_sources or any(is_vhdl_source(src) for src in self.sources):
1503
+ # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1504
+ # following define is set.
1505
+ vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1276
1506
 
1277
1507
  cmds = [
1278
1508
  ["xrun"]
@@ -1289,13 +1519,12 @@ class Xcelium(Simulator):
1289
1519
  + ["-loadvpi"]
1290
1520
  # always start with VPI on Xcelium
1291
1521
  + [
1292
- cocotb.config.lib_name_path("vpi", "xcelium")
1522
+ cocotb_tools.config.lib_name_path("vpi", "xcelium").as_posix()
1293
1523
  + ":vlog_startup_routines_bootstrap"
1294
1524
  ]
1295
1525
  + vhpi_opts
1296
1526
  + [f"-work {self.hdl_library}"]
1297
1527
  + self.build_args
1298
- + ["-define COCOTB_SIM"]
1299
1528
  + self._get_include_options(self.includes)
1300
1529
  + self._get_define_options(self.defines)
1301
1530
  + self._get_parameter_options(self.parameters)
@@ -1310,9 +1539,9 @@ class Xcelium(Simulator):
1310
1539
 
1311
1540
  return cmds
1312
1541
 
1313
- def _test_command(self) -> List[Command]:
1314
- if self.pre_cmd:
1315
- print("WARNING: pre_cmd is not implemented for Xcelium.")
1542
+ def _test_command(self) -> List[_Command]:
1543
+ if self.pre_cmd is not None:
1544
+ raise RuntimeError("pre_cmd is not implemented for Xcelium.")
1316
1545
 
1317
1546
  self.env["CDS_AUTO_64BIT"] = "all"
1318
1547
 
@@ -1349,39 +1578,242 @@ class Xcelium(Simulator):
1349
1578
  input_tcl = ["-input", "@run; exit;"]
1350
1579
 
1351
1580
  vhpi_opts = []
1352
- # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1353
- # following define is set.
1354
- vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1581
+ if self.vhdl_sources or any(is_vhdl_source(src) for src in self.sources):
1582
+ # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1583
+ # following define is set.
1584
+ vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1355
1585
 
1356
1586
  cmds = [["mkdir", "-p", tmpdir]]
1357
1587
  cmds += [
1358
- ["xrun"]
1359
- + ["-logfile"]
1360
- + [f"xrun_{self.current_test_name}.log"]
1361
- + ["-xmlibdirname"]
1362
- + [f"{self.build_dir}/xrun_snapshot"]
1363
- + ["-cds_implicit_tmpdir"]
1364
- + [tmpdir]
1365
- + ["-licqueue"]
1366
- + vhpi_opts
1367
- + verbosity_opts
1368
- + ["-R"]
1369
- + self.test_args
1370
- + self.plusargs
1371
- + ["-gui" if self.gui else ""]
1372
- + input_tcl
1588
+ [
1589
+ "xrun",
1590
+ "-logfile",
1591
+ f"xrun_{self.current_test_name}.log",
1592
+ "-xmlibdirname",
1593
+ f"{self.build_dir}/xrun_snapshot",
1594
+ "-cds_implicit_tmpdir",
1595
+ tmpdir,
1596
+ "-licqueue",
1597
+ *vhpi_opts,
1598
+ *verbosity_opts,
1599
+ "-R",
1600
+ *self.test_args,
1601
+ *self.plusargs,
1602
+ "-gui" if self.gui else "",
1603
+ *input_tcl,
1604
+ ]
1373
1605
  ]
1374
1606
  self.env["GPI_EXTRA"] = (
1375
- cocotb.config.lib_name_path("vhpi", "xcelium") + ":cocotbvhpi_entry_point"
1607
+ cocotb_tools.config.lib_name_path("vhpi", "xcelium").as_posix()
1608
+ + ":cocotbvhpi_entry_point"
1376
1609
  )
1377
1610
 
1378
1611
  return cmds
1379
1612
 
1380
1613
 
1381
- def get_runner(simulator_name: str) -> Simulator:
1382
- """Return the *simulator_name* instance."""
1614
+ class Vcs(Runner):
1615
+ """Implementation of :class:`Runner` for VCS.
1616
+
1617
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1618
+ * Does not support VHDL.
1619
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1620
+ """
1621
+
1622
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
1623
+
1624
+ def _simulator_in_path(self) -> None:
1625
+ if shutil.which("vcs") is None:
1626
+ raise SystemExit("ERROR: vcs executable not found!")
1627
+
1628
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1629
+ return [f"+incdir+{include}" for include in includes]
1630
+
1631
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1632
+ return [
1633
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
1634
+ ]
1635
+
1636
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1637
+ assert self.hdl_toplevel is not None
1638
+ return [
1639
+ f"-pvalue+{self.hdl_toplevel}.{name}={value}"
1640
+ for name, value in parameters.items()
1641
+ ]
1642
+
1643
+ @property
1644
+ def sim_file(self) -> Path:
1645
+ return self.build_dir / "simv"
1646
+
1647
+ @property
1648
+ def _build_opts(self) -> List[str]:
1649
+ opts = [
1650
+ "-full64",
1651
+ "-debug_access+all",
1652
+ "+acc+3",
1653
+ "-sverilog",
1654
+ "-LDFLAGS -Wl,--no-as-needed",
1655
+ ]
1656
+
1657
+ if self.verbose:
1658
+ opts += ["-diag all"]
1659
+ else:
1660
+ opts += ["-q"]
1661
+ opts += ["-suppress=VPI-CT-NS"]
1662
+
1663
+ return opts
1664
+
1665
+ def _build_command(self) -> List[_Command]:
1666
+ cmds: List[_Command] = []
1667
+ sources = list(self.sources + self.vhdl_sources + self.verilog_sources)
1668
+
1669
+ if outdated(self.sim_file, sources) or self.always:
1670
+ cmds = [
1671
+ ["vcs"]
1672
+ + self._build_opts
1673
+ + ["-load", cocotb_tools.config.lib_name_path("vpi", "vcs").as_posix()]
1674
+ + self.build_args
1675
+ + self._get_include_options(self.includes)
1676
+ + self._get_define_options(self.defines)
1677
+ + self._get_parameter_options(self.parameters)
1678
+ + ["-top", f"{self.hdl_toplevel}"]
1679
+ + [str(source) for source in sources]
1680
+ + ["-o", str(self.sim_file)]
1681
+ ]
1682
+ else:
1683
+ self.log.warning("Skipping compilation of %s", self.sim_file)
1684
+
1685
+ return cmds
1686
+
1687
+ def _test_command(self) -> List[_Command]:
1688
+ if self.pre_cmd is not None:
1689
+ raise RuntimeError("pre_cmd is not implemented for Vcs.")
1690
+
1691
+ verbosity_opts = []
1692
+ if self.verbose:
1693
+ verbosity_opts += ["-diag all"]
1694
+ else:
1695
+ verbosity_opts += ["-suppress=ASLR_DETECTED_INFO"]
1696
+
1697
+ cmds = [[str(self.sim_file), *verbosity_opts, *self.test_args, *self.plusargs]]
1698
+
1699
+ return cmds
1700
+
1701
+
1702
+ class Dsim(Runner):
1703
+ """Implementation of :class:`Runner` for DSim.
1704
+
1705
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1706
+ """
1707
+
1708
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
1709
+
1710
+ def _simulator_in_path(self) -> None:
1711
+ if shutil.which("dsim") is None:
1712
+ raise SystemExit("ERROR: dsim executable not found!")
1713
+
1714
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1715
+ return [f"+incdir+{include}" for include in includes]
1716
+
1717
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1718
+ return [
1719
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
1720
+ ]
1721
+
1722
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1723
+ return [
1724
+ f"-defparam {name}={_as_sv_literal(value)}"
1725
+ for name, value in parameters.items()
1726
+ ]
1727
+
1728
+ @property
1729
+ def sim_file(self) -> Path:
1730
+ return self.build_dir / "image.so"
1731
+
1732
+ def _use_external_viewer(self) -> bool:
1733
+ return True
1734
+
1735
+ def _waves_file(self) -> Optional[str]:
1736
+ return "file.vcd"
1737
+
1738
+ def _test_command(self) -> List[_Command]:
1739
+ plusargs = self.plusargs
1740
+ if self.waves or self.gui:
1741
+ plusargs += [f"-waves {self._waves_file()}"]
1742
+
1743
+ if self.timescale:
1744
+ plusargs += ["-timescale {}/{}".format(*self.timescale)]
1745
+ if self.pre_cmd is not None:
1746
+ raise ValueError("WARNING: pre_cmd is not implemented for DSim.")
1747
+
1748
+ return [
1749
+ [
1750
+ "dsim",
1751
+ "-work",
1752
+ str(self.build_dir),
1753
+ "-pli_lib",
1754
+ cocotb_tools.config.lib_name_path("vpi", "dsim").as_posix(),
1755
+ "+acc+rwcbfsWF",
1756
+ "-image",
1757
+ "image",
1758
+ *self.test_args,
1759
+ *plusargs,
1760
+ ]
1761
+ ]
1762
+
1763
+ def _build_command(self) -> List[_Command]:
1764
+ for source in self.sources:
1765
+ if not is_verilog_source(source):
1766
+ raise ValueError(
1767
+ f"{type(self).__qualname__} only supports Verilog. {str(source)!r} cannot be compiled."
1768
+ )
1769
+ for arg in self.build_args:
1770
+ if type(arg) not in (str, Verilog):
1771
+ raise ValueError(
1772
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} cannot be applied."
1773
+ )
1774
+
1775
+ build_args = list(self.build_args)
1776
+
1777
+ cmds: list[_Command] = []
1778
+ sources = [
1779
+ source for source in self.sources if is_verilog_source(source)
1780
+ ] + self.verilog_sources
1781
+ if outdated(self.sim_file, sources) or self.always:
1782
+ cmds = [
1783
+ [
1784
+ "dsim",
1785
+ "-work",
1786
+ str(self.build_dir),
1787
+ "-pli_lib",
1788
+ cocotb_tools.config.lib_name_path("vpi", "dsim").as_posix(),
1789
+ "+acc+rwcbfsWF",
1790
+ "-genimage",
1791
+ "image",
1792
+ ]
1793
+ + self._get_define_options(self.defines)
1794
+ + self._get_include_options(self.includes)
1795
+ + self._get_parameter_options(self.parameters)
1796
+ + [arg for arg in build_args if type(arg) in (str, Verilog)]
1797
+ + [str(source_file) for source_file in sources]
1798
+ ]
1799
+
1800
+ else:
1801
+ self.log.warning("Skipping compilation of %s", self.sim_file)
1802
+
1803
+ return cmds
1804
+
1805
+
1806
+ def get_runner(simulator_name: str) -> Runner:
1807
+ """Return an instance of a runner for *simulator_name*.
1808
+
1809
+ Args:
1810
+ simulator_name: Name of simulator to get runner for.
1811
+
1812
+ Raises:
1813
+ ValueError: If *simulator_name* is not one of the supported simulators or an alias of one.
1814
+ """
1383
1815
 
1384
- supported_sims: Dict[str, Type[Simulator]] = {
1816
+ supported_sims: Dict[str, Type[Runner]] = {
1385
1817
  "icarus": Icarus,
1386
1818
  "questa": Questa,
1387
1819
  "ghdl": Ghdl,
@@ -1389,7 +1821,8 @@ def get_runner(simulator_name: str) -> Simulator:
1389
1821
  "verilator": Verilator,
1390
1822
  "xcelium": Xcelium,
1391
1823
  "nvc": Nvc,
1392
- # TODO: "vcs": Vcs,
1824
+ "vcs": Vcs,
1825
+ "dsim": Dsim,
1393
1826
  # TODO: "activehdl": ActiveHdl,
1394
1827
  }
1395
1828
  try: