opencos-eda 0.2.51__py3-none-any.whl → 0.2.53__py3-none-any.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.
Files changed (43) hide show
  1. opencos/commands/__init__.py +2 -0
  2. opencos/commands/build.py +1 -1
  3. opencos/commands/deps_help.py +259 -0
  4. opencos/commands/export.py +1 -1
  5. opencos/commands/flist.py +3 -0
  6. opencos/commands/lec.py +1 -1
  7. opencos/commands/open.py +2 -0
  8. opencos/commands/proj.py +1 -1
  9. opencos/commands/shell.py +1 -1
  10. opencos/commands/sim.py +32 -8
  11. opencos/commands/synth.py +1 -1
  12. opencos/commands/upload.py +3 -0
  13. opencos/commands/waves.py +13 -2
  14. opencos/deps/defaults.py +1 -0
  15. opencos/deps/deps_file.py +30 -4
  16. opencos/deps/deps_processor.py +72 -2
  17. opencos/deps_schema.py +3 -0
  18. opencos/eda.py +66 -29
  19. opencos/eda_base.py +159 -20
  20. opencos/eda_config.py +2 -2
  21. opencos/eda_config_defaults.yml +83 -3
  22. opencos/eda_extract_targets.py +1 -58
  23. opencos/tests/helpers.py +16 -0
  24. opencos/tests/test_eda.py +13 -2
  25. opencos/tests/test_tools.py +227 -6
  26. opencos/tools/cocotb.py +492 -0
  27. opencos/tools/modelsim_ase.py +67 -51
  28. opencos/tools/quartus.py +638 -0
  29. opencos/tools/questa.py +167 -88
  30. opencos/tools/questa_fse.py +10 -0
  31. opencos/tools/riviera.py +1 -0
  32. opencos/tools/verilator.py +31 -0
  33. opencos/tools/vivado.py +3 -3
  34. opencos/util.py +22 -3
  35. opencos/utils/str_helpers.py +85 -0
  36. opencos/utils/vscode_helper.py +47 -0
  37. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/METADATA +2 -1
  38. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/RECORD +43 -39
  39. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/WHEEL +0 -0
  40. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/entry_points.txt +0 -0
  41. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE +0 -0
  42. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE.spdx +0 -0
  43. {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,492 @@
1
+ ''' opencos.tools.cocotb - Used by opencos.eda for sim commands with --tool=cocotb.
2
+
3
+ Contains classes for ToolCocotb, CommandSimCocotb.
4
+ '''
5
+
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+
10
+ from opencos import util
11
+ from opencos.eda_base import Tool
12
+ from opencos.commands import CommandSim
13
+ from opencos.utils.str_helpers import sanitize_defines_for_sh
14
+
15
+
16
+ class ToolCocotb(Tool):
17
+ '''ToolCocotb used by opencos.eda for --tool=cocotb'''
18
+
19
+ _TOOL = 'cocotb'
20
+ _EXE = 'python'
21
+ _URL = 'https://github.com/cocotb/cocotb'
22
+
23
+ cocotb_version = ''
24
+ python_exe = ''
25
+
26
+ def get_versions(self) -> str:
27
+ if self._VERSION:
28
+ return self._VERSION
29
+
30
+ # Check if python is available
31
+ python_path = shutil.which('python') or shutil.which('python3')
32
+ if not python_path:
33
+ self.error('"python" or "python3" not in path, required for cocotb')
34
+ else:
35
+ self.python_exe = python_path
36
+
37
+ # Check if cocotb is installed
38
+ try:
39
+ version_ret = subprocess.run(
40
+ [self.python_exe, '-c', 'import cocotb; print(cocotb.__version__)'],
41
+ capture_output=True,
42
+ check=True,
43
+ text=True
44
+ )
45
+ version = version_ret.stdout.strip()
46
+ self.cocotb_version = version
47
+ self._VERSION = version
48
+ util.debug(f'Found cocotb version: {version}')
49
+ return self._VERSION
50
+ except subprocess.CalledProcessError:
51
+ self.error('cocotb package not installed in python environment. '
52
+ 'Install with: pip install cocotb')
53
+ except Exception as e:
54
+ self.error(f'Failed to check cocotb version: {e}')
55
+
56
+ return ''
57
+
58
+ def set_tool_defines(self):
59
+ super().set_tool_defines()
60
+ self.defines.update({
61
+ 'SIMULATION': 1,
62
+ 'COCOTB': 1,
63
+ 'OC_TOOL_COCOTB': None,
64
+ })
65
+
66
+
67
+ class CommandSimCocotb(CommandSim, ToolCocotb):
68
+ '''CommandSimCocotb is a command handler for: eda sim --tool=cocotb'''
69
+
70
+ def __init__(self, config: dict):
71
+ CommandSim.__init__(self, config)
72
+ ToolCocotb.__init__(self, config=self.config)
73
+
74
+ self.args.update({
75
+ 'gui': False,
76
+ 'tcl-file': None,
77
+ 'cocotb-test-module': None,
78
+ 'cocotb-test-runner': 'python',
79
+ 'cocotb-simulator': 'verilator',
80
+ 'cocotb-makefile': False,
81
+ 'cocotb-python-runner': True,
82
+ 'cocotb-standalone-makefile': False,
83
+ })
84
+
85
+ self.args_help.update({
86
+ 'waves': 'Include waveforms by setting COCOTB_ENABLE_WAVES=1',
87
+ 'cocotb-test-module': 'Python test module name (e.g., test_my_design)',
88
+ 'cocotb-test-runner': 'Test runner to use: python (default) or pytest',
89
+ 'cocotb-simulator': ('Simulator backend: verilator (default), icarus, etc.'
90
+ ' Note that iverilog will convert to icarus here'),
91
+ 'cocotb-makefile': 'Use traditional Makefile system instead of Python runner',
92
+ 'cocotb-python-runner': 'Use Python-based runner system (default, cocotb 1.8+)',
93
+ 'cocotb-standalone-makefile': ('Use provided Makefile as-is, '
94
+ 'run make in source directory'),
95
+ })
96
+
97
+ self.cocotb_command_lists = []
98
+ self.cocotb_test_files = []
99
+
100
+ def set_tool_defines(self):
101
+ ToolCocotb.set_tool_defines(self)
102
+
103
+ def prepare_compile(self):
104
+ self.set_tool_defines()
105
+
106
+ # Fix iverilog -> icarus
107
+ if self.args['cocotb-simulator'] == 'iverilog':
108
+ self.args['cocotb-simulator'] = 'icarus'
109
+
110
+ # Find cocotb test files
111
+ self._find_cocotb_test_files()
112
+
113
+ if self.args['cocotb-standalone-makefile']:
114
+ self._prepare_standalone_makefile_system()
115
+ elif self.args['cocotb-makefile']:
116
+ self._prepare_makefile_system()
117
+ else:
118
+ self._prepare_python_runner_system()
119
+
120
+ # Create directories
121
+ paths = ['logs', 'sim_build']
122
+ util.safe_mkdirs(base=self.args['work-dir'], new_dirs=paths)
123
+
124
+ # Write shell scripts
125
+ util.write_shell_command_file(
126
+ dirpath=self.args['work-dir'],
127
+ filename='cocotb_test.sh',
128
+ command_lists=self.cocotb_command_lists,
129
+ line_breaks=True
130
+ )
131
+
132
+ util.write_shell_command_file(
133
+ dirpath=self.args['work-dir'],
134
+ filename='all.sh',
135
+ command_lists=[
136
+ ['./pre_compile_dep_shell_commands.sh'],
137
+ ['./cocotb_test.sh'],
138
+ ]
139
+ )
140
+
141
+ def _find_cocotb_test_files(self):
142
+ '''Find Python test files that contain cocotb tests'''
143
+ self.cocotb_test_files = []
144
+
145
+ # Look for test files in the current directory and deps
146
+ for file_path in self.files_non_source:
147
+ if (file_path.endswith('.py') and
148
+ ('test' in file_path.lower() or 'tb' in file_path.lower())):
149
+ # Check if it's a cocotb test file
150
+ try:
151
+ with open(file_path, 'r', encoding='utf-8') as f:
152
+ content = f.read()
153
+ if 'import cocotb' in content or 'from cocotb' in content:
154
+ self.cocotb_test_files.append(file_path)
155
+ util.debug(f'Found cocotb test file: {file_path}')
156
+ except Exception as e:
157
+ util.debug(f'Could not read {file_path}: {e}')
158
+
159
+ # If no test files found, look for explicit test module
160
+ if not self.cocotb_test_files and self.args.get('cocotb-test-module'):
161
+ test_module = self.args['cocotb-test-module']
162
+ if not test_module.endswith('.py'):
163
+ test_module += '.py'
164
+
165
+ # Look in work directory and target directory
166
+ for search_dir in [self.args['work-dir'], os.path.dirname(self.target or '.')]:
167
+ test_path = os.path.join(search_dir, test_module)
168
+ if os.path.exists(test_path):
169
+ self.cocotb_test_files.append(test_path)
170
+ break
171
+
172
+ if not self.cocotb_test_files:
173
+ self.error('No cocotb test files found. Either include .py files with '
174
+ 'cocotb imports in your deps, or specify --cocotb-test-module')
175
+
176
+ def _prepare_python_runner_system(self):
177
+ '''Prepare cocotb using the Python-based runner system (cocotb 1.8+)'''
178
+
179
+ # Create a Python runner script
180
+ runner_script = self._create_python_runner_script()
181
+
182
+ if self.args['cocotb-test-runner'] == 'pytest':
183
+ # Use pytest to run the tests
184
+ runner_script_name = os.path.basename(runner_script)
185
+ cmd_list = [
186
+ self.python_exe, '-m', 'pytest',
187
+ runner_script_name,
188
+ '-v', '-s'
189
+ ]
190
+ else:
191
+ # Run the Python script directly
192
+ runner_script_name = os.path.basename(runner_script)
193
+ cmd_list = [self.python_exe, runner_script_name]
194
+
195
+ # Set environment variables
196
+ env_vars = self._get_cocotb_env_vars()
197
+
198
+ # Create command with environment variables
199
+ command_list = self._create_env_command(env_vars) + cmd_list
200
+ self.cocotb_command_lists = [util.ShellCommandList(command_list,
201
+ tee_fpath='cocotb_test.log')]
202
+
203
+ def _prepare_makefile_system(self):
204
+ '''Prepare cocotb using the traditional Makefile system'''
205
+
206
+ makefile_path = os.path.join(self.args['work-dir'], 'Makefile')
207
+ with open(makefile_path, 'w', encoding='utf-8') as f:
208
+ f.write(self._create_makefile_content())
209
+
210
+ cmd_list = self._create_shell_command_with_success('make -f Makefile')
211
+ self.cocotb_command_lists = [util.ShellCommandList(cmd_list,
212
+ tee_fpath='cocotb_makefile.log')]
213
+
214
+ def _prepare_standalone_makefile_system(self):
215
+ '''Use provided Makefile as-is, run make in source directory'''
216
+
217
+ # Find the Makefile in our dependencies
218
+ makefile_path = None
219
+ for file_path in self.files_non_source:
220
+ if os.path.basename(file_path).lower() == 'makefile':
221
+ makefile_path = file_path
222
+ break
223
+
224
+ if not makefile_path:
225
+ self.error('No Makefile found in deps for --cocotb-standalone-makefile')
226
+
227
+ makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
228
+ cmd_list = self._create_shell_command_with_success(f'cd {makefile_dir} && make')
229
+ self.cocotb_command_lists = [util.ShellCommandList(
230
+ cmd_list, tee_fpath='cocotb_standalone.log')]
231
+
232
+ def _get_test_module_name(self) -> str:
233
+ '''Get the test module name from args or detected files'''
234
+ if self.args.get('cocotb-test-module'):
235
+ return self.args['cocotb-test-module']
236
+ if self.cocotb_test_files:
237
+ # Use the first test file found, strip .py extension
238
+ test_file = os.path.basename(self.cocotb_test_files[0])
239
+ return test_file.replace('.py', '')
240
+ return 'test_design'
241
+
242
+
243
+ def _get_hdl_sources(self) -> dict:
244
+ '''Get HDL source files organized by type'''
245
+ return {
246
+ 'verilog': (self.files_sv or []) + (self.files_v or []),
247
+ 'vhdl': self.files_vhd or [],
248
+ 'all': (self.files_sv or []) + (self.files_v or []) + (self.files_vhd or [])
249
+ }
250
+
251
+ def _create_python_runner_script(self) -> str:
252
+ '''Create the Python runner script and return its path'''
253
+ test_module = self._get_test_module_name()
254
+ hdl_sources = self._get_hdl_sources()['all']
255
+
256
+ script_content = self._generate_runner_script_content(test_module, hdl_sources)
257
+ script_path = os.path.join(self.args['work-dir'], 'cocotb_runner.py')
258
+
259
+ with open(script_path, 'w', encoding='utf-8') as f:
260
+ f.write(script_content)
261
+
262
+ os.chmod(script_path, 0o755)
263
+
264
+ return script_path
265
+
266
+ def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
267
+ '''Generate the content for the Python runner script'''
268
+ return f'''#!/usr/bin/env python3
269
+ """
270
+ Cocotb test runner script generated by opencos
271
+ """
272
+
273
+ import os
274
+ import sys
275
+ from pathlib import Path
276
+
277
+ # Add current directory to Python path for test imports
278
+ sys.path.insert(0, os.getcwd())
279
+
280
+ try:
281
+ from cocotb_tools.runner import get_runner
282
+ except ImportError:
283
+ print("ERROR: cocotb not found. Install with: pip install cocotb")
284
+ sys.exit(1)
285
+
286
+ def run_cocotb_test():
287
+ """Run cocotb test using the Python runner system"""
288
+
289
+ # Configuration
290
+ simulator = "{self.args['cocotb-simulator']}"
291
+ hdl_toplevel = "{self.args.get('top', 'top')}"
292
+ test_module = "{test_module}"
293
+
294
+ # HDL source files
295
+ hdl_sources = {hdl_sources!r}
296
+
297
+ # Convert to Path objects and make absolute
298
+ sources = []
299
+ for src in hdl_sources:
300
+ src_path = Path(src)
301
+ if not src_path.is_absolute():
302
+ src_path = Path.cwd() / src_path
303
+ sources.append(src_path)
304
+
305
+ # Include directories
306
+ include_dirs = {list(self.incdirs)!r}
307
+
308
+ # Defines (filter out None values for cocotb compatibility)
309
+ all_defines = {dict(self.defines)!r}
310
+ defines = {{k: v for k, v in all_defines.items() if v is not None}}
311
+
312
+ # Parameters (empty for simple modules without parameters)
313
+ parameters = {{}}
314
+
315
+ try:
316
+ # Get the runner for the specified simulator
317
+ runner = get_runner(simulator)
318
+
319
+ build_args = []
320
+
321
+ if simulator == "verilator":
322
+ build_args.extend({list(self.args.get('verilate-args', []))!r})
323
+
324
+ # Build the design
325
+ runner.build(
326
+ sources=sources,
327
+ hdl_toplevel=hdl_toplevel,
328
+ includes=include_dirs,
329
+ defines=defines,
330
+ parameters=parameters,
331
+ build_dir="sim_build",
332
+ build_args=build_args,
333
+ )
334
+
335
+ # Run the test
336
+ runner.test(
337
+ hdl_toplevel=hdl_toplevel,
338
+ test_module=test_module,
339
+ test_dir=".",
340
+ )
341
+
342
+ print("{self._get_success_message()}")
343
+
344
+ except Exception as e:
345
+ print(f"ERROR: Cocotb test failed: {{e}}")
346
+ sys.exit(1)
347
+
348
+ if __name__ == "__main__":
349
+ run_cocotb_test()
350
+ '''
351
+
352
+ def _create_makefile_content(self) -> str:
353
+ '''Create Makefile content for cocotb traditional system'''
354
+
355
+ # Determine test module
356
+ test_module = self._get_test_module_name()
357
+
358
+ # Get HDL sources and determine language
359
+ hdl_sources = self._get_hdl_sources()
360
+ verilog_sources = hdl_sources['verilog']
361
+ vhdl_sources = hdl_sources['vhdl']
362
+
363
+ # Determine HDL language
364
+ if vhdl_sources and not verilog_sources:
365
+ hdl_lang = 'vhdl'
366
+ sources_var = 'VHDL_SOURCES'
367
+ sources_list = ' '.join(vhdl_sources)
368
+ else:
369
+ hdl_lang = 'verilog'
370
+ sources_var = 'VERILOG_SOURCES'
371
+ sources_list = ' '.join(verilog_sources)
372
+
373
+ makefile_content = f'''# Cocotb Makefile generated by opencos
374
+
375
+ # Simulator selection
376
+ SIM ?= {self.args['cocotb-simulator']}
377
+
378
+ # HDL language
379
+ TOPLEVEL_LANG = {hdl_lang}
380
+
381
+ # HDL sources
382
+ {sources_var} = {sources_list}
383
+
384
+ # Top level module
385
+ TOPLEVEL = {self.args.get('top', 'top')}
386
+
387
+ # Test module
388
+ MODULE = {test_module}
389
+
390
+ # Include directories
391
+ '''
392
+
393
+ if self.incdirs:
394
+ makefile_content += ('COMPILE_ARGS += ' +
395
+ ' '.join(f'-I{inc}' for inc in self.incdirs) + '\n')
396
+
397
+ # Add defines
398
+ if self.defines:
399
+ define_args = []
400
+ for k, v in self.defines.items():
401
+ if v is None:
402
+ define_args.append(f'-D{k}')
403
+ else:
404
+ define_args.append(f'-D{k}={sanitize_defines_for_sh(v)}')
405
+ makefile_content += 'COMPILE_ARGS += ' + ' '.join(define_args) + '\n'
406
+
407
+ if self.args['cocotb-simulator'] == 'verilator' and self.args.get('verilate-args'):
408
+ makefile_content += 'COMPILE_ARGS += ' + ' '.join(self.args['verilate-args']) + '\n'
409
+
410
+ makefile_content += '''
411
+ # Waves support
412
+ ifeq ($(WAVES),1)
413
+ COCOTB_ENABLE_WAVES = 1
414
+ export COCOTB_ENABLE_WAVES
415
+ endif
416
+
417
+ # Include cocotb's Makefile
418
+ include $(shell cocotb-config --makefiles)/Makefile.sim
419
+ '''
420
+
421
+ return makefile_content
422
+
423
+ def _get_success_message(self) -> str:
424
+ '''Get standardized success message'''
425
+ return "Cocotb test completed successfully!"
426
+
427
+ def _create_shell_command_with_success(self, base_command: str) -> list:
428
+ '''Create a shell command list with success message'''
429
+ return ['sh', '-c', f'{base_command} && echo "{self._get_success_message()}"']
430
+
431
+ def _get_cocotb_env_vars(self) -> dict:
432
+ '''Get environment variables for cocotb execution'''
433
+ env_vars = {}
434
+
435
+ # Basic cocotb configuration
436
+ env_vars['SIM'] = self.args['cocotb-simulator']
437
+ env_vars['TOPLEVEL'] = self.args.get('top', 'top')
438
+
439
+ # Enable waves if requested
440
+ if self.args.get('waves', False):
441
+ env_vars['COCOTB_ENABLE_WAVES'] = '1'
442
+
443
+ # Set log level based on verbosity
444
+ if util.args.get('verbose', False):
445
+ env_vars['COCOTB_LOG_LEVEL'] = 'DEBUG'
446
+ else:
447
+ env_vars['COCOTB_LOG_LEVEL'] = 'INFO'
448
+
449
+ # Random seed
450
+ if self.args.get('seed'):
451
+ env_vars['COCOTB_RANDOM_SEED'] = str(self.args['seed'])
452
+
453
+ return env_vars
454
+
455
+ def _create_env_command(self, env_vars: dict) -> list:
456
+ '''Create environment variable command prefix'''
457
+ env_cmd = []
458
+ for key, value in env_vars.items():
459
+ env_cmd.extend(['env', f'{key}={value}'])
460
+ return env_cmd
461
+
462
+ def compile(self):
463
+ # For cocotb, compilation happens as part of the test run
464
+ if self.args['stop-before-compile']:
465
+ return
466
+ util.info('Cocotb: compilation will happen during test execution')
467
+
468
+ def elaborate(self):
469
+ # For cocotb, elaboration happens as part of the test run
470
+ pass
471
+
472
+ def simulate(self):
473
+ if self.args['stop-before-compile'] or self.args['stop-after-compile'] or \
474
+ self.args['stop-after-elaborate']:
475
+ return
476
+
477
+ # Run the cocotb tests
478
+ self.run_commands_check_logs(self.cocotb_command_lists)
479
+
480
+ def get_compile_command_lists(self, **kwargs) -> list:
481
+ # Cocotb handles compilation internally
482
+ return []
483
+
484
+ def get_elaborate_command_lists(self, **kwargs) -> list:
485
+ # Cocotb handles elaboration internally
486
+ return []
487
+
488
+ def get_simulate_command_lists(self, **kwargs) -> list:
489
+ return self.cocotb_command_lists
490
+
491
+ def get_post_simulate_command_lists(self, **kwargs) -> list:
492
+ return []
@@ -19,6 +19,7 @@ class ToolModelsimAse(ToolQuesta):
19
19
 
20
20
  _TOOL = 'modelsim_ase' # otherwise it's 'questa' from base class.
21
21
  _EXE = 'vsim'
22
+ use_vopt = False
22
23
 
23
24
  class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
24
25
  '''CommandSimModelsimAse is a command handler for: eda sim --tool=modelsim_ase'''
@@ -44,6 +45,15 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
44
45
  )
