cocotb 2.0.0rc2__cp310-cp310-macosx_11_0_arm64.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 (115) hide show
  1. cocotb/_ANSI.py +65 -0
  2. cocotb/__init__.py +125 -0
  3. cocotb/_base_triggers.py +515 -0
  4. cocotb/_bridge.py +186 -0
  5. cocotb/_decorators.py +515 -0
  6. cocotb/_deprecation.py +36 -0
  7. cocotb/_exceptions.py +7 -0
  8. cocotb/_extended_awaitables.py +419 -0
  9. cocotb/_gpi_triggers.py +385 -0
  10. cocotb/_init.py +301 -0
  11. cocotb/_outcomes.py +54 -0
  12. cocotb/_profiling.py +46 -0
  13. cocotb/_py_compat.py +148 -0
  14. cocotb/_scheduler.py +448 -0
  15. cocotb/_test.py +248 -0
  16. cocotb/_test_factory.py +312 -0
  17. cocotb/_test_functions.py +42 -0
  18. cocotb/_typing.py +7 -0
  19. cocotb/_utils.py +274 -0
  20. cocotb/_version.py +4 -0
  21. cocotb/_xunit_reporter.py +66 -0
  22. cocotb/clock.py +419 -0
  23. cocotb/debug.py +24 -0
  24. cocotb/handle.py +1752 -0
  25. cocotb/libs/libcocotb.so +0 -0
  26. cocotb/libs/libcocotbfli_modelsim.so +0 -0
  27. cocotb/libs/libcocotbutils.so +0 -0
  28. cocotb/libs/libcocotbvhpi_aldec.so +0 -0
  29. cocotb/libs/libcocotbvhpi_ius.so +0 -0
  30. cocotb/libs/libcocotbvhpi_modelsim.so +0 -0
  31. cocotb/libs/libcocotbvhpi_nvc.so +0 -0
  32. cocotb/libs/libcocotbvpi_aldec.so +0 -0
  33. cocotb/libs/libcocotbvpi_dsim.so +0 -0
  34. cocotb/libs/libcocotbvpi_ghdl.so +0 -0
  35. cocotb/libs/libcocotbvpi_icarus.vpl +0 -0
  36. cocotb/libs/libcocotbvpi_ius.so +0 -0
  37. cocotb/libs/libcocotbvpi_modelsim.so +0 -0
  38. cocotb/libs/libcocotbvpi_vcs.so +0 -0
  39. cocotb/libs/libcocotbvpi_verilator.so +0 -0
  40. cocotb/libs/libembed.so +0 -0
  41. cocotb/libs/libgpi.so +0 -0
  42. cocotb/libs/libgpilog.so +0 -0
  43. cocotb/libs/libpygpilog.so +0 -0
  44. cocotb/logging.py +424 -0
  45. cocotb/py.typed +0 -0
  46. cocotb/queue.py +225 -0
  47. cocotb/regression.py +896 -0
  48. cocotb/result.py +38 -0
  49. cocotb/share/def/.gitignore +2 -0
  50. cocotb/share/def/README.md +4 -0
  51. cocotb/share/def/aldec.def +61 -0
  52. cocotb/share/def/ghdl.def +43 -0
  53. cocotb/share/def/icarus.def +43 -0
  54. cocotb/share/def/modelsim.def +138 -0
  55. cocotb/share/include/cocotb_utils.h +70 -0
  56. cocotb/share/include/embed.h +33 -0
  57. cocotb/share/include/exports.h +20 -0
  58. cocotb/share/include/gpi.h +459 -0
  59. cocotb/share/include/gpi_logging.h +291 -0
  60. cocotb/share/include/py_gpi_logging.h +33 -0
  61. cocotb/share/include/vhpi_user_ext.h +26 -0
  62. cocotb/share/include/vpi_user_ext.h +33 -0
  63. cocotb/share/lib/verilator/verilator.cpp +209 -0
  64. cocotb/simtime.py +230 -0
  65. cocotb/simulator.cpython-310-darwin.so +0 -0
  66. cocotb/simulator.pyi +107 -0
  67. cocotb/task.py +590 -0
  68. cocotb/triggers.py +67 -0
  69. cocotb/types/__init__.py +31 -0
  70. cocotb/types/_abstract_array.py +151 -0
  71. cocotb/types/_array.py +295 -0
  72. cocotb/types/_indexing.py +17 -0
  73. cocotb/types/_logic.py +333 -0
  74. cocotb/types/_logic_array.py +868 -0
  75. cocotb/types/_range.py +197 -0
  76. cocotb/types/_resolve.py +76 -0
  77. cocotb/utils.py +110 -0
  78. cocotb-2.0.0rc2.dist-info/METADATA +60 -0
  79. cocotb-2.0.0rc2.dist-info/RECORD +115 -0
  80. cocotb-2.0.0rc2.dist-info/WHEEL +5 -0
  81. cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
  82. cocotb-2.0.0rc2.dist-info/licenses/LICENSE +29 -0
  83. cocotb-2.0.0rc2.dist-info/top_level.txt +23 -0
  84. cocotb_tools/__init__.py +0 -0
  85. cocotb_tools/_coverage.py +33 -0
  86. cocotb_tools/_vendor/__init__.py +3 -0
  87. cocotb_tools/_vendor/distutils_version.py +346 -0
  88. cocotb_tools/check_results.py +65 -0
  89. cocotb_tools/combine_results.py +152 -0
  90. cocotb_tools/config.py +241 -0
  91. cocotb_tools/ipython_support.py +99 -0
  92. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  93. cocotb_tools/makefiles/Makefile.inc +198 -0
  94. cocotb_tools/makefiles/Makefile.sim +96 -0
  95. cocotb_tools/makefiles/simulators/Makefile.activehdl +72 -0
  96. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  97. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  98. cocotb_tools/makefiles/simulators/Makefile.ghdl +84 -0
  99. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  100. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  101. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  102. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  103. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  104. cocotb_tools/makefiles/simulators/Makefile.questa-compat +143 -0
  105. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  106. cocotb_tools/makefiles/simulators/Makefile.riviera +144 -0
  107. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  108. cocotb_tools/makefiles/simulators/Makefile.verilator +79 -0
  109. cocotb_tools/makefiles/simulators/Makefile.xcelium +104 -0
  110. cocotb_tools/py.typed +0 -0
  111. cocotb_tools/runner.py +1868 -0
  112. cocotb_tools/sim_versions.py +140 -0
  113. pygpi/__init__.py +0 -0
  114. pygpi/entry.py +42 -0
  115. pygpi/py.typed +0 -0
