opencos-eda 0.3.3__py3-none-any.whl → 0.3.5__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/deps/defaults.py CHANGED
@@ -33,6 +33,7 @@ SUPPORTED_TARGET_TABLE_KEYS = set([
33
33
  'defines',
34
34
  'parameters',
35
35
  'incdirs',
36
+ 'plusargs',
36
37
  'top',
37
38
  'deps',
38
39
  'reqs',
@@ -77,7 +77,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
77
77
  ).get('command_handler', {}).keys()
78
78
 
79
79
 
80
- def apply_defines(self, defines_dict: dict):
80
+ def apply_defines(self, defines_dict: dict) -> None:
81
81
  '''Given defines_dict, applies them to our self.command_design_ref obj'''
82
82
  if not isinstance(defines_dict, dict):
83
83
  self.error(f"{defines_dict=} is not type dict, can't apply defines,",
@@ -95,7 +95,19 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
95
95
  self.command_design_ref.process_plusarg(f'+define+{k}={v}')
96
96
 
97
97
 
98
- def apply_parameters(self, parameters_dict: dict):
98
+ def apply_plusargs(self, plusargs_dict: dict) -> None:
99
+ '''Given plusarsg_dict, applies them to our self.command_design_ref obj'''
100
+ if not isinstance(plusargs_dict, dict):
101
+ self.error(f"{plusargs_dict=} is not type dict, can't apply plusargs,",
102
+ f"in {self.caller_info}")
103
+ for k,v in plusargs_dict.items():
104
+ if v is None or v == '':
105
+ self.command_design_ref.process_plusarg(f'+{k}')
106
+ else:
107
+ self.command_design_ref.process_plusarg(f'+{k}={v}')
108
+
109
+
110
+ def apply_parameters(self, parameters_dict: dict) -> None:
99
111
  '''Given parameters_dict, applies them to our self.command_design_ref obj'''
100
112
  if not isinstance(parameters_dict, dict):
101
113
  self.error(f"{parameters_dict=} is not type dict, can't apply defines,",
@@ -110,7 +122,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
110
122
  )
111
123
 
112
124
 
113
- def apply_incdirs(self, incdirs_list:list):
125
+ def apply_incdirs(self, incdirs_list:list) -> None:
114
126
  '''Given incdirs_list, applies them to our self.command_design_ref obj'''
115
127
  if not isinstance(incdirs_list, (str, list)):
116
128
  self.error(f"{incdirs_list=} is not type str/list, can't apply incdirs",
@@ -290,7 +302,9 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
290
302
  caller_info=self.caller_info
291
303
  )
292
304
 
293
- def process_deps_entry(self):
305
+ def process_deps_entry( # pylint: disable=too-many-branches
306
+ self
307
+ ) -> list:
294
308
  '''Main entry point (after creating DepsProcessor obj) to resolve a deps target
295
309
 
296
310
  Example usage:
@@ -303,22 +317,6 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
303
317
  This method will apply all target features to the CommandDesign ref object as
304
318
  we traverse.
305
319
 
306
- Supported target keys:
307
- -- tags (or equivalent, to support multiple define/incdir/deps for a target)
308
- -- supports tag-name, with-tools, with-args, args, defines, incdirs, deps
309
- ** to be applied if a tool matches.
310
- -- TODO(drew): other features in docs/DEPS.md not yet implemented.
311
- -- multi: ignore-this-target: - commands (handled in eda.py CommandMulti.resolve_target)
312
- -- Named eda commands
313
- -- (partially done) sim or other eda commands (eda.py command specific things)
314
- basically, check the command, and apply/merge values to 'entry'?
315
- -- args
316
- -- defines
317
- -- incdirs
318
- -- top.
319
- -- commands (not in deps)
320
- -- deps
321
-
322
320
  TODO(drew): This does not yet support conditional inclusions based on defines,
323
321
  like the old DEPS files did with pattern:
324
322
  SOME_DEFINE ? dep_if_define_present : dep_if_define_not_present
@@ -344,6 +342,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
344
342
  remaining_deps_list += self.process_tags()
345
343
  elif key == 'defines':
346
344
  self.process_defines()
345
+ elif key == 'plusargs':
346
+ self.process_plusargs()
347
347
  elif key == 'parameters':
348
348
  self.process_parameters()
349
349
  elif key == 'incdirs':
@@ -520,6 +520,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
520
520
  # apply defines:
521
521
  self.apply_defines(value.get('defines', {}))
522
522
 
523
+ if key == 'plusargs':
524
+ # apply plusargs:
525
+ self.apply_plusargs(value.get('plusargs', {}))
526
+
523
527
  elif key == 'parameters':
524
528
  self.apply_parameters(value.get('parameters', {}))
525
529
 
@@ -598,7 +602,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
598
602
  return ret_deps_added_from_tags
599
603
 
600
604
 
601
- def process_defines(self):
605
+ def process_defines(self) -> None:
602
606
  '''Returns None, applies defines (dict, if any) from self.deps_entry to
603
607
  self.command_design_ref.'''
604
608
 
@@ -614,7 +618,28 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
614
618
 
615
619
  self.apply_defines(entry_defines)
616
620
 
617
- def process_parameters(self):
621
+
622
+ def process_plusargs(self) -> None:
623
+ '''Returns None, applies plusargs (dict, if any) from self.deps_entry to
624
+ self.command_design_ref.
625
+
626
+ These work w/ the same rules as defines (no value, or value int/str)
627
+ '''
628
+
629
+ # Plusargs:
630
+ # apply command specific plusargs, with higher priority than the a
631
+ # deps_entry['sim']['plusargs'] entry,
632
+ # do this with dict1.update(dict2):
633
+ entry_plusargs = {}
634
+ entry_plusargs.update(self.deps_entry.get('plusargs', {}))
635
+ entry_plusargs.update(self.entry_eda_command.get('plusargs', {}))
636
+ assert isinstance(entry_plusargs, dict), \
637
+ f'{entry_plusargs=} for in {self.caller_info} must be a dict'
638
+
639
+ self.apply_plusargs(entry_plusargs)
640
+
641
+
642
+ def process_parameters(self) -> None:
618
643
  '''Returns None, applies parameters (dict, if any) from self.deps_entry to
619
644
  self.command_design_ref.'''
620
645
 
opencos/deps_schema.py CHANGED
@@ -17,6 +17,9 @@ my_target_name:
17
17
  incdirs: <---- incdirs, optional array (or string)
18
18
  - ./
19
19
 
20
+ plusargs: <---- plusargs, optional table
21
+ some_plusarg: 32
22
+
20
23
  top: tb <---- top, optional string
21
24
 
22
25
  deps:
@@ -63,6 +66,7 @@ my_target_name:
63
66
  defines: <---- defines, optional table
64
67
  parameters: <---- parameters, optional table
65
68
  incdirs: <---- incdirs, optional array (or string)
69
+ plusargs: <---- plusargs, optional table
66
70
  top: tb <---- top, optional string
67
71
  deps: <---- TARGET_DEPS_CONTENTS schema
68
72
  - some_file.sv <---- string file name
@@ -216,6 +220,9 @@ TARGET_EDA_COMMAND_ENTRY_TABLE = {
216
220
  Optional('defines'): {
217
221
  Optional(str): Or(str, int, type(None)),
218
222
  },
223
+ Optional('plusargs'): {
224
+ Optional(str): Or(str, int, type(None)),
225
+ },
219
226
  Optional('parameters'): {
220
227
  Optional(str): Or(str, int),
221
228
  },
@@ -241,6 +248,9 @@ TARGET_TAGS_TABLE = {
241
248
  Optional('defines'): {
242
249
  Optional(str): Or(str, int, type(None)),
243
250
  },
251
+ Optional('plusargs'): {
252
+ Optional(str): Or(str, int, type(None)),
253
+ },
244
254
  Optional('parameters'): {
245
255
  Optional(str): Or(str, int),
246
256
  },
@@ -262,6 +272,10 @@ TARGET_CONTENTS = Or(
262
272
  Optional('defines'): {
263
273
  Optional(str): Or(str, int, type(None)),
264
274
  },
275
+ # plusargs: table of key-value; value null or string
276
+ Optional('plusargs'): {
277
+ Optional(str): Or(str, int, type(None)),
278
+ },
265
279
  # parameters: table of key-value
266
280
  Optional('parameters'): {
267
281
  Optional(str): Or(str, int),
@@ -324,6 +338,9 @@ FILE_SIMPLIFIED = Schema(
324
338
  Optional('defines'): {
325
339
  Optional(str): Or(type(None), str),
326
340
  },
341
+ Optional('plusargs'): {
342
+ Optional(str): Or(type(None), str),
343
+ },
327
344
  Optional('parameters'): {
328
345
  Optional(str): str,
329
346
  },
opencos/eda.py CHANGED
@@ -23,7 +23,7 @@ from opencos import util, eda_config, eda_base
23
23
  from opencos.eda_base import Tool, which_tool, get_eda_exec
24
24
  from opencos.utils import vsim_helper, vscode_helper
25
25
  from opencos.utils.subprocess_helpers import subprocess_run_background
26
- from opencos.utils import status_constants, str_helpers
26
+ from opencos.utils import status_constants, str_helpers, subprocess_helpers
27
27
 
28
28
  # Configure util:
29
29
  util.progname = "EDA"
@@ -50,7 +50,7 @@ def init_config(
50
50
  # For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
51
51
  # the actual class using importlib (via opencos.util)
52
52
 
53
- eda_config.tool_try_add_to_path(tool)
53
+ eda_config.update_config_auto_tool_order_for_tool(tool=tool, config=config)
54
54
 
55
55
  config['command_handler'] = {}
56
56
  for _cmd, str_class in config['DEFAULT_HANDLERS'].items():
@@ -163,10 +163,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
163
163
  If so, updates config['auto_tools_order'][tool]['exe']
164
164
  '''
165
165
 
166
-
167
- tool = eda_config.update_config_auto_tool_order_for_tool(
168
- tool=tool, config=config
169
- )
166
+ tool = eda_config.tool_arg_remove_path_information(tool)
170
167
 
171
168
  assert 'auto_tools_order' in config
172
169
  assert isinstance(config['auto_tools_order'], list)
@@ -275,9 +272,7 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
275
272
 
276
273
  '''
277
274
 
278
- tool = eda_config.update_config_auto_tool_order_for_tool(
279
- tool=tool, config=config
280
- )
275
+ tool = eda_config.tool_arg_remove_path_information(tool)
281
276
 
282
277
  if not quiet and not auto_setup:
283
278
  util.info(f"Setup for tool: '{tool}'")
@@ -389,6 +384,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
389
384
  if not is_interactive:
390
385
  # Run init_config() now, we deferred it in main(), but only run it
391
386
  # for this tool (or tool=None to figure it out)
387
+ # This will handle any --tool=<name>=/path/to/bin also, so don't have to
388
+ # run tool_setup(..) on its own.
392
389
  config = init_config(
393
390
  config, tool=parsed.tool,
394
391
  run_auto_tool_setup=run_auto_tool_setup
@@ -406,13 +403,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
406
403
  for arg in unparsed:
407
404
  if not arg.startswith('-'):
408
405
  command = arg
409
- if parsed.tool:
410
- tool_setup(parsed.tool, config=config)
411
406
  return usage(tokens=unparsed, config=config, command=command, tool=parsed.tool)
412
407
 
413
- if parsed.tool:
414
- tool_setup(parsed.tool, config=config)
415
-
416
408
  deferred_tokens = unparsed
417
409
  if not command:
418
410
  util.error("Didn't get a command!")
@@ -504,6 +496,7 @@ def signal_handler(sig, frame) -> None: # pylint: disable=unused-argument
504
496
  '''Handles Ctrl-C, called by main_cli() if running from command line'''
505
497
  util.fancy_stop()
506
498
  util.info('Received Ctrl+C...', start='\nINFO: [EDA] ')
499
+ subprocess_helpers.cleanup_all()
507
500
  util.exit(-1)
508
501
 
509
502
  # **************************************************************
opencos/eda_base.py CHANGED
@@ -594,6 +594,7 @@ class Command: # pylint: disable=too-many-public-methods
594
594
  help_kwargs = {'help': f'default={value}'}
595
595
  else:
596
596
  help_kwargs = {'help': f'{type(value).__name__} default={value}'}
597
+ help_kwargs['help'] = help_kwargs['help'].replace('%', '%%')
597
598
 
598
599
 
599
600
  # It's important to set the default=None on these, except for list types where default
opencos/eda_config.py CHANGED
@@ -18,7 +18,10 @@ from opencos import util
18
18
  from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
19
19
 
20
20
  class Defaults:
21
- '''Defaults is a global placeholder for constants and supported features.'''
21
+ '''Defaults is a global namespace for constants and supported features.
22
+
23
+ Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
24
+ '''
22
25
 
23
26
  environ_override_config_yml = os.environ.get('EDA_CONFIG_YML', '')
24
27
  home_override_config_yml = os.path.join(
@@ -63,14 +66,15 @@ class Defaults:
63
66
  'simulate-args',
64
67
  'simulate-waves-args',
65
68
  'simulate-waivers',
69
+ 'simulate-coverage-tcl',
66
70
  'coverage-args',
67
71
  ])
68
72
 
69
73
  EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
70
74
 
71
- if os.path.exists(Defaults.environ_override_config_yml):
75
+ if os.path.isfile(Defaults.environ_override_config_yml):
72
76
  Defaults.config_yml = Defaults.environ_override_config_yml
73
- elif os.path.exists(Defaults.home_override_config_yml):
77
+ elif os.path.isfile(Defaults.home_override_config_yml):
74
78
  Defaults.config_yml = Defaults.home_override_config_yml
75
79
  else:
76
80
  Defaults.config_yml = Defaults.opencos_config_yml
@@ -149,29 +153,9 @@ def update_config_auto_tool_order_for_tool(tool: str, config: dict) -> str:
149
153
  Input arg tool can be in the form (for example):
150
154
  tool='verlator', tool='verilator=/path/to/verilator.exe'
151
155
 
152
- Performs no update if tool has no = in it. Returns tool (str) w/out = in it
156
+ Performs no update if tool has no = or : in it. Returns tool (str) w/out = in it
153
157
  '''
154
- if not tool or '=' not in tool:
155
- return tool
156
-
157
- tool, user_exe = tool.split('=')[0:2]
158
-
159
- user_exe = shutil.which(user_exe)
160
-
161
- # try adding to $PATH if in form --tool=/path/to/exe
162
- tool_try_add_to_path(tool)
163
-
164
- if tool not in config['auto_tools_order'][0]:
165
- return tool
166
- if not user_exe:
167
- return tool
168
-
169
- old_exe = config['auto_tools_order'][0][tool].get('exe', str())
170
- if isinstance(old_exe, list):
171
- config['auto_tools_order'][0][tool]['exe'][0] = user_exe
172
- else:
173
- config['auto_tools_order'][0][tool]['exe'] = user_exe
174
- return tool
158
+ return tool_try_add_to_path(tool=tool, config=config, update_config=True)
175
159
 
176
160
 
177
161
  def update_config_auto_tool_order_for_tools(tools: list, config: dict) -> list:
@@ -327,32 +311,92 @@ def write_eda_config_and_args(
327
311
  yaml_safe_writer(data=data, filepath=fullpath)
328
312
 
329
313
 
330
- def tool_try_add_to_path(tool: str) -> None:
331
- '''Since we support --tool=<name>=/path/to/bin/exe, attempt to prepend $PATH
314
+ def tool_arg_get_parts(tool: str) -> list:
315
+ '''Given a tool (str or None) that may be in form <name>=/path/to/something
316
+
317
+ Return the parts [<name>, <path>, ..]
318
+ '''
319
+ if not tool or ('=' not in tool and ':' not in tool):
320
+ return [tool]
321
+
322
+ if '=' in tool:
323
+ parts = tool.split('=')
324
+ else:
325
+ parts = tool.split(':')
326
+
327
+ return parts
328
+
329
+ def tool_arg_remove_path_information(tool: str) -> str:
330
+ '''Given a tool (str or None) that may be in form <name>=/path/to/something
331
+
332
+ Return the <name> only
333
+ '''
334
+ if not tool:
335
+ return tool
336
+ return tool_arg_get_parts(tool)[0]
337
+
338
+
339
+ def tool_try_add_to_path( # pylint: disable=too-many-branches
340
+ tool: str, config: dict, update_config: bool
341
+ ) -> str:
342
+ '''Since we support --tool=<name>=/path/to/bin[/exe], attempt to prepend $PATH
343
+
344
+ (also works for --tool=<name>:/path/to/bin[/exe] )
332
345
 
333
346
  with this information for this tool (which will nicely affect all subprocesses,
334
347
  but not wreck our original shell).'''
335
348
 
336
- if not tool or '=' not in tool:
337
- return
349
+ name_path_parts = tool_arg_get_parts(tool)
350
+ if len(name_path_parts) == 1:
351
+ return name_path_parts[0]
352
+
353
+ name, path_arg = name_path_parts[0:2]
354
+
355
+ if name not in config['auto_tools_order'][0]:
356
+ return name
357
+
358
+ config_exe = config['auto_tools_order'][0][name].get('exe', str())
359
+ if isinstance(config_exe, list):
360
+ orig_exe = config_exe[0]
361
+ else:
362
+ orig_exe = config_exe
338
363
 
339
- name, exe = tool.split('=')
340
- if os.path.isdir(name):
364
+ if path_arg and os.path.isfile(path_arg):
365
+ # Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe from path:
366
+ path, exe = os.path.split(path_arg)
367
+ elif path_arg and os.path.isdir(path_arg):
341
368
  # Someone passes us --tool=<name>=/path/to/bin/ (did not have exe)
342
- path = name
369
+ path, exe = path_arg, orig_exe
343
370
  else:
344
- # Someone passes us --tool=<name>=/path/to/bin/exe, remove the exe.
345
- path, _ = os.path.split(exe)
346
- if not path:
347
- return
371
+ path, exe = '', ''
372
+
373
+ if not path or not exe:
374
+ util.error(f'Can not find path or exe for --tool={tool}: {name=} path={path_arg}')
375
+ return name
348
376
 
349
377
  path = os.path.abspath(path)
350
378
  if os.path.isdir(path):
351
- paths = os.environ['PATH'].split(':')
379
+ paths = os.environ.get('PATH', '').split(':')
352
380
  if path not in paths:
353
- os.environ['PATH'] = path + ':' + os.environ['PATH']
354
381
  util.info(f'--tool={tool} has path information, prepending PATH with: {path}')
382
+ os.environ['PATH'] = path + ':' + os.environ.get('PATH', '')
355
383
  else:
356
384
  util.info(f'--tool={tool} has path information, but {path} already in $PATH')
357
- if exe and os.path.isfile(exe):
358
- util.info(f'--tool={tool} has path information, using exe {shutil.which(exe)}')
385
+
386
+ user_exe = os.path.join(path, exe)
387
+ if not os.access(user_exe, os.X_OK):
388
+ util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
389
+ return name
390
+
391
+ user_exe = shutil.which(exe)
392
+
393
+ if update_config:
394
+ if isinstance(config_exe, list):
395
+ config['auto_tools_order'][0][name]['exe'][0] = user_exe
396
+ else:
397
+ config['auto_tools_order'][0][name]['exe'] = user_exe
398
+ util.debug(f'For {tool=}, auto_tools_order config updated')
399
+
400
+ util.debug(f'For {tool=}, final {user_exe=}')
401
+
402
+ return name
@@ -247,6 +247,9 @@ tools:
247
247
  +accb +accr +access +r+w
248
248
  coverage-args: |
249
249
  -acdb -acdb_cov sbfectapm
250
+ simulate-coverage-tcl:
251
+ - acdb save
252
+ - acdb report -db work.acdb -txt -o cov.txt
250
253
 
251
254
 
252
255
  modelsim_ase:
File without changes
@@ -68,3 +68,22 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
68
68
  all_handler_commands[command].append(tool)
69
69
 
70
70
  return all_handler_commands
71
+
72
+
73
+ def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
74
+ '''Attempts to get a Command Handler's version given tool + eda_command'''
75
+
76
+ entry = config['auto_tools_order'][0].get(tool, {})
77
+ if not entry:
78
+ return ''
79
+
80
+ handler_name = entry.get('handlers', {}).get(eda_command, '')
81
+ if not handler_name:
82
+ return ''
83
+
84
+ module = util.import_class_from_string(handler_name)
85
+ obj = module(config=config)
86
+ if not getattr(obj, 'get_versions', None):
87
+ return ''
88
+
89
+ return obj.get_versions()
opencos/hw/oc_cli.py CHANGED
File without changes
opencos/tests/helpers.py CHANGED
@@ -16,6 +16,7 @@ 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(..)
18
18
  config, tools_loaded = eda_tool_helper.get_config_and_tools_loaded()
19
+ known_tool_versions = {}
19
20
 
20
21
 
21
22
  def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
@@ -32,6 +33,16 @@ def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
32
33
  status_constants.EDA_DEFAULT_ERROR
33
34
  )
34
35
 
36
+ def handle_tool_version(tool: str, eda_command: str, cfg: dict = config) -> None:
37
+ '''Attempts to use a command handler for tool + eda_command and we'll
38
+ track the version globally'''
39
+
40
+ if not known_tool_versions.get(tool, ''):
41
+ handler_version = eda_tool_helper.get_handler_tool_version(
42
+ tool=tool, eda_command=eda_command, config=cfg
43
+ )
44
+ known_tool_versions[tool] = handler_version
45
+
35
46
  def can_run_eda_command(*commands, cfg: dict = config) -> bool:
36
47
  '''Returns True if we have any installed tool that can run: eda <command>'''
37
48
  runnable = []
@@ -50,10 +61,36 @@ def can_run_eda_command(*commands, cfg: dict = config) -> bool:
50
61
  if entry and entry.get('disable-auto', False):
51
62
  # This tool cannot automatically run our command.
52
63
  return False
64
+ # While we're here, set known tool versions.
65
+ handle_tool_version(tool=tool, eda_command=command, cfg=cfg)
53
66
 
54
67
  runnable.append(True)
55
68
  return runnable and all(runnable)
56
69
 
70
+ def can_uvm(tool: str) -> bool:
71
+ '''Returns True if we can run UVM, per tool'''
72
+
73
+ if tool not in tools_loaded:
74
+ return False
75
+
76
+ if tool == 'verilator':
77
+ # requires UVM_HOME to be set with uvm_pkg.sv existing.
78
+ uvm_home = os.environ.get('UVM_HOME', '')
79
+ uvm_pkg = os.path.join(uvm_home, 'uvm_pkg.sv')
80
+ if not all((os.path.isdir(uvm_home), os.path.isfile(uvm_pkg))):
81
+ return False
82
+
83
+ handle_tool_version(tool=tool, eda_command='sim', cfg=config)
84
+ version_list = known_tool_versions.get('verilator', '').split('.')
85
+ if int(version_list[0]) < 5 or \
86
+ (int(version_list[0]) == 5 and int(version_list[1]) < 42):
87
+ return False
88
+
89
+ return True
90
+
91
+ # default return False:
92
+ return False
93
+
57
94
  def can_run_eda_sim(cfg: dict = config) -> bool:
58
95
  '''Returns True if we have any installed tool that can run: eda sim'''
59
96
  return can_run_eda_command('sim', cfg=cfg)
opencos/tests/test_eda.py CHANGED
@@ -100,9 +100,19 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
100
100
  def test_args_sim_tool_with_path(self):
101
101
  '''Test for calling a tool as --tool=<tool>=</path/to/tool-exe>'''
102
102
  verilator_fullpath = shutil.which('verilator')
103
+ verilator_path, _ = os.path.split(verilator_fullpath)
104
+
103
105
  chdir_remove_work_dir('../../lib/tests')
104
106
  rc = eda_sim_wrap('--tool', f'verilator={verilator_fullpath}', 'oclib_fifo_test')
105
- print(f'{rc=}')
107
+ assert rc == 0
108
+
109
+ rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
110
+ assert rc == 0
111
+
112
+ rc = eda_sim_wrap('--tool', f'verilator={verilator_path}', 'oclib_fifo_test')
113
+ assert rc == 0
114
+
115
+ rc = eda_sim_wrap('--tool', f'verilator:{verilator_fullpath}', 'oclib_fifo_test')
106
116
  assert rc == 0
107
117
 
108
118
  def test_args_sim_with_coverage(self):
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
 
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
+
215
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()
@@ -486,6 +503,75 @@ class VerilatorSim(CommandSim, ToolVerilator):
486
503
  return verilate_args
487
504
 
488
505
 
506
+ def _verilator_support_uvm_pkg_fpath(self, add_if_found: bool = True) -> bool:
507
+ '''Returns False if we could not find a suitable uvm_pkg.sv to use, or if --no-uvm.
508
+
509
+ This will also auto-add uvm_pkg.sv from $UVM_HOME/uvm_pkg.sv if not present in
510
+ self.files already (adds to front of self.files_sv)
511
+ '''
512
+
513
+ if not self.args['uvm']:
514
+ return False
515
+
516
+ for fname, exists in self.files.items():
517
+ if exists and os.path.split(fname)[1] == 'uvm_pkg.sv':
518
+ # already present in our source files (assume someone doing it manually
519
+ # or via DEPS)
520
+ return True
521
+
522
+ uvm_home = os.environ.get('UVM_HOME', '')
523
+ if not uvm_home:
524
+ return False
525
+
526
+ uvm_pkg_fpath = os.path.join(uvm_home, 'uvm_pkg.sv')
527
+ if add_if_found and os.path.isfile(uvm_pkg_fpath):
528
+ uvm_pkg_fpath = os.path.abspath(uvm_pkg_fpath)
529
+ util.info(f'For --uvm, adding to source files: {uvm_pkg_fpath}')
530
+ self.files[uvm_pkg_fpath] = True
531
+ self.files_sv.insert(0, uvm_pkg_fpath)
532
+ self.files_caller_info[uvm_pkg_fpath] = 'verilator.py'
533
+ util.info(f'For --uvm, adding +incdir+: {uvm_home}')
534
+ self.incdirs.append(os.path.abspath(uvm_home))
535
+ return True
536
+
537
+ return False
538
+
539
+
540
+ def _verilator_args_uvm(
541
+ self, warnings: bool = True, add_uvm_pkg_if_found: bool = True
542
+ ) -> list:
543
+ '''Returns list of args to be added to verilator (compile) step if --uvm present
544
+
545
+ Warnings on potential issues (Veriltor version, missing uvm_pkg.sv).
546
+ Optionally adds uvm_pkg.sv to source files.
547
+ '''
548
+
549
+ # Handle --uvm args:
550
+ if not self.args['uvm']:
551
+ return []
552
+
553
+ if warnings:
554
+
555
+ # prefers Verilator >= v5.042, $UVM_HOME to be set, or warning.
556
+ version_list = self._VERSION.split('.')
557
+ if int(version_list[0]) < 5 or \
558
+ (int(version_list[0]) == 5 and int(version_list[1]) < 42):
559
+ util.warning(f'Verilator version is {self._VERSION}, --uvm set prefers Verilator',
560
+ 'version > v5.042')
561
+
562
+ if not os.environ.get('UVM_HOME', ''):
563
+ util.warning('--uvm set, however env (or .env or --env-file) $UVM_HOME is not set')
564
+
565
+ uvm_pkg_found = self._verilator_support_uvm_pkg_fpath(add_if_found=add_uvm_pkg_if_found)
566
+ if warnings and not uvm_pkg_found:
567
+ util.warning(
568
+ '--uvm set, however no suitable uvm_pkg.sv is source files,',
569
+ f'nor in $UVM_HOME/uvm_pkg.sv. $UVM_HOME={os.environ.get("UVM_HOME", "")}'
570
+ )
571
+
572
+ return ['-Wno-fatal', '+define+UVM_NO_DPI']
573
+
574
+
489
575
  def artifacts_add(self, name: str, typ: str, description: str) -> None:
490
576
  '''Override from Command.artifacts_add, so we can catch known file
491
577
 
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
opencos/util.py CHANGED
@@ -21,6 +21,7 @@ from dotenv import load_dotenv
21
21
  from supports_color import supportsColor
22
22
 
23
23
  from opencos.utils import status_constants
24
+ from opencos.utils.str_helpers import strip_ansi_color
24
25
 
25
26
  global_exit_allowed = False # pylint: disable=invalid-name
26
27
  progname = "UNKNOWN" # pylint: disable=invalid-name
@@ -383,8 +384,10 @@ def get_argparser() -> argparse.ArgumentParser:
383
384
  'Input .f file to be expanded as eda'
384
385
  ' args/defines/incdirs/files/targets'))
385
386
  parser.add_argument('--env-file', default=[], action='append',
386
- help='dotenv file(s) to pass ENV vars, (default: .env, loaded last)'
387
- )
387
+ help=(
388
+ "dotenv file(s) to pass ENV vars, (default: .env loaded first,"
389
+ " subsequent files' vars override .env"
390
+ ))
388
391
  return parser
389
392
 
390
393
 
@@ -407,10 +410,10 @@ def get_argparser_short_help(parser: object = None) -> str:
407
410
  '''Returns short help for our ArgumentParser'''
408
411
  if not parser:
409
412
  parser = get_argparser()
410
- full_lines = parser.format_help().split('\n')
413
+ full_lines = strip_ansi_color(parser.format_help()).split('\n')
411
414
  lineno = 0
412
415
  for lineno, line in enumerate(full_lines):
413
- if line.startswith('options:'):
416
+ if any(line.startswith(x) for x in ('options:', 'optional arguments:')):
414
417
  break
415
418
  # skip the line that says 'options:', repalce with the progname:
416
419
  return f'{parser.prog}:\n' + '\n'.join(full_lines[lineno + 1:])
@@ -432,11 +435,12 @@ def process_token(arg: list) -> bool:
432
435
  def load_env_file(env_file: str) -> None:
433
436
  '''Handles .env file (from util CLI args --env-file)'''
434
437
  if os.path.isfile(env_file):
435
- load_dotenv(env_file)
438
+ load_dotenv(env_file, override=True)
436
439
  env_files_loaded.add(os.path.abspath(env_file))
437
440
  else:
438
441
  warning(f'--env-file {env_file} does not exist and is not loaded.')
439
442
 
443
+
440
444
  def patch_args_for_dir(tokens: list, patch_dir: str, caller_info: str) -> list:
441
445
  '''Given list of args, attempt to correct for relative dir'''
442
446
 
@@ -536,8 +540,10 @@ def process_tokens( # pylint: disable=too-many-branches
536
540
  parser.add_argument('--debug-level', type=int, default=0,
537
541
  help='Set debug level messaging (default: 0)')
538
542
  parser.add_argument('--env-file', default=[], action='append',
539
- help='dotenv file(s) to pass ENV vars, (default: .env, loaded last)'
540
- )
543
+ help=(
544
+ "dotenv file(s) to pass ENV vars, (default: .env loaded first,"
545
+ " subsequent files' vars override .env"
546
+ ))
541
547
  parser.add_argument('-f', '--input-file', default=[], action='append',
542
548
  help=(
543
549
  'Input .f file to be expanded as eda args, defines, incdirs,'
@@ -553,7 +559,7 @@ def process_tokens( # pylint: disable=too-many-branches
553
559
  debug(f'util.process_tokens: {parsed=} {unparsed=} from: {tokens}')
554
560
 
555
561
  if os.path.isfile(str(Path('.env'))):
556
- parsed.env_file.append('.env')
562
+ parsed.env_file.insert(0, '.env')
557
563
  if parsed.env_file:
558
564
  for env_file in parsed.env_file:
559
565
  load_env_file(env_file)
@@ -172,7 +172,7 @@ def pretty_list_columns_manual(data: list, num_columns: int = 4, auto_columns: b
172
172
  pretty_list_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
173
173
  )
174
174
  return ret_lines
175
- if max_line_len + max_item_len + _spacing < window_cols:
175
+ if max_line_len + max_item_len + _spacing <= window_cols:
176
176
  # add 1 more column if we're guaranteed to have room.
177
177
  ret_lines.extend(
178
178
  pretty_list_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
@@ -198,3 +198,8 @@ def print_columns_manual(data: list, num_columns: int = 4, auto_columns: bool =
198
198
  data=data, num_columns=num_columns, auto_columns=auto_columns
199
199
  )
200
200
  print('\n'.join(lines))
201
+
202
+ def strip_ansi_color(text: str) -> str:
203
+ '''Strip ANSI color characters from str'''
204
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
205
+ return ansi_escape.sub('', text)
@@ -5,10 +5,18 @@ import shutil
5
5
  import subprocess
6
6
  import sys
7
7
 
8
- from opencos.util import debug, error, info, progname, global_log
8
+ import psutil
9
+ from opencos.util import debug, error, info, warning, progname, global_log
9
10
 
10
11
  IS_WINDOWS = sys.platform.startswith('win')
11
12
 
13
+ # For non-Windows, we track the background parent PIDs, because some tools (vivado XSim,
14
+ # most Modelsim/Questa variants) tend to spawn children PIDs that don't always respond
15
+ # nicely to a friendly *nix SIGTERM. So we'll remember what our parent PIDs are, and
16
+ # eda.py's (or other CLI opencos script) can use signal to cleanup any remaining
17
+ # parents + children using subprocess_helpers.cleanup_all()
18
+ ALL_PARENT_PIDS = set()
19
+
12
20
  def subprocess_run(
13
21
  work_dir: str, command_list: list, fake: bool = False, shell: bool = False
14
22
  ) -> int:
@@ -35,10 +43,11 @@ def subprocess_run(
35
43
 
36
44
  debug(f"subprocess_run: About to call subprocess.run({c}, **{proc_kwargs}")
37
45
  proc = subprocess.run(c, check=True, **proc_kwargs)
46
+ # Note - we do not get PID management for subprocess_run(...)
38
47
  return proc.returncode
39
48
 
40
49
 
41
- def subprocess_run_background(
50
+ def subprocess_run_background( # pylint: disable=too-many-branches
42
51
  work_dir: str, command_list: list, background: bool = True, fake : bool = False,
43
52
  shell: bool = False, tee_fpath: str = ''
44
53
  ) -> (str, str, int):
@@ -76,6 +85,9 @@ def subprocess_run_background(
76
85
 
77
86
  debug(f"subprocess_run_background: about to call subprocess.Popen({c}, **{proc_kwargs})")
78
87
  proc = subprocess.Popen(c, **proc_kwargs) # pylint: disable=consider-using-with
88
+ if not background:
89
+ info(f'PID {proc.pid} for {command_list[0]}')
90
+ add_running_parent_pid(proc.pid)
79
91
 
80
92
  stdout = ''
81
93
  tee_fpath_f = None
@@ -99,10 +111,61 @@ def subprocess_run_background(
99
111
  stdout += line + '\n'
100
112
 
101
113
  proc.communicate()
114
+ remove_completed_parent_pid(proc.pid)
115
+
102
116
  rc = proc.returncode
103
117
  if tee_fpath_f:
104
118
  tee_fpath_f.write(f'INFO: [{progname}] subprocess_run_background: returncode={rc}\n')
105
119
  tee_fpath_f.close()
106
- info('subprocess_run_background: wrote: ' + os.path.abspath(tee_fpath))
120
+ if not background:
121
+ info('subprocess_run_background: wrote: ' + os.path.abspath(tee_fpath))
107
122
 
108
123
  return stdout, '', rc
124
+
125
+
126
+ def add_running_parent_pid(pid: int) -> None:
127
+ '''Adds pid (if still alive) to ALL_PARENT_PIDS'''
128
+ try:
129
+ p = psutil.Process(pid)
130
+ ALL_PARENT_PIDS.add(p.pid)
131
+ except psutil.NoSuchProcess:
132
+ pass
133
+ except Exception as e:
134
+ error(f'{pid=} exception {e}')
135
+
136
+ def remove_completed_parent_pid(pid: int) -> None:
137
+ '''Removes pid (if no longer alive) from ALL_PARENT_PIDS.'''
138
+ try:
139
+ p = psutil.Process(pid)
140
+ warning(f'PID {p.pid} still running')
141
+ except psutil.NoSuchProcess:
142
+ ALL_PARENT_PIDS.remove(pid)
143
+ except Exception as e:
144
+ error(f'{pid=} exception {e}')
145
+
146
+
147
+ def cleanup_all() -> None:
148
+ '''Kills everything from ALL_PARENT_PIDS.'''
149
+ for parent in ALL_PARENT_PIDS:
150
+ kill_proc_tree(parent)
151
+
152
+
153
+ def kill_proc_tree(pid: int, including_parent: bool = True) -> None:
154
+ '''Kills a process and its entire descendant tree'''
155
+ try:
156
+ parent = psutil.Process(pid)
157
+ children = parent.children(recursive=True)
158
+ info(f'{pid=} {parent=} {children=}')
159
+ for child in children:
160
+ if psutil.Process(child.pid):
161
+ info(f'parent {pid=} killing {child=}')
162
+ child.kill()
163
+ _, still_alive = psutil.wait_procs(children, timeout=5)
164
+ if still_alive:
165
+ warning(f'parent {pid=} {still_alive=}')
166
+ if including_parent:
167
+ info(f'parent {pid=} killing {parent=}')
168
+ parent.kill()
169
+ parent.wait(5)
170
+ except psutil.NoSuchProcess:
171
+ pass # Process already terminated
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: A simple Python package for wrapping RTL simuliatons and synthesis
5
5
  Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
6
6
  Project-URL: Homepage, https://github.com/cognichip/opencos
@@ -9,8 +9,8 @@ License-File: LICENSE
9
9
  License-File: LICENSE.spdx
10
10
  Requires-Dist: mergedeep>=1.3.4
11
11
  Requires-Dist: peakrdl>=1.1.0
12
+ Requires-Dist: psutil>=7.0.0
12
13
  Requires-Dist: pyyaml>=6.0.2
13
- Requires-Dist: pytest>=8.3.5
14
14
  Requires-Dist: python-dotenv>=1.0.1
15
15
  Requires-Dist: schema>=0.7.7
16
16
  Requires-Dist: toml>=0.10.2
@@ -18,4 +18,12 @@ Requires-Dist: yamllint>=1.35.1
18
18
  Requires-Dist: PySerial>=3.5
19
19
  Requires-Dist: cocotb>=2.0
20
20
  Requires-Dist: supports_color>=0.2.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pylint>=3.0.0; extra == "dev"
23
+ Requires-Dist: pytest>=8.3.5; extra == "dev"
24
+ Provides-Extra: docs
25
+ Requires-Dist: mkdocs; extra == "docs"
26
+ Requires-Dist: mkdocs-material; extra == "docs"
27
+ Requires-Dist: mkdocs-wavedrom-plugin; extra == "docs"
28
+ Requires-Dist: mkdocs-plantuml; extra == "docs"
21
29
  Dynamic: license-file
@@ -1,24 +1,24 @@
1
1
  opencos/__init__.py,sha256=RwJA9oc1uUlvNX7v5zoqwjnSRNq2NZwRlHqtS-ICJkI,122
2
2
  opencos/_version.py,sha256=KaWIjS0c08g-C0fgYY1kXwSPqhOFxaq5pYEeoZhOR_I,617
3
3
  opencos/_waves_pkg.sv,sha256=TL5YT9lT-fn2FD54MbVVZROmZ7vtW3ScA_rM2eRzKmU,2068
4
- opencos/deps_schema.py,sha256=VUdXuq43mKfM-U4x7DSA28-MH1Xqxre6V7Ttw2DeOqI,16762
5
- opencos/eda.py,sha256=91E-EsyZS-uRadApP-h2onW6rpvLBnrpJoT_9tRtsS8,23322
6
- opencos/eda_base.py,sha256=jf4t11UPj39swL41z-EJiGTOnHvRScaQc-SAjpZ5XI4,109651
7
- opencos/eda_config.py,sha256=z3yQOPGBX7-yKp6BdQYfJ9eOJf-Jctl-mwCDj3vj2BI,12712
8
- opencos/eda_config_defaults.yml,sha256=Bs9UyXckIpauK_tf27iRkig6hBvsuI-PL7HhgCQpA1k,16168
4
+ opencos/deps_schema.py,sha256=fx1_IJhsDYkUciwwCPTXHP6ftFjTsPVjO4xg12twIjw,17384
5
+ opencos/eda.py,sha256=m_Jq1Iht0I7dSN4ceI4c4QZCkKjW85ewvoLfCaOo7-s,23315
6
+ opencos/eda_base.py,sha256=PK89dJMmTHAqvfrxkVXkYZlvyk4er2AVDJ5FMVHry6A,109724
7
+ opencos/eda_config.py,sha256=afimZEfvVitWS-k3IsR7fwzIz5dHWKfiJp5qHzzBF6c,14110
8
+ opencos/eda_config_defaults.yml,sha256=2IX9i7VCwgXHKzsjcEGdLJ6SqxiQqXbVIupYNhjHIes,16263
9
9
  opencos/eda_config_max_verilator_waivers.yml,sha256=lTAU4IOEbUWVlPzuer1YYhIyxpPINeA4EJqcRIT-Ymk,840
10
10
  opencos/eda_config_reduced.yml,sha256=cQ9jY4J7EvAbeHTiP6bvpDSVJAYiitjLZPSxxLKIEbk,1440
11
11
  opencos/eda_deps_bash_completion.bash,sha256=jMkQKY82HBgOnQeMdA1hMrXguRFtB52SMBxUemKovL4,1958
12
12
  opencos/eda_deps_sanitize.py,sha256=SQjvrte9Hv9JesRY0wljvbdC6pAmLCikI-Wdzzy-D04,1939
13
13
  opencos/eda_extract_targets.py,sha256=POlxZfqf2dNH2nc1CEw5B_53vSHAicSTkpU9_-2_6Zw,2851
14
- opencos/eda_tool_helper.py,sha256=_YgobDLEWW6Fzdr976LxaCDZ4DKRyuMs5CrYQHaTPrU,2558
14
+ opencos/eda_tool_helper.py,sha256=Xm6nr9XweCjueWFLkrH5U3nK96JGeeh86f2GCPhwY-o,3108
15
15
  opencos/export_helper.py,sha256=5BnrkhiieJBgYKAryhXD7HSGtrgvXQpZ8B5ltdrhbRY,22649
16
16
  opencos/export_json_convert.py,sha256=tSIMbLFtc_Fo66EhFovMii1v_qJYyFZJrPNnoPdW7L0,4182
17
17
  opencos/files.py,sha256=AQOnsrvoc0r76LiFrkoMbwOGdUO1FpBiFY_jyyI_ve8,1566
18
18
  opencos/names.py,sha256=Y2aJ5wgpbNIJ-_P5xUXnHMv_h-zMOX2Rt6iLuduqC1Q,1213
19
19
  opencos/peakrdl_cleanup.py,sha256=vHNGtalTrIVP335PhRjPt9RhoccgpK1HJAi-E4M8Kc8,736
20
20
  opencos/seed.py,sha256=IL9Yg-r9SLSRseMVWaEHmuw2_DNi_eyut11EafoNTsU,942
21
- opencos/util.py,sha256=tgDOvkIGCPfuuJmP1nMsxsxaxmhlH9DOUsJ3SVjtXy4,42123
21
+ opencos/util.py,sha256=PGkc0JYKsx2sKiuoCmL3rLdEFyXeM6xY3r42Q-8KUJw,42454
22
22
  opencos/commands/__init__.py,sha256=oOOQmn5_jHAMSOfA3swJJ7mdoyHsJA0lJwKPTudlTns,1125
23
23
  opencos/commands/build.py,sha256=mvJYxk5J15k0Cr8R7oIdIIdsEtWV3gE-LnPweVwtSDo,1487
24
24
  opencos/commands/deps_help.py,sha256=WDrU7H9sypzDAxe_CHqhW5B_scbQMzBEdf-v-Jcfd5Q,10682
@@ -38,20 +38,20 @@ opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1
38
38
  opencos/commands/upload.py,sha256=oyImgcEFGxDkdeY9EYyX2R6fTOmN-lTs-HYxAZqXUUo,871
39
39
  opencos/commands/waves.py,sha256=nrp3ALwfJujZns44tgCgia_dEedQyKe0T3fuws8h39U,7697
40
40
  opencos/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- opencos/deps/defaults.py,sha256=NXh3V4oInrBVlDw64B2OCI77wzdn1NtaD64srhBnmZU,1486
41
+ opencos/deps/defaults.py,sha256=Z6mIVJEV0zQ9rC-HkQFMBFAkixjqKS1TATPSc27wOeA,1502
42
42
  opencos/deps/deps_commands.py,sha256=q4JfSfzRO2nM2zdNT4enCy33FokEytZYQJn1HJ6osJk,16606
43
43
  opencos/deps/deps_file.py,sha256=YQ5ftYvppRTqUto22r-XDH6-bcMO7cA-WiJ7QzPjljY,17103
44
- opencos/deps/deps_processor.py,sha256=DBaMUEnpoIL4xaNPs2f2AFGcWLST5pP_Qgup9r-8D7M,41403
44
+ opencos/deps/deps_processor.py,sha256=5eGE__Isssm6JDnVxI2hM1_ljS968K-24eUWGDEr_vs,42303
45
45
  opencos/hw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  opencos/hw/oc_cli.py,sha256=U1JGlshLZhtd0LgndZFBZVltAj_HemdhbjO_Zo8ZuVM,132252
47
47
  opencos/hw/pcie.py,sha256=VUJljaZJYgScAAx5yn7F6GoA8K9eTcw24otYZbkMpYs,3035
48
48
  opencos/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  opencos/tests/custom_config.yml,sha256=TRoVM9ZFKPOA_8JmlpzaMhnGO1txmaD14N_8P1oqzew,257
50
- opencos/tests/helpers.py,sha256=0KWHUQNXeyQ40F2sdW_R2r0P8qZpV88ndTZmoix3NcU,12474
50
+ opencos/tests/helpers.py,sha256=77ljULYa-DhnC9VgT_R1wic_GdgeqsAJdkOXDt1KjDo,13839
51
51
  opencos/tests/test_build.py,sha256=FQAxOpLVQShAHD_L5rqJctPeSAoqoOCNFI0RXflLuY0,387
52
52
  opencos/tests/test_deps_helpers.py,sha256=uQZxleh6aKO-mZQhagHh5xLIBbpQ8dav7-5D0eemq_g,8164
53
53
  opencos/tests/test_deps_schema.py,sha256=T3P9KjaMyKsk8b7snNVvNSsom2hIJcg6Z9apYiXoH9Y,941
54
- opencos/tests/test_eda.py,sha256=PhAFCqoZxUhjuSvNeHpTHdwt8UhHjRdVppesgcWBX64,37407
54
+ opencos/tests/test_eda.py,sha256=8X6kej5-uPv2l0xeWOhitCE640XZtiGLwzCktra_Ehg,37784
55
55
  opencos/tests/test_eda_elab.py,sha256=AjU4WMYtFoHpNe1Z4yWWpxDKy4V_hAjL5rl3jqphZrk,3179
56
56
  opencos/tests/test_eda_synth.py,sha256=BtBrNVJ9C-LJt3K0wNNS5ukEVrET16AbRXl2IzxudJ8,5744
57
57
  opencos/tests/test_oc_cli.py,sha256=w-F-LjSSWVql3D2WG8tcV4_C52i-hL_2WT3oDpKQn9s,734
@@ -68,30 +68,30 @@ opencos/tools/cocotb.py,sha256=hlBR6M4k_amOOWa7cZDuw9fTGJtJha1jSpGgyJN8Iis,17734
68
68
  opencos/tools/invio.py,sha256=S2ChWr8xMZHSOOhX2hGKQhMmtQY2potVQjc-lsMg73o,3299
69
69
  opencos/tools/invio_helpers.py,sha256=86WOGmSf4m_lEqBtK3DLjWqI0jnqAWzBEBRYfBUGiSY,8804
70
70
  opencos/tools/invio_yosys.py,sha256=CszGeTdE1ilnMmWPLW77BrtobbsGb1CKXqot0hGimFU,5996
71
- opencos/tools/iverilog.py,sha256=8dK4z8ktbNYS9cW5QQPm586WoE-pSmOAVJfXajw-Sbg,6420
71
+ opencos/tools/iverilog.py,sha256=3IQIZVDioChKEJIVVJki-q7NlvBg0k3n61oM7ltG9c8,6551
72
72
  opencos/tools/modelsim_ase.py,sha256=Jt-6N3BZZyu25fT1ehFQLRUTVvrcCo4e2Gl7UtsQcuk,17834
73
73
  opencos/tools/quartus.py,sha256=_TfmPSYpbhmDLw7Dur-rRP0iGwv9hhQ6E5G-XLiYPEM,30486
74
74
  opencos/tools/questa.py,sha256=nHImM0Wydcf4YHGibHmQAwmqKHmMxKZUqY-E-vz1o8M,9827
75
75
  opencos/tools/questa_fse.py,sha256=hytkeuGg4qImj7rStV1i2kxkz9B0KFheGtcadxmpYAo,2550
76
- opencos/tools/riviera.py,sha256=FAumXIt9u4JXwR5BHe-0APX3K0Lg3RT2lWQ-GNGL3vA,13016
76
+ opencos/tools/riviera.py,sha256=_-vsN7TD6WdW4PVsSJaEhJls3RgXGRowhY_QV1hdFqE,13678
77
77
  opencos/tools/slang.py,sha256=S_vODMT5Zl5vi9FMGHfahp5B0oMNyDIRJXtRAldVCwY,8625
78
78
  opencos/tools/slang_yosys.py,sha256=MKh13eAmLJDkynZiezyT8E2gI4CKnXipzgFCZppaMXo,10230
79
79
  opencos/tools/surelog.py,sha256=S2RAZJyjdISm_tRvAhXbla7_z_tJfotZih5f9Y3m7DQ,5648
80
80
  opencos/tools/tabbycad_yosys.py,sha256=2LePPgYXBVdsy7YcffPIWN-I0B7queLQ_f_pme2SCGw,7803
81
- opencos/tools/verilator.py,sha256=yh3DZXhGxt9RyJVtbn2RoqIiwxZW6_sTiXs8xGf3iNg,21065
82
- opencos/tools/vivado.py,sha256=GQdPd1mp02it_uyhMqpFfJgh0AKIMn-20BhHNEDp5HY,41398
81
+ opencos/tools/verilator.py,sha256=42SLAuqP2TmAE8pPgcLZBcXaALlvgc7z7v48FFvnCyo,24654
82
+ opencos/tools/vivado.py,sha256=I9yVFK5wnFl1268l8WCk5Xh8wZBlSCODVTg4qStVF8c,41475
83
83
  opencos/tools/yosys.py,sha256=t3Au8gdwTepIKCPCXHpRXEdtmORQK8xqNvF6baIa7DM,28260
84
84
  opencos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
85
  opencos/utils/markup_helpers.py,sha256=A8Ev5UJ4EVKjdcF2g85SQbjdPZR4jGpNqCLaBy_4v7Q,4569
86
86
  opencos/utils/status_constants.py,sha256=na6YsqlsCwIYzTXWE14dPadUYRNTrOS6YTXHCer2NbA,635
87
- opencos/utils/str_helpers.py,sha256=726ScK5-v7QkBi-zqESKZLsOl2_ya4vVJ5ZhxJqmBFo,6440
88
- opencos/utils/subprocess_helpers.py,sha256=xemAGPey6M0sWY_FElvr-Z0phCfdjaC-znP8FKihPaE,3535
87
+ opencos/utils/str_helpers.py,sha256=-hR7MAQLOoY2lIfqtxNtnzb3apeJPkh8shEGFzkwQfs,6637
88
+ opencos/utils/subprocess_helpers.py,sha256=idWc-sy_XJaxIl06tt_QjThYWoLL_Wmy7aLCpEo9y3c,5829
89
89
  opencos/utils/vscode_helper.py,sha256=9nHyMUIL-gzfW-qLH06sgaCnVK-YTOtu6pusitNNhL8,1363
90
90
  opencos/utils/vsim_helper.py,sha256=1johPOGbjbMgnCDSTpgsQcSuAquiqq1Y2MBxS6WY6b4,1552
91
- opencos_eda-0.3.3.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
- opencos_eda-0.3.3.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
- opencos_eda-0.3.3.dist-info/METADATA,sha256=MGcmalurglc-9-KPz1YHp-KOVgLwqOZRRu3cOz6asI0,703
94
- opencos_eda-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
- opencos_eda-0.3.3.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
- opencos_eda-0.3.3.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
- opencos_eda-0.3.3.dist-info/RECORD,,
91
+ opencos_eda-0.3.5.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
+ opencos_eda-0.3.5.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
+ opencos_eda-0.3.5.dist-info/METADATA,sha256=1H76gr0Vw6Cz16Kf3sbAQR2pMjBzxGLAHbdm2oSNEvI,1024
94
+ opencos_eda-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
+ opencos_eda-0.3.5.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
+ opencos_eda-0.3.5.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
+ opencos_eda-0.3.5.dist-info/RECORD,,