opencos-eda 0.3.3__py3-none-any.whl → 0.3.6__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/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
 
opencos/tools/iverilog.py CHANGED
@@ -160,6 +160,10 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
160
160
  cmd_list += self.tool_config.get('simulate-args', '').split()
161
161
  if self.args['waves']:
162
162
  cmd_list += self.tool_config.get('simulate-waves-args', '').split()
163
+ for x in self.args['sim-plusargs']:
164
+ if x[0] != '+':
165
+ x = f'+{x}'
166
+ cmd_list.append(x)
163
167
  return [ util.ShellCommandList(cmd_list, tee_fpath='sim.log') ]
164
168
 
165
169
  def get_post_simulate_command_lists(self, **kwargs) -> list:
opencos/tools/riviera.py CHANGED
@@ -13,6 +13,7 @@ import subprocess
13
13
  from opencos import util
14
14
  from opencos.tools.modelsim_ase import ToolModelsimAse, CommandSimModelsimAse
15
15
  from opencos.utils.str_helpers import sanitize_defines_for_sh
16
+ from opencos.utils import status_constants
16
17
 
17
18
  class ToolRiviera(ToolModelsimAse):
18
19
  '''ToolRiviera used by opencos.eda for --tool=riviera'''
@@ -61,6 +62,7 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
61
62
  'gui': False,
62
63
  'waves-fst': True,
63
64
  'waves-vcd': False,
65
+ 'coverage-tcl': '',
64
66
  })
65
67
  self.args_help.update({
66
68
  'waves-fst': (
@@ -70,6 +72,10 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
70
72
  ),
71
73
  'waves-vcd': 'If using --waves, apply simulation runtime arg +trace=vcd',
72
74
  'waves': 'Save a .asdb offline wavefile, can be used with --waves-fst or --waves-vcd',
75
+ 'coverage-tcl': (
76
+ 'bring your own .tcl file to run in Riviera (vsim) for coverage. The default'
77
+ ' tcl steps are (from tool config in --config-yml): '
78
+ ) + '; '.join(self.tool_config.get('simulate-coverage-tcl', [])),
73
79
  })
74
80
 
75
81
  def set_tool_defines(self):
@@ -183,7 +189,9 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
183
189
 
184
190
 
185
191
 
186
- def write_vsim_dot_do(self, dot_do_to_write: list) -> None:
192
+ def write_vsim_dot_do( # pylint: disable=too-many-branches
193
+ self, dot_do_to_write: list
194
+ ) -> None:
187
195
  '''Writes files(s) based on dot_do_to_write(list of str)
188
196
 
189
197
  list arg values can be empty (all) or have items 'all', 'sim', 'lint', 'vlog'.'''
@@ -278,18 +286,21 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
278
286
  "}",
279
287
  ]
280
288
 
289
+ vsim_dot_do_lines += [
290
+ "run -all;",
291
+ ]
281
292
  if self.args['coverage']:
282
- vsim_dot_do_lines += [
283
- "run -all;",
284
- "acdb save",
285
- "acdb report -db work.acdb -txt -o cov.txt",
286
- # Note - could try:
287
- ##"cover report -o cov.report.txt -fullverbose -all_columns",
288
- ]
289
- else:
290
- vsim_dot_do_lines += [
291
- "run -all;",
292
- ]
293
+ if self.args['coverage-tcl']:
294
+ tclfile = os.path.abspath(self.args['coverage-tcl'])
295
+ if not os.path.isfile(tclfile):
296
+ self.error(f'--coverage-tcl file not found: {tclfile}',
297
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
298
+ vsim_dot_do_lines += [
299
+ f'source {tclfile}'
300
+ ]
301
+ else:
302
+ # default TCL for coverage:
303
+ vsim_dot_do_lines += self.tool_config.get('simulate-coverage-tcl', [])
293
304
 
294
305
 
295
306
  vsim_dot_do_lines += [
@@ -83,6 +83,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
83
83
  'lint-only': False,
84
84
  'cc-mode': False,
85
85
  'verilator-coverage-args': [],
86
+ 'uvm': False,
86
87
  'x-assign': '',
87
88
  'x-initial': '',
88
89
  })
@@ -116,6 +117,15 @@ class VerilatorSim(CommandSim, ToolVerilator):
116
117
  ' where valid string values are: 0, unique, fast.'
117
118
  ' Also conditinally adds to verilated exe call:'
118
119
  ' +verilator+rand+reset+[0,2] for arg values 0, unique|fast'),
120
+ 'uvm': (
121
+ 'Warns on Verilator < 5.042, or missing $UVM_HOME environment var set (or in'
122
+ ' .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator with args:'
123
+ ' -Wno-fatal +define+UVM_NO_DPI'
124
+ ),
125
+ 'verilator-coverage-args': (
126
+ 'Requires --coverage, args to be applied to verilator_coverage, which runs'
127
+ ' after running the compiled executable simulation'
128
+ ),
119
129
  })
120
130
 
121
131
  self.verilate_command_lists = []
@@ -212,12 +222,19 @@ class VerilatorSim(CommandSim, ToolVerilator):
212
222
 
213
223
  verilate_command_list = self._get_start_verilator_command_list(lint_only=lint_only)
214
224
 