cocotb_tools/runner.py ADDED
@@ -0,0 +1,1868 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ """Build HDL and run cocotb tests."""
6
+
7
+ # TODO: maybe do globbing and expanduser/expandvars in --include, --vhdl-sources, --verilog-sources
8
+ # TODO: create a short README and a .gitignore (content: "*") in both build_dir and test_dir? (Some other tools do this.)
9
+ # TODO: support timescale on all simulators
10
+ # TODO: support custom dependencies
11
+
12
+ import logging
13
+ import multiprocessing
14
+ import os
15
+ import re
16
+ import shlex
17
+ import shutil
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+ import warnings
22
+ from abc import ABC, abstractmethod
23
+ from contextlib import suppress
24
+ from pathlib import Path
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
+ )
37
+
38
+ import find_libpython
39
+
40
+ import cocotb_tools.config
41
+ from cocotb_tools.check_results import get_results
42
+ from cocotb_tools.sim_versions import NvcVersion
43
+
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`"
46
+
47
+ _Command = List[str]
48
+
49
+ _magic_re = re.compile(r"([\\{}])")
50
+ _space_re = re.compile(r"([\s])", re.ASCII)
51
+
52
+
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:
66
+ # add '\' before special characters and spaces
67
+ value = _magic_re.sub(r"\\\1", value)
68
+ value = value.replace("\n", r"\n")
69
+ value = _space_re.sub(r"\\\1", value)
70
+ if value[:1] == '"':
71
+ value = "\\" + value
72
+
73
+ return value
74
+
75
+
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: object) -> 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:
102
+ """
103
+ Return a shell-escaped string from *split_command*
104
+ This is here more for compatibility purposes
105
+ """
106
+ return " ".join(shlex.quote(arg) for arg in split_command)
107
+
108
+
109
+ class VHDL(str):
110
+ """Tags source files and build arguments to :meth:`Runner.build() <cocotb_tools.runner.Runner.build>` as VHDL-specific."""
111
+
112
+
113
+ class Verilog(str):
114
+ """Tags source files and build arguments to :meth:`Runner.build() <cocotb_tools.runner.Runner.build>` as Verilog-specific."""
115
+
116
+
117
+ class Runner(ABC):
118
+ supported_gpi_interfaces: Dict[str, List[str]] = {}
119
+
120
+ def __init__(self) -> None:
121
+ self._simulator_in_path()
122
+
123
+ self.env: Dict[str, str] = {}
124
+
125
+ # for running test() independently of build()
126
+ self.build_dir: Path = get_abs_path("sim_build")
127
+ self.parameters: Mapping[str, object] = {}
128
+
129
+ self.log = logging.getLogger(type(self).__qualname__)
130
+ self.log.setLevel(logging.INFO)
131
+
132
+ @abstractmethod
133
+ def _simulator_in_path(self) -> None:
134
+ """Raise exception if the simulator executable does not exist in :envvar:`PATH`.
135
+
136
+ Raises:
137
+ SystemExit: Simulator executable does not exist in :envvar:`PATH`.
138
+ """
139
+
140
+ def _check_hdl_toplevel_lang(self, hdl_toplevel_lang: Optional[str]) -> str:
141
+ """Return *hdl_toplevel_lang* if supported by simulator, raise exception otherwise.
142
+
143
+ Returns:
144
+ *hdl_toplevel_lang* if supported by the simulator.
145
+
146
+ Raises:
147
+ ValueError: *hdl_toplevel_lang* is not supported by the simulator.
148
+ """
149
+ if hdl_toplevel_lang is None:
150
+ if self.vhdl_sources and not self.verilog_sources and not self.sources:
151
+ lang = "vhdl"
152
+ elif self.verilog_sources and not self.vhdl_sources and not self.sources:
153
+ lang = "verilog"
154
+ elif self.sources and not self.vhdl_sources and not self.verilog_sources:
155
+ if is_vhdl_source(self.sources[-1]):
156
+ lang = "vhdl"
157
+ elif is_verilog_source(self.sources[-1]):
158
+ lang = "verilog"
159
+ else:
160
+ raise UnknownFileExtension(self.sources[-1])
161
+ else:
162
+ raise ValueError(
163
+ f"{type(self).__qualname__}: Must specify a hdl_toplevel_lang in a mixed-language design"
164
+ )
165
+ else:
166
+ lang = hdl_toplevel_lang
167
+
168
+ if lang in self.supported_gpi_interfaces:
169
+ return lang
170
+ else:
171
+ raise ValueError(
172
+ f"{type(self).__qualname__}: hdl_toplevel_lang {hdl_toplevel_lang!r} is not "
173
+ f"in supported list: {', '.join(self.supported_gpi_interfaces)}"
174
+ )
175
+
176
+ def _set_env(self) -> None:
177
+ """Set environment variables for sub-processes."""
178
+
179
+ for e in os.environ:
180
+ self.env[e] = os.environ[e]
181
+
182
+ if "LIBPYTHON_LOC" not in self.env:
183
+ libpython_path = find_libpython.find_libpython()
184
+ if not libpython_path:
185
+ raise ValueError(
186
+ "Unable to find libpython, please make sure the appropriate libpython is installed"
187
+ )
188
+ self.env["LIBPYTHON_LOC"] = libpython_path
189
+
190
+ self.env["PATH"] += os.pathsep + str(cocotb_tools.config.libs_dir)
191
+ self.env["PYTHONPATH"] = os.pathsep.join(sys.path)
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
196
+
197
+ @abstractmethod
198
+ def _build_command(self) -> Sequence[_Command]:
199
+ """Return command to build the HDL sources."""
200
+
201
+ @abstractmethod
202
+ def _test_command(self) -> Sequence[_Command]:
203
+ """Return command to run a test."""
204
+
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
212
+
213
+ def build(
214
+ self,
215
+ hdl_library: str = "top",
216
+ verilog_sources: Sequence[PathLike] = [],
217
+ vhdl_sources: Sequence[PathLike] = [],
218
+ sources: Sequence[Union[PathLike, VHDL, Verilog]] = [],
219
+ includes: Sequence[PathLike] = [],
220
+ defines: Mapping[str, object] = {},
221
+ parameters: Mapping[str, object] = {},
222
+ build_args: Sequence[Union[str, VHDL, Verilog]] = [],
223
+ hdl_toplevel: Optional[str] = None,
224
+ always: bool = False,
225
+ build_dir: PathLike = "sim_build",
226
+ clean: bool = False,
227
+ verbose: bool = False,
228
+ timescale: Optional[Tuple[str, str]] = None,
229
+ waves: bool = False,
230
+ log_file: Optional[PathLike] = None,
231
+ ) -> None:
232
+ """Build the HDL sources.
233
+
234
+ With mixed language simulators, *sources* will be built,
235
+ followed by *vhdl_sources*, then *verilog_sources*.
236
+ With simulators that only support either VHDL or Verilog, *sources* will be built,
237
+ followed by *vhdl_sources* and *verilog_sources*, respectively.
238
+
239
+ If your source files use an atypical file extension,
240
+ use :class:`VHDL` and :class:`Verilog` to tag the path as a VHDL or Verilog source file, respectively.
241
+ If the filepaths aren't tagged, the extension is used to determine if they are VHDL or Verilog files.
242
+
243
+ +----------+------------------------------------+
244
+ | Language | File Extensions |
245
+ +==========+====================================+
246
+ | VHDL | ``.vhd``, ``.vhdl`` |
247
+ +----------+------------------------------------+
248
+ | Verilog | ``.v``, ``.sv``, ``.vh``, ``.svh`` |
249
+ +----------+------------------------------------+
250
+
251
+
252
+ .. code-block:: python
253
+
254
+ runner.build(
255
+ sources=[
256
+ VHDL("/my/file.is_actually_vhdl"),
257
+ Verilog("/other/file.verilog"),
258
+ ],
259
+ )
260
+
261
+ The same tagging works for *build_args*.
262
+ Tagged *build_args* only supply that option to the compiler when building the source file for the tagged language.
263
+ Non-tagged *build_args* are supplied when compiling any language.
264
+
265
+ Args:
266
+ hdl_library: The library name to compile into.
267
+ verilog_sources: Verilog source files to build.
268
+ vhdl_sources: VHDL source files to build.
269
+ sources: Language-agnostic list of source files to build.
270
+ includes: Verilog include directories.
271
+ defines: Defines to set.
272
+ parameters: Verilog parameters or VHDL generics.
273
+ build_args: Extra build arguments for the simulator.
274
+ hdl_toplevel: The name of the HDL toplevel module.
275
+ always: Always run the build step.
276
+ build_dir: Directory to run the build step in.
277
+ clean: Delete *build_dir* before building.
278
+ verbose: Enable verbose messages.
279
+ timescale: Tuple containing time unit and time precision for simulation.
280
+ waves: Record signal traces. Overridden by the :envvar:`WAVES` environment variable.
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.
286
+ """
287
+
288
+ self.clean: bool = clean
289
+ self.build_dir = get_abs_path(build_dir)
290
+ if self.clean:
291
+ self.rm_build_folder(self.build_dir)
292
+ self.build_dir.mkdir(parents=True, exist_ok=True)
293
+
294
+ # note: to avoid mutating argument defaults, we ensure that no value
295
+ # is written without a copy. This is much more concise and leads to
296
+ # a better docstring than using `None` as a default in the parameters
297
+ # list.
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
+ )
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
+ )
312
+ self.vhdl_sources: List[Path] = get_abs_paths(vhdl_sources)
313
+ self.sources: List[Path] = get_abs_paths(sources)
314
+ self.includes: List[Path] = get_abs_paths(includes)
315
+ self.defines = dict(defines)
316
+ self.parameters = dict(parameters)
317
+ self.build_args = list(build_args)
318
+ self.always: bool = always
319
+ self.hdl_toplevel: Optional[str] = hdl_toplevel
320
+ self.verbose: bool = verbose
321
+ self.timescale: Optional[Tuple[str, str]] = timescale
322
+ self.log_file: Optional[PathLike] = log_file
323
+
324
+ self.waves = bool(os.getenv("WAVES", waves))
325
+
326
+ self.env.update(os.environ)
327
+
328
+ cmds: Sequence[_Command] = self._build_command()
329
+ self._execute(cmds, cwd=self.build_dir)
330
+
331
+ def test(
332
+ self,
333
+ test_module: Union[str, Sequence[str]],
334
+ hdl_toplevel: str,
335
+ hdl_toplevel_library: str = "top",
336
+ hdl_toplevel_lang: Optional[str] = None,
337
+ gpi_interfaces: Optional[List[str]] = None,
338
+ testcase: Optional[Union[str, Sequence[str]]] = None,
339
+ seed: Optional[Union[str, int]] = None,
340
+ elab_args: Sequence[str] = [],
341
+ test_args: Sequence[str] = [],
342
+ plusargs: Sequence[str] = [],
343
+ extra_env: Mapping[str, str] = {},
344
+ waves: bool = False,
345
+ gui: bool = False,
346
+ parameters: Optional[Mapping[str, object]] = None,
347
+ build_dir: Optional[PathLike] = None,
348
+ test_dir: Optional[PathLike] = None,
349
+ results_xml: Optional[str] = None,
350
+ pre_cmd: Optional[List[str]] = None,
351
+ verbose: bool = False,
352
+ timescale: Optional[Tuple[str, str]] = None,
353
+ log_file: Optional[PathLike] = None,
354
+ test_filter: Optional[str] = None,
355
+ ) -> Path:
356
+ """Run the tests.
357
+
358
+ Args:
359
+ test_module: Name(s) of the Python module(s) containing the tests to run.
360
+ Can be a comma-separated list.
361
+ hdl_toplevel: Name of the HDL toplevel module.
362
+ hdl_toplevel_library: The library name for HDL toplevel module.
363
+ hdl_toplevel_lang: Language of the HDL toplevel module.
364
+ gpi_interfaces: List of GPI interfaces to use, with the first one being the entry point.
365
+ testcase: Name(s) of a specific testcase(s) to run.
366
+ If not set, run all testcases found in *test_module*.
367
+ Can be a comma-separated list.
368
+ seed: A specific random seed to use.
369
+ elab_args: A list of elaboration arguments for the simulator.
370
+ test_args: A list of extra arguments for the simulator.
371
+ plusargs: 'plusargs' to set for the simulator.
372
+ extra_env: Extra environment variables to set.
373
+ waves: Record signal traces. Overridden by the :envvar:`WAVES` environment variable.
374
+ gui: Run with simulator GUI. Overridden by the :envvar:`GUI` environment variable.
375
+ parameters: Verilog parameters or VHDL generics.
376
+ build_dir: Directory the build step has been run in.
377
+ test_dir: Directory to run the tests in.
378
+ results_xml: Name of xUnit XML file to store test results in.
379
+ If an absolute path is provided it will be used as-is,
380
+ :file:`{build_dir}/results.xml` otherwise.
381
+ This argument should not be set when run with ``pytest``.
382
+ verbose: Enable verbose messages.
383
+ pre_cmd: Commands to run before simulation begins.
384
+ Typically Tcl commands for simulators that support them.
385
+ timescale: Tuple containing time unit and time precision for simulation.
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.
389
+
390
+ Returns:
391
+ The absolute location of the results XML file which can be
392
+ defined by the *results_xml* argument.
393
+ """
394
+
395
+ __tracebackhide__ = True # Hide the traceback when using pytest
396
+
397
+ if build_dir is not None:
398
+ self.build_dir = get_abs_path(build_dir)
399
+
400
+ if parameters is not None:
401
+ self.parameters = dict(parameters)
402
+
403
+ if test_dir is None:
404
+ self.test_dir = self.build_dir
405
+ else:
406
+ self.test_dir = get_abs_path(test_dir)
407
+ self.test_dir.mkdir(parents=True, exist_ok=True)
408
+
409
+ if isinstance(test_module, str):
410
+ self.test_module = test_module
411
+ else:
412
+ self.test_module = ",".join(test_module)
413
+
414
+ # note: to avoid mutating argument defaults, we ensure that no value
415
+ # is written without a copy. This is much more concise and leads to
416
+ # a better docstring than using `None` as a default in the parameters
417
+ # list.
418
+ self.sim_hdl_toplevel = hdl_toplevel
419
+ self.hdl_toplevel_library: str = hdl_toplevel_library
420
+ self.hdl_toplevel_lang = self._check_hdl_toplevel_lang(hdl_toplevel_lang)
421
+ if gpi_interfaces:
422
+ self.gpi_interfaces = gpi_interfaces
423
+ else:
424
+ self.gpi_interfaces = []
425
+ for gpi_if in self.supported_gpi_interfaces.values():
426
+ self.gpi_interfaces.append(gpi_if[0])
427
+
428
+ self.pre_cmd = pre_cmd
429
+
430
+ self.elab_args = list(elab_args)
431
+ self.test_args = list(test_args)
432
+ self.plusargs = list(plusargs)
433
+ self.env = dict(extra_env)
434
+
435
+ if testcase is not None:
436
+ if isinstance(testcase, str):
437
+ self.env["COCOTB_TESTCASE"] = testcase
438
+ else:
439
+ self.env["COCOTB_TESTCASE"] = ",".join(testcase)
440
+
441
+ if test_filter is not None:
442
+ self.env["COCOTB_TEST_FILTER"] = test_filter
443
+
444
+ if seed is not None:
445
+ self.env["COCOTB_RANDOM_SEED"] = str(seed)
446
+
447
+ self.log_file = log_file
448
+ self.waves = bool(os.getenv("WAVES", waves))
449
+ self.gui = bool(os.getenv("GUI", gui))
450
+ self.timescale = timescale
451
+
452
+ if verbose is not None:
453
+ self.verbose = verbose
454
+
455
+ # Pytest test name is used by the next couple sections.
456
+ pytest_current_test = os.getenv("PYTEST_CURRENT_TEST", None)
457
+
458
+ if pytest_current_test is not None:
459
+ self.current_test_name = pytest_current_test.split(":")[-1].split(" ")[0]
460
+ else:
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"
484
+
485
+ with suppress(OSError):
486
+ results_xml_file.unlink()
487
+
488
+ # transport the settings to cocotb via environment variables
489
+ self._set_env()
490
+ self.env["COCOTB_RESULTS_FILE"] = str(results_xml_file)
491
+
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
500
+
501
+ # Only when running under pytest, check the results file here,
502
+ # potentially raising an exception with failing testcases,
503
+ # otherwise return the results file for later analysis.
504
+ if pytest_current_test:
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)
516
+
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)
543
+ return results_xml_file
544
+
545
+ @abstractmethod
546
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
547
+ """Return simulator-specific formatted option strings with *includes* directories."""
548
+
549
+ @abstractmethod
550
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
551
+ """Return simulator-specific formatted option strings with *defines* macros."""
552
+
553
+ @abstractmethod
554
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
555
+ """Return simulator-specific formatted option strings with *parameters*/generics."""
556
+
557
+ def _execute(self, cmds: Sequence[_Command], cwd: PathLike) -> None:
558
+ __tracebackhide__ = True # Hide the traceback when using PyTest.
559
+
560
+ if self.log_file is None:
561
+ self._execute_cmds(cmds, cwd)
562
+ else:
563
+ with open(self.log_file, "w") as f:
564
+ self._execute_cmds(cmds, cwd, f)
565
+
566
+ def _execute_cmds(
567
+ self, cmds: Sequence[_Command], cwd: PathLike, stdout: Optional[TextIO] = None
568
+ ) -> None:
569
+ __tracebackhide__ = True # Hide the traceback when using PyTest.
570
+
571
+ for cmd in cmds:
572
+ self.log.info("Running command %s in directory %s", _shlex_join(cmd), cwd)
573
+
574
+ # TODO: create a thread to handle stderr and log as error?
575
+ # TODO: log forwarding
576
+
577
+ stderr = None if stdout is None else subprocess.STDOUT
578
+ subprocess.run(
579
+ cmd, cwd=cwd, env=self.env, check=True, stdout=stdout, stderr=stderr
580
+ )
581
+
582
+ def rm_build_folder(self, build_dir: Path) -> None:
583
+ if build_dir.is_dir():
584
+ self.log.info("Removing: %s", build_dir)
585
+ shutil.rmtree(build_dir, ignore_errors=True)
586
+
587
+
588
+ def outdated(output: Path, dependencies: Sequence[Path]) -> bool:
589
+ """Return ``True`` if any source files in *dependencies* are newer than the *output* directory.
590
+
591
+ Returns:
592
+ ``True`` if any source files are newer, ``False`` otherwise.
593
+ """
594
+
595
+ if not output.is_file():
596
+ return True
597
+
598
+ output_mtime = output.stat().st_mtime
599
+
600
+ dep_mtime = 0.0
601
+ for dependency in dependencies:
602
+ mtime = dependency.stat().st_mtime
603
+ dep_mtime = max(mtime, dep_mtime)
604
+
605
+ return dep_mtime > output_mtime
606
+
607
+
608
+ def get_abs_path(path: PathLike) -> Path:
609
+ """Return *path* in absolute form."""
610
+
611
+ path = Path(path)
612
+ if path.is_absolute():
613
+ return path.resolve()
614
+ else:
615
+ return Path(Path.cwd() / path).resolve()
616
+
617
+
618
+ def get_abs_paths(paths: Sequence[PathLike]) -> List[Path]:
619
+ """Return list of *paths* in absolute form."""
620
+
621
+ return [get_abs_path(path) for path in paths]
622
+
623
+
624
+ _verilog_extensions = (".v", ".sv", ".vh", ".svh")
625
+ _vhdl_extensions = (".vhd", ".vhdl")
626
+
627
+ _vhdl_extensions_s = ", ".join(f"`{c}`" for c in _vhdl_extensions)
628
+ _verilog_extensions_s = ", ".join(f"`{c}`" for c in _verilog_extensions)
629
+
630
+
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
+
636
+ def __init__(self, source: PathLike) -> None:
637
+ super().__init__(
638
+ f"Can't determine if {source} is a VHDL or Verilog file. "
639
+ f"Use a standard file extension ({_vhdl_extensions_s} for VHDL files and {_verilog_extensions_s} for Verilog files) "
640
+ "or tag the source with `VHDL(source)` or `Verilog(source)`."
641
+ )
642
+
643
+
644
+ def is_vhdl_source(source: PathLike) -> bool:
645
+ if isinstance(source, VHDL):
646
+ return True
647
+ source_as_path = Path(source)
648
+ return source_as_path.suffix in _vhdl_extensions
649
+
650
+
651
+ def is_verilog_source(source: PathLike) -> bool:
652
+ if isinstance(source, Verilog):
653
+ return True
654
+ source_as_path = Path(source)
655
+ return source_as_path.suffix in _verilog_extensions
656
+
657
+
658
+ class Icarus(Runner):
659
+ """Implementation of :class:`Runner` for Icarus Verilog.
660
+
661
+ .. admonition:: Simulator-specific Usage
662
+
663
+ * ``hdl_toplevel`` argument to :meth:`.build` is *required*.
664
+ * ``waves=True`` *must* be given to :meth:`.build` if either ``waves`` or ``gui`` are to be used during :meth:`.test`.
665
+ * ``timescale`` argument to :meth:`.build` must be given to support dumping the command file.
666
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
667
+ """
668
+
669
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
670
+
671
+ def _simulator_in_path(self) -> None:
672
+ if shutil.which("iverilog") is None:
673
+ raise SystemExit("ERROR: iverilog executable not found!")
674
+
675
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
676
+ return [f"-I{include}" for include in includes]
677
+
678
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
679
+ return [f"-D{name}={_as_sv_literal(value)}" for name, value in defines.items()]
680
+
681
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
682
+ assert self.hdl_toplevel is not None
683
+ return [
684
+ f"-P{self.hdl_toplevel}.{name}={value}"
685
+ for name, value in parameters.items()
686
+ ]
687
+
688
+ def _use_external_viewer(self) -> bool:
689
+ return True
690
+
691
+ def _waves_file(self) -> Optional[str]:
692
+ return f"{self.hdl_toplevel}.fst"
693
+
694
+ def _create_cmd_file(self) -> None:
695
+ assert self.timescale is not None
696
+ with open(self.cmds_file, "w") as f:
697
+ f.write("+timescale+{}/{}\n".format(*self.timescale))
698
+
699
+ def _create_iverilog_dump_file(self) -> None:
700
+ dumpfile_path = _as_sv_literal(str(self.build_dir / f"{self.hdl_toplevel}.fst"))
701
+ with open(self.iverilog_dump_file, "w") as f:
702
+ f.write("module cocotb_iverilog_dump();\n")
703
+ f.write("initial begin\n")
704
+ f.write(" string dumpfile_path;")
705
+ f.write(
706
+ ' if ($value$plusargs("dumpfile_path=%s", dumpfile_path)) begin\n'
707
+ )
708
+ f.write(" $dumpfile(dumpfile_path);\n")
709
+ f.write(" end else begin\n")
710
+ f.write(f" $dumpfile({dumpfile_path});\n")
711
+ f.write(" end\n")
712
+ f.write(f" $dumpvars(0, {self.hdl_toplevel});\n")
713
+ f.write("end\n")
714
+ f.write("endmodule\n")
715
+
716
+ @property
717
+ def sim_file(self) -> Path:
718
+ return self.build_dir / "sim.vvp"
719
+
720
+ @property
721
+ def iverilog_dump_file(self) -> Path:
722
+ return self.build_dir / "cocotb_iverilog_dump.v"
723
+
724
+ @property
725
+ def cmds_file(self) -> Path:
726
+ return self.build_dir / "cmds.f"
727
+
728
+ def _build_command(self) -> List[_Command]:
729
+ assert self.hdl_toplevel is not None
730
+
731
+ for source in self.sources:
732
+ if not is_verilog_source(source):
733
+ raise ValueError(
734
+ f"{type(self).__qualname__} only supports Verilog. {str(source)!r} cannot be compiled."
735
+ )
736
+ for arg in self.build_args:
737
+ if type(arg) not in (str, Verilog):
738
+ raise ValueError(
739
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} cannot be applied."
740
+ )
741
+
742
+ build_args = list(self.build_args)
743
+ if self.waves:
744
+ self._create_iverilog_dump_file()
745
+ build_args += ["-s", "cocotb_iverilog_dump"]
746
+
747
+ if self.timescale is not None:
748
+ self._create_cmd_file()
749
+ build_args += ["-f", str(self.cmds_file)]
750
+
751
+ cmds: list[_Command] = []
752
+ sources = [
753
+ source for source in self.sources if is_verilog_source(source)
754
+ ] + self.verilog_sources
755
+ if outdated(self.sim_file, sources) or self.always:
756
+ cmds = [
757
+ [
758
+ "iverilog",
759
+ "-o",
760
+ str(self.sim_file),
761
+ "-s",
762
+ self.hdl_toplevel,
763
+ "-g2012",
764
+ ]
765
+ + self._get_define_options(self.defines)
766
+ + self._get_include_options(self.includes)
767
+ + self._get_parameter_options(self.parameters)
768
+ + [arg for arg in build_args if type(arg) in (str, Verilog)]
769
+ + [str(source_file) for source_file in sources]
770
+ + [
771
+ str(source_file)
772
+ for source_file in [self.iverilog_dump_file]
773
+ if self.waves
774
+ ]
775
+ ]
776
+
777
+ else:
778
+ self.log.warning("Skipping compilation of %s", self.sim_file)
779
+
780
+ return cmds
781
+
782
+ def _test_command(self) -> List[_Command]:
783
+ if self.pre_cmd is not None:
784
+ raise RuntimeError("pre_cmd is not implemented for Icarus Verilog.")
785
+
786
+ plusargs = self.plusargs
787
+ if self.waves or self.gui:
788
+ plusargs += ["-fst"]
789
+ else:
790
+ # Disable waveform output
791
+ plusargs += ["-none"]
792
+
793
+ return [
794
+ [
795
+ "vvp",
796
+ "-M",
797
+ str(cocotb_tools.config.libs_dir),
798
+ "-m",
799
+ cocotb_tools.config.lib_name("vpi", "icarus"),
800
+ *self.test_args,
801
+ str(self.sim_file),
802
+ *plusargs,
803
+ ]
804
+ ]
805
+
806
+
807
+ class Questa(Runner):
808
+ """Implementation of :class:`Runner` for Siemens Questa.
809
+
810
+ .. admonition:: Simulator-specific Usage
811
+
812
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
813
+ """
814
+
815
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["fli", "vhpi"]}
816
+
817
+ def _simulator_in_path(self) -> None:
818
+ if shutil.which("vsim") is None:
819
+ raise SystemExit("ERROR: vsim executable not found!")
820
+
821
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
822
+ return [f"+incdir+{_as_tcl_value(str(include))}" for include in includes]
823
+
824
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
825
+ return [
826
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
827
+ ]
828
+
829
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
830
+ return [f"-g{name}={value}" for name, value in parameters.items()]
831
+
832
+ def _build_command(self) -> List[_Command]:
833
+ cmds = []
834
+
835
+ cmds.append(["vlib", _as_tcl_value(self.hdl_library)])
836
+ for source in self.sources:
837
+ if is_vhdl_source(source):
838
+ cmds.append(self._build_vhdl_command(source))
839
+ elif is_verilog_source(source):
840
+ cmds.append(self._build_verilog_command(source))
841
+ else:
842
+ raise UnknownFileExtension(source)
843
+ for source in self.vhdl_sources:
844
+ cmds.append(self._build_vhdl_command(source))
845
+ for source in self.verilog_sources:
846
+ cmds.append(self._build_verilog_command(source))
847
+
848
+ return cmds
849
+
850
+ def _build_vhdl_command(self, source: PathLike) -> _Command:
851
+ return (
852
+ ["vcom"]
853
+ + ["-work", _as_tcl_value(self.hdl_library)]
854
+ + [_as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)]
855
+ + [_as_tcl_value(str(source))]
856
+ )
857
+
858
+ def _build_verilog_command(self, source: PathLike) -> _Command:
859
+ return (
860
+ ["vlog"]
861
+ + ([] if self.always else ["-incr"])
862
+ + ["-work", _as_tcl_value(self.hdl_library)]
863
+ + ["-sv"]
864
+ + self._get_define_options(self.defines)
865
+ + self._get_include_options(self.includes)
866
+ + [_as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)]
867
+ + [_as_tcl_value(str(source))]
868
+ )
869
+
870
+ def _test_command(self) -> List[_Command]:
871
+ cmds = []
872
+
873
+ if self.pre_cmd is not None:
874
+ pre_cmd = ["-do", *self.pre_cmd]
875
+ else:
876
+ pre_cmd = []
877
+
878
+ do_script = ""
879
+ if self.waves:
880
+ do_script += "log -recursive /*;"
881
+
882
+ if not self.gui:
883
+ do_script += "run -all; quit"
884
+
885
+ gpi_if_entry = self.gpi_interfaces[0]
886
+ if gpi_if_entry == "fli":
887
+ lib_opts = [
888
+ "-foreign",
889
+ "cocotb_init "
890
+ + _as_tcl_value(
891
+ cocotb_tools.config.lib_name_path("fli", "questa").as_posix()
892
+ ),
893
+ ]
894
+ elif gpi_if_entry == "vhpi":
895
+ lib_opts = ["-voptargs=-access=rw+/."]
896
+ lib_opts += [
897
+ "-foreign",
898
+ "vhpi_startup_routines_bootstrap "
899
+ + _as_tcl_value(
900
+ cocotb_tools.config.lib_name_path("vhpi", "questa").as_posix()
901
+ ),
902
+ ]
903
+ else:
904
+ lib_opts = [
905
+ "-pli",
906
+ _as_tcl_value(
907
+ cocotb_tools.config.lib_name_path("vpi", "questa").as_posix()
908
+ ),
909
+ ]
910
+
911
+ cmds.append(
912
+ ["vsim"]
913
+ + ["-gui" if self.gui else "-c"]
914
+ + ["-onfinish", "stop" if self.gui else "exit"]
915
+ + lib_opts
916
+ + [_as_tcl_value(v) for v in self.test_args]
917
+ + [_as_tcl_value(v) for v in self._get_parameter_options(self.parameters)]
918
+ + [_as_tcl_value(f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}")]
919
+ + [_as_tcl_value(v) for v in self.plusargs]
920
+ + pre_cmd
921
+ + ["-do", do_script]
922
+ )
923
+
924
+ gpi_extra_list = []
925
+ for gpi_if in self.gpi_interfaces[1:]:
926
+ gpi_if_lib_path = cocotb_tools.config.lib_name_path(gpi_if, "questa")
927
+ if gpi_if_lib_path.is_file():
928
+ gpi_extra_list.append(
929
+ gpi_if_lib_path.as_posix() + f":cocotb{gpi_if}_entry_point"
930
+ )
931
+ else:
932
+ raise RuntimeError(f"{gpi_if_lib_path} library not found.")
933
+ self.env["GPI_EXTRA"] = ",".join(gpi_extra_list)
934
+
935
+ return cmds
936
+
937
+
938
+ class Ghdl(Runner):
939
+ """Implementation of :class:`Runner` for GHDL.
940
+
941
+ .. admonition:: Simulator-specific Usage
942
+
943
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
944
+ """
945
+
946
+ supported_gpi_interfaces = {"vhdl": ["vpi"]}
947
+
948
+ def _set_env(self) -> None:
949
+ super()._set_env()
950
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
951
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
952
+
953
+ def _simulator_in_path(self) -> None:
954
+ if shutil.which("ghdl") is None:
955
+ raise SystemExit("ERROR: ghdl executable not found!")
956
+
957
+ def _is_mcode_backend(self) -> bool:
958
+ """Is GHDL using the mcode backend?"""
959
+ result = subprocess.run(
960
+ ["ghdl", "--version"],
961
+ check=True,
962
+ text=True,
963
+ stdout=subprocess.PIPE,
964
+ stderr=subprocess.STDOUT,
965
+ )
966
+ return "mcode" in result.stdout
967
+
968
+ def _use_external_viewer(self) -> bool:
969
+ return True
970
+
971
+ def _waves_file(self) -> Optional[str]:
972
+ return f"{self.hdl_toplevel}.ghw"
973
+
974
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
975
+ raise RuntimeError
976
+
977
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
978
+ raise RuntimeError
979
+
980
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
981
+ return [f"-g{name}={value}" for name, value in parameters.items()]
982
+
983
+ def _build_command(self) -> List[_Command]:
984
+ for source in self.sources:
985
+ if not is_vhdl_source(source):
986
+ raise ValueError(
987
+ f"{type(self).__qualname__} only supports VHDL. {str(source)!r} cannot be compiled."
988
+ )
989
+ for arg in self.build_args:
990
+ if type(arg) not in (str, VHDL):
991
+ raise ValueError(
992
+ f"{type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
993
+ )
994
+
995
+ cmds = [
996
+ ["ghdl", "-i"]
997
+ + [f"--work={self.hdl_library}"]
998
+ + [arg for arg in self.build_args if type(arg) in (str, VHDL)]
999
+ + [str(source) for source in self.sources if is_vhdl_source(source)]
1000
+ + [str(source) for source in self.vhdl_sources]
1001
+ ]
1002
+
1003
+ if self.hdl_toplevel is not None:
1004
+ cmds += [
1005
+ [
1006
+ "ghdl",
1007
+ "-m",
1008
+ f"--work={self.hdl_library}",
1009
+ *self.build_args,
1010
+ self.hdl_toplevel,
1011
+ ]
1012
+ ]
1013
+
1014
+ return cmds
1015
+
1016
+ def _test_command(self) -> List[_Command]:
1017
+ if self.pre_cmd is not None:
1018
+ raise RuntimeError("pre_cmd is not implemented for GHDL.")
1019
+
1020
+ ghdl_run_args = self.test_args
1021
+
1022
+ if self._is_mcode_backend() and self.timescale:
1023
+ _, precision = self.timescale
1024
+ # Convert the time precision to a format string supported by GHDL,
1025
+ # if possible.
1026
+ # GHDL only supports setting the time precision if the mcode backend
1027
+ # is used, using the --time-resolution argument causes GHDL to error
1028
+ # out otherwise.
1029
+ # https://ghdl.github.io/ghdl/using/InvokingGHDL.html#cmdoption-ghdl-time-resolution
1030
+ if precision == "1fs":
1031
+ ghdl_time_resolution = "fs"
1032
+ elif precision == "1ps":
1033
+ ghdl_time_resolution = "ps"
1034
+ elif precision == "1ns":
1035
+ ghdl_time_resolution = "ns"
1036
+ elif precision == "1us":
1037
+ ghdl_time_resolution = "us"
1038
+ elif precision == "1ms":
1039
+ ghdl_time_resolution = "ms"
1040
+ elif precision == "1s":
1041
+ ghdl_time_resolution = "sec"
1042
+ else:
1043
+ raise ValueError(
1044
+ "GHDL only supports the following precisions in timescale: 1fs, 1ps, 1us, 1ms, 1s"
1045
+ )
1046
+
1047
+ ghdl_run_args.append(f"--time-resolution={ghdl_time_resolution}")
1048
+
1049
+ cmds = [
1050
+ ["ghdl", "-r"]
1051
+ + [f"--work={self.hdl_toplevel_library}"]
1052
+ + ghdl_run_args
1053
+ + [self.sim_hdl_toplevel]
1054
+ + ["--vpi=" + cocotb_tools.config.lib_name_path("vpi", "ghdl").as_posix()]
1055
+ + self.plusargs
1056
+ + self._get_parameter_options(self.parameters)
1057
+ + ([f"--wave={self._waves_file()}"] if self.waves or self.gui else [])
1058
+ ]
1059
+
1060
+ return cmds
1061
+
1062
+
1063
+ class Nvc(Runner):
1064
+ """Implementation of :class:`Runner` for NVC.
1065
+
1066
+ .. admonition:: Simulator-specific Usage
1067
+
1068
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1069
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1070
+ """
1071
+
1072
+ supported_gpi_interfaces = {"vhdl": ["vhpi"]}
1073
+
1074
+ def __init__(self) -> None:
1075
+ super().__init__()
1076
+
1077
+ version_str = subprocess.run(
1078
+ ["nvc", "--version"],
1079
+ check=True,
1080
+ text=True,
1081
+ stdout=subprocess.PIPE,
1082
+ ).stdout
1083
+ version = NvcVersion.from_commandline(version_str)
1084
+ if version > NvcVersion("1.16"):
1085
+ self._preserve_case = ["--preserve-case"]
1086
+ else:
1087
+ self._preserve_case = []
1088
+
1089
+ def _set_env(self) -> None:
1090
+ super()._set_env()
1091
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
1092
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
1093
+
1094
+ def _simulator_in_path(self) -> None:
1095
+ if shutil.which("nvc") is None:
1096
+ raise SystemExit("ERROR: nvc executable not found!")
1097
+
1098
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1099
+ raise RuntimeError
1100
+
1101
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1102
+ raise RuntimeError
1103
+
1104
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1105
+ return [f"-g{name}={value}" for name, value in parameters.items()]
1106
+
1107
+ def _use_external_viewer(self) -> bool:
1108
+ return True
1109
+
1110
+ def _waves_file(self) -> Optional[str]:
1111
+ return f"{self.hdl_toplevel}.fst"
1112
+
1113
+ def _build_command(self) -> List[_Command]:
1114
+ for source in self.sources:
1115
+ if not is_vhdl_source(source):
1116
+ raise ValueError(
1117
+ f"{type(self).__qualname__} only supports VHDL. {str(source)!r} cannot be compiled."
1118
+ )
1119
+ for arg in self.build_args:
1120
+ if type(arg) not in (str, VHDL):
1121
+ raise ValueError(
1122
+ f"{type(self).__qualname__} only supports VHDL. build_args {arg!r} will not be applied."
1123
+ )
1124
+
1125
+ cmds = [
1126
+ [
1127
+ "nvc",
1128
+ f"--work={self.hdl_library}",
1129
+ "-L",
1130
+ str(get_abs_path(self.build_dir)),
1131
+ ]
1132
+ + [arg for arg in self.build_args if type(arg) in (str, VHDL)]
1133
+ + ["-a"]
1134
+ + [str(source) for source in self.sources if is_vhdl_source(source)]
1135
+ + [str(source) for source in self.vhdl_sources]
1136
+ + self._preserve_case
1137
+ ]
1138
+
1139
+ return cmds
1140
+
1141
+ def _test_command(self) -> List[_Command]:
1142
+ work_library = str(get_abs_path(self.build_dir / self.hdl_toplevel_library))
1143
+ cmds = [
1144
+ [
1145
+ "nvc",
1146
+ f"--work={self.hdl_toplevel_library}:{work_library}",
1147
+ "-L",
1148
+ str(get_abs_path(self.build_dir)),
1149
+ ]
1150
+ + self.build_args
1151
+ + ["-e", self.sim_hdl_toplevel, "--no-save", "--jit"]
1152
+ + self.elab_args
1153
+ + self._get_parameter_options(self.parameters)
1154
+ + ["-r"]
1155
+ + self.test_args
1156
+ + ["--load=" + cocotb_tools.config.lib_name_path("vhpi", "nvc").as_posix()]
1157
+ + self.plusargs
1158
+ + ([f"--wave={self._waves_file()}"] if self.waves or self.gui else [])
1159
+ ]
1160
+
1161
+ return cmds
1162
+
1163
+
1164
+ class Riviera(Runner):
1165
+ """Implementation of :class:`Runner` for Aldec Riviera-PRO.
1166
+
1167
+ .. admonition:: Simulator-specific Usage
1168
+
1169
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1170
+ * Does not support the ``gui`` argument to :meth:`.test`.
1171
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1172
+ """
1173
+
1174
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
1175
+
1176
+ def _simulator_in_path(self) -> None:
1177
+ if shutil.which("vsimsa") is None:
1178
+ raise SystemExit("ERROR: vsimsa executable not found!")
1179
+
1180
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1181
+ return [f"+incdir+{_as_tcl_value(str(include))}" for include in includes]
1182
+
1183
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1184
+ return [
1185
+ f"+define+{name}={self._as_define_value(value)}"
1186
+ for name, value in defines.items()
1187
+ ]
1188
+
1189
+ def _as_define_value(self, value: object) -> str:
1190
+ if isinstance(value, int):
1191
+ return str(value)
1192
+ elif isinstance(value, str):
1193
+ for char in value:
1194
+ if ord(char) < 32 or ord(char) >= 255 or char in '\\"':
1195
+ # Control characters are generally not supported.
1196
+ # Not sure if there's any way to escape quotes or backslashes.
1197
+ raise ValueError(
1198
+ f"Character {char!r} not supported in define value"
1199
+ )
1200
+ return '\\"\\\\"' + value + '\\\\"\\"'
1201
+ else:
1202
+ raise TypeError("Can't serialize this type as an SV literal")
1203
+
1204
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1205
+ return [f"-g{name}={value}" for name, value in parameters.items()]
1206
+
1207
+ def _build_command(self) -> List[_Command]:
1208
+ do_script: List[str] = ["onerror {\n quit -code 1 \n}"]
1209
+
1210
+ out_file = self.build_dir / self.hdl_library / f"{self.hdl_library}.lib"
1211
+
1212
+ if outdated(out_file, self.verilog_sources + self.vhdl_sources) or self.always:
1213
+ do_script.append(f"alib {_as_tcl_value(self.hdl_library)}")
1214
+
1215
+ for source in self.sources:
1216
+ if is_verilog_source(source):
1217
+ do_script.append(self._build_verilog_source(source))
1218
+ elif is_vhdl_source(source):
1219
+ do_script.append(self._build_vhdl_source(source))
1220
+ else:
1221
+ raise UnknownFileExtension(source)
1222
+ for source in self.vhdl_sources:
1223
+ do_script.append(self._build_vhdl_source(source))
1224
+ for source in self.verilog_sources:
1225
+ do_script.append(self._build_verilog_source(source))
1226
+
1227
+ # Explicitly exit the script at the end. In batch mode, which is invoked
1228
+ # implicitly by redirecting STDOUT/STDERR of the alog/acom commands,
1229
+ # the tool exits by itself even without this 'exit' command -- but not
1230
+ # when running from an interactive terminal. Be explicit for predictable
1231
+ # behavior.
1232
+ do_script.append("exit")
1233
+
1234
+ with tempfile.NamedTemporaryFile(delete=False) as do_file:
1235
+ do_file.write("\n".join(do_script).encode())
1236
+
1237
+ return [["vsimsa", "-do", "do", do_file.name]]
1238
+
1239
+ def _build_vhdl_source(self, source: PathLike) -> str:
1240
+ return "acom -work {RTL_LIBRARY} {EXTRA_ARGS} {VHDL_SOURCES}".format(
1241
+ RTL_LIBRARY=_as_tcl_value(self.hdl_library),
1242
+ VHDL_SOURCES=_as_tcl_value(str(source)),
1243
+ EXTRA_ARGS=" ".join(
1244
+ _as_tcl_value(v) for v in self.build_args if type(v) in (str, VHDL)
1245
+ ),
1246
+ )
1247
+
1248
+ def _build_verilog_source(self, source: PathLike) -> str:
1249
+ return "alog -work {RTL_LIBRARY} -pli {EXT_NAME} -sv {DEFINES} {INCDIR} {EXTRA_ARGS} {VERILOG_SOURCES}".format(
1250
+ RTL_LIBRARY=_as_tcl_value(self.hdl_library),
1251
+ EXT_NAME=_as_tcl_value(
1252
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1253
+ ),
1254
+ VERILOG_SOURCES=_as_tcl_value(str(source)),
1255
+ DEFINES=" ".join(self._get_define_options(self.defines)),
1256
+ INCDIR=" ".join(self._get_include_options(self.includes)),
1257
+ EXTRA_ARGS=" ".join(
1258
+ _as_tcl_value(v) for v in self.build_args if type(v) in (str, Verilog)
1259
+ ),
1260
+ )
1261
+
1262
+ def _test_command(self) -> List[_Command]:
1263
+ if self.pre_cmd is not None:
1264
+ raise RuntimeError("pre_cmd is not implemented for Riviera.")
1265
+
1266
+ do_script: str = "\nonerror {\n quit -code 1 \n} \n"
1267
+
1268
+ if self.hdl_toplevel_lang == "vhdl":
1269
+ do_script += "asim +access +w_nets -interceptcoutput -loadvhpi {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS}\n".format(
1270
+ TOPLEVEL=_as_tcl_value(
1271
+ f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
1272
+ ),
1273
+ EXT_NAME=_as_tcl_value(
1274
+ cocotb_tools.config.lib_name_path("vhpi", "riviera").as_posix()
1275
+ + ":vhpi_startup_routines_bootstrap"
1276
+ ),
1277
+ EXTRA_ARGS=" ".join(
1278
+ _as_tcl_value(v)
1279
+ for v in (
1280
+ self.test_args + self._get_parameter_options(self.parameters)
1281
+ )
1282
+ ),
1283
+ PLUSARGS=" ".join(_as_tcl_value(v) for v in self.plusargs),
1284
+ )
1285
+
1286
+ self.env["GPI_EXTRA"] = (
1287
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1288
+ + ":cocotbvpi_entry_point"
1289
+ )
1290
+ else:
1291
+ do_script += "asim +access +w_nets -interceptcoutput -pli {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS} \n".format(
1292
+ TOPLEVEL=_as_tcl_value(
1293
+ f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
1294
+ ),
1295
+ EXT_NAME=_as_tcl_value(
1296
+ cocotb_tools.config.lib_name_path("vpi", "riviera").as_posix()
1297
+ ),
1298
+ EXTRA_ARGS=" ".join(
1299
+ _as_tcl_value(v)
1300
+ for v in (
1301
+ self.test_args + self._get_parameter_options(self.parameters)
1302
+ )
1303
+ ),
1304
+ PLUSARGS=" ".join(_as_tcl_value(v) for v in self.plusargs),
1305
+ )
1306
+
1307
+ self.env["GPI_EXTRA"] = (
1308
+ cocotb_tools.config.lib_name_path("vhpi", "riviera").as_posix()
1309
+ + ":cocotbvhpi_entry_point"
1310
+ )
1311
+
1312
+ if self.waves:
1313
+ do_script += "log -recursive /*;"
1314
+
1315
+ do_script += "run -all \nexit"
1316
+
1317
+ with tempfile.NamedTemporaryFile(delete=False) as do_file:
1318
+ do_file.write(do_script.encode())
1319
+
1320
+ return [["vsimsa", "-do", "do", do_file.name]]
1321
+
1322
+
1323
+ class Verilator(Runner):
1324
+ """Implementation of :class:`Runner` for Verilator.
1325
+
1326
+ .. admonition:: Simulator-specific Usage
1327
+
1328
+ * ``waves=True`` *must* be given to :meth:`.build` if either ``waves`` or ``gui`` are to be used during :meth:`.test`.
1329
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1330
+ """
1331
+
1332
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
1333
+
1334
+ def _set_env(self) -> None:
1335
+ super()._set_env()
1336
+ if "COCOTB_TRUST_INERTIAL_WRITES" not in self.env:
1337
+ self.env["COCOTB_TRUST_INERTIAL_WRITES"] = "1"
1338
+
1339
+ def _simulator_in_path(self) -> None:
1340
+ # the verilator binary is only needed for building
1341
+ return
1342
+
1343
+ def _use_external_viewer(self) -> bool:
1344
+ return True
1345
+
1346
+ def _waves_file(self) -> Optional[str]:
1347
+ return "dump.vcd"
1348
+
1349
+ def _simulator_in_path_build_only(self) -> None:
1350
+ executable = shutil.which("verilator")
1351
+ if executable is None:
1352
+ raise SystemExit("ERROR: verilator executable not found!")
1353
+ self.executable: str = executable
1354
+
1355
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1356
+ return [f"-I{include}" for include in includes]
1357
+
1358
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1359
+ return [f"-D{name}={_as_sv_literal(value)}" for name, value in defines.items()]
1360
+
1361
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1362
+ return [f"-G{name}={value}" for name, value in parameters.items()]
1363
+
1364
+ def _build_command(self) -> List[_Command]:
1365
+ self._simulator_in_path_build_only()
1366
+
1367
+ for source in self.sources:
1368
+ if not is_verilog_source(source):
1369
+ raise ValueError(
1370
+ f"{type(self).__qualname__} only supports Verilog. {str(source)!r} cannot be compiled."
1371
+ )
1372
+ for arg in self.build_args:
1373
+ if type(arg) not in (str, Verilog):
1374
+ raise ValueError(
1375
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} will not be applied."
1376
+ )
1377
+
1378
+ if self.hdl_toplevel is None:
1379
+ raise ValueError(
1380
+ f"{type(self).__qualname__} requires the hdl_toplevel parameter to be specified."
1381
+ )
1382
+
1383
+ # TODO: set "--debug" if self.verbose
1384
+ # TODO: support "--always"
1385
+
1386
+ verilator_cpp = str(
1387
+ cocotb_tools.config.share_dir / "lib" / "verilator" / "verilator.cpp"
1388
+ )
1389
+
1390
+ cmds = []
1391
+ cmds.append(
1392
+ [
1393
+ "perl",
1394
+ self.executable,
1395
+ "-cc",
1396
+ "--exe",
1397
+ "-Mdir",
1398
+ str(self.build_dir),
1399
+ "--top-module",
1400
+ self.hdl_toplevel,
1401
+ "--vpi",
1402
+ "--public-flat-rw",
1403
+ "--prefix",
1404
+ "Vtop",
1405
+ "-o",
1406
+ self.hdl_toplevel,
1407
+ "-LDFLAGS",
1408
+ f"-Wl,-rpath,{cocotb_tools.config.libs_dir} -L{cocotb_tools.config.libs_dir} -lcocotbvpi_verilator",
1409
+ ]
1410
+ + (["--trace"] if self.waves else [])
1411
+ + [arg for arg in self.build_args if type(arg) in (str, Verilog)]
1412
+ + (
1413
+ ["--timescale", "{}/{}".format(*self.timescale)]
1414
+ if self.timescale is not None
1415
+ else []
1416
+ )
1417
+ + self._get_define_options(self.defines)
1418
+ + self._get_include_options(self.includes)
1419
+ + self._get_parameter_options(self.parameters)
1420
+ + [verilator_cpp]
1421
+ + [str(source) for source in self.sources if is_verilog_source(source)]
1422
+ + [str(source) for source in self.verilog_sources]
1423
+ )
1424
+
1425
+ cmds.append(
1426
+ [
1427
+ "make",
1428
+ "-j",
1429
+ f"{_get_max_parallel_build_jobs()}",
1430
+ "-C",
1431
+ str(self.build_dir),
1432
+ "-f",
1433
+ "Vtop.mk",
1434
+ f"VM_TRACE={int(self.waves)}",
1435
+ ]
1436
+ )
1437
+
1438
+ return cmds
1439
+
1440
+ def _test_command(self) -> List[_Command]:
1441
+ if self.pre_cmd is not None:
1442
+ raise RuntimeError("pre_cmd is not implemented for Verilator.")
1443
+
1444
+ out_file = self.build_dir / self.sim_hdl_toplevel
1445
+ return [
1446
+ [str(out_file)]
1447
+ + (["--trace"] if self.waves or self.gui else [])
1448
+ + self.test_args
1449
+ + self.plusargs
1450
+ ]
1451
+
1452
+
1453
+ class Xcelium(Runner):
1454
+ """Implementation of :class:`Runner` for Cadence Xcelium.
1455
+
1456
+ .. admonition:: Simulator-specific Usage
1457
+
1458
+ * Does not support the ``waves`` argument to :meth:`.build` (must be set in :meth:`.test` instead).
1459
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1460
+ * Does not support the ``timescale`` argument to :meth:`.test`.
1461
+ """
1462
+
1463
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
1464
+
1465
+ def _simulator_in_path(self) -> None:
1466
+ if shutil.which("xrun") is None:
1467
+ raise SystemExit("ERROR: xrun executable not found!")
1468
+
1469
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1470
+ return [f"-incdir {include}" for include in includes]
1471
+
1472
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1473
+ return [
1474
+ f"-define {name}={self._as_define_value(value)}"
1475
+ for name, value in defines.items()
1476
+ ]
1477
+
1478
+ def _as_define_value(self, value: object) -> str:
1479
+ if isinstance(value, int):
1480
+ return str(value)
1481
+ elif isinstance(value, str):
1482
+ for char in value:
1483
+ if ord(char) < 32 or ord(char) >= 255 or char == '"':
1484
+ # Control characters are generally not supported.
1485
+ # Not sure if there's any way to escape quotes.
1486
+ raise ValueError(
1487
+ f"Character {char!r} not supported in define value"
1488
+ )
1489
+ return '"\\"' + value.replace("\\", "\\\\") + '\\""'
1490
+ else:
1491
+ raise TypeError("Can't serialize this type as an SV literal")
1492
+
1493
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1494
+ return [f'-gpg "{name} => {value}"' for name, value in parameters.items()]
1495
+
1496
+ def _build_command(self) -> List[_Command]:
1497
+ self.env["CDS_AUTO_64BIT"] = "all"
1498
+
1499
+ if self.waves:
1500
+ raise RuntimeError(
1501
+ "waves is not supported in the build step. Please set it in the test step."
1502
+ )
1503
+
1504
+ assert self.hdl_toplevel, "A HDL toplevel is required in all Xcelium compiles."
1505
+
1506
+ verbosity_opts = []
1507
+ if self.verbose:
1508
+ verbosity_opts += ["-messages"]
1509
+ verbosity_opts += ["-status"]
1510
+ verbosity_opts += ["-gverbose"] # print assigned generics/parameters
1511
+ verbosity_opts += ["-pliverbose"]
1512
+ verbosity_opts += ["-plidebug"] # Enhance the profile output with PLI info
1513
+ verbosity_opts += [
1514
+ "-plierr_verbose"
1515
+ ] # Expand handle info in PLI/VPI/VHPI messages
1516
+
1517
+ else:
1518
+ verbosity_opts += ["-quiet"]
1519
+ verbosity_opts += ["-plinowarn"]
1520
+
1521
+ vhpi_opts = []
1522
+ if self.vhdl_sources or any(is_vhdl_source(src) for src in self.sources):
1523
+ # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1524
+ # following define is set.
1525
+ vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1526
+
1527
+ cmds = [
1528
+ ["xrun"]
1529
+ + ["-logfile"]
1530
+ + ["xrun_build.log"]
1531
+ + ["-elaborate"]
1532
+ + ["-xmlibdirname"]
1533
+ + [f"{self.build_dir}/xrun_snapshot"]
1534
+ + ["-licqueue"]
1535
+ + (["-clean"] if self.always else [])
1536
+ + verbosity_opts
1537
+ # + ["-vpicompat 1800v2005"] # <1364v1995|1364v2001|1364v2005|1800v2005> Specify the IEEE VPI
1538
+ + ["-access +rwc"]
1539
+ + ["-loadvpi"]
1540
+ # always start with VPI on Xcelium
1541
+ + [
1542
+ cocotb_tools.config.lib_name_path("vpi", "xcelium").as_posix()
1543
+ + ":vlog_startup_routines_bootstrap"
1544
+ ]
1545
+ + vhpi_opts
1546
+ + [f"-work {self.hdl_library}"]
1547
+ + (
1548
+ ["-timescale", "{}/{}".format(*self.timescale)]
1549
+ if self.timescale is not None
1550
+ else []
1551
+ )
1552
+ + self.build_args
1553
+ + self._get_include_options(self.includes)
1554
+ + self._get_define_options(self.defines)
1555
+ + self._get_parameter_options(self.parameters)
1556
+ + [f"-top {self.hdl_toplevel}"]
1557
+ + [
1558
+ str(source_file)
1559
+ for source_file in (
1560
+ self.sources + self.vhdl_sources + self.verilog_sources
1561
+ )
1562
+ ]
1563
+ ]
1564
+
1565
+ return cmds
1566
+
1567
+ def _test_command(self) -> List[_Command]:
1568
+ if self.pre_cmd is not None:
1569
+ raise RuntimeError("pre_cmd is not implemented for Xcelium.")
1570
+
1571
+ if self.timescale is not None:
1572
+ raise RuntimeError(
1573
+ "timescale is not supported in the test step. Please set it in the build step."
1574
+ )
1575
+
1576
+ self.env["CDS_AUTO_64BIT"] = "all"
1577
+
1578
+ verbosity_opts = []
1579
+ if self.verbose:
1580
+ verbosity_opts += ["-messages"]
1581
+ verbosity_opts += ["-status"]
1582
+ verbosity_opts += ["-gverbose"] # print assigned generics/parameters
1583
+ verbosity_opts += ["-pliverbose"]
1584
+ verbosity_opts += ["-plidebug"] # Enhance the profile output with PLI info
1585
+ verbosity_opts += [
1586
+ "-plierr_verbose"
1587
+ ] # Expand handle info in PLI/VPI/VHPI messages
1588
+
1589
+ else:
1590
+ verbosity_opts += ["-quiet"]
1591
+ verbosity_opts += ["-plinowarn"]
1592
+
1593
+ tmpdir = f"implicit_tmpdir_{self.current_test_name}"
1594
+
1595
+ if self.hdl_toplevel_lang == "vhdl":
1596
+ xrun_top = ":"
1597
+ else:
1598
+ xrun_top = self.sim_hdl_toplevel
1599
+
1600
+ if self.waves:
1601
+ input_tcl = [
1602
+ f'-input "@database -open cocotb_waves -default" '
1603
+ f'-input "@probe -database cocotb_waves -create {xrun_top} -all -depth all" '
1604
+ f'-input "@run" '
1605
+ f'-input "@exit" '
1606
+ ]
1607
+ else:
1608
+ input_tcl = ["-input", "@run; exit;"]
1609
+
1610
+ vhpi_opts = []
1611
+ if self.vhdl_sources or any(is_vhdl_source(src) for src in self.sources):
1612
+ # Xcelium 23.09.004 fixes cocotb issue #1076 as long as the
1613
+ # following define is set.
1614
+ vhpi_opts.append("-NEW_VHPI_PROPAGATE_DELAY")
1615
+
1616
+ cmds = [["mkdir", "-p", tmpdir]]
1617
+ cmds += [
1618
+ [
1619
+ "xrun",
1620
+ "-logfile",
1621
+ f"xrun_{self.current_test_name}.log",
1622
+ "-xmlibdirname",
1623
+ f"{self.build_dir}/xrun_snapshot",
1624
+ "-cds_implicit_tmpdir",
1625
+ tmpdir,
1626
+ "-licqueue",
1627
+ *vhpi_opts,
1628
+ *verbosity_opts,
1629
+ "-R",
1630
+ *self.test_args,
1631
+ *self.plusargs,
1632
+ "-gui" if self.gui else "",
1633
+ *input_tcl,
1634
+ ]
1635
+ ]
1636
+ self.env["GPI_EXTRA"] = (
1637
+ cocotb_tools.config.lib_name_path("vhpi", "xcelium").as_posix()
1638
+ + ":cocotbvhpi_entry_point"
1639
+ )
1640
+
1641
+ return cmds
1642
+
1643
+
1644
+ class Vcs(Runner):
1645
+ """Implementation of :class:`Runner` for Synopsys VCS.
1646
+
1647
+ .. admonition:: Simulator-specific Usage
1648
+
1649
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1650
+ * Does not support VHDL.
1651
+ * Does not support the ``timescale`` argument to :meth:`.build` or :meth:`.test`.
1652
+ """
1653
+
1654
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
1655
+
1656
+ def _simulator_in_path(self) -> None:
1657
+ if shutil.which("vcs") is None:
1658
+ raise SystemExit("ERROR: vcs executable not found!")
1659
+
1660
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1661
+ return [f"+incdir+{include}" for include in includes]
1662
+
1663
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1664
+ return [
1665
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
1666
+ ]
1667
+
1668
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1669
+ assert self.hdl_toplevel is not None
1670
+ return [
1671
+ f"-pvalue+{self.hdl_toplevel}.{name}={value}"
1672
+ for name, value in parameters.items()
1673
+ ]
1674
+
1675
+ @property
1676
+ def sim_file(self) -> Path:
1677
+ return self.build_dir / "simv"
1678
+
1679
+ @property
1680
+ def _build_opts(self) -> List[str]:
1681
+ opts = [
1682
+ "-full64",
1683
+ "-debug_access+all",
1684
+ "+acc+3",
1685
+ "-sverilog",
1686
+ "-LDFLAGS -Wl,--no-as-needed",
1687
+ ]
1688
+
1689
+ if self.verbose:
1690
+ opts += ["-diag all"]
1691
+ else:
1692
+ opts += ["-q"]
1693
+ opts += ["-suppress=VPI-CT-NS"]
1694
+
1695
+ return opts
1696
+
1697
+ def _build_command(self) -> List[_Command]:
1698
+ cmds: List[_Command] = []
1699
+ sources = list(self.sources + self.vhdl_sources + self.verilog_sources)
1700
+
1701
+ if outdated(self.sim_file, sources) or self.always:
1702
+ cmds = [
1703
+ ["vcs"]
1704
+ + self._build_opts
1705
+ + ["-load", cocotb_tools.config.lib_name_path("vpi", "vcs").as_posix()]
1706
+ + self.build_args
1707
+ + self._get_include_options(self.includes)
1708
+ + self._get_define_options(self.defines)
1709
+ + self._get_parameter_options(self.parameters)
1710
+ + ["-top", f"{self.hdl_toplevel}"]
1711
+ + [str(source) for source in sources]
1712
+ + ["-o", str(self.sim_file)]
1713
+ ]
1714
+ else:
1715
+ self.log.warning("Skipping compilation of %s", self.sim_file)
1716
+
1717
+ return cmds
1718
+
1719
+ def _test_command(self) -> List[_Command]:
1720
+ if self.pre_cmd is not None:
1721
+ raise RuntimeError("pre_cmd is not implemented for Vcs.")
1722
+
1723
+ verbosity_opts = []
1724
+ if self.verbose:
1725
+ verbosity_opts += ["-diag all"]
1726
+ else:
1727
+ verbosity_opts += ["-suppress=ASLR_DETECTED_INFO"]
1728
+
1729
+ cmds = [[str(self.sim_file), *verbosity_opts, *self.test_args, *self.plusargs]]
1730
+
1731
+ return cmds
1732
+
1733
+
1734
+ class Dsim(Runner):
1735
+ """Implementation of :class:`Runner` for Siemens DSim.
1736
+
1737
+ .. admonition:: Simulator-specific Usage
1738
+
1739
+ * Does not support the ``pre_cmd`` argument to :meth:`.test`.
1740
+ """
1741
+
1742
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
1743
+
1744
+ def _simulator_in_path(self) -> None:
1745
+ if shutil.which("dsim") is None:
1746
+ raise SystemExit("ERROR: dsim executable not found!")
1747
+
1748
+ def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
1749
+ return [f"+incdir+{include}" for include in includes]
1750
+
1751
+ def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
1752
+ return [
1753
+ f"+define+{name}={_as_sv_literal(value)}" for name, value in defines.items()
1754
+ ]
1755
+
1756
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
1757
+ return [
1758
+ f"-defparam {name}={_as_sv_literal(value)}"
1759
+ for name, value in parameters.items()
1760
+ ]
1761
+
1762
+ @property
1763
+ def sim_file(self) -> Path:
1764
+ return self.build_dir / "image.so"
1765
+
1766
+ def _use_external_viewer(self) -> bool:
1767
+ return True
1768
+
1769
+ def _waves_file(self) -> Optional[str]:
1770
+ return "file.vcd"
1771
+
1772
+ def _test_command(self) -> List[_Command]:
1773
+ if self.pre_cmd is not None:
1774
+ raise RuntimeError("pre_cmd is not implemented for DSim.")
1775
+
1776
+ plusargs = self.plusargs
1777
+ if self.waves or self.gui:
1778
+ plusargs += [f"-waves {self._waves_file()}"]
1779
+
1780
+ if self.timescale:
1781
+ plusargs += ["-timescale {}/{}".format(*self.timescale)]
1782
+
1783
+ return [
1784
+ [
1785
+ "dsim",
1786
+ "-work",
1787
+ str(self.build_dir),
1788
+ "-pli_lib",
1789
+ cocotb_tools.config.lib_name_path("vpi", "dsim").as_posix(),
1790
+ "+acc+rwcbfsWF",
1791
+ "-image",
1792
+ "image",
1793
+ *self.test_args,
1794
+ *plusargs,
1795
+ ]
1796
+ ]
1797
+
1798
+ def _build_command(self) -> List[_Command]:
1799
+ for source in self.sources:
1800
+ if not is_verilog_source(source):
1801
+ raise ValueError(
1802
+ f"{type(self).__qualname__} only supports Verilog. {str(source)!r} cannot be compiled."
1803
+ )
1804
+ for arg in self.build_args:
1805
+ if type(arg) not in (str, Verilog):
1806
+ raise ValueError(
1807
+ f"{type(self).__qualname__} only supports Verilog. build_args {arg!r} cannot be applied."
1808
+ )
1809
+
1810
+ build_args = list(self.build_args)
1811
+
1812
+ cmds: list[_Command] = []
1813
+ sources = [
1814
+ source for source in self.sources if is_verilog_source(source)
1815
+ ] + self.verilog_sources
1816
+ if outdated(self.sim_file, sources) or self.always:
1817
+ cmds = [
1818
+ [
1819
+ "dsim",
1820
+ "-work",
1821
+ str(self.build_dir),
1822
+ "-pli_lib",
1823
+ cocotb_tools.config.lib_name_path("vpi", "dsim").as_posix(),
1824
+ "+acc+rwcbfsWF",
1825
+ "-genimage",
1826
+ "image",
1827
+ ]
1828
+ + self._get_define_options(self.defines)
1829
+ + self._get_include_options(self.includes)
1830
+ + self._get_parameter_options(self.parameters)
1831
+ + [arg for arg in build_args if type(arg) in (str, Verilog)]
1832
+ + [str(source_file) for source_file in sources]
1833
+ ]
1834
+
1835
+ else:
1836
+ self.log.warning("Skipping compilation of %s", self.sim_file)
1837
+
1838
+ return cmds
1839
+
1840
+
1841
+ def get_runner(simulator_name: str) -> Runner:
1842
+ """Return an instance of a runner for *simulator_name*.
1843
+
1844
+ Args:
1845
+ simulator_name: Name of simulator to get runner for.
1846
+
1847
+ Raises:
1848
+ ValueError: If *simulator_name* is not one of the supported simulators or an alias of one.
1849
+ """
1850
+
1851
+ supported_sims: Dict[str, Type[Runner]] = {
1852
+ "icarus": Icarus,
1853
+ "questa": Questa,
1854
+ "ghdl": Ghdl,
1855
+ "riviera": Riviera,
1856
+ "verilator": Verilator,
1857
+ "xcelium": Xcelium,
1858
+ "nvc": Nvc,
1859
+ "vcs": Vcs,
1860
+ "dsim": Dsim,
1861
+ # TODO: "activehdl": ActiveHdl,
1862
+ }
1863
+ try:
1864
+ return supported_sims[simulator_name]()
1865
+ except KeyError:
1866
+ raise ValueError(
1867
+ f"Simulator {simulator_name!r} is not in supported list: {', '.join(supported_sims)}"
1868
+ ) from None