cocotb 1.7.1__cp310-cp310-win32.whl → 1.8.0rc1__cp310-cp310-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 (87) hide show
  1. cocotb/__init__.py +7 -18
  2. cocotb/_sim_versions.py +16 -1
  3. cocotb/_version.py +1 -1
  4. cocotb/clock.py +8 -3
  5. cocotb/config.py +2 -1
  6. cocotb/decorators.py +40 -318
  7. cocotb/ipython_support.py +3 -1
  8. cocotb/libs/cocotb.dll +0 -0
  9. cocotb/libs/cocotb.exp +0 -0
  10. cocotb/libs/cocotb.lib +0 -0
  11. cocotb/libs/cocotbfli_modelsim.dll +0 -0
  12. cocotb/libs/cocotbfli_modelsim.exp +0 -0
  13. cocotb/libs/cocotbfli_modelsim.lib +0 -0
  14. cocotb/libs/cocotbutils.dll +0 -0
  15. cocotb/libs/cocotbutils.exp +0 -0
  16. cocotb/libs/cocotbutils.lib +0 -0
  17. cocotb/libs/cocotbvhpi_aldec.dll +0 -0
  18. cocotb/libs/cocotbvhpi_aldec.exp +0 -0
  19. cocotb/libs/cocotbvhpi_aldec.lib +0 -0
  20. cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
  21. cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
  22. cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
  23. cocotb/libs/cocotbvpi_aldec.dll +0 -0
  24. cocotb/libs/cocotbvpi_aldec.exp +0 -0
  25. cocotb/libs/cocotbvpi_aldec.lib +0 -0
  26. cocotb/libs/cocotbvpi_ghdl.dll +0 -0
  27. cocotb/libs/cocotbvpi_ghdl.exp +0 -0
  28. cocotb/libs/cocotbvpi_ghdl.lib +0 -0
  29. cocotb/libs/cocotbvpi_icarus.exp +0 -0
  30. cocotb/libs/cocotbvpi_icarus.lib +0 -0
  31. cocotb/libs/cocotbvpi_icarus.vpl +0 -0
  32. cocotb/libs/cocotbvpi_modelsim.dll +0 -0
  33. cocotb/libs/cocotbvpi_modelsim.exp +0 -0
  34. cocotb/libs/cocotbvpi_modelsim.lib +0 -0
  35. cocotb/libs/embed.dll +0 -0
  36. cocotb/libs/embed.exp +0 -0
  37. cocotb/libs/embed.lib +0 -0
  38. cocotb/libs/gpi.dll +0 -0
  39. cocotb/libs/gpi.exp +0 -0
  40. cocotb/libs/gpi.lib +0 -0
  41. cocotb/libs/gpilog.dll +0 -0
  42. cocotb/libs/gpilog.exp +0 -0
  43. cocotb/libs/gpilog.lib +0 -0
  44. cocotb/libs/pygpilog.dll +0 -0
  45. cocotb/libs/pygpilog.exp +0 -0
  46. cocotb/libs/pygpilog.lib +0 -0
  47. cocotb/regression.py +32 -14
  48. cocotb/runner.py +541 -454
  49. cocotb/scheduler.py +35 -18
  50. cocotb/share/def/aldec.exp +0 -0
  51. cocotb/share/def/aldec.lib +0 -0
  52. cocotb/share/def/ghdl.exp +0 -0
  53. cocotb/share/def/ghdl.lib +0 -0
  54. cocotb/share/def/icarus.exp +0 -0
  55. cocotb/share/def/icarus.lib +0 -0
  56. cocotb/share/def/modelsim.def +2 -0
  57. cocotb/share/def/modelsim.exp +0 -0
  58. cocotb/share/def/modelsim.lib +0 -0
  59. cocotb/share/include/embed.h +1 -2
  60. cocotb/share/include/gpi.h +10 -15
  61. cocotb/share/include/vpi_user_ext.h +3 -0
  62. cocotb/share/lib/verilator/verilator.cpp +8 -4
  63. cocotb/share/makefiles/Makefile.inc +16 -4
  64. cocotb/share/makefiles/Makefile.sim +2 -2
  65. cocotb/share/makefiles/simulators/Makefile.icarus +19 -0
  66. cocotb/share/makefiles/simulators/Makefile.ius +12 -1
  67. cocotb/share/makefiles/simulators/Makefile.questa +2 -1
  68. cocotb/share/makefiles/simulators/Makefile.riviera +4 -0
  69. cocotb/share/makefiles/simulators/Makefile.vcs +4 -0
  70. cocotb/share/makefiles/simulators/Makefile.verilator +5 -1
  71. cocotb/share/makefiles/simulators/Makefile.xcelium +5 -1
  72. cocotb/simulator.cp310-win32.exp +0 -0
  73. cocotb/simulator.cp310-win32.lib +0 -0
  74. cocotb/simulator.cp310-win32.pyd +0 -0
  75. cocotb/task.py +325 -0
  76. cocotb/triggers.py +23 -7
  77. cocotb/types/logic_array.py +34 -3
  78. {cocotb-1.7.1.dist-info → cocotb-1.8.0rc1.dist-info}/METADATA +166 -149
  79. cocotb-1.8.0rc1.dist-info/RECORD +120 -0
  80. {cocotb-1.7.1.dist-info → cocotb-1.8.0rc1.dist-info}/WHEEL +1 -1
  81. cocotb/_vendor/find_libpython/__init__.py +0 -358
  82. cocotb/_vendor/find_libpython/__main__.py +0 -5
  83. cocotb/_vendor/find_libpython/_version.py +0 -5
  84. cocotb-1.7.1.dist-info/RECORD +0 -122
  85. {cocotb-1.7.1.dist-info → cocotb-1.8.0rc1.dist-info}/LICENSE +0 -0
  86. {cocotb-1.7.1.dist-info → cocotb-1.8.0rc1.dist-info}/entry_points.txt +0 -0
  87. {cocotb-1.7.1.dist-info → cocotb-1.8.0rc1.dist-info}/top_level.txt +0 -0
cocotb/runner.py CHANGED
@@ -2,18 +2,29 @@
2
2
  # Licensed under the Revised BSD License, see LICENSE for details.
3
3
  # SPDX-License-Identifier: BSD-3-Clause
4
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
10
+ # TODO: support custom dependencies
11
+
5
12
  import abc
6
13
  import os
7
14
  import re
15
+ import shlex
8
16
  import shutil
9
17
  import subprocess
10
18
  import sys
11
19
  import tempfile
12
20
  import warnings
13
21
  from contextlib import suppress
14
- from typing import Dict, List, Mapping, Optional, Sequence, Type, Union
22
+ from pathlib import Path
23
+ from typing import Dict, List, Mapping, Optional, Sequence, Tuple, Type, Union
15
24
  from xml.etree import cElementTree as ET
16
25
 
26
+ import find_libpython
27
+
17
28
  import cocotb.config
18
29
 
19
30
  PathLike = Union["os.PathLike[str]", str]
@@ -40,220 +51,307 @@ def as_tcl_value(value: str) -> str:
40
51
  return value
41
52
 
42
53
 
54
+ def shlex_join(split_command):
55
+ """
56
+ Return a shell-escaped string from *split_command*
57
+ This is here more for compatibility purposes
58
+ """
59
+ return " ".join(shlex.quote(arg) for arg in split_command)
60
+
61
+
43
62
  class Simulator(abc.ABC):
63
+
64
+ supported_gpi_interfaces: Dict[str, List[str]] = {}
65
+
44
66
  def __init__(self) -> None:
45
67
 
46
- self.simulator_in_path()
68
+ self._simulator_in_path()
47
69
 
48
70
  self.env: Dict[str, str] = {}
49
71
 
50
72
  # for running test() independently of build()
51
- self.build_dir = "sim_build"
52
- self.parameters = {}
73
+ self.build_dir: Path = get_abs_path("sim_build")
74
+ self.parameters: Mapping[str, object] = {}
53
75
 
54
76
  @abc.abstractmethod
55
- def simulator_in_path(self) -> None:
56
- """Check that the simulator executable exists in :envvar:`PATH`."""
77
+ def _simulator_in_path(self) -> None:
78
+ """Raise exception if the simulator executable does not exist in :envvar:`PATH`.
79
+
80
+ Raises:
81
+ SystemExit: Simulator executable does not exist in :envvar:`PATH`.
82
+ """
57
83
 
58
84
  raise NotImplementedError()
59
85
 