45
46
  )
46
47
 
48
+ def run_in_batch_mode(self) -> bool:
49
+ '''Returns bool if we should run in batch mode (-c) from command line'''
50
+ # TODO(drew): make CommandSimQuesta a parent and inherit this method instead.
51
+ if self.args['test-mode']:
52
+ return True
53
+ if self.args['gui']:
54
+ return False
55
+ return True
56
+
47
57
  # We do override do_it() to avoid using CommandSimQuesta.do_it()
48
58
  def do_it(self):
49
59
  CommandSim.do_it(self)
@@ -111,7 +121,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
111
121
  # This will also set up a compile.
112
122
  vsim_command_list = [
113
123
  self.sim_exe,
114
- '' if self.args['gui'] else '-c',
124
+ '-c' if self.run_in_batch_mode() else '',
115
125
  '-do', 'vsim_vlogonly.do', '-logfile', 'sim.log',
116
126
  ]
117
127
  return [vsim_command_list]
@@ -120,7 +130,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
120
130
  # This will also set up a compile, for vlog + vsim (0 time)
121
131
  vsim_command_list = [
122
132
  self.sim_exe,
123
- '' if self.args['gui'] else '-c',
133
+ '-c' if self.run_in_batch_mode() else '',
124
134
  '-do', 'vsim_lintonly.do', '-logfile', 'sim.log',
125
135
  ]
