opencos-eda 0.3.5__py3-none-any.whl → 0.3.7__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.
opencos/export_helper.py CHANGED
@@ -42,12 +42,18 @@ def json_paths_to_jsonl(
42
42
  with open(output_json_path, 'w', encoding='utf-8') as outf:
43
43
 
44
44
  # jsonl is every line of the file is a json.
45
+ # We would expect the JSON to have "tests": [ ... ] with >= 1 test.
45
46
  for json_file_path in json_file_paths:
46
47
  with open(json_file_path, encoding='utf-8') as f:
47
48
  data = json.load(f)
48
49
  if len(assert_json_types) > 0 and type(data) not in assert_json_types:
49
50
  error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
50
- json.dump(data, outf)
51
+ if 'tests' in data and isinstance(data['tests'], list):
52
+ for test in data['tests']:
53
+ json.dump(test, outf)
54
+ else:
55
+ json.dump(data, outf)
56
+
51
57
  outf.write('\n')
52
58
  info(f'Wrote {len(json_file_paths)} tests to {output_json_path=}')
53
59
 
@@ -78,7 +84,12 @@ def json_paths_to_single_json(
78
84
  data = json.load(f)
79
85
  if len(assert_json_types) > 0 and type(data) not in assert_json_types:
80
86
  error(f'{json_file_path=} JSON data is not a Table (py dict) {type(data)=}')
81
- out_json_data['tests'].append(data)
87
+ if 'tests' in data and isinstance(data['tests'], list):
88
+ for test in data['tests']:
89
+ out_json_data['tests'].append(test)
90
+ else:
91
+ out_json_data['tests'].append(data)
92
+
82
93
  json.dump(out_json_data, outf)
83
94
  outf.write('\n')
84
95
  info(f'Wrote {len(json_file_paths)} tests {output_json_path=}')
@@ -471,7 +482,52 @@ class ExportHelper:
471
482
  yaml_safe_writer(data=data, filepath=dst)
472
483
 
473
484
 
474
- def create_export_json_in_out_dir( # pylint: disable=unused-argument
485
+ def create_combined_env_file_in_out_dir(self) -> list:
486
+ ''' Returns list of all .env lines to be put in output directory
487
+
488
+ Creates single exported .env file if any --env-file(s)
489
+ were loaded by CLI or DEPS targets. --input-file/-f was handled as args
490
+ already, but --env-files need to be special cased. We do not add this
491
+ combined ".env" file to the all_files list, just build it on the fly if needed.
492
+ '''
493
+ env_lines = []
494
+ for filepath in util.env_files_loaded:
495
+ with open(filepath, encoding='utf-8') as f:
496
+ env_lines.extend(f.readlines() + ['\n'])
497
+
498
+ if env_lines:
499
+ dst = os.path.join(self.out_dir, '.env')
500
+
501
+ # Write this to the export directory too:
502
+ with open(dst, 'w', encoding='utf-8') as f:
503
+ for lineno, line in enumerate(env_lines):
504
+
505
+ # modify VERILOG_SOURCES line if present, although it is known via DEPS.yml,
506
+ # we can set it according to files_v and files_sv:
507
+ if line.strip().startswith('VERILOG_SOURCES'):
508
+ base_verilog_filenames = [
509
+ os.path.split(x)[1] for x in \
510
+ self.cmd_design_obj.files_v + self.cmd_design_obj.files_sv
511
+ ]
512
+ env_lines[lineno] = (
513
+ f'VERILOG_SOURCES = {" ".join(base_verilog_filenames)}'
514
+ )
515
+ continue
516
+
517
+ # remove PYTHONPATH from .env
518
+ if line.strip().startswith('PYTHONPATH'):
519
+ env_lines[lineno] = ''
520
+ continue
521
+
522
+ f.write(line)
523
+
524
+
525
+ info(f'export_helper: Wrote {dst}')
526
+
527
+ return env_lines
528
+
529
+
530
+ def create_export_json_in_out_dir( # pylint: disable=unused-argument,too-many-locals,too-many-branches
475
531
  self, eda_config:dict={}, **kwargs
476
532
  ) -> None:
477
533
  '''Optionally creates an exported JSON file in the output directory'''
@@ -482,40 +538,43 @@ class ExportHelper:
482
538
  # assumes we've run self.create_deps_yml_in_out_dir():
483
539
  assert self.target
484
540
  assert self.out_deps_file
541
+ full_eda_cmd_list = ['eda', self.eda_command]
542
+ if self.args.get('waves', False):
543
+ full_eda_cmd_list.append('--waves')
544
+ if self.args.get('tool', ''):
545
+ full_eda_cmd_list.append(f'--tool={self.args["tool"]}')
546
+ full_eda_cmd_list.append(self.target)
485
547
 
486
548
  data = {
487
- 'name': self.target,
488
- 'eda': {
489
- 'enable': True,
490
- 'multi': False, # Not yet implemented.
491
- 'command': self.eda_command,
492
- 'targets': [self.target],
493
- 'args': [],
494
- 'waves': self.args.get('waves', False),
495
- # tool - eda.CommandSimVerilator has this set in self.args:
496
- 'tool': self.args.get('tool', None),
497
- },
498
- 'files': [],
549
+ 'correlationId': self.target,
550
+ 'jobType': 'edaCmd',
551
+ 'cmd': ' '.join(full_eda_cmd_list),
552
+ 'timeout': 600,
553
+ 'filesList': [], # filename (str), content (str)
499
554
  }
500
555
 
501
- # allow caller to override eda - tool, or eda - args, etc.
502
- for k,v in eda_config.items():
503
- if k in data['eda'] and v is not None:
504
- data['eda'][k] = v
505
-
506
556
  # Note that args may already be set via:
507
557
  # create_deps_yml_in_out_dir(deps_file_args=some_list)
508
558
  # For example, eda.CommandSim.do_export() will set certain allow-listed
509
559
  # args if present with non-default values.
510
560
 
511
-
512
561
  all_files = [self.out_deps_file] + self.included_files \
513
562
  + self.cmd_design_obj.files_sv + self.cmd_design_obj.files_v \
514
563
  + self.cmd_design_obj.files_vhd + self.cmd_design_obj.files_cpp \
564
+ + self.cmd_design_obj.files_sdc + self.cmd_design_obj.files_py \
565
+ + self.cmd_design_obj.files_makefile + self.cmd_design_obj.files_non_source
566
+
567
+ all_files = list(dict.fromkeys(all_files)) # uniqify list.
568
+
569
+ # The last file we handle is a single exported .env file if any --env-file(s)
570
+ env_lines = self.create_combined_env_file_in_out_dir()
571
+ if env_lines:
572
+ # write the updated env_lines to the export.json file.
573
+ data['filesList'].append({
574
+ 'filename': '.env',
575
+ 'content': ''.join(env_lines), # already has \n per line
576
+ })
515
577
 
516
- for x in self.cmd_design_obj.files_non_source:
517
- if x not in all_files:
518
- all_files.append(x)
519
578
 
520
579
  for somefile in all_files:
521
580
 
@@ -532,18 +591,17 @@ class ExportHelper:
532
591
 
533
592
  assert os.path.exists(somefile)
534
593
  with open(somefile, encoding='utf-8') as f:
535
- filestr = ''.join(f.readlines())
536
- data['files'].append({
537
- 'name': os.path.split(somefile)[1],
538
- 'content': filestr,
594
+ data['filesList'].append({
595
+ 'filename': os.path.split(somefile)[1],
596
+ 'content': ''.join(f.readlines()),
539
597
  })
540
598
 
541
-
599
+ test_runner_data = {'tests': [data]} # single test for test runner.
542
600
  dst = os.path.join(self.out_dir, 'export.json')
543
601
  with open(dst, 'w', encoding='utf-8') as f:
544
- json.dump(data, f)
602
+ json.dump(test_runner_data, f)
545
603
  f.write('\n')
546
- info(f'export_helper: Wrote {dst=}')
604
+ info(f'export_helper: Wrote {dst}')
547
605
 
548
606
  # If this was from an `export` command, and the self.out_dir != self.args['work-dir'], then
549
607
  # copy the export.json to the work-dir:
opencos/files.py CHANGED
@@ -24,7 +24,9 @@ FORCE_PREFIX_DICT = {
24
24
  'vhdl@': 'vhdl',
25
25
  'cpp@': 'cpp',
26
26
  'sdc@': 'synth_constraints',
27
- 'f@': 'dotf'
27
+ 'f@': 'dotf',
28
+ 'py@' : 'python',
29
+ 'makefile@': 'makefile'
28
30
  }
29
31
 
30
32
  ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))
@@ -29,3 +29,16 @@ target_test_with_post_tool_commands:
29
29
  run-after-tool: true
30
30
  - target_echo_hi_bye
31
31
  top: foo
32
+
33
+
34
+ test_run_from_work_dir:
35
+ deps:
36
+ - commands:
37
+ - shell: echo "pwd=$PWD"
38
+ run-from-work-dir: true
39
+
40
+ test_run_from_work_dir_false:
41
+ deps:
42
+ - commands:
43
+ - shell: echo "pwd=$PWD"
44
+ run-from-work-dir: false
opencos/tests/helpers.py CHANGED
@@ -10,8 +10,8 @@ from pathlib import Path
10
10
  from contextlib import redirect_stdout, redirect_stderr
11
11
 
12
12
  from opencos import eda, eda_tool_helper, deps_schema
13
- from opencos.utils.markup_helpers import yaml_safe_load
14
13
  from opencos.utils import status_constants
14
+ from opencos.utils.markup_helpers import yaml_safe_load
15
15
  from opencos.utils.subprocess_helpers import subprocess_run_background
16
16
 
17
17
  # Figure out what tools the system has available, without calling eda.main(..)
@@ -164,38 +164,43 @@ def assert_gen_deps_yml_good(filepath:str, want_target:str='') -> dict:
164
164
 
165
165
  def assert_export_json_good(filepath:str) -> dict:
166
166
  '''Checks that an exported JSON (from eda export, or eda <command> --export) has known keys'''
167
- assert os.path.exists(filepath), f'{filepath=} does not exist'
167
+ assert os.path.isfile(filepath), f'{filepath=} does not exist'
168
168
  with open(filepath, encoding='utf-8') as f:
169
169
  data = json.load(f)
170
- assert 'name' in data
171
- assert 'eda' in data
172
- assert any(x in data for x in ['files', 'tb'])
170
+ assert 'tests' in data
171
+ assert len(data.get('tests', [])) >= 1
172
+ for test in data.get('tests', []):
173
+ check_test_runner_schema(test)
173
174
  return data
174
175
 
176
+ def check_test_runner_schema(test: dict) -> None:
177
+ '''Confirm that a single test's JSON/JSONL schema is OK.'''
178
+ assert 'correlationId' in test
179
+ assert 'jobType' in test
180
+ assert 'cmd' in test
181
+ assert 'filesList' in test # 0 files is OK.
182
+
175
183
 
176
184
  def assert_export_jsonl_good(filepath:str, jsonl:bool=True) -> list:
177
185
  '''Checks that an exported JSONL (from eda multi --export) has known keys'''
178
- assert os.path.exists(filepath), f'{filepath=} does not exist'
186
+ assert os.path.isfile(filepath), f'{filepath=} does not exist'
179
187
  ret = []
180
188
  with open(filepath, encoding='utf-8') as f:
181
189
  if jsonl:
190
+ print(f'Using JSONL for {filepath=}')
182
191
  for line in f.readlines():
183
192
  line = line.strip()
184
- data = json.loads(line)
185
- assert 'name' in data
186
- assert 'eda' in data
187
- assert any(x in data for x in ['files', 'tb'])
188
- ret.append(data)
193
+ test = json.loads(line)
194
+ check_test_runner_schema(test)
195
+ ret.append(test)
189
196
  else:
197
+ print(f'Using JSON for {filepath=}')
190
198
  data = json.load(f)
191
199
  assert 'tests' in data
192
- assert isinstance(data['tests'], list)
193
- for entry in data['tests']:
194
- assert 'name' in entry
195
- assert 'eda' in entry
196
- assert any(x in entry for x in ['files', 'tb'])
197
- ret.append(entry)
198
-
200
+ assert len(data.get('tests', [])) >= 1
201
+ for test in data.get('tests', []):
202
+ check_test_runner_schema(test)
203
+ ret.append(test)
199
204
 
200
205
  return ret
201
206
 
@@ -233,7 +238,7 @@ class Helpers:
233
238
 
234
239
  def log_it(
235
240
  self, command_str: str, logfile=None, use_eda_wrap: bool = True,
236
- run_in_subprocess: bool = False,
241
+ run_in_subprocess: bool = False, include_default_log: bool = False,
237
242
  preserve_env: bool = False
238
243
  ) -> int:
239
244
  '''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
@@ -252,26 +257,31 @@ class Helpers:
252
257
  logfile = self._resolve_logfile(logfile)
253
258
  rc = 50
254
259
 
260
+ eda_log_arg = '--no-default-log'
261
+ if include_default_log:
262
+ eda_log_arg = ''
263
+
255
264
  # TODO(drew): There are some issues with log_it redirecting stdout from vivado
256
265
  # and modelsim_ase. So this may not work for all tools, you may have to directly
257
266
  # look at eda.work/{target}.sim/sim.log or xsim.log.
258
267
  print(f'{os.getcwd()=}')
259
268
  print(f'{command_str=}')
260
269
  if run_in_subprocess or self.RUN_IN_SUBPROCESS:
261
- command_list = ['eda', '--no-default-log'] + command_str.split()
270
+ command_list = ['eda', eda_log_arg] + command_str.split()
262
271
  _, _, rc = subprocess_run_background(
263
272
  work_dir=self.DEFAULT_DIR,
264
273
  command_list=command_list,
265
274
  background=True,
266
275
  tee_fpath=logfile
267
276
  )
277
+ print(f'Wrote: {os.path.abspath(logfile)=}')
268
278
  else:
269
279
  with open(logfile, 'w', encoding='utf-8') as f:
270
280
  with redirect_stdout(f), redirect_stderr(f):
271
281
  if use_eda_wrap or self.USE_EDA_WRAP:
272
- rc = eda_wrap('--no-default-log', *(command_str.split()))
282
+ rc = eda_wrap(eda_log_arg, *(command_str.split()))
273
283
  else:
274
- rc = eda.main('--no-default-log', *(command_str.split()))
284
+ rc = eda.main(eda_log_arg, *(command_str.split()))
275
285
  print(f'Wrote: {os.path.abspath(logfile)=}')
276
286
 
277
287
  if self.PRESERVE_ENV or preserve_env:
opencos/tests/test_eda.py CHANGED
@@ -648,7 +648,7 @@ class TestsRequiresIVerilog(Helpers):
648
648
  print(f'{rc=}')
649
649
  assert rc == 0
650
650
  assert self.is_in_log('Detected iverilog')
651
- assert self.is_in_log("Generic help for command='sim' (using 'CommandSimIverilog')")
651
+ assert self.is_in_log("Generic help for command=sim tool=iverilog")
652
652
 
653
653
  def test_iverilog_sim(self):
654
654
  '''Test for command sim'''
@@ -3,6 +3,7 @@
3
3
  # pylint: disable=R0801 # (similar lines in 2+ files)
4
4
 
5
5
  import os
6
+ import shutil
6
7
  import sys
7
8
  import pytest
8
9
 
@@ -32,14 +33,19 @@ def test_tools_loaded():
32
33
  assert config
33
34
  assert len(config.keys()) > 0
34
35
 
36
+
35
37
  # It's possible we're running in some container or install that has no tools, for example,
36
38
  # Windows.
37
39
  if sys.platform.startswith('win') and \
38
40
  not helpers.can_run_eda_command('elab', 'sim', cfg=config):
39
- # Windows, not handlers for elab or sim:
41
+ # Windows, no handlers for elab or sim:
40
42
  pass
41
43
  else:
42
- assert len(tools_loaded) > 0
44
+ basic_tools_available_via_path = any(shutil.which(x) for x in (
45
+ 'verilator', 'iverilog', 'xsim', 'vsim', 'slang', 'yosys'
46
+ ))
47
+ if basic_tools_available_via_path:
48
+ assert len(tools_loaded) > 0
43
49
 
44
50
  def version_checker(
45
51
  obj: eda_base.Tool, chk_str: str
@@ -49,7 +55,7 @@ def test_tools_loaded():
49
55
  assert chk_str in full_ver, f'{chk_str=} not in {full_ver=}'
50
56
  ver_num = full_ver.rsplit(':', maxsplit=1)[-1]
51
57
  if 'b' in ver_num:
52
- ver_num = ver_num.split('b')[0] # TODO(chaitanya): remove once cocotb 2.0 is released
58
+ ver_num = ver_num.split('b')[0]
53
59
  if '.' in ver_num:
54
60
  major_ver = ver_num.split('.')[0]
55
61
  assert major_ver.isdigit(), (
opencos/tools/cocotb.py CHANGED
@@ -10,7 +10,9 @@ import subprocess
10
10
  from opencos import util
11
11
  from opencos.eda_base import Tool
12
12
  from opencos.commands import CommandSim
13
+ from opencos.utils import status_constants
13
14
  from opencos.utils.str_helpers import sanitize_defines_for_sh
15
+ from opencos.tools import verilator # For default waivers.
14
16
 
15
17
 
16
18
  class ToolCocotb(Tool):
@@ -76,6 +78,8 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
76
78
  'tcl-file': None,
77
79
  'cocotb-test-module': None,
78
80
  'cocotb-test-runner': 'python',
81
+ 'cocotb-test-runner-file': '',
82
+ 'cocotb-test-run-dir': None, # If None, use self.args['work-dir']
79
83
  'cocotb-simulator': 'verilator',
80
84
  'cocotb-makefile': False,
81
85
  'cocotb-python-runner': True,
@@ -86,17 +90,25 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
86
90
  'waves': 'Include waveforms by setting COCOTB_ENABLE_WAVES=1',
87
91
  'cocotb-test-module': 'Python test module name (e.g., test_my_design)',
88
92
  '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'),
93
+ 'cocotb-test-runner-file': 'Bring your own test_runner.py file',
94
+ 'cocotb-test-run-dir': (
95
+ 'Directory where cocotb-test-runner will run, if unset uses default eda.work/(name)'
96
+ ),
97
+ 'cocotb-simulator': (
98
+ 'Simulator backend: verilator (default), icarus, etc.'
99
+ ' Note that iverilog will convert to icarus here'
100
+ ),
91
101
  'cocotb-makefile': 'Use traditional Makefile system instead of Python runner',
92
102
  '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'),
103
+ 'cocotb-standalone-makefile': (
104
+ 'Use provided Makefile as-is, run make in source directory'
105
+ ),
95
106
  })
96
107
 
97
108
  self.cocotb_command_lists = []
98
109
  self.cocotb_test_files = []
99
110
 
111
+
100
112
  def set_tool_defines(self):
101
113
  ToolCocotb.set_tool_defines(self)
102
114
 
@@ -155,12 +167,13 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
155
167
  simulate_sh_fname='cocotb_test.sh'
156
168
  )
157
169
 
170
+
158
171
  def _find_cocotb_test_files(self):
159
172
  '''Find Python test files that contain cocotb tests'''
160
173
  self.cocotb_test_files = []
161
174
 
162
175
  # Look for test files in the current directory and deps
163
- for file_path in self.files_non_source:
176
+ for file_path in self.files_non_source + self.files_py:
164
177
  if (file_path.endswith('.py') and
165
178
  ('test' in file_path.lower() or 'tb' in file_path.lower())):
166
179
  # Check if it's a cocotb test file
@@ -193,8 +206,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
193
206
  def _prepare_python_runner_system(self):
194
207
  '''Prepare cocotb using the Python-based runner system (cocotb 1.8+)'''
195
208
 
196
- # Create a Python runner script
197
- runner_script = self._create_python_runner_script()
209
+ # Create a Python runner script, or use from --cocotb-test-runner-file
210
+ if self.args['cocotb-test-runner-file']:
211
+
212
+ runner_script = self.args['cocotb-test-runner-file']
213
+ if not os.path.isfile(self.args['cocotb-test-runner-file']):
214
+ self.error(
215
+ "File does not exist, for: --cocotb-test-runner-file=",
216
+ f"{self.args['cocotb-test-runner-file']}",
217
+ error_code=status_constants.EDA_GENERAL_FILE_NOT_FOUND
218
+ )
219
+ return
220
+ else:
221
+ runner_script = self._create_python_runner_script()
198
222
 
199
223
  if self.args['cocotb-test-runner'] == 'pytest':
200
224
  # Use pytest to run the tests
@@ -212,28 +236,41 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
212
236
  # Set environment variables
213
237
  env_vars = self._get_cocotb_env_vars()
214
238
 
215
- # Create command with environment variables
216
- command_list = self._create_env_command(env_vars) + cmd_list
217
- self.cocotb_command_lists = [util.ShellCommandList(command_list,
218
- tee_fpath='cocotb_test.log')]
239
+ # Create command with environment variables, needs to be list-of-(cmd_list)
240
+ self.cocotb_command_lists = [
241
+ util.ShellCommandList(
242
+ self._create_env_command(env_vars) + cmd_list,
243
+ tee_fpath='cocotb_test.log',
244
+ # If someone wanted to run this from a specific dir, then
245
+ # run it from there.
246
+ work_dir=self.args['cocotb-test-run-dir']
247
+ )
248
+ ]
219
249
 
220
250
  def _prepare_makefile_system(self):
221
251
  '''Prepare cocotb using the traditional Makefile system'''
222
252
 
223
253
  makefile_path = os.path.join(self.args['work-dir'], 'Makefile')
254
+ self.files_makefile.append(makefile_path)
224
255
  with open(makefile_path, 'w', encoding='utf-8') as f:
225
256
  f.write(self._create_makefile_content())
226
257
 
227
258
  cmd_list = self._create_shell_command_with_success('make -f Makefile')
228
- self.cocotb_command_lists = [util.ShellCommandList(cmd_list,
229
- tee_fpath='cocotb_makefile.log')]
259
+ self.cocotb_command_lists = [
260
+ util.ShellCommandList(
261
+ cmd_list, tee_fpath='cocotb_makefile.log',
262
+ # If someone wanted to run this from a specific dir, then
263
+ # run it from there.
264
+ work_dir=self.args['cocotb-test-run-dir']
265
+ )
266
+ ]
230
267
 
231
268
  def _prepare_standalone_makefile_system(self):
232
269
  '''Use provided Makefile as-is, run make in source directory'''
233
270
 
234
271
  # Find the Makefile in our dependencies
235
272
  makefile_path = None
236
- for file_path in self.files_non_source:
273
+ for file_path in self.files_non_source + self.files_makefile:
237
274
  if os.path.basename(file_path).lower() == 'makefile':
238
275
  makefile_path = file_path
239
276
  break
@@ -241,10 +278,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
241
278
  if not makefile_path:
242
279
  self.error('No Makefile found in deps for --cocotb-standalone-makefile')
243
280
 
281
+ if any(value for key,value in self.args.items() if key.startswith('export')):
282
+ util.warning(f'Using an --export arg with Cocotb standalong Makefile {makefile_path}',
283
+ 'is not fully supported')
284
+
244
285
  makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
245
- cmd_list = self._create_shell_command_with_success(f'cd {makefile_dir} && make')
246
- self.cocotb_command_lists = [util.ShellCommandList(
247
- cmd_list, tee_fpath='cocotb_standalone.log')]
286
+ cmd_list = self._create_shell_command_with_success('make')
287
+ self.cocotb_command_lists = [
288
+ util.ShellCommandList(
289
+ cmd_list,
290
+ tee_fpath='cocotb_standalone.log',
291
+ work_dir=makefile_dir
292
+ )
293
+ ]
248
294
 
249
295
  def _get_test_module_name(self) -> str:
250
296
  '''Get the test module name from args or detected files'''
@@ -280,8 +326,22 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
280
326
 
281
327
  return script_path
282
328
 
329
+
283
330
  def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
284
331
  '''Generate the content for the Python runner script'''
332
+
333
+ if shutil.which('verilator'):
334
+ # TODO(drew): this shortcuts if verilator is truly usable,
335
+ # consider using eda_tool_helper to get "tools_loaded", which
336
+ # is not set in self.config['tools_loaded'] when --tool=cocotb.
337
+ # Would need minor refactor for eda.py methods auto_tools_order,
338
+ # tool_setup to go into eda_tool_helper.py, and those methods would
339
+ # need hooks to be non-destructive to config.
340
+ tmp_verilator_obj = verilator.VerilatorSim(config=self.config)
341
+ verilator_waivers = tmp_verilator_obj.get_verilator_tool_config_waivers()
342
+ else:
343
+ verilator_waivers = []
344
+
285
345
  return f'''#!/usr/bin/env python3
286
346
  """
287
347
  Cocotb test runner script generated by opencos
@@ -337,6 +397,8 @@ def run_cocotb_test():
337
397
 
338
398
  if simulator == "verilator":
339
399
  build_args.extend({list(self.args.get('verilate-args', []))!r})
400
+ build_args.extend({list(self.args.get('compile-args', []))!r})
401
+ build_args.extend({list(verilator_waivers)!r})
340
402
 
341
403
  # Build the design
342
404
  runner.build(
@@ -449,9 +511,20 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
449
511
  '''Get environment variables for cocotb execution'''
450
512
  env_vars = {}
451
513
 
514
+ top = self.args.get('top', 'top')
515
+
452
516
  # Basic cocotb configuration
453
- env_vars['SIM'] = self.args['cocotb-simulator']
454
- env_vars['TOPLEVEL'] = self.args.get('top', 'top')
517
+ env_vars.update({
518
+ 'SIM': self.args['cocotb-simulator'],
519
+ 'TOPLEVEL': top,
520
+ 'COCOTB_TOPLEVEL': top,
521
+ })
522
+
523
+ if test_module := self.args['cocotb-test-module']:
524
+ env_vars.update({
525
+ 'MODULE': test_module,
526
+ 'COCOTB_TEST_MODULES': test_module,
527
+ })
455
528
 
456
529
  # Enable waves if requested
457
530
  if self.args.get('waves', False):
@@ -464,8 +537,8 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
464
537
  env_vars['COCOTB_LOG_LEVEL'] = 'INFO'
465
538
 
466
539
  # Random seed
467
- if self.args.get('seed'):
468
- env_vars['COCOTB_RANDOM_SEED'] = str(self.args['seed'])
540
+ if seed := self.args.get('seed'):
541
+ env_vars['COCOTB_RANDOM_SEED'] = str(seed)
469
542
 
470
543
  return env_vars
471
544
 
@@ -76,7 +76,6 @@ class VerilatorSim(CommandSim, ToolVerilator):
76
76
  ToolVerilator.__init__(self, config=self.config)
77
77
  self.args.update({
78
78
  'gui': False,
79
- 'tcl-file': None,
80
79
  'dump-vcd': False,
81
80
  'waves-fst': True,
82
81
  'waves-vcd': False,
@@ -95,7 +94,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
95
94
  ' plusarg and apply $dumpfile("dump.fst").'
96
95
  ),
97
96
  'waves-fst': (
98
- '(Default True) If using --waves, apply simulation runtime arg +trace.'
97
+ 'If using --waves, apply simulation runtime arg +trace.'
99
98
  ' Note that if you do not have SV code using $dumpfile, eda will add'
100
99
  ' _waves_pkg.sv to handle this for you with +trace runtime plusarg.'
101
100
  ),
@@ -228,7 +227,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
228
227
  warnings=(not lint_only), add_uvm_pkg_if_found=True
229
228
  )
230
229
 
231
- verilate_command_list += self._get_verilator_tool_config_waivers()
230
+ verilate_command_list += self.get_verilator_tool_config_waivers()
232
231
 
233
232
  verilate_command_list += self._verilator_args_defaults_cflags_nproc()
234
233
 
@@ -408,7 +407,10 @@ class VerilatorSim(CommandSim, ToolVerilator):
408
407
 
409
408
  return verilate_command_list
410
409
 
411
- def _get_verilator_tool_config_waivers(self) -> list:
410
+ def get_verilator_tool_config_waivers(self) -> list:
411
+ '''Returns list of args to verilator for waviers, from --compile-waivers and
412
+
413
+ --config-yml for tool: (config)'''
412
414
 
413
415
  # Add compile waivers from self.config (tools.verilator.compile-waivers list):
414
416
  # list(set(mylist)) to get unique.
opencos/tools/yosys.py CHANGED
@@ -70,9 +70,9 @@ class ToolYosys(Tool):
70
70
  [self.sta_exe, '-version'], capture_output=True, check=False
71
71
  )
72
72
  util.debug(f'{self.yosys_exe} {sta_version_ret=}')
73
- sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()[0]
74
- if sta_ver:
75
- self.sta_version = sta_ver
73
+ sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()
74
+ if sta_ver and isinstance(sta_ver, list):
75
+ self.sta_version = sta_ver[0]
76
76
 
77
77
  version_ret = subprocess.run(
78
78
  [self.yosys_exe, '--version'], capture_output=True, check=False