215
- verilate_command_list += self._get_verilator_tool_config_waivers()
225
+ # Handle UVM things (return args), but also handles uvm_pkg.sv in self.files_sv:
226
+ # since we run this 2x (lint-only and normal) only do warnings for one of them:
227
+ verilate_command_list += self._verilator_args_uvm(
228
+ warnings=(not lint_only), add_uvm_pkg_if_found=True
229
+ )
230
+
231
+ verilate_command_list += self.get_verilator_tool_config_waivers()
216
232
 
217
233
  verilate_command_list += self._verilator_args_defaults_cflags_nproc()
218
234
 
219
235
  verilate_command_list += self._get_verilator_waves_args(lint_only=lint_only)
220
236
 
237
+
221
238
  if self.args.get('coverage', True):
222
239
  verilate_command_list += self.tool_config.get(
223
240
  'compile-coverage-args', '--coverage').split()
@@ -391,7 +408,10 @@ class VerilatorSim(CommandSim, ToolVerilator):
391
408
 
392
409
  return verilate_command_list
393
410
 
394
- def _get_verilator_tool_config_waivers(self) -> list:
411
+ def get_verilator_tool_config_waivers(self) -> list:
412
+ '''Returns list of args to verilator for waviers, from --compile-waivers and
413
+
414
+ --config-yml for tool: (config)'''
395
415
 
396
416
  # Add compile waivers from self.config (tools.verilator.compile-waivers list):
397
417
  # list(set(mylist)) to get unique.
@@ -486,6 +506,75 @@ class VerilatorSim(CommandSim, ToolVerilator):
486
506
  return verilate_args
487
507
 
488
508
 
509
+ def _verilator_support_uvm_pkg_fpath(self, add_if_found: bool = True) -> bool:
510
+ '''Returns False if we could not find a suitable uvm_pkg.sv to use, or if --no-uvm.
511
+
512
+ This will also auto-add uvm_pkg.sv from $UVM_HOME/uvm_pkg.sv if not present in
513
+ self.files already (adds to front of self.files_sv)
514
+ '''
515
+
516
+ if not self.args['uvm']:
517
+ return False
518
+
519
+ for fname, exists in self.files.items():
520
+ if exists and os.path.split(fname)[1] == 'uvm_pkg.sv':
521
+ # already present in our source files (assume someone doing it manually
522
+ # or via DEPS)
523
+ return True
524
+
525
+ uvm_home = os.environ.get('UVM_HOME', '')
526
+ if not uvm_home:
527
+ return False
528
+
529
+ uvm_pkg_fpath = os.path.join(uvm_home, 'uvm_pkg.sv')
530
+ if add_if_found and os.path.isfile(uvm_pkg_fpath):
531
+ uvm_pkg_fpath = os.path.abspath(uvm_pkg_fpath)
532
+ util.info(f'For --uvm, adding to source files: {uvm_pkg_fpath}')
533
+ self.files[uvm_pkg_fpath] = True
534
+ self.files_sv.insert(0, uvm_pkg_fpath)
535
+ self.files_caller_info[uvm_pkg_fpath] = 'verilator.py'
536
+ util.info(f'For --uvm, adding +incdir+: {uvm_home}')
537
+ self.incdirs.append(os.path.abspath(uvm_home))
538
+ return True
539
+
540
+ return False
541
+
542
+
543
+ def _verilator_args_uvm(
544
+ self, warnings: bool = True, add_uvm_pkg_if_found: bool = True
545
+ ) -> list:
546
+ '''Returns list of args to be added to verilator (compile) step if --uvm present
547
+
548
+ Warnings on potential issues (Veriltor version, missing uvm_pkg.sv).
549
+ Optionally adds uvm_pkg.sv to source files.
550
+ '''
551
+
552
+ # Handle --uvm args:
553
+ if not self.args['uvm']:
554
+ return []
555
+
556
+ if warnings:
557
+
558
+ # prefers Verilator >= v5.042, $UVM_HOME to be set, or warning.
559
+ version_list = self._VERSION.split('.')
560
+ if int(version_list[0]) < 5 or \
561
+ (int(version_list[0]) == 5 and int(version_list[1]) < 42):
562
+ util.warning(f'Verilator version is {self._VERSION}, --uvm set prefers Verilator',
563
+ 'version > v5.042')
564
+
565
+ if not os.environ.get('UVM_HOME', ''):
566
+ util.warning('--uvm set, however env (or .env or --env-file) $UVM_HOME is not set')
567
+
568
+ uvm_pkg_found = self._verilator_support_uvm_pkg_fpath(add_if_found=add_uvm_pkg_if_found)
569
+ if warnings and not uvm_pkg_found:
570
+ util.warning(
571
+ '--uvm set, however no suitable uvm_pkg.sv is source files,',
572
+ f'nor in $UVM_HOME/uvm_pkg.sv. $UVM_HOME={os.environ.get("UVM_HOME", "")}'
573
+ )
574
+
575
+ return ['-Wno-fatal', '+define+UVM_NO_DPI']
576
+
577
+
489
578
  def artifacts_add(self, name: str, typ: str, description: str) -> None:
490
579
  '''Override from Command.artifacts_add, so we can catch known file
491
580
 
opencos/tools/vivado.py CHANGED
@@ -272,6 +272,7 @@ class CommandSimVivado(CommandSim, ToolVivado):
272
272
  xsim_plusargs_list.append('--testplusarg')
273
273
  if x[0] == '+':
274
274
  x = x[1:]
275
+ x = x.replace('"', '\\\"') # we have to preserve " in the value.
275
276
  xsim_plusargs_list.append(f'\"{x}\"')
276
277
 
277
278
  # execute snapshot