126
136
  return [vsim_command_list]
@@ -129,7 +139,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
129
139
  # This will also set up a compile, for vlog + vsim (with run -a)
130
140
  vsim_command_list = [
131
141
  self.sim_exe,
132
- '' if self.args['gui'] else '-c',
142
+ '-c' if self.run_in_batch_mode() else '',
133
143
  '-do', 'vsim.do', '-logfile', 'sim.log',
134
144
  ]
135
145
  return [vsim_command_list]
@@ -193,7 +203,9 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
193
203
  with open(vlog_dot_f_fpath, 'w', encoding='utf-8') as f:
194
204
  f.writelines(line + "\n" for line in vlog_dot_f_lines)
195
205
 
196
- def write_vsim_dot_do(self, dot_do_to_write: list) -> None:
206
+ def write_vsim_dot_do( # pylint: disable=too-many-locals
207
+ self, dot_do_to_write: list
208
+ ) -> None:
197
209
  '''Writes files(s) based on dot_do_to_write(list of str)
198
210
 
199
211
  list arg values can be empty (all) or have items 'all', 'sim', 'lint', 'vlog'.'''
@@ -216,27 +228,28 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
216
228
  # TODO(drew): support self.args['sim_libary', 'elab-args', sim-args'] (3 lists)
217
229
  # to add to vsim_one_liner.
