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