60
- @abc.abstractmethod
61
- def check_toplevel_lang(self, toplevel_lang: Optional[str]) -> str:
62
- """Return *toplevel_lang* if supported by simulator, raise exception otherwise."""
86
+ def _check_hdl_toplevel_lang(self, hdl_toplevel_lang: Optional[str]) -> str:
87
+ """Return *hdl_toplevel_lang* if supported by simulator, raise exception otherwise.
63
88
 
64
- raise NotImplementedError()
89
+ Returns:
90
+ *hdl_toplevel_lang* if supported by the simulator.
91
+
92
+ Raises:
93
+ ValueError: *hdl_toplevel_lang* is not supported by the simulator.
94
+ """
95
+ if hdl_toplevel_lang is None:
96
+ if self.vhdl_sources and not self.verilog_sources:
97
+ lang = "vhdl"
98
+ elif self.verilog_sources and not self.vhdl_sources:
99
+ lang = "verilog"
100
+ else:
101
+ raise ValueError(
102
+ f"{type(self).__qualname__}: Must specify a hdl_toplevel_lang in a mixed-language design"
103
+ )
104
+ else:
105
+ lang = hdl_toplevel_lang
106
+
107
+ if lang in self.supported_gpi_interfaces.keys():
108
+ return lang
109
+ else:
110
+ raise ValueError(
111
+ f"{type(self).__qualname__}: hdl_toplevel_lang {hdl_toplevel_lang!r} is not "
112
+ f"in supported list: {', '.join(self.supported_gpi_interfaces.keys())}"
113
+ )
65
114
 
66
- def set_env(self) -> None:
115
+ def _set_env(self) -> None:
67
116
  """Set environment variables for sub-processes."""
68
117
 
69
118
  for e in os.environ:
70
119
  self.env[e] = os.environ[e]
71
120
 
72
121
  if "LIBPYTHON_LOC" not in self.env:
73
- self.env["LIBPYTHON_LOC"] = cocotb._vendor.find_libpython.find_libpython()
122
+ self.env["LIBPYTHON_LOC"] = find_libpython.find_libpython()
74
123
 
75
124
  self.env["PATH"] += os.pathsep + cocotb.config.libs_dir
76
-
77
125
  self.env["PYTHONPATH"] = os.pathsep.join(sys.path)
78
- for path in self.python_search:
79
- self.env["PYTHONPATH"] += os.pathsep + str(path)
80
-
81
126
  self.env["PYTHONHOME"] = sys.prefix
82
-
83
- self.env["TOPLEVEL"] = self.sim_toplevel
84
- self.env["MODULE"] = self.module
127
+ self.env["TOPLEVEL"] = self.sim_hdl_toplevel
128
+ self.env["MODULE"] = self.test_module
85
129
 
86
130
  @abc.abstractmethod
87
- def build_command(self) -> Sequence[Command]:
131
+ def _build_command(self) -> Sequence[Command]:
88
132
  """Return command to build the HDL sources."""
89
133
 
90
134
  raise NotImplementedError()
91
135
 
92
136
  @abc.abstractmethod
93
- def test_command(self) -> Sequence[Command]:
137
+ def _test_command(self) -> Sequence[Command]:
94
138
  """Return command to run a test."""
95
139
 
96
140
  raise NotImplementedError()
97
141
 
98
142
  def build(
99
143
  self,
100
- library_name: str = "work",
144
+ hdl_library: str = "top",
101
145
  verilog_sources: Sequence[PathLike] = [],
102
146
  vhdl_sources: Sequence[PathLike] = [],
103
147
  includes: Sequence[PathLike] = [],
104
- defines: Sequence[str] = [],
148
+ defines: Mapping[str, object] = {},
105
149
  parameters: Mapping[str, object] = {},
106
- extra_args: Sequence[str] = [],
107
- toplevel: Optional[str] = None,
150
+ build_args: Sequence[str] = [],
151
+ hdl_toplevel: Optional[str] = None,
108
152
  always: bool = False,
109
153
  build_dir: PathLike = "sim_build",
154
+ verbose: bool = False,
110
155
  ) -> None:
111
- """Build the HDL sources."""
112
-
113
- self.build_dir = os.path.abspath(build_dir)
156
+ """Build the HDL sources.
157
+
158
+ Args:
159
+ hdl_library: The library name to compile into.
160
+ verilog_sources: Verilog source files to build.
161
+ vhdl_sources: VHDL source files to build.
162
+ includes: Verilog include directories.
163
+ defines: Defines to set.
164
+ parameters: Verilog parameters or VHDL generics.
165
+ build_args: Extra build arguments for the simulator.
166
+ hdl_toplevel: The name of the HDL toplevel module.
167
+ always: Always run the build step.
168
+ build_dir: Directory to run the build step in.
169
+ verbose: Enable verbose messages.
170
+ """
171
+
172
+ self.build_dir = get_abs_path(build_dir)
114
173
  os.makedirs(self.build_dir, exist_ok=True)
115
174
 
116
175
  # note: to avoid mutating argument defaults, we ensure that no value
117
176
  # is written without a copy. This is much more concise and leads to
118
177
  # a better docstring than using `None` as a default in the parameters
119
178
  # list.
120
- self.library_name = library_name
121
- self.verilog_sources = get_abs_paths(verilog_sources)
122
- self.vhdl_sources = get_abs_paths(vhdl_sources)
123
- self.includes = get_abs_paths(includes)
124
- self.defines = list(defines)
179
+ self.hdl_library: str = hdl_library
180
+ self.verilog_sources: List[Path] = get_abs_paths(verilog_sources)
181
+ self.vhdl_sources: List[Path] = get_abs_paths(vhdl_sources)
182
+ self.includes: List[Path] = get_abs_paths(includes)
183
+ self.defines = dict(defines)
125
184
  self.parameters = dict(parameters)
126
- self.compile_args = list(extra_args)
127
- self.always = always
128
- self.hdl_toplevel = toplevel
185
+ self.build_args = list(build_args)
186
+ self.always: bool = always
187
+ self.hdl_toplevel: Optional[str] = hdl_toplevel
188
+ self.verbose: bool = verbose
129
189
 
130
190
  for e in os.environ:
131
191
  self.env[e] = os.environ[e]
132
192
 
133
- cmds = self.build_command()
134
- self.execute(cmds, cwd=self.build_dir)
193
+ cmds: Sequence[Command] = self._build_command()
194
+ self._execute(cmds, cwd=self.build_dir)
135
195
 
136
196
  def test(
137
197
  self,
138
- py_module: Union[str, Sequence[str]],
139
- toplevel: str,
140
- toplevel_lang: Optional[str] = None,
141
- testcase: Optional[str] = None,
198
+ test_module: Union[str, Sequence[str]],
199
+ hdl_toplevel: str,
200
+ hdl_toplevel_library: str = "top",
201
+ hdl_toplevel_lang: Optional[str] = None,
202
+ gpi_interfaces: Optional[List[str]] = None,
203
+ testcase: Optional[Union[str, Sequence[str]]] = None,
142
204
  seed: Optional[Union[str, int]] = None,
143
- python_search: Sequence[PathLike] = [],
144
- extra_args: Sequence[str] = [],
145
- plus_args: Sequence[str] = [],
205
+ test_args: Sequence[str] = [],
206
+ plusargs: Sequence[str] = [],
146
207
  extra_env: Mapping[str, str] = {},
147
208
  waves: Optional[bool] = None,
148
209
  gui: Optional[bool] = None,
149
210
  parameters: Mapping[str, object] = None,
150
211
  build_dir: Optional[PathLike] = None,
151
- sim_dir: Optional[PathLike] = None,
152
- ) -> PathLike:
153
- """Run a test."""
212
+ test_dir: Optional[PathLike] = None,
213
+ results_xml: str = "results.xml",
214
+ verbose: bool = False,
215
+ ) -> Path:
216
+ """Run the tests.
217
+
218
+ Args:
219
+ test_module: Name(s) of the Python module(s) containing the tests to run.
220
+ Can be a comma-separated list.
221
+ hdl_toplevel: Name of the HDL toplevel module.
222
+ hdl_toplevel_library: The library name for HDL toplevel module.
223
+ hdl_toplevel_lang: Language of the HDL toplevel module.
224
+ gpi_interfaces: List of GPI interfaces to use, with the first one being the entry point.
225
+ testcase: Name(s) of a specific testcase(s) to run.
226
+ If not set, run all testcases found in *test_module*.
227
+ Can be a comma-separated list.
228
+ seed: A specific random seed to use.
229
+ test_args: Extra arguments for the simulator.
230
+ plusargs: 'plusargs' to set for the simulator.
231
+ extra_env: Extra environment variables to set.
232
+ waves: Record signal traces.
233
+ gui: Run with simulator GUI.
234
+ parameters: Verilog parameters or VHDL generics.
235
+ build_dir: Directory the build step has been run in.
236
+ test_dir: Directory to run the tests in.
237
+ results_xml: Name of xUnit XML file to store test results in.
238
+ When running with pytest, the testcase name is prefixed to this name.
239
+ verbose: Enable verbose messages.
240
+
241
+ Returns:
242
+ The absolute location of the results XML file which can be
243
+ defined by the *results_xml* argument.
244
+ The default is :file:`{build_dir}/{pytest_test_name}.results.xml`
245
+ when run with ``pytest``,
246
+ :file:`{build_dir}/results.xml` otherwise.
247
+ """
154
248
 
155
249
  __tracebackhide__ = True # Hide the traceback when using pytest
156
250
 
157
251
  if build_dir is not None:
158
- self.build_dir = build_dir
252
+ self.build_dir = get_abs_path(build_dir)
159
253
 
160
254
  if parameters is not None:
161
255
  self.parameters = dict(parameters)
162
256
 
163
- if sim_dir is None:
164
- self.sim_dir = self.build_dir
257
+ if test_dir is None:
258
+ self.test_dir = self.build_dir
165
259
  else:
166
- self.sim_dir = os.path.abspath(sim_dir)
260
+ self.test_dir = get_abs_path(test_dir)
261
+ os.makedirs(self.test_dir, exist_ok=True)
167
262
 
168
- if isinstance(py_module, str):
169
- self.module = py_module
263
+ if isinstance(test_module, str):
264
+ self.test_module = test_module
170
265
  else:
171
- self.module = ",".join(py_module)
266
+ self.test_module = ",".join(test_module)
172
267
 
173
268
  # note: to avoid mutating argument defaults, we ensure that no value
174
269
  # is written without a copy. This is much more concise and leads to
175
270
  # a better docstring than using `None` as a default in the parameters
176
271
  # list.
177
- self.python_search = list(python_search)
178
- self.sim_toplevel = toplevel
179
- self.toplevel_lang = self.check_toplevel_lang(toplevel_lang)
180
- self.sim_args = list(extra_args)
181
- self.plus_args = list(plus_args)
272
+ self.sim_hdl_toplevel = hdl_toplevel
273
+ self.hdl_toplevel_library: str = hdl_toplevel_library
274
+ self.hdl_toplevel_lang = self._check_hdl_toplevel_lang(hdl_toplevel_lang)
275
+ if gpi_interfaces:
276
+ self.gpi_interfaces = gpi_interfaces
277
+ else:
278
+ self.gpi_interfaces = []
279
+ for gpi_if in self.supported_gpi_interfaces.values():
280
+ self.gpi_interfaces.append(gpi_if[0])
281
+
282
+ self.test_args = list(test_args)
283
+ self.plusargs = list(plusargs)
182
284
  self.env = dict(extra_env)
183
285
 
184
286
  if testcase is not None:
185
- self.env["TESTCASE"] = testcase
287
+ if isinstance(testcase, str):
288
+ self.env["TESTCASE"] = testcase
289
+ else:
290
+ self.env["TESTCASE"] = ",".join(testcase)
186
291
 
187
292
  if seed is not None:
188
293
  self.env["RANDOM_SEED"] = str(seed)
189
294
 
190
- if waves is None:
191
- self.waves = bool(int(os.getenv("COCOTB_WAVES", 0)))
192
- else:
193
- self.waves = bool(waves)
295
+ self.waves = bool(waves)
296
+ self.gui = bool(gui)
194
297
 
195
- if gui is None:
196
- self.gui = bool(int(os.getenv("COCOTB_GUI", 0)))
197
- else:
198
- self.gui = bool(gui)
298
+ if verbose is not None:
299
+ self.verbose = verbose
199
300
 
200
301
  # When using pytest, use test name as result file name
201
302
  pytest_current_test = os.getenv("PYTEST_CURRENT_TEST", "")
202
303
  if pytest_current_test:
203
304
  self.current_test_name = pytest_current_test.split(":")[-1].split(" ")[0]
204
- results_xml_name = f"{self.current_test_name}.results.xml"
305
+ results_xml_name = f"{self.current_test_name}.{results_xml}"
205
306
  else:
206
307
  self.current_test_name = "test"
207
- results_xml_name = "results.xml"
208
-
209
- results_xml_file = os.getenv(
210
- "COCOTB_RESULTS_FILE", os.path.join(self.build_dir, results_xml_name)
211
- )
308
+ results_xml_name = results_xml
212
309
 
213
- self.env["COCOTB_RESULTS_FILE"] = results_xml_file
310
+ results_xml_file = Path(self.test_dir) / results_xml_name
214
311
 
215
312
  with suppress(OSError):
216
313
  os.remove(results_xml_file)
217
314
 
218
- cmds = self.test_command()
219
- self.set_env()
220
- self.execute(cmds, cwd=self.sim_dir)
315
+ # transport the settings to cocotb via environment variables
316
+ self._set_env()
317
+ self.env["COCOTB_RESULTS_FILE"] = str(results_xml_file)
221
318
 
222
- check_results_file(results_xml_file)
319
+ cmds: Sequence[Command] = self._test_command()
320
+ self._execute(cmds, cwd=self.test_dir)
321
+
322
+ # Only when running under pytest, check the results file here,
323
+ # potentially raising an exception with failing testcases,
324
+ # otherwise return the results file for later analysis.
325
+ if pytest_current_test:
326
+ check_results_file(results_xml_file)
223
327
 
224
328
  print(f"INFO: Results file: {results_xml_file}")
225
329
  return results_xml_file
226
330
 
227
- @abc.abstractmethod
228
- def get_include_options(self, includes: Sequence[str]) -> List[str]:
229
- """Return simulator options setting directories searched for Verilog include files."""
331
+ @staticmethod
332
+ def _get_include_options(self, includes: Sequence[PathLike]) -> Command:
333
+ """Return simulator-specific formatted option strings with *includes* directories."""
230
334
 
231
335
  raise NotImplementedError()
232
336
 
233
- @abc.abstractmethod
234
- def get_define_options(self, defines: Sequence[str]) -> List[str]:
235
- """Return simulator options defining macros."""
337
+ @staticmethod
338
+ def _get_define_options(self, defines: Mapping[str, object]) -> Command:
339
+ """Return simulator-specific formatted option strings with *defines* macros."""
236
340
 
237
341
  raise NotImplementedError()
238
342
 
239
343
  @abc.abstractmethod
240
- def get_parameter_options(self, parameters: Mapping[str, object]) -> List[str]:
241
- """Return simulator options for module parameters/generics."""
344
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> Command:
345
+ """Return simulator-specific formatted option strings with *parameters*/generics."""
242
346
 
243
347
  raise NotImplementedError()
244
348
 
245
- def execute(self, cmds: Sequence[Command], cwd: PathLike) -> None:
349
+ def _execute(self, cmds: Sequence[Command], cwd: PathLike) -> None:
246
350
 
247
351
  __tracebackhide__ = True # Hide the traceback when using PyTest.
248
352
 
249
353
  for cmd in cmds:
250
- print(
251
- "INFO: Running command: "
252
- + ' "'.join(cmd)
253
- + '" in directory:"'
254
- + str(cwd)
255
- + '"'
256
- )
354
+ print(f"INFO: Running command {shlex_join(cmd)} in directory {cwd}")
257
355
 
258
356
  # TODO: create a thread to handle stderr and log as error?
259
357
  # TODO: log forwarding
@@ -266,86 +364,105 @@ class Simulator(abc.ABC):
266
364
  )
267
365
 
268
366
 
269
- def check_results_file(results_xml_file: PathLike) -> None:
270
- """Check whether cocotb result file exists and contains failed tests."""
367
+ def get_results(results_xml_file: Path) -> Tuple[int, int]:
368
+ """Return number of tests and fails in *results_xml_file*.
369
+
370
+ Returns:
371
+ Tuple of number of tests and number of fails.
372
+
373
+ Raises:
374
+ SystemExit: *results_xml_file* is non-existent.
375
+ """
271
376
 
272
377
  __tracebackhide__ = True # Hide the traceback when using PyTest.
273
378
 
274
- results_file_exist = os.path.isfile(results_xml_file)
275
- if not results_file_exist:
379
+ if not results_xml_file.is_file():
276
380
  raise SystemExit(
277
- "ERROR: Simulation terminated abnormally. Results file not found."
381
+ f"ERROR: Simulation terminated abnormally. Results file {results_xml_file} not found."
278
382
  )
279
383
 
280
- failed = 0
384
+ num_tests = 0
385
+ num_failed = 0
281
386
 
282
387
  tree = ET.parse(results_xml_file)
283
388
  for ts in tree.iter("testsuite"):
284
389
  for tc in ts.iter("testcase"):
390
+ num_tests += 1
285
391
  for _ in tc.iter("failure"):
286
- failed += 1
392
+ num_failed += 1
393
+
394
+ return (num_tests, num_failed)
395
+
396
+
397
+ def check_results_file(results_xml_file: Path) -> None:
398
+ """Raise exception if *results_xml_file* does not exist or contains failed tests.
399
+
400
+ Raises:
401
+ SystemExit: *results_xml_file* is non-existent or contains fails.
402
+ """
403
+
404
+ __tracebackhide__ = True # Hide the traceback when using PyTest.
405
+
406
+ (num_tests, num_failed) = get_results(results_xml_file)
287
407
 
288
- if failed:
289
- raise SystemExit(f"ERROR: Failed {failed} tests.")
408
+ if num_failed:
409
+ raise SystemExit(f"ERROR: Failed {num_failed} of {num_tests} tests.")
290
410
 
291
411
 
292
- def outdated(output: PathLike, dependencies: Sequence[PathLike]) -> bool:
293
- """Check if source files are newer than output."""
412
+ def outdated(output: Path, dependencies: Sequence[Path]) -> bool:
413
+ """Return ``True`` if any source files in *dependencies* are newer than the *output* directory.
294
414
 
295
- if not os.path.isfile(output):
415
+ Returns:
416
+ ``True`` if any source files are newer, ``False`` otherwise.
417
+ """
418
+
419
+ if not output.is_file():
296
420
  return True
297
421
 
298
- output_mtime = os.path.getmtime(output)
422
+ output_mtime = output.stat().st_mtime
299
423
 
300
424
  dep_mtime = 0.0
301
- for file in dependencies:
302
- mtime = os.path.getmtime(file)
425
+ for dependency in dependencies:
426
+ mtime = dependency.stat().st_mtime
303
427
  if mtime > dep_mtime:
304
428
  dep_mtime = mtime
305
429
 
306
- if dep_mtime > output_mtime:
307
- return True
430
+ return dep_mtime > output_mtime
308
431
 
309
- return False
310
432
 
433
+ def get_abs_path(path: PathLike) -> Path:
434
+ """Return *path* in absolute form."""
311
435
 
312
- def get_abs_paths(paths: Sequence[PathLike]) -> List[str]:
313
- """Return list of *paths* in absolute form."""
436
+ path = Path(path)
437
+ if path.is_absolute():
438
+ return path.resolve()
439
+ else:
440
+ return Path(Path.cwd() / path).resolve()
314
441
 
315
- paths_abs: List[str] = []
316
- for path in paths:
317
- if os.path.isabs(path):
318
- paths_abs.append(os.path.abspath(path))
319
- else:
320
- paths_abs.append(os.path.abspath(os.path.join(os.getcwd(), path)))
321
442
 
322
- return paths_abs
443
+ def get_abs_paths(paths: Sequence[PathLike]) -> List[Path]:
444
+ """Return list of *paths* in absolute form."""
445
+
446
+ return [get_abs_path(path) for path in paths]
323
447
 
324
448
 
325
449
  class Icarus(Simulator):
326
- @staticmethod
327
- def simulator_in_path() -> None:
328
- if shutil.which("iverilog") is None:
329
- raise SystemExit("ERROR: iverilog exacutable not found!")
450
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
330
451
 
331
452
  @staticmethod
332
- def check_toplevel_lang(toplevel_lang: Optional[str]) -> str:
333
- if toplevel_lang is None or toplevel_lang == "verilog":
334
- return "verilog"
335
- else:
336
- raise ValueError(
337
- f"iverilog does not support {toplevel_lang!r} as a toplevel_lang"
338
- )
453
+ def _simulator_in_path() -> None:
454
+ if shutil.which("iverilog") is None:
455
+ raise SystemExit("ERROR: iverilog executable not found!")
339
456
 
340
457
  @staticmethod
341
- def get_include_options(includes: Sequence[str]) -> List[str]:
342
- return ["-I" + dir for dir in includes]
458
+ def _get_include_options(includes: Sequence[PathLike]) -> Command:
459
+ return [f"-I{include}" for include in includes]
343
460
 
344
461
  @staticmethod
345
- def get_define_options(defines: Sequence[str]) -> List[str]:
346
- return ["-D" + define for define in defines]
462
+ def _get_define_options(defines: Mapping[str, object]) -> Command:
463
+ return [f"-D{name}={value}" for name, value in defines.items()]
347
464
 
348
- def get_parameter_options(self, parameters: Mapping[str, object]) -> List[str]:
465
+ def _get_parameter_options(self, parameters: Mapping[str, object]) -> Command:
349
466
  assert self.hdl_toplevel is not None
350
467
  return [
351
468
  f"-P{self.hdl_toplevel}.{name}={value}"
@@ -353,10 +470,10 @@ class Icarus(Simulator):
353
470
  ]
354
471
 
355
472
  @property
356
- def sim_file(self) -> PathLike:
357
- return os.path.join(self.build_dir, "sim.vvp")
473
+ def sim_file(self) -> Path:
474
+ return self.build_dir / "sim.vvp"
358
475
 
359
- def test_command(self) -> List[Command]:
476
+ def _test_command(self) -> List[Command]:
360
477
 
361
478
  return [
362
479
  [
@@ -366,99 +483,91 @@ class Icarus(Simulator):
366
483
  "-m",
367
484
  cocotb.config.lib_name("vpi", "icarus"),
368
485
  ]
369
- + self.sim_args
370
- + [self.sim_file]
371
- + self.plus_args
486
+ + self.test_args
487
+ + [str(self.sim_file)]
488
+ + self.plusargs
372
489
  ]
373
490
 
374
- def build_command(self) -> List[Command]:
491
+ def _build_command(self) -> List[Command]:
375
492
 
376
493
  if self.vhdl_sources:
377
- raise ValueError("This simulator does not support VHDL")
494
+ raise ValueError(
495
+ f"{type(self).__qualname__}: Simulator does not support VHDL"
496
+ )
378
497
 
379
- cmd = []
498
+ cmds = []
380
499
  if outdated(self.sim_file, self.verilog_sources) or self.always:
381
500
 
382
- cmd = [
383
- ["iverilog", "-o", self.sim_file, "-D", "COCOTB_SIM=1", "-g2012"]
384
- + self.get_define_options(self.defines)
385
- + self.get_include_options(self.includes)
386
- + self.get_parameter_options(self.parameters)
387
- + self.compile_args
388
- + self.verilog_sources
501
+ cmds = [
502
+ ["iverilog", "-o", str(self.sim_file), "-D", "COCOTB_SIM=1", "-g2012"]
503
+ + self._get_define_options(self.defines)
504
+ + self._get_include_options(self.includes)
505
+ + self._get_parameter_options(self.parameters)
506
+ + self.build_args
507
+ + [str(source_file) for source_file in self.verilog_sources]
389
508
  ]
390
509
 
391
510
  else:
392
- print("WARNING: Skipping compilation:" + self.sim_file)
511
+ print("WARNING: Skipping compilation of", self.sim_file)
393
512
 
394
- return cmd
513
+ return cmds
395
514
 
396
515
 
397
516
  class Questa(Simulator):
517
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["fli", "vhpi"]}
518
+
398
519
  @staticmethod
399
- def simulator_in_path() -> None:
520
+ def _simulator_in_path() -> None:
400
521
  if shutil.which("vsim") is None:
401
522
  raise SystemExit("ERROR: vsim executable not found!")
402
523
 
403
- def check_toplevel_lang(self, toplevel_lang: Optional[str]) -> str:
404
- if toplevel_lang is None:
405
- if self.vhdl_sources and not self.verilog_sources:
406
- return "vhdl"
407
- elif self.verilog_sources and not self.vhdl_sources:
408
- return "verilog"
409
- else:
410
- raise ValueError("Must specify a toplevel_lang in a mixed design")
411
- elif toplevel_lang in ("verilog", "vhdl"):
412
- return toplevel_lang
413
- else:
414
- raise ValueError(
415
- f"Questa does not support {toplevel_lang!r} as a toplevel_lang"
416
- )
417
-
418
524
  @staticmethod
419
- def get_include_options(includes: Sequence[str]) -> List[str]:
420
- return ["+incdir+" + as_tcl_value(dir) for dir in includes]
525
+ def _get_include_options(includes: Sequence[PathLike]) -> Command:
526
+ return [f"+incdir+{as_tcl_value(str(include))}" for include in includes]
421
527
 
422
528
  @staticmethod
423
- def get_define_options(defines: Sequence[str]) -> List[str]:
424
- return ["+define+" + as_tcl_value(define) for define in defines]
529
+ def _get_define_options(defines: Mapping[str, object]) -> Command:
530
+ return [
531
+ f"+define+{as_tcl_value(name)}={as_tcl_value(str(value))}"
532
+ for name, value in defines.items()
533
+ ]
425
534
 
426
535
  @staticmethod
427
- def get_parameter_options(parameters: Mapping[str, object]) -> List[str]:
428
- return ["-g" + name + "=" + str(value) for name, value in parameters.items()]
536
+ def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
537
+ return [f"-g{name}={value}" for name, value in parameters.items()]
429
538
 
430
- def build_command(self) -> List[Command]:
539
+ def _build_command(self) -> List[Command]:
431
540
 
432
- cmd = []
541
+ cmds = []
433
542
 
434
543
  if self.vhdl_sources:
435
- cmd.append(["vlib", as_tcl_value(self.library_name)])
436
- cmd.append(
437
- ["vcom", "-mixedsvvh"]
438
- + ["-work", as_tcl_value(self.library_name)]
439
- + [as_tcl_value(v) for v in self.compile_args]
440
- + [as_tcl_value(v) for v in self.vhdl_sources]
544
+ cmds.append(["vlib", as_tcl_value(self.hdl_library)])
545
+ cmds.append(
546
+ ["vcom"]
547
+ + ["-work", as_tcl_value(self.hdl_library)]
548
+ + [as_tcl_value(v) for v in self.build_args]
549
+ + [as_tcl_value(str(v)) for v in self.vhdl_sources]
441
550
  )
442
551
 
443
552
  if self.verilog_sources:
444
- cmd.append(["vlib", as_tcl_value(self.library_name)])
445
- cmd.append(
446
- ["vlog", "-mixedsvvh"]
553
+ cmds.append(["vlib", as_tcl_value(self.hdl_library)])
554
+ cmds.append(
555
+ ["vlog"]
447
556
  + ([] if self.always else ["-incr"])
448
- + ["-work", as_tcl_value(self.library_name)]
557
+ + ["-work", as_tcl_value(self.hdl_library)]
449
558
  + ["+define+COCOTB_SIM"]
450
559
  + ["-sv"]
451
- + self.get_define_options(self.defines)
452
- + self.get_include_options(self.includes)
453
- + [as_tcl_value(v) for v in self.compile_args]
454
- + [as_tcl_value(v) for v in self.verilog_sources]
560
+ + self._get_define_options(self.defines)
561
+ + self._get_include_options(self.includes)
562
+ + [as_tcl_value(v) for v in self.build_args]
563
+ + [as_tcl_value(str(v)) for v in self.verilog_sources]
455
564
  )
456
565
 
457
- return cmd
566
+ return cmds
458
567
 
459
- def test_command(self) -> List[Command]:
568
+ def _test_command(self) -> List[Command]:
460
569
 
461
- cmd = []
570
+ cmds = []
462
571
 
463
572
  do_script = ""
464
573
  if self.waves:
@@ -467,200 +576,169 @@ class Questa(Simulator):
467
576
  if not self.gui:
468
577
  do_script += "run -all; quit"
469
578
 
470
- fli_lib_path = cocotb.config.lib_name_path("fli", "questa")
471
-
472
- if self.toplevel_lang == "vhdl":
473
-
474
- if not os.path.isfile(fli_lib_path):
475
- raise SystemExit(
476
- "ERROR: cocotb was not installed with an FLI library, as the mti.h header could not be located.\n\
477
- If you installed an FLI-capable simulator after cocotb, you will need to reinstall cocotb.\n\
478
- Please check the cocotb documentation on ModelSim support."
479
- )
579
+ gpi_if_entry = self.gpi_interfaces[0]
580
+ gpi_if_entry_lib_path = cocotb.config.lib_name_path(gpi_if_entry, "questa")
480
581
 
481
- cmd.append(
482
- ["vsim"]
483
- + ["-gui" if self.gui else "-c"]
484
- + ["-onfinish", "stop" if self.gui else "exit"]
485
- + [
486
- "-foreign",
487
- "cocotb_init "
488
- + as_tcl_value(cocotb.config.lib_name_path("fli", "questa")),
489
- ]
490
- + [as_tcl_value(v) for v in self.sim_args]
491
- + [as_tcl_value(v) for v in self.get_parameter_options(self.parameters)]
492
- + [as_tcl_value(self.sim_toplevel)]
493
- + ["-do", do_script]
494
- )
582
+ if gpi_if_entry == "fli":
583
+ lib_opts = [
584
+ "-foreign",
585
+ "cocotb_init "
586
+ + as_tcl_value(cocotb.config.lib_name_path("fli", "questa")),
587
+ ]
588
+ elif gpi_if_entry == "vhpi":
589
+ lib_opts = ["-voptargs=-access=rw+/."]
590
+ lib_opts += [
591
+ "-foreign",
592
+ "vhpi_startup_routines_bootstrap "
593
+ + as_tcl_value(cocotb.config.lib_name_path("vhpi", "questa")),
594
+ ]
595
+ else:
596
+ lib_opts = [
597
+ "-pli",
598
+ as_tcl_value(cocotb.config.lib_name_path("vpi", "questa")),
599
+ ]
495
600
 
496
- self.env["GPI_EXTRA"] = (
497
- cocotb.config.lib_name_path("vpi", "questa") + ":cocotbvpi_entry_point"
601
+ if not Path(gpi_if_entry_lib_path).is_file():
602
+ raise SystemExit(
603
+ "ERROR: cocotb was not installed with a {gpi_if_entry} library."
498
604
  )
499
605
 
500
- else:
501
- cmd.append(
502
- ["vsim"]
503
- + ["-gui" if self.gui else "-c"]
504
- + ["-onfinish", "stop" if self.gui else "exit"]
505
- + ["-pli", as_tcl_value(cocotb.config.lib_name_path("vpi", "questa"))]
506
- + [as_tcl_value(v) for v in self.sim_args]
507
- + [as_tcl_value(v) for v in self.get_parameter_options(self.parameters)]
508
- + [as_tcl_value(self.sim_toplevel)]
509
- + [as_tcl_value(v) for v in self.plus_args]
510
- + ["-do", do_script]
511
- )
606
+ cmds.append(
607
+ ["vsim"]
608
+ + ["-gui" if self.gui else "-c"]
609
+ + ["-onfinish", "stop" if self.gui else "exit"]
610
+ + lib_opts
611
+ + [as_tcl_value(v) for v in self.test_args]
612
+ + [as_tcl_value(v) for v in self._get_parameter_options(self.parameters)]
613
+ + [as_tcl_value(f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}")]
614
+ + [as_tcl_value(v) for v in self.plusargs]
615
+ + ["-do", do_script]
616
+ )
512
617
 
513
- if os.path.isfile(fli_lib_path):
514
- self.env["GPI_EXTRA"] = (
515
- cocotb.config.lib_name_path("fli", "questa")
516
- + ":cocotbfli_entry_point"
618
+ gpi_extra_list = []
619
+ for gpi_if in self.gpi_interfaces[1:]:
620
+ gpi_if_lib_path = cocotb.config.lib_name_path(gpi_if, "questa")
621
+ if Path(gpi_if_lib_path).is_file():
622
+ gpi_extra_list.append(
623
+ cocotb.config.lib_name_path(gpi_if, "questa")
624
+ + f":cocotb{gpi_if}_entry_point"
517
625
  )
518
626
  else:
519
- print(
520
- "WARNING: FLI library not found. Mixed-mode simulation will not be available."
521
- )
627
+ print("WARNING: {gpi_if_lib_path} library not found.")
628
+ self.env["GPI_EXTRA"] = ",".join(gpi_extra_list)
522
629
 
523
- return cmd
630
+ return cmds
524
631
 
525
632
 
526
633
  class Ghdl(Simulator):
634
+ supported_gpi_interfaces = {"vhdl": ["vpi"]}
635
+
527
636
  @staticmethod
528
- def simulator_in_path() -> None:
637
+ def _simulator_in_path() -> None:
529
638
  if shutil.which("ghdl") is None:
530
639
  raise SystemExit("ERROR: ghdl executable not found!")
531
640
 
532
- def check_toplevel_lang(self, toplevel_lang: Optional[str]) -> str:
533
- if toplevel_lang is None or toplevel_lang == "vhdl":
534
- return "vhdl"
535
- else:
536
- raise ValueError(
537
- f"GHDL does not support {toplevel_lang!r} as a toplevel_lang"
538
- )
539
-
540
- @staticmethod
541
- def get_include_options(includes: Sequence[str]) -> List[str]:
542
- return [f"-I{dir}" for dir in includes]
543
-
544
- @staticmethod
545
- def get_define_options(defines: Sequence[str]) -> List[str]:
546
- return [f"-D{define}" for define in defines]
547
-
548
641
  @staticmethod
549
- def get_parameter_options(parameters: Mapping[str, object]) -> List[str]:
550
- return ["-g" + name + "=" + str(value) for name, value in parameters.items()]
642
+ def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
643
+ return [f"-g{name}={value}" for name, value in parameters.items()]
551
644
 
552
- def build_command(self) -> List[Command]:
645
+ def _build_command(self) -> List[Command]:
553
646
 
554
647
  if self.verilog_sources:
555
- raise ValueError("This simulator does not support Verilog")
556
-
557
- if self.hdl_toplevel is None:
558
648
  raise ValueError(
559
- "This simulator requires the hdl_toplevel parameter to be specified"
649
+ f"{type(self).__qualname__}: Simulator does not support Verilog"
560
650
  )
561
651
 
562
- cmd = [
652
+ cmds = [
563
653
  ["ghdl", "-i"]
564
- + [f"--work={self.library_name}"]
565
- + self.compile_args
566
- + [source_file]
567
- for source_file in self.vhdl_sources
654
+ + [f"--work={self.hdl_library}"]
655
+ + self.build_args
656
+ + [str(source_file) for source_file in self.vhdl_sources]
568
657
  ]
569
658
 
570
- cmd += [
571
- ["ghdl", "-m"]
572
- + [f"--work={self.library_name}"]
573
- + self.compile_args
574
- + [self.hdl_toplevel]
575
- ]
659
+ if self.hdl_toplevel is not None:
660
+ cmds += [
661
+ ["ghdl", "-m"]
662
+ + [f"--work={self.hdl_library}"]
663
+ + self.build_args
664
+ + [self.hdl_toplevel]
665
+ ]
576
666
 
577
- return cmd
667
+ return cmds
578
668
 
579
- def test_command(self) -> List[Command]:
669
+ def _test_command(self) -> List[Command]:
580
670
 
581
- cmd = [
671
+ cmds = [
582
672
  ["ghdl", "-r"]
583
- + [self.sim_toplevel]
673
+ + [f"--work={self.hdl_toplevel_library}"]
674
+ + self.test_args
675
+ + [self.sim_hdl_toplevel]
584
676
  + ["--vpi=" + cocotb.config.lib_name_path("vpi", "ghdl")]
585
- + self.sim_args
586
- + self.get_parameter_options(self.parameters)
677
+ + self.plusargs
678
+ + self._get_parameter_options(self.parameters)
587
679
  ]
588
680
 
589
- return cmd
681
+ return cmds
590
682
 
591
683
 
592
684
  class Riviera(Simulator):
685
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
686
+
593
687
  @staticmethod
594
- def simulator_in_path() -> None:
688
+ def _simulator_in_path() -> None:
595
689
  if shutil.which("vsimsa") is None:
596
690
  raise SystemExit("ERROR: vsimsa executable not found!")
597
691
 
598
- def check_toplevel_lang(self, toplevel_lang: Optional[str]) -> str:
599
- if toplevel_lang is None:
600
- if self.vhdl_sources and not self.verilog_sources:
601
- return "vhdl"
602
- elif self.verilog_sources and not self.vhdl_sources:
603
- return "verilog"
604
- else:
605
- raise ValueError(
606
- "Must specify a toplevel_lang in a mixed-language design"
607
- )
608
- elif toplevel_lang in ("verilog", "vhdl"):
609
- return toplevel_lang
610
- else:
611
- raise ValueError(
612
- f"Riviera does not support {toplevel_lang!r} as a toplevel_lang"
613
- )
614
-
615
692
  @staticmethod
616
- def get_include_options(includes: Sequence[str]) -> List[str]:
617
- return ["+incdir+" + as_tcl_value(dir) for dir in includes]
693
+ def _get_include_options(includes: Sequence[PathLike]) -> Command:
694
+ return [f"+incdir+{as_tcl_value(str(include))}" for include in includes]
618
695
 
619
696
  @staticmethod
620
- def get_define_options(defines: Sequence[str]) -> List[str]:
621
- return ["+define+" + as_tcl_value(define) for define in defines]
697
+ def _get_define_options(defines: Mapping[str, object]) -> Command:
698
+ return [
699
+ f"+define+{as_tcl_value(name)}={as_tcl_value(str(value))}"
700
+ for name, value in defines.items()
701
+ ]
622
702
 
623
703
  @staticmethod
624
- def get_parameter_options(parameters: Mapping[str, object]) -> List[str]:
625
- return ["-g" + name + "=" + str(value) for name, value in parameters.items()]
704
+ def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
705
+ return [f"-g{name}={value}" for name, value in parameters.items()]
626
706
 
627
- def build_command(self) -> List[Command]:
707
+ def _build_command(self) -> List[Command]:
628
708
 
629
709
  do_script = "\nonerror {\n quit -code 1 \n} \n"
630
710
 
631
- out_file = os.path.join(
632
- self.build_dir, self.library_name, self.library_name + ".lib"
633
- )
711
+ out_file = self.build_dir / self.hdl_library / f"{self.hdl_library}.lib"
634
712
 
635
713
  if outdated(out_file, self.verilog_sources + self.vhdl_sources) or self.always:
636
714
 
637
715
  do_script += "alib {RTL_LIBRARY} \n".format(
638
- RTL_LIBRARY=as_tcl_value(self.library_name)
716
+ RTL_LIBRARY=as_tcl_value(self.hdl_library)
639
717
  )
640
718
 
641
719
  if self.vhdl_sources:
642
720
  do_script += (
643
721
  "acom -work {RTL_LIBRARY} {EXTRA_ARGS} {VHDL_SOURCES}\n".format(
644
- RTL_LIBRARY=as_tcl_value(self.library_name),
722
+ RTL_LIBRARY=as_tcl_value(self.hdl_library),
645
723
  VHDL_SOURCES=" ".join(
646
- as_tcl_value(v) for v in self.vhdl_sources
724
+ as_tcl_value(str(v)) for v in self.vhdl_sources
647
725
  ),
648
- EXTRA_ARGS=" ".join(as_tcl_value(v) for v in self.compile_args),
726
+ EXTRA_ARGS=" ".join(as_tcl_value(v) for v in self.build_args),
649
727
  )
650
728
  )
651
729
 
652
730
  if self.verilog_sources:
653
731
  do_script += "alog -work {RTL_LIBRARY} +define+COCOTB_SIM -sv {DEFINES} {INCDIR} {EXTRA_ARGS} {VERILOG_SOURCES} \n".format(
654
- RTL_LIBRARY=as_tcl_value(self.library_name),
732
+ RTL_LIBRARY=as_tcl_value(self.hdl_library),
655
733
  VERILOG_SOURCES=" ".join(
656
- as_tcl_value(v) for v in self.verilog_sources
734
+ as_tcl_value(str(v)) for v in self.verilog_sources
657
735
  ),
658
- DEFINES=" ".join(self.get_define_options(self.defines)),
659
- INCDIR=" ".join(self.get_include_options(self.includes)),
660
- EXTRA_ARGS=" ".join(as_tcl_value(v) for v in self.compile_args),
736
+ DEFINES=" ".join(self._get_define_options(self.defines)),
737
+ INCDIR=" ".join(self._get_include_options(self.includes)),
738
+ EXTRA_ARGS=" ".join(as_tcl_value(v) for v in self.build_args),
661
739
  )
662
740
  else:
663
- print("WARNING: Skipping compilation:" + out_file)
741
+ print("WARNING: Skipping compilation of", out_file)
664
742
 
665
743
  do_file = tempfile.NamedTemporaryFile(delete=False)
666
744
  do_file.write(do_script.encode())
@@ -668,36 +746,41 @@ class Riviera(Simulator):
668
746
 
669
747
  return [["vsimsa"] + ["-do"] + ["do"] + [do_file.name]]
670
748
 
671
- def test_command(self) -> List[Command]:
749
+ def _test_command(self) -> List[Command]:
672
750
 
673
751
  do_script = "\nonerror {\n quit -code 1 \n} \n"
674
752
 
675
- if self.toplevel_lang == "vhdl":
676
- do_script += "asim +access +w -interceptcoutput -O2 -loadvhpi {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} \n".format(
677
- TOPLEVEL=as_tcl_value(self.sim_toplevel),
753
+ if self.hdl_toplevel_lang == "vhdl":
754
+ do_script += "asim +access +w -interceptcoutput -O2 -loadvhpi {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS}\n".format(
755
+ TOPLEVEL=as_tcl_value(
756
+ f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
757
+ ),
678
758
  EXT_NAME=as_tcl_value(cocotb.config.lib_name_path("vhpi", "riviera")),
679
759
  EXTRA_ARGS=" ".join(
680
760
  as_tcl_value(v)
681
761
  for v in (
682
- self.sim_args + self.get_parameter_options(self.parameters)
762
+ self.test_args + self._get_parameter_options(self.parameters)
683
763
  )
684
764
  ),
765
+ PLUSARGS=" ".join(as_tcl_value(v) for v in self.plusargs),
685
766
  )
686
767
 
687
768
  self.env["GPI_EXTRA"] = (
688
769
  cocotb.config.lib_name_path("vpi", "riviera") + ":cocotbvpi_entry_point"
689
770
  )
690
771
  else:
691
- do_script += "asim +access +w -interceptcoutput -O2 -pli {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUS_ARGS} \n".format(
692
- TOPLEVEL=as_tcl_value(self.sim_toplevel),
772
+ do_script += "asim +access +w -interceptcoutput -O2 -pli {EXT_NAME} {EXTRA_ARGS} {TOPLEVEL} {PLUSARGS} \n".format(
773
+ TOPLEVEL=as_tcl_value(
774
+ f"{self.hdl_toplevel_library}.{self.sim_hdl_toplevel}"
775
+ ),
693
776
  EXT_NAME=as_tcl_value(cocotb.config.lib_name_path("vpi", "riviera")),
694
777
  EXTRA_ARGS=" ".join(
695
778
  as_tcl_value(v)
696
779
  for v in (
697
- self.sim_args + self.get_parameter_options(self.parameters)
780
+ self.test_args + self._get_parameter_options(self.parameters)
698
781
  )
699
782
  ),
700
- PLUS_ARGS=" ".join(as_tcl_value(v) for v in self.plus_args),
783
+ PLUSARGS=" ".join(as_tcl_value(v) for v in self.plusargs),
701
784
  )
702
785
 
703
786
  self.env["GPI_EXTRA"] = (
@@ -718,61 +801,58 @@ class Riviera(Simulator):
718
801
 
719
802
 
720
803
  class Verilator(Simulator):
721
- def simulator_in_path(self) -> None:
804
+ supported_gpi_interfaces = {"verilog": ["vpi"]}
805
+
806
+ def _simulator_in_path(self) -> None:
722
807
  executable = shutil.which("verilator")
723
808
  if executable is None:
724
809
  raise SystemExit("ERROR: verilator executable not found!")
725
- self.executable = executable
726
-
727
- @staticmethod
728
- def check_toplevel_lang(toplevel_lang: Optional[str]) -> str:
729
- if toplevel_lang is None or toplevel_lang == "verilog":
730
- return "verilog"
731
- else:
732
- raise ValueError(
733
- f"Verilator does not support {toplevel_lang!r} as a toplevel_lang"
734
- )
810
+ self.executable: str = executable
735
811
 
736
812
  @staticmethod
737
- def get_include_options(includes: Sequence[str]) -> List[str]:
738
- return ["-I" + dir for dir in includes]
813
+ def _get_include_options(includes: Sequence[PathLike]) -> Command:
814
+ return [f"-I{include}" for include in includes]
739
815
 
740
816
  @staticmethod
741
- def get_define_options(defines: Sequence[str]) -> List[str]:
742
- return ["-D" + define for define in defines]
817
+ def _get_define_options(defines: Mapping[str, object]) -> Command:
818
+ return [f"-D{name}={value}" for name, value in defines.items()]
743
819
 
744
820
  @staticmethod
745
- def get_parameter_options(parameters: Mapping[str, object]) -> List[str]:
746
- return ["-G" + name + "=" + str(value) for name, value in parameters.items()]
821
+ def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
822
+ return [f"-G{name}={value}" for name, value in parameters.items()]
747
823
 
748
- def build_command(self) -> List[Command]:
824
+ def _build_command(self) -> List[Command]:
749
825
 
750
826
  if self.vhdl_sources:
751
- raise ValueError("This simulator does not support VHDL")
827
+ raise ValueError(
828
+ f"{type(self).__qualname__}: Simulator does not support VHDL"
829
+ )
752
830
 
753
831
  if self.hdl_toplevel is None:
754
832
  raise ValueError(
755
- "This simulator requires hdl_toplevel parameter to be specified"
833
+ f"{type(self).__qualname__}: Simulator requires the hdl_toplevel parameter to be specified"
756
834
  )
757
835
 
758
- cmd = []
836
+ # TODO: set "--debug" if self.verbose
837
+ # TODO: support "--always"
759
838
 
760
- verilator_cpp = os.path.join(
761
- os.path.dirname(cocotb.__file__),
762
- "share",
763
- "lib",
764
- "verilator",
765
- "verilator.cpp",
839
+ verilator_cpp = str(
840
+ Path(cocotb.__file__).parent
841
+ / "share"
842
+ / "lib"
843
+ / "verilator"
844
+ / "verilator.cpp"
766
845
  )
767
846
 
768
- cmd.append(
847
+ cmds = []
848
+ cmds.append(
769
849
  [
770
850
  "perl",
771
851
  self.executable,
772
852
  "-cc",
773
853
  "--exe",
774
854
  "-Mdir",
775
- self.build_dir,
855
+ str(self.build_dir),
776
856
  "-DCOCOTB_SIM=1",
777
857
  "--top-module",
778
858
  self.hdl_toplevel,
@@ -787,132 +867,154 @@ class Verilator(Simulator):
787
867
  LIB_DIR=cocotb.config.libs_dir
788
868
  ),
789
869
  ]
790
- + self.compile_args
791
- + self.get_define_options(self.defines)
792
- + self.get_include_options(self.includes)
793
- + self.get_parameter_options(self.parameters)
870
+ + self.build_args
871
+ + self._get_define_options(self.defines)
872
+ + self._get_include_options(self.includes)
873
+ + self._get_parameter_options(self.parameters)
794
874
  + [verilator_cpp]
795
- + self.verilog_sources
875
+ + [str(source_file) for source_file in self.verilog_sources]
796
876
  )
797
877
 
798
- cmd.append(["make", "-C", self.build_dir, "-f", "Vtop.mk"])
878
+ cmds.append(["make", "-C", str(self.build_dir), "-f", "Vtop.mk"])
799
879
 
800
- return cmd
880
+ return cmds
801
881
 
802
- def test_command(self) -> List[Command]:
803
- out_file = os.path.join(self.build_dir, self.sim_toplevel)
804
- return [[out_file] + self.plus_args]
882
+ def _test_command(self) -> List[Command]:
883
+ out_file = self.build_dir / self.sim_hdl_toplevel
884
+ return [[str(out_file)] + self.plusargs]
805
885
 
806
886
 
807
887
  class Xcelium(Simulator):
888
+ supported_gpi_interfaces = {"verilog": ["vpi"], "vhdl": ["vhpi"]}
889
+
808
890
  @staticmethod
809
- def simulator_in_path() -> None:
891
+ def _simulator_in_path() -> None:
810
892
  if shutil.which("xrun") is None:
811
893
  raise SystemExit("ERROR: xrun executable not found!")
812
894
 
813
- def check_toplevel_lang(self, toplevel_lang: Optional[str]) -> str:
814
- if toplevel_lang is None:
815
- if self.vhdl_sources and not self.verilog_sources:
816
- return "vhdl"
817
- elif self.verilog_sources and not self.vhdl_sources:
818
- return "verilog"
819
- else:
820
- raise ValueError("Must specify a toplevel_lang in a mixed design")
821
- elif toplevel_lang in ("verilog", "vhdl"):
822
- return toplevel_lang
823
- else:
824
- raise ValueError(
825
- f"Xcelium does not support {toplevel_lang!r} as a toplevel_lang"
826
- )
827
-
828
895
  @staticmethod
829
- def get_include_options(includes: Sequence[str]) -> List[str]:
896
+ def _get_include_options(includes: Sequence[PathLike]) -> Command:
830
897
  return [f"-incdir {include}" for include in includes]
831
898
 
832
899
  @staticmethod
833
- def get_define_options(defines: Sequence[str]) -> List[str]:
834
- return [f"-define {define}" for define in defines]
900
+ def _get_define_options(defines: Mapping[str, object]) -> Command:
901
+ return [f"-define {name}={value}" for name, value in defines.items()]
835
902
 
836
903
  @staticmethod
837
- def get_parameter_options(parameters: Mapping[str, object]) -> List[str]:
904
+ def _get_parameter_options(parameters: Mapping[str, object]) -> Command:
838
905
  return [f'-gpg "{name} => {value}"' for name, value in parameters.items()]
839
906
 
840
- def build_command(self) -> List[Command]:
841
-
907
+ def _build_command(self) -> List[Command]:
842
908
  self.env["CDS_AUTO_64BIT"] = "all"
843
- cmd = [
909
+
910
+ verbosity_opts = []
911
+ if self.verbose:
912
+ verbosity_opts += ["-messages"]
913
+ verbosity_opts += ["-status"]
914
+ verbosity_opts += ["-gverbose"] # print assigned generics/parameters
915
+ verbosity_opts += ["-pliverbose"]
916
+ verbosity_opts += ["-plidebug"] # Enhance the profile output with PLI info
917
+ verbosity_opts += [
918
+ "-plierr_verbose"
919
+ ] # Expand handle info in PLI/VPI/VHPI messages
920
+
921
+ else:
922
+ verbosity_opts += ["-quiet"]
923
+ verbosity_opts += ["-plinowarn"]
924
+
925
+ cmds = [
844
926
  ["xrun"]
845
- + ["-logfile xrun_build.log"]
927
+ + ["-logfile"]
928
+ + ["xrun_build.log"]
846
929
  + ["-elaborate"]
847
- + [f"-xmlibdirname {self.build_dir}/xrun_snapshot"]
930
+ + ["-xmlibdirname"]
931
+ + [f"{self.build_dir}/xrun_snapshot"]
848
932
  + ["-licqueue"]
849
- # TODO: way to switch to these verbose messages?:
850
- + ["-messages"]
851
- + ["-gverbose"] # print assigned generics/parameters
852
- + ["-plinowarn"]
853
- # + ["-pliverbose"]
854
- # + ["-plidebug"] # Enhance the profile output with PLI info
855
- # + ["-plierr_verbose"] # Expand handle info in PLI/VPI/VHPI messages
933
+ + (["-clean"] if self.always else [])
934
+ + verbosity_opts
856
935
  # + ["-vpicompat 1800v2005"] # <1364v1995|1364v2001|1364v2005|1800v2005> Specify the IEEE VPI
857
936
  + ["-access +rwc"]
937
+ + ["-loadvpi"]
938
+ # always start with VPI on Xcelium
858
939
  + [
859
- "-loadvpi "
860
- + cocotb.config.lib_name_path("vpi", "xcelium")
940
+ cocotb.config.lib_name_path("vpi", "xcelium")
861
941
  + ":vlog_startup_routines_bootstrap"
862
942
  ]
863
- + [f"-work {self.library_name}"]
864
- + self.compile_args
943
+ + [f"-work {self.hdl_library}"]
944
+ + self.build_args
865
945
  + ["-define COCOTB_SIM"]
866
- + self.get_define_options(self.defines)
867
- + self.get_include_options(self.includes)
868
- + self.get_parameter_options(self.parameters)
946
+ + self._get_include_options(self.includes)
947
+ + self._get_define_options(self.defines)
948
+ + self._get_parameter_options(self.parameters)
869
949
  + [f"-top {self.hdl_toplevel}"]
870
- + self.vhdl_sources
871
- + self.verilog_sources
950
+ + [
951
+ str(source_file)
952
+ for source_file in self.vhdl_sources + self.verilog_sources
953
+ ]
872
954
  ]
873
955
 
874
- return cmd
956
+ return cmds
875
957
 
876
- def test_command(self) -> List[Command]:
958
+ def _test_command(self) -> List[Command]:
877
959
  self.env["CDS_AUTO_64BIT"] = "all"
878
960
 
961
+ verbosity_opts = []
962
+ if self.verbose:
963
+ verbosity_opts += ["-messages"]
964
+ verbosity_opts += ["-status"]
965
+ verbosity_opts += ["-gverbose"] # print assigned generics/parameters
966
+ verbosity_opts += ["-pliverbose"]
967
+ verbosity_opts += ["-plidebug"] # Enhance the profile output with PLI info
968
+ verbosity_opts += [
969
+ "-plierr_verbose"
970
+ ] # Expand handle info in PLI/VPI/VHPI messages
971
+
972
+ else:
973
+ verbosity_opts += ["-quiet"]
974
+ verbosity_opts += ["-plinowarn"]
975
+
879
976
  tmpdir = f"implicit_tmpdir_{self.current_test_name}"
880
- cmd = [["mkdir", "-p", tmpdir]]
881
977
 
882
- cmd += [
978
+ if self.hdl_toplevel_lang == "vhdl":
979
+ xrun_top = ":"
980
+ else:
981
+ xrun_top = self.sim_hdl_toplevel
982
+
983
+ cmds = [["mkdir", "-p", tmpdir]]
984
+ cmds += [
883
985
  ["xrun"]
884
- + [f"-logfile xrun_{self.current_test_name}.log"]
885
- + [
886
- f"-xmlibdirname {self.build_dir}/xrun_snapshot -cds_implicit_tmpdir {tmpdir}"
887
- ]
986
+ + ["-logfile"]
987
+ + [f"xrun_{self.current_test_name}.log"]
988
+ + ["-xmlibdirname"]
989
+ + [f"{self.build_dir}/xrun_snapshot"]
990
+ + ["-cds_implicit_tmpdir"]
991
+ + [tmpdir]
888
992
  + ["-licqueue"]
889
- # TODO: way to switch to these verbose messages?:
890
- + ["-messages"]
891
- + ["-plinowarn"]
892
- # + ["-pliverbose"]
893
- # + ["-plidebug"] # Enhance the profile output with PLI info
894
- # + ["-plierr_verbose"] # Expand handle info in PLI/VPI/VHPI messages
895
- # + ["-vpicompat 1800v2005"] # <1364v1995|1364v2001|1364v2005|1800v2005> Specify the IEEE VPI
993
+ + verbosity_opts
896
994
  + ["-R"]
897
- + self.sim_args
898
- + self.plus_args
995
+ + self.test_args
996
+ + self.plusargs
899
997
  + ["-gui" if self.gui else ""]
998
+ + ["-input"]
900
999
  + [
901
- '-input "@probe -create {self.sim_toplevel} -all -depth all"'
1000
+ f'-input "@database -open cocotb_waves -default" '
1001
+ f'-input "@probe -database cocotb_waves -create {xrun_top} -all -depth all" '
1002
+ f'-input "@run" '
1003
+ f'-input "@exit" '
902
1004
  if self.waves
903
- else ""
1005
+ else "@run; exit;"
904
1006
  ]
905
1007
  ]
906
1008
  self.env["GPI_EXTRA"] = (
907
1009
  cocotb.config.lib_name_path("vhpi", "xcelium") + ":cocotbvhpi_entry_point"
908
1010
  )
909
1011
 
910
- return cmd
1012
+ return cmds
911
1013
 
912
1014
 
913
- def get_runner(simulator_name: str) -> Type[Simulator]:
1015
+ def get_runner(simulator_name: str) -> Simulator:
1016
+ """Return the *simulator_name* instance."""
914
1017
 
915
- sim_name = simulator_name.lower()
916
1018
  supported_sims: Dict[str, Type[Simulator]] = {
917
1019
  "icarus": Icarus,
918
1020
  "questa": Questa,
@@ -921,26 +1023,11 @@ def get_runner(simulator_name: str) -> Type[Simulator]:
921
1023
  "verilator": Verilator,
922
1024
  "xcelium": Xcelium,
923
1025
  # TODO: "vcs": Vcs,
1026
+ # TODO: "activehdl": ActiveHdl,
924
1027
  }
925
1028
  try:
926
- return supported_sims[sim_name]
1029
+ return supported_sims[simulator_name]()
927
1030
  except KeyError:
928
- raise NotImplementedError(
929
- "Set SIM variable. Supported: " + ", ".join(supported_sims)
1031
+ raise ValueError(
1032
+ f"Simulator {simulator_name!r} is not in supported list: {', '.join(supported_sims)}"
930
1033
  ) from None
931
-
932
-
933
- def clean(recursive: bool = False) -> None:
934
- dir = os.getcwd()
935
-
936
- def rm_clean() -> None:
937
- build_dir = os.path.join(dir, "sim_build")
938
- if os.path.isdir(build_dir):
939
- print("INFO: Removing:", build_dir)
940
- shutil.rmtree(build_dir, ignore_errors=True)
941
-
942
- rm_clean()
943
-
944
- if recursive:
945
- for dir, _, _ in os.walk(dir):
946
- rm_clean()