218
230
 
219
- vsim_one_liner = "vsim -onfinish stop" \
220
- + f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
221
- + f" {voptargs_str} work.{self.args['top']}"
231
+ vopt_one_liner = ""
232
+ if self.use_vopt:
233
+ vopt_one_liner = (
234
+ f"vopt {voptargs_str} work.{self.args['top']} -o opt__{self.args['top']}"
235
+ )
236
+ vopt_one_liner = vopt_one_liner.replace('\n', ' ') # needs to be a one-liner
237
+ # vopt doesn't need -voptargs=(value) like vsim does, simply use (value).
238
+ vopt_one_liner = vopt_one_liner.replace('-voptargs=', '')
222
239
 
223
- vsim_one_liner = vsim_one_liner.replace('\n', ' ') # needs to be a one-liner
240
+ vsim_one_liner = "vsim -onfinish stop" \
241
+ + f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
242
+ + f" opt__{self.args['top']}"
243
+ else:
244
+ # vopt doesn't exist, use single vsim call after vlog call:
245
+ vsim_one_liner = "vsim -onfinish stop" \
246
+ + f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
247
+ + f" {voptargs_str} work.{self.args['top']}"
224
248
 
225
- vsim_vlogonly_dot_do_lines = [
226
- "if {[file exists work]} { vdel -all work; }",
227
- "vlib work;",
228
- "if {[catch {vlog -f vlog.f} result]} {",
229
- " echo \"Caught $result \";",
230
- " if {[batch_mode]} {",
231
- " quit -f -code 20;",
232
- " }",
233
- "}",
234
- "if {[batch_mode]} {",
235
- " quit -f -code 0;",
236
- "}",
237
- ]
238
249
 
239
- vsim_lintonly_dot_do_lines = [
250
+ vsim_one_liner = vsim_one_liner.replace('\n', ' ')
251
+
252
+ vlog_do_lines = [
240
253
  "if {[file exists work]} { vdel -all work; }",
241
254
  "vlib work;",
242
255
  "quietly set qc 30;",
@@ -246,44 +259,35 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
246
259
  " quit -f -code 20;",
247
260
  " }",
248
261
  "}",
262
+ ]
263
+
264
+ vopt_do_lines = []
265
+ if self.use_vopt:
266
+ vopt_do_lines = [
267
+ "if {[catch { " + vopt_one_liner + " } result] } {",
268
+ " echo \"Caught $result\";",
269
+ " if {[batch_mode]} {",
270
+ " quit -f -code 19;",
271
+ " }",
272
+ "}",
273
+ ]
274
+
275
+ vsim_do_lines = [
249
276
  "if {[catch { " + vsim_one_liner + " } result] } {",
250
277
  " echo \"Caught $result\";",
251
278
  " if {[batch_mode]} {",
252
- " quit -f -code 19;",
279
+ " quit -f -code 18;",
253
280
  " }",
254
281
  "}",
255
- "set TestStatus [coverage attribute -name SEED -name TESTSTATUS];",
256
- "if {[regexp \"TESTSTATUS += 0\" $TestStatus]} {",
257
- " quietly set qc 0;",
258
- "} elseif {[regexp \"TESTSTATUS += 1\" $TestStatus]} {",
259
- " quietly set qc 0;",
260
- "} else {",
261
- " quietly set qc 2;",
262
- "}",
282
+ ]
283
+
284
+ vsim_vlogonly_dot_do_lines = vlog_do_lines + [
263
285
  "if {[batch_mode]} {",
264
- " quit -f -code $qc;",
286
+ " quit -f -code 0;",
265
287
  "}",
266
288
  ]
267
289
 
268
- vsim_dot_do_lines = [
269
- "if {[file exists work]} { vdel -all work; }",
270
- "vlib work;",
271
- "quietly set qc 30;",
272
- "if {[catch {vlog -f vlog.f} result]} {",
273
- " echo \"Caught $result \";",
274
- " if {[batch_mode]} {",
275
- " quit -f -code 20;",
276
- " }",
277
- "}",
278
- "if {[catch { " + vsim_one_liner + " } result] } {",
279
- " echo \"Caught $result\";",
280
- " if {[batch_mode]} {",
281
- " quit -f -code 19;",
282
- " }",
283
- "}",
284
- "onbreak { resume; };",
285
- "catch {log -r *};",
286
- "run -a;",
290
+ final_check_teststatus_do_lines = [
287
291
  "set TestStatus [coverage attribute -name SEED -name TESTSTATUS];",
288
292
  "if {[regexp \"TESTSTATUS += 0\" $TestStatus]} {",
289
293
  " quietly set qc 0;",
@@ -297,6 +301,18 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
297
301
  "}",
298
302
  ]
299
303
 
304
+ # final vlog/vopt/vsim lint-only .do command (want to make sure it can completely
305
+ # build for 'elab' style eda job), runs for 0ns, logs nothing for a waveform, quits
306
+ vsim_lintonly_dot_do_lines = vlog_do_lines + vopt_do_lines + vsim_do_lines \
307
+ + final_check_teststatus_do_lines
308
+
309
+ # final vlog/opt/vsim full simulation .do command.
310
+ vsim_dot_do_lines = vlog_do_lines + vopt_do_lines + vsim_do_lines + [
311
+ "onbreak { resume; };",
312
+ "catch {log -r *};",
313
+ "run -a;",
314
+ ] + final_check_teststatus_do_lines
315
+
300
316
  write_all = len(dot_do_to_write) == 0 or 'all' in dot_do_to_write
301
317
  if write_all or 'sim' in dot_do_to_write:
302
318
  with open(vsim_dot_do_fpath, 'w', encoding='utf-8') as f: