opencos-eda 0.2.50__py3-none-any.whl → 0.2.51__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/commands/lec.py CHANGED
@@ -69,16 +69,13 @@ class CommandLec(CommandDesign):
69
69
  if self.status_any_error():
70
70
  return unparsed
71
71
 
72
- # add defines for this job type
73
- if not self.args['top']:
74
- self.args['top'] = 'eda.lec'
75
-
76
72
  # we require there to be two --designs set.
77
73
  if not self.args['designs'] and len(self.args['designs']) != 2:
78
74
  self.error('Requires two designs via --designs=<target1> --designs=<target2>',
79
75
  f'designs={self.args["designs"]}')
80
76
  return []
81
77
 
78
+
82
79
  # Before we do anything else, make sure the two designs actually exist.
83
80
  for design in self.args['designs']:
84
81
  # maybe it's a file?
@@ -89,6 +86,12 @@ class CommandLec(CommandDesign):
89
86
  else:
90
87
  self.error(f'--designs={design}, value is not a file or target')
91
88
 
89
+ if not self.args['top']:
90
+ # Correct the 'top' name so it's eda.<target1>.<target2>.lec (shortnames)
91
+ _, short_target1 = os.path.split(self.args['designs'][0])
92
+ _, short_target2 = os.path.split(self.args['designs'][1])
93
+ self.args['top'] = f'eda.{short_target1}.{short_target2}'
94
+
92
95
  # create our work dir
93
96
  self.create_work_dir()
94
97
  self.run_dep_commands()
opencos/commands/shell.py CHANGED
@@ -16,6 +16,8 @@ import os
16
16
  from opencos import util, export_helper
17
17
  from opencos.eda_base import CommandDesign
18
18
 
19
+ from opencos.utils import status_constants
20
+
19
21
 
20
22
  class CommandShell(CommandDesign):
21
23
  '''Base class command handler for: eda sim ...'''
@@ -181,7 +183,6 @@ class CommandShell(CommandDesign):
181
183
  _must_strings.append(self.args['pass-pattern'])
182
184
 
183
185
  if len(_bad_strings) > 0 or len(_must_strings) > 0:
184
- hit_bad_string = False
185
186
  hit_must_string_dict = dict.fromkeys(_must_strings)
186
187
  fname = os.path.join(self.args['work-dir'], filename)
187
188
  with open(fname, 'r', encoding='utf-8') as f:
@@ -191,12 +192,14 @@ class CommandShell(CommandDesign):
191
192
  if k in line:
192
193
  hit_must_string_dict[k] = True
193
194
  if any(bad_str in line for bad_str in _bad_strings):
194
- hit_bad_string = True
195
- self.error(f"log {fname}:{lineno} contains one of {_bad_strings=}")
195
+ self.error(
196
+ f"log {fname}:{lineno} contains one of {_bad_strings=}",
197
+ error_code=status_constants.EDA_SHELL_LOG_HAS_BAD_STRING
198
+ )
196
199
 
197
- if hit_bad_string:
198
- self.status += 1
199
200
  if any(x is None for x in hit_must_string_dict.values()):
200
- self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
201
- f" {hit_must_string_dict=}")
202
- self.status += 1
201
+ self.error(
202
+ f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
203
+ f" {hit_must_string_dict=}",
204
+ error_code=status_constants.EDA_SHELL_LOG_MISSING_MUST_STRING
205
+ )
opencos/commands/sim.py CHANGED
@@ -15,6 +15,7 @@ import os
15
15
 
16
16
  from opencos import util, export_helper
17
17
  from opencos.eda_base import CommandDesign, Tool
18
+ from opencos.utils import status_constants
18
19
 
19
20
  class CommandSim(CommandDesign):
20
21
  '''Base class command handler for: eda sim ...'''
@@ -170,11 +171,14 @@ class CommandSim(CommandDesign):
170
171
  if log_filename:
171
172
  log_fname = log_filename
172
173
 
173
- self.exec(work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath)
174
+ _, stdout, _ = self.exec(
175
+ work_dir=self.args['work-dir'], command_list=clist, tee_fpath=tee_fpath
176
+ )
174
177
 
175
178
  if check_logs and log_fname:
176
179
  self.check_logs_for_errors(
177
- filename=log_fname, bad_strings=bad_strings, must_strings=must_strings,
180
+ filename='', file_contents_str=stdout,
181
+ bad_strings=bad_strings, must_strings=must_strings,
178
182
  use_bad_strings=use_bad_strings, use_must_strings=use_must_strings
179
183
  )
180
184
  if log_fname:
@@ -273,8 +277,8 @@ class CommandSim(CommandDesign):
273
277
  '''
274
278
  return
275
279
 
276
- def check_logs_for_errors( # pylint: disable=dangerous-default-value
277
- self, filename: str,
280
+ def check_logs_for_errors( # pylint: disable=dangerous-default-value,too-many-locals,too-many-branches
281
+ self, filename: str = '', file_contents_str: str = '',
278
282
  bad_strings: list = [], must_strings: list = [],
279
283
  use_bad_strings: bool = True, use_must_strings: bool = True
280
284
  ) -> None:
@@ -294,26 +298,38 @@ class CommandSim(CommandDesign):
294
298
  if self.args['pass-pattern'] != "":
295
299
  _must_strings.append(self.args['pass-pattern'])
296
300
 
297
- if len(_bad_strings) > 0 or len(_must_strings) > 0:
298
- hit_bad_string = False
299
- hit_must_string_dict = dict.fromkeys(_must_strings)
301
+ if len(_bad_strings) == 0 and len(_must_strings) == 0:
302
+ return
303
+
304
+ hit_must_string_dict = dict.fromkeys(_must_strings)
305
+
306
+ lines = []
307
+ fname = ''
308
+ if file_contents_str:
309
+ lines = file_contents_str.split('\n')
310
+ elif filename:
300
311
  fname = os.path.join(self.args['work-dir'], filename)
301
312
  with open(fname, 'r', encoding='utf-8') as f:
302
- for lineno, line in enumerate(f):
303
- if any(must_str in line for must_str in _must_strings):
304
- for k, _ in hit_must_string_dict.items():
305
- if k in line:
306
- hit_must_string_dict[k] = True
307
- if any(bad_str in line for bad_str in _bad_strings):
308
- hit_bad_string = True
309
- self.error(f"log {fname}:{lineno} contains one of {_bad_strings=}")
310
-
311
- if hit_bad_string:
312
- self.status += 1
313
- if any(x is None for x in hit_must_string_dict.values()):
314
- self.error(f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
315
- f" {hit_must_string_dict=}")
316
- self.status += 1
313
+ lines = f.readl.splitlines()
314
+
315
+ if lines:
316
+ for lineno, line in enumerate(lines):
317
+ if any(must_str in line for must_str in _must_strings):
318
+ for k, _ in hit_must_string_dict.items():
319
+ if k in line:
320
+ hit_must_string_dict[k] = True
321
+ if any(bad_str in line for bad_str in _bad_strings):
322
+ self.error(
323
+ f"log {fname}:{lineno} contains one of {_bad_strings=}",
324
+ error_code=status_constants.EDA_SIM_LOG_HAS_BAD_STRING
325
+ )
326
+
327
+ if any(x is None for x in hit_must_string_dict.values()):
328
+ self.error(
329
+ f"Didn't get all passing patterns in log {fname}: {_must_strings=}",
330
+ f" {hit_must_string_dict=}",
331
+ error_code=status_constants.EDA_SIM_LOG_MISSING_MUST_STRING
332
+ )
317
333
 
318
334
  # Methods that derived classes must override:
319
335
 
opencos/deps/deps_file.py CHANGED
@@ -10,15 +10,20 @@ from pathlib import Path
10
10
 
11
11
  import toml
12
12
 
13
+ from opencos import util
13
14
  from opencos.deps.defaults import DEPS_FILE_EXTS, ROOT_TABLE_KEYS_NOT_TARGETS
14
15
  from opencos.util import debug, error
15
- from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers
16
+ from opencos.utils.markup_helpers import yaml_safe_load, toml_load_only_root_line_numbers, \
17
+ markup_writer, markup_dumper
16
18
  from opencos.utils.str_helpers import fnmatch_or_re, dep_str2list
17
19
  from opencos.utils.subprocess_helpers import subprocess_run_background
20
+ from opencos.utils.status_constants import EDA_DEPS_FILE_NOT_FOUND, EDA_DEPS_TARGET_NOT_FOUND
18
21
 
19
22
 
20
23
  def deps_data_get_all_targets(data: dict) -> list:
21
24
  '''Given extracted DEPS data (dict) get all the root level keys that aren't defaults'''
25
+ if data is None:
26
+ return []
22
27
  return [x for x in data.keys() if x not in ROOT_TABLE_KEYS_NOT_TARGETS]
23
28
 
24
29
 
@@ -166,32 +171,43 @@ def deps_target_get_deps_list(
166
171
  ret = []
167
172
  for dep in deps:
168
173
  if isinstance(dep, str):
169
- if dep.startswith('#') or dep == '':
174
+ if not dep or dep.startswith('#'):
170
175
  continue
171
176
  ret.append(dep)
172
177
  return ret
173
178
 
174
179
 
175
180
  def deps_list_target_sanitize(
176
- entry, default_key: str = 'deps', target_node: str = '', deps_file: str = ''
181
+ entry, default_key: str = 'deps', target_node: str = '', deps_file: str = '',
182
+ entry_fix_deps_key: bool = True
177
183
  ) -> dict:
178
184
  '''Returns a sanitized DEPS markup table entry (dict --> dict)
179
185
 
180
186
  Since we support target entries that can be dict, list, or str(), sanitize
181
187
  them so they are a dict, with a key named 'deps' that has a list of deps.
182
188
  '''
189
+ ret = None
183
190
  if isinstance(entry, dict):
184
- return entry
191
+ ret = entry
185
192
 
186
193
  if isinstance(entry, str):
187
194
  mylist = dep_str2list(entry) # convert str to list
188
- return {default_key: mylist}
195
+ ret = {default_key: mylist}
189
196
 
190
197
  if isinstance(entry, list):
191
198
  # it's already a list
192
- return {default_key: entry}
199
+ ret = {default_key: entry}
200
+
201
+ if ret is not None:
202
+ if entry_fix_deps_key and 'deps' in ret:
203
+ ret['deps'] = deps_target_get_deps_list(
204
+ entry=ret, default_key='deps', deps_file=deps_file,
205
+ entry_must_have_default_key=True
206
+ )
207
+ else:
208
+ assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
193
209
 
194
- assert False, f"Can't convert to list {entry=} {default_key=} {target_node=} {deps_file=}"
210
+ return ret
195
211
 
196
212
 
197
213
  class DepsFile:
@@ -230,7 +246,10 @@ class DepsFile:
230
246
  if deps_path and os.path.exists(deps_path):
231
247
  self.rel_deps_file = os.path.join(os.path.relpath(deps_path), deps_leaf)
232
248
 
233
- self.error = command_design_ref.error # method.
249
+ self.error = getattr(command_design_ref, 'error', None)
250
+ if not self.error:
251
+ self.error = util.error
252
+
234
253
 
235
254
  def found(self) -> bool:
236
255
  '''Returns true if this DEPS file exists and extracted non-empty data'''
@@ -270,15 +289,18 @@ class DepsFile:
270
289
  if '.' in target_node:
271
290
  # Likely a filename (target_node does not include path)
272
291
  self.error(f'Trying to resolve command-line target={t_full} (file?):',
273
- f'File={t_node} not found in directory={t_path}')
292
+ f'File={t_node} not found in directory={t_path}',
293
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
274
294
  elif not self.rel_deps_file:
275
295
  # target, but there's no DEPS file
276
296
  self.error(f'Trying to resolve command-line target={t_full}:',
277
- f'but path {t_path} has no DEPS markup file (DEPS.yml)')
297
+ f'but path {t_path} has no DEPS markup file (DEPS.yml)',
298
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
278
299
  else:
279
300
  self.error(f'Trying to resolve command-line target={t_full}:',
280
301
  f'was not found in deps_file={self.rel_deps_file},',
281
- f'possible targets in deps file = {list(self.data.keys())}')
302
+ f'possible targets in deps file = {list(self.data.keys())}',
303
+ error_code=EDA_DEPS_TARGET_NOT_FOUND)
282
304
  else:
283
305
  # If we have caller_info, then this was a recursive call from another
284
306
  # DEPS file. It should already have the useful error messaging:
@@ -287,16 +309,19 @@ class DepsFile:
287
309
  # Likely a filename (target_node does not include path)
288
310
  self.error(f'Trying to resolve target={t_full} (file?):',
289
311
  f'called from {caller_info},',
290
- f'File={t_node} not found in directory={t_path}')
312
+ f'File={t_node} not found in directory={t_path}',
313
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
291
314
  elif not self.rel_deps_file:
292
315
  # target, but there's no DEPS file
293
316
  self.error(f'Trying to resolve target={t_full}:',
294
317
  f'called from {caller_info},',
295
- f'but {t_path} has no DEPS markup file (DEPS.yml)')
318
+ f'but {t_path} has no DEPS markup file (DEPS.yml)',
319
+ error_code=EDA_DEPS_FILE_NOT_FOUND)
296
320
  else:
297
321
  self.error(f'Trying to resolve target={t_full}:',
298
322
  f'called from {caller_info},',
299
- f'Target not found in deps_file={self.rel_deps_file}')
323
+ f'Target not found in deps_file={self.rel_deps_file}',
324
+ error_code=EDA_DEPS_TARGET_NOT_FOUND)
300
325
  else:
301
326
  debug(f'Found {target_node=} in deps_file={self.rel_deps_file}')
302
327
  found_target = True
@@ -324,3 +349,31 @@ class DepsFile:
324
349
  entry.update(entry_sanitized)
325
350
 
326
351
  return entry
352
+
353
+ def get_all_targets(self) -> list:
354
+ '''Returns list of all targets in this obj, skipping keys like DEFAULTS and METADATA'''
355
+ return deps_data_get_all_targets(self.data)
356
+
357
+ def get_sanitized_data(self) -> dict:
358
+ '''Returns a sanitized dict of self.data, resolves DEFAULTS, METADATA, and implicit
359
+
360
+ space-separated-str, lists for 'deps' in a target entry
361
+ '''
362
+ new_data = {}
363
+ targets = self.get_all_targets()
364
+ for target in targets:
365
+ new_data[target] = self.get_entry(target)
366
+ return new_data
367
+
368
+ def write_sanitized_markup(self, filepath: str) -> None:
369
+ '''Writes the sanitized dict of self.data as JSON/YAML (filepath extension)'''
370
+ markup_writer(data=self.get_sanitized_data(), filepath=filepath)
371
+
372
+ def str_sanitized_markup(self, as_yaml: bool = False) -> str:
373
+ '''Returns str sanitized YAML or JSON str of self.data'''
374
+ return markup_dumper(self.get_sanitized_data(), as_yaml=as_yaml)
375
+
376
+
377
+ def print_sanitized_markup(self, as_yaml: bool = False) -> None:
378
+ '''Prints sanitized JSON str of self.data to STDOUT'''
379
+ print(self.str_sanitized_markup(as_yaml=as_yaml))
opencos/eda.py CHANGED
@@ -16,9 +16,12 @@ import argparse
16
16
  import shlex
17
17
  import importlib.util
18
18
 
19
+ from pathlib import Path
20
+
19
21
  import opencos
20
22
  from opencos import util, eda_config, eda_base
21
23
  from opencos.eda_base import Tool, which_tool
24
+ from opencos.utils import vsim_helper
22
25
 
23
26
  # Configure util:
24
27
  util.progname = "EDA"
@@ -151,6 +154,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
151
154
  If so, updates config['auto_tools_order'][tool]['exe']
152
155
  '''
153
156
 
157
+
154
158
  tool = eda_config.update_config_auto_tool_order_for_tool(
155
159
  tool=tool, config=config
156
160
  )
@@ -192,16 +196,23 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
192
196
  has_all_exe = True
193
197
  has_all_in_exe_path = True
194
198
  for exe in exe_list:
195
- assert exe != '', f'{tool=} {value=} value missing "exe" {exe=}'
199
+ assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
196
200
  p = shutil.which(exe)
197
201
  if not p:
198
202
  has_all_exe = False
199
203
  util.debug(f"... No, missing exe {exe}")
200
204
  for req in value.get('requires_in_exe_path', []):
201
- if p and req and str(req).lower() not in str(p).lower():
205
+ if p and req and str(Path(req)) not in str(Path(p)):
202
206
  has_all_in_exe_path = False
203
207
  util.debug(f"... No, missing path requirement {req}")
204
208
 
209
+ has_vsim_helper = True
210
+ if value.get('requires_vsim_helper', False):
211
+ # This tool name must be in opencos.utils.vsim_helper.TOOL_IS[name].
212
+ # Special case for vsim being used by a lot of tools.
213
+ vsim_helper.init() # only runs checks once internally
214
+ has_vsim_helper = vsim_helper.TOOL_IS.get(name, False)
215
+
205
216
  if has_all_exe:
206
217
  requires_cmd_list = value.get('requires_cmd', [])
207
218
  for cmd in requires_cmd_list:
@@ -219,7 +230,8 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
219
230
  util.debug(f"... No, exception {e} running {cmd_list}")
220
231
 
221
232
 
222
- if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path]):
233
+ if all([has_all_py, has_all_env, has_all_exe, has_all_in_exe_path,
234
+ has_vsim_helper]):
223
235
  exe = exe_list[0]
224
236
  p = shutil.which(exe)
225
237
  config['auto_tools_found'][name] = exe # populate key-value pairs w/ first exe in list
opencos/eda_base.py CHANGED
@@ -28,6 +28,7 @@ from opencos.util import Colors
28
28
  from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or_space, \
29
29
  indent_wrap_long_text
30
30
  from opencos.utils.subprocess_helpers import subprocess_run_background
31
+ from opencos.utils import status_constants
31
32
 
32
33
  from opencos.deps.deps_file import DepsFile, deps_data_get_all_targets
33
34
  from opencos.deps.deps_processor import DepsProcessor
@@ -274,7 +275,7 @@ class Command:
274
275
  file=self.errors_log_f
275
276
  )
276
277
 
277
- self.status = util.error(*args, **kwargs)
278
+ self.status = util.error(*args, **kwargs) # error_code passed and returned via kwargs
278
279
 
279
280
  def status_any_error(self, report=True) -> bool:
280
281
  '''Used by derived classes process_tokens() to know an error was reached
@@ -416,10 +417,16 @@ class Command:
416
417
  shell=shell
417
418
  )
418
419
 
419
- if return_code:
420
- self.status += return_code
420
+ if return_code > 0:
421
+ if return_code == 1:
422
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE1
423
+ if return_code == 255:
424
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE255
425
+ else:
426
+ self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE2
421
427
  if stop_on_error:
422
- self.error(f"exec: returned with error (return code: {return_code})")
428
+ self.error(f"exec: returned with error (return code: {return_code})",
429
+ error_code=self.status)
423
430
  else:
424
431
  util.debug(f"exec: returned with error (return code: {return_code})")
425
432
  else:
@@ -437,7 +444,8 @@ class Command:
437
444
  # Do some minimal type handling, preserving the type(self.args[key])
438
445
  if key not in self.args:
439
446
  self.error(f'set_arg, {key=} not in self.args {value=}',
440
- f'({self.command_name=}, {self.__class__.__name__=})')
447
+ f'({self.command_name=}, {self.__class__.__name__=})',
448
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
441
449
 
442
450
  cur_value = self.args[key]
443
451
 
@@ -568,7 +576,8 @@ class Command:
568
576
  parsed, unparsed = parser.parse_known_args(tokens + [''])
569
577
  unparsed = list(filter(None, unparsed))
570
578
  except argparse.ArgumentError:
571
- self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}')
579
+ self.error(f'problem {self.command_name=} attempting to parse_known_args for {tokens=}',
580
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
572
581
 
573
582
  parsed_as_dict = vars(parsed)
574
583
 
@@ -630,7 +639,8 @@ class Command:
630
639
  _, unparsed = self.run_argparser_on_list(tokens)
631
640
  if process_all and len(unparsed) > 0:
632
641
  self.error(f"Didn't understand argument: '{unparsed=}' in",
633
- f" {self.command_name=} context, {pwd=}")
642
+ f" {self.command_name=} context, {pwd=}",
643
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
634
644
 
635
645
  return unparsed
636
646
 
@@ -650,7 +660,8 @@ class Command:
650
660
 
651
661
  if not ret and error_if_no_command:
652
662
  self.error(f"Looking for a valid eda {self.command_name} <command>",
653
- f"but didn't find one in {tokens=}")
663
+ f"but didn't find one in {tokens=}",
664
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
654
665
  return ret
655
666
 
656
667
 
@@ -692,7 +703,8 @@ class Command:
692
703
  process_tokens(..) is the starting entrypoint from eda.py'''
693
704
  self.write_eda_config_and_args()
694
705
  self.error(f"No tool bound to command '{self.command_name}', you",
695
- " probably need to setup tool, or use '--tool <name>'")
706
+ " probably need to setup tool, or use '--tool <name>'",
707
+ error_code=status_constants.EDA_CONFIG_ERROR)
696
708
 
697
709
  def command_safe_set_tool_defines(self) -> None:
698
710
  '''Safe wrapper for calling self.set_tool_defines() in case a Tool parent class is
@@ -1006,7 +1018,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1006
1018
  self.defines[k] = v
1007
1019
  util.debug(f"Defined {k}={v}")
1008
1020
  return None
1009
- self.error(f"Didn't understand +define+: '{plusarg}'")
1021
+ self.error(f"Didn't understand +define+: '{plusarg}'",
1022
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1010
1023
  return None
1011
1024
 
1012
1025
  if plusarg.startswith('+incdir+'):
@@ -1018,7 +1031,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1018
1031
  self.incdirs.append(os.path.abspath(incdir))
1019
1032
  util.debug(f"Added include dir '{os.path.abspath(incdir)}'")
1020
1033
  return None
1021
- self.error(f"Didn't understand +incdir+: '{plusarg}'")
1034
+ self.error(f"Didn't understand +incdir+: '{plusarg}'",
1035
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1022
1036
  return None
1023
1037
 
1024
1038
  # remaining plusargs as stored in self.args['unprocessed-plusargs'] (list)
@@ -1032,7 +1046,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1032
1046
  # derived classes have the option to handle it
1033
1047
  return plusarg
1034
1048
 
1035
- self.error(f"Didn't understand +plusarg: '{plusarg}'")
1049
+ self.error(f"Didn't understand +plusarg: '{plusarg}'",
1050
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1036
1051
  return None
1037
1052
 
1038
1053
 
@@ -1224,7 +1239,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1224
1239
  found_target = True
1225
1240
  break # move on to the next target
1226
1241
  if not found_target: # if STILL not found_this_target...
1227
- self.error(f"Unable to resolve {target=}")
1242
+ self.error(f"Unable to resolve {target=}",
1243
+ error_code=status_constants.EDA_DEPS_TARGET_NOT_FOUND)
1228
1244
 
1229
1245
  # if we've found any target since being called, it means we found the one we were called for
1230
1246
  return found_target
@@ -1354,7 +1370,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1354
1370
  if len(unparsed) == 0:
1355
1371
  # If unparsed is still empty, then error.
1356
1372
  self.error(f"For command '{self.command_name}' no files or targets were",
1357
- f"presented at the command line: {orig_tokens}")
1373
+ f"presented at the command line: {orig_tokens}",
1374
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1358
1375
 
1359
1376
  # by this point hopefully this is a target ... is it a simple filename?
1360
1377
  remove_list = []
@@ -1408,7 +1425,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1408
1425
 
1409
1426
  # we were unable to figure out what this command line token is for...
1410
1427
  if process_all and len(unparsed) > 0:
1411
- self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign")
1428
+ self.error(f"Didn't understand command remaining tokens {unparsed=} in CommandDesign",
1429
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1412
1430
 
1413
1431
  # handle a missing self.args['top'] with last filepath or last target:
1414
1432
  if not self.args.get('top', ''):
@@ -1441,7 +1459,8 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1441
1459
 
1442
1460
  if self.error_on_missing_top and not self.args.get('top', ''):
1443
1461
  self.error("Did not get a --top or DEPS top, required to run command",
1444
- f"'{self.command_name}' for tool={self.args.get('tool', None)}")
1462
+ f"'{self.command_name}' for tool={self.args.get('tool', None)}",
1463
+ error_code=status_constants.EDA_COMMAND_MISSING_TOP)
1445
1464
 
1446
1465
  return unparsed
1447
1466
 
@@ -1943,7 +1962,8 @@ class CommandParallel(Command):
1943
1962
  bad_remaining_args = [x for x in single_cmd_unparsed if x.startswith('-')]
1944
1963
  if bad_remaining_args:
1945
1964
  self.error(f'for {self.command_name} {command=} the following args are unknown',
1946
- f'{bad_remaining_args}')
1965
+ f'{bad_remaining_args}',
1966
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1947
1967
 
1948
1968
  # Remove unparsed args starting with '+', since those are commonly sent downstream to
1949
1969
  # single job (example, CommandSim plusargs).
@@ -1985,7 +2005,8 @@ class CommandParallel(Command):
1985
2005
 
1986
2006
  key = get_job_arg(job_dict, arg_name='job-name')
1987
2007
  if not key:
1988
- self.error(f'{job_dict=} needs to have a --job-name= arg attached')
2008
+ self.error(f'{job_dict=} needs to have a --job-name= arg attached',
2009
+ error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
1989
2010
  if key not in job_names_count_dict:
1990
2011
  job_names_count_dict[key] = 1
1991
2012
  else:
opencos/eda_config.py CHANGED
@@ -46,6 +46,7 @@ class Defaults:
46
46
  supported_config_auto_tools_order_keys = set([
47
47
  'exe', 'handlers',
48
48
  'requires_env', 'requires_py', 'requires_cmd', 'requires_in_exe_path',
49
+ 'requires_vsim_helper',
49
50
  'disable-tools-multi',
50
51
  ])
51
52
  supported_config_tool_keys = set([
@@ -398,9 +398,7 @@ auto_tools_order:
398
398
 
399
399
  questa:
400
400
  exe: qrun
401
- requires_cmd:
402
- - qrun -version
403
- - vsim -version
401
+ requires_vsim_helper: True
404
402
  handlers:
405
403
  elab: opencos.tools.queta.CommandElabQuesta
406
404
  sim: opencos.tools.queta.CommandSimQuesta
@@ -408,30 +406,22 @@ auto_tools_order:
408
406
  riviera:
409
407
  exe: vsim
410
408
  requires_cmd:
411
- - vsim -version
412
409
  - which riviera # Do not run it, make sure it's in PATH
413
- requires_in_exe_path:
414
- - riviera
410
+ requires_vsim_helper: True
415
411
  handlers:
416
412
  elab: opencos.tools.riviera.CommandElabRiviera
417
413
  sim: opencos.tools.riviera.CommandSimRiviera
418
414
 
419
415
  modelsim_ase:
420
416
  exe: vsim
421
- requires_cmd:
422
- - vsim -version
423
- requires_in_exe_path:
424
- - modelsim
417
+ requires_vsim_helper: True
425
418
  handlers:
426
419
  elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
427
420
  sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
428
421
 
429
422
  questa_fse: # free student edition, works similar to modelsim_ase
430
423
  exe: vsim
431
- requires_cmd:
432
- - vsim -version
433
- requires_in_exe_path:
434
- - questa_fse
424
+ requires_vsim_helper: True
435
425
  handlers:
436
426
  elab: opencos.tools.questa_fse.CommandElabQuestaFse
437
427
  sim: opencos.tools.questa_fse.CommandSimQuestaFse
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env python3
2
+
3
+ '''opencos.eda_deps_sanitize is an executable script
4
+
5
+ Usage:
6
+ eda_deps_sanitize --dir=<path>
7
+
8
+ Will print santized JSON data for the DEPS file found in --dir=<path>
9
+ '''
10
+
11
+ import argparse
12
+ import os
13
+ import sys
14
+
15
+ from pathlib import Path
16
+
17
+ from opencos import util
18
+ from opencos.deps import deps_file
19
+ from opencos.utils import status_constants
20
+
21
+
22
+ def run(*args) -> (int, str):
23
+ '''Runs the DEPS sanitizer, prints results to stdout'''
24
+
25
+
26
+ bool_kwargs = util.get_argparse_bool_action_kwargs()
27
+
28
+ parser = argparse.ArgumentParser(
29
+ prog='opencos eda_deps_sanitize', add_help=True, allow_abbrev=False
30
+ )
31
+
32
+ parser.add_argument('--yaml', **bool_kwargs,
33
+ help='Print output as YAML text, otherwise default is JSON text')
34
+ parser.add_argument('dir', type=str, default=str(Path('.')),
35
+ help='Directory to look for DEPS.[markup] file')
36
+
37
+ try:
38
+ parsed, unparsed = parser.parse_known_args(list(args) + [''])
39
+ unparsed = list(filter(None, unparsed))
40
+ except argparse.ArgumentError:
41
+ return 1, f'problem attempting to parse_known_args for {args=}'
42
+
43
+ deps_path = parsed.dir
44
+ if os.path.isfile(deps_path):
45
+ deps_path, _ = os.path.split(deps_path)
46
+
47
+ try:
48
+ my_depsfile_obj = deps_file.DepsFile(None, deps_path, {})
49
+ if not my_depsfile_obj.deps_file:
50
+ return status_constants.EDA_DEPS_FILE_NOT_FOUND, f'No DEPS markup file at {parsed.dir}'
51
+
52
+ ret_str = my_depsfile_obj.str_sanitized_markup(as_yaml=parsed.yaml)
53
+ rc = util.get_return_code()
54
+ return rc, ret_str
55
+ except Exception as e:
56
+ rc = 1, str(e)
57
+
58
+ return 0, ''
59
+
60
+
61
+ def main() -> None:
62
+ '''calls sys.exit(), main entrypoint'''
63
+ args = []
64
+ if len(sys.argv) > 1 and not args:
65
+ args = sys.argv[1:]
66
+
67
+ rc, deps_str = run(*args)
68
+ print(deps_str)
69
+ sys.exit(rc)
70
+
71
+
72
+ if __name__ == '__main__':
73
+ main()
@@ -1,6 +1,8 @@
1
1
  ''' opencos.tools.questa_fse - Used by opencos.eda for sim/elab commands w/ --tool=questa_fse.
2
2
 
3
3
  Contains classes for CommandSimQuestaFse, CommandElabQuestaFse.
4
+ For: Questa Intel Starter FPGA Edition-64 vsim 20XX.X Simulator
5
+
4
6
  '''
5
7
 
6
8
  # pylint: disable=R0801 # (duplicate code in derived classes, such as if-condition return.)
opencos/tools/yosys.py CHANGED
@@ -117,6 +117,7 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
117
117
  'yosys-blackbox': [], # list of modules that yosys will blackbox.
118
118
  'yosys-scriptfile': [],
119
119
  'sta-scriptfile': [],
120
+ 'rename-module': ''
120
121
  })
121
122
  self.args_help.update({
122
123
  'sta': (
@@ -150,12 +151,14 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
150
151
  ' via: sta -no_init -exit <this-arg>. You can set multiple args for multiple'
151
152
  ' scriptfile (appends)'
152
153
  ),
154
+ 'rename-module': 'Renames the output .v and module name',
153
155
  })
154
156
 
155
157
  self.yosys_out_dir = ''
156
158
  self.yosys_v_path = ''
157
159
  self.full_work_dir = ''
158
160
  self.blackbox_list = []
161
+ self.top_module = ''
159
162
 
160
163
  def do_it(self) -> None:
161
164
  self.set_tool_defines()
@@ -333,6 +336,13 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
333
336
  ]
334
337
  lines += [
335
338
  'opt_clean',
339
+ ]
340
+ if self.args['rename-module']:
341
+ lines += [f'rename {self.args["top"]} {self.args["rename-module"]}']
342
+ self.top_module = self.args['rename-module']
343
+ else:
344
+ self.top_module = self.args["top"]
345
+ lines += [
336
346
  f'write_verilog {self.yosys_v_path}',
337
347
  f'write_json {self.yosys_v_path}.json',
338
348
  ]
@@ -388,7 +398,7 @@ class CommonSynthYosys(CommandSynth, ToolYosys):
388
398
  lines = [
389
399
  'read_liberty ' + self.args['liberty-file'],
390
400
  'read_verilog ' + self.yosys_v_path,
391
- 'link_design ' + self.args['top'],
401
+ 'link_design ' + self.top_module,
392
402
  ]
393
403
  for _file in self.files_sdc:
394
404
  lines.append('read_sdc ' + _file)
@@ -505,12 +515,13 @@ class CommandLecYosys(CommandLec, ToolYosys):
505
515
  })
506
516
 
507
517
  self.synth_work_dirs = [
508
- os.path.join('eda.work', 'lec.design1.synth'),
509
- os.path.join('eda.work', 'lec.design2.synth')
518
+ os.path.join('eda.work', 'lec.Design1.synth'),
519
+ os.path.join('eda.work', 'lec.Design2.synth')
510
520
  ]
511
521
 
512
522
  self.synth_designs_tops = [None, None]
513
523
  self.synth_designs_fpaths = [None, None]
524
+ self.synth_design_top_module_names = [None, None]
514
525
 
515
526
  def get_synth_result_fpath(self, target: str) -> str:
516
527
  '''Overridden from CommandLec'''
@@ -534,9 +545,12 @@ class CommandLecYosys(CommandLec, ToolYosys):
534
545
 
535
546
  synth_cmd_list += [
536
547
  '--work-dir=' + self.synth_work_dirs[design_num],
537
- self.args['designs'][design_num]
548
+ self.args['designs'][design_num],
549
+ f'--rename-module=Design{design_num + 1}'
538
550
  ]
539
551
 
552
+ self.synth_design_top_module_names[design_num] = f'Design{design_num + 1}'
553
+
540
554
  return synth_cmd_list
541
555
 
542
556
 
@@ -663,10 +677,12 @@ class CommandLecYosys(CommandLec, ToolYosys):
663
677
  else:
664
678
  self.error(f' --pre-read-verilog file {x} does not exist')
665
679
  lec_cmd_f_list += [
680
+ '# Design1 (module):',
666
681
  f'read_verilog -sv -icells {self.synth_designs_fpaths[0]}',
682
+ '# Design2 (module):',
667
683
  f'read_verilog -sv -icells {self.synth_designs_fpaths[1]}',
668
684
  'clk2fflogic;',
669
- f'miter -equiv -flatten {" ".join(self.synth_designs_tops)} miter',
685
+ f'miter -equiv -flatten {" ".join(self.synth_design_top_module_names)} miter',
670
686
  ('sat -seq 50 -verify -prove trigger 0 -show-all -show-inputs -show-outputs'
671
687
  ' -set-init-zero miter'),
672
688
  ]
opencos/util.py CHANGED
@@ -16,6 +16,8 @@ from enum import Enum
16
16
  from pathlib import Path
17
17
  from importlib import import_module
18
18
 
19
+ from opencos.utils import status_constants
20
+
19
21
  global_exit_allowed = False # pylint: disable=invalid-name
20
22
  progname = "UNKNOWN" # pylint: disable=invalid-name
21
23
  progname_in_message = True # pylint: disable=invalid-name
@@ -32,6 +34,8 @@ args = { # pylint: disable=invalid-name
32
34
  'artifacts-json': True,
33
35
  }
34
36
 
37
+ max_error_code = 0 # pylint: disable=invalid-name
38
+
35
39
  class Colors:
36
40
  '''Namespace class for color printing help'''
37
41
  red = "\x1B[31m"
@@ -191,17 +195,20 @@ class Artifacts:
191
195
  return # do nothing if we have no artifacts
192
196
 
193
197
  # Update all file sizes:
198
+ remove_keys = set()
194
199
  for key, entry in self.data.items():
195
200
  if os.path.isfile(entry['name']):
196
201
  entry['size'] = os.path.getsize(entry['name'])
197
202
  else:
198
203
  # file doesn't exist, remove it from artifacts.
199
204
  warning(f'Removing {key} ({entry["name"]}) from artifacts (file does not exist)')
200
- del self.data[key]
205
+ remove_keys.add(key)
206
+ for key in remove_keys:
207
+ del self.data[key]
201
208
 
202
209
  with open(self.artifacts_json_filepath, 'w', encoding='utf-8') as f:
203
210
  json.dump(self.data, f, indent=4)
204
- info(f'Wrote artifacts JSON: {self.artifacts_json_filepath}')
211
+ info(f'Wrote artifacts JSON: {self.artifacts_json_filepath}')
205
212
 
206
213
 
207
214
  artifacts = Artifacts()
@@ -217,6 +224,7 @@ class UtilLogger:
217
224
  # util's argparser: --no-default-log, --logfile=<name>, or --force-logfile=<name>
218
225
  default_log_enabled = False
219
226
  default_log_filepath = os.path.join('eda.work', 'eda.log')
227
+ enable = True
220
228
 
221
229
  def clear(self) -> None:
222
230
  '''Resets internals'''
@@ -234,6 +242,9 @@ class UtilLogger:
234
242
 
235
243
  def start(self, filename: str, force: bool = False) -> None:
236
244
  '''Starts (opens) log'''
245
+ if not self.enable:
246
+ self.file = None
247
+ return
237
248
  if not filename:
238
249
  error('Trying to start a logfile, but filename is missing')
239
250
  return
@@ -420,14 +431,12 @@ def process_tokens(tokens:list) -> (argparse.Namespace, list):
420
431
  # avoid this if someone has --help arg not yet parsed.
421
432
  start_log(global_log.default_log_filepath, force=True)
422
433
 
423
-
424
434
  parsed_as_dict = vars(parsed)
425
435
  for key,value in parsed_as_dict.items():
426
436
  key = key.replace('_', '-')
427
437
  if value is not None:
428
438
  args[key] = value # only update with non-None values to our global 'args' dict
429
439
 
430
-
431
440
  return parsed, unparsed
432
441
 
433
442
  # ********************
@@ -616,7 +625,9 @@ def warning(*text, start: object = None, end: str = '\n') -> None:
616
625
 
617
626
 
618
627
  def error(
619
- *text, error_code: int = 255, do_exit: bool = True, start: object = None, end: str = '\n'
628
+ *text,
629
+ error_code: int = status_constants.EDA_DEFAULT_ERROR,
630
+ do_exit: bool = True, start: object = None, end: str = '\n'
620
631
  ) -> int:
621
632
  '''Print error messaging (in red if possible).
622
633
 
@@ -629,9 +640,12 @@ def error(
629
640
 
630
641
  Note these messages append to global logging. Increments global args['errors'] int.
631
642
  '''
643
+ global max_error_code # pylint: disable=global-statement
644
+
632
645
  if start is None:
633
646
  start = "ERROR: " + (f"[{progname}] " if progname_in_message else "")
634
647
  args['errors'] += 1
648
+ max_error_code = max(max_error_code, error_code)
635
649
  print_red(f"{start}{' '.join(list(text))}", end=end)
636
650
  if do_exit:
637
651
  if args['debug']:
@@ -644,6 +658,15 @@ def error(
644
658
  return abs(int(error_code))
645
659
 
646
660
 
661
+ def get_return_code() -> int:
662
+ '''Checks global max_error_code and args['errors']'''
663
+ if args['errors']:
664
+ if max_error_code == 0:
665
+ return 255
666
+ return max_error_code
667
+ return 0
668
+
669
+
647
670
  def exit( # pylint: disable=redefined-builtin
648
671
  error_code: int = 0, quiet: bool = False
649
672
  ) -> int:
@@ -1,5 +1,7 @@
1
1
  ''' opencos.utils.markup_helpers - function helpers for YAML, TOML reading and writing'''
2
2
 
3
+ import json
4
+ import os
3
5
  import re
4
6
  import shutil
5
7
  import subprocess
@@ -89,10 +91,37 @@ def yaml_safe_load(filepath: str, only_root_line_numbers:bool = False) -> dict:
89
91
 
90
92
 
91
93
  def yaml_safe_writer(data: dict, filepath: str) -> None:
92
- '''Warpper for yaml.dump, enforces file extension otherwise warning'''
93
- if filepath.endswith('.yml') or filepath.endswith('.yaml'):
94
+ '''Wrapper for yaml.dump, enforces file extension otherwise warning'''
95
+ _, ext = os.path.splitext(filepath)
96
+ if ext.lower() in ('.yml', '.yaml'):
94
97
  with open(filepath, 'w', encoding='utf-8') as f:
95
98
  yaml.dump(data, f, allow_unicode=True,
96
99
  default_flow_style=False, sort_keys=False, encoding='utf-8')
97
100
  else:
98
101
  warning(f'{filepath=} to be written for this extension not implemented.')
102
+
103
+ def json_writer(data: dict, filepath: str) -> None:
104
+ '''Wrapper for json.dump'''
105
+ with open(filepath, 'w', encoding='utf-8') as f:
106
+ json.dump(data, f, indent=4)
107
+
108
+
109
+ def markup_writer(data: dict, filepath: str) -> None:
110
+ '''Wrapper for yaml_safe_writer or json_writer'''
111
+ _, ext = os.path.splitext(filepath)
112
+ if ext.lower in ('.yml', '.yaml'):
113
+ yaml_safe_writer(data, filepath)
114
+ else:
115
+ json_writer(data, filepath)
116
+
117
+
118
+ def markup_dumper(data: dict, as_yaml: bool = False) -> str:
119
+ '''Returns JSON str; if as_yaml=True returns YAML str, from data'''
120
+ if as_yaml:
121
+ return yaml.dump(
122
+ data=data, allow_unicode=True,
123
+ default_flow_style=False, sort_keys=False
124
+ )
125
+
126
+ # else return JSON:
127
+ return str(json.dumps(data, indent=4))
@@ -0,0 +1,27 @@
1
+ '''opencos.utils.status_constants is a module containing known Error return codes
2
+
3
+ (0 pass, > 0 fail) from eda or other opencos executables.
4
+ '''
5
+
6
+ NO_ERROR = 0
7
+
8
+ PYTHON_EXCEPTION = 1
9
+
10
+ EDA_DEPS_FILE_NOT_FOUND = 10
11
+ EDA_DEPS_TARGET_NOT_FOUND = 11
12
+
13
+ EDA_COMMAND_OR_ARGS_ERROR = 20
14
+ EDA_CONFIG_ERROR = 21
15
+ EDA_COMMAND_MISSING_TOP = 22
16
+
17
+ EDA_SIM_LOG_HAS_BAD_STRING = 30
18
+ EDA_SIM_LOG_MISSING_MUST_STRING = 31
19
+
20
+ EDA_SHELL_LOG_HAS_BAD_STRING = 38
21
+ EDA_SHELL_LOG_MISSING_MUST_STRING = 39
22
+
23
+ EDA_EXEC_NONZERO_RETURN_CODE1 = 41
24
+ EDA_EXEC_NONZERO_RETURN_CODE2 = 42
25
+ EDA_EXEC_NONZERO_RETURN_CODE255 = 255
26
+
27
+ EDA_DEFAULT_ERROR = 255
@@ -0,0 +1,55 @@
1
+ '''Because so many tools use the exe vsim, I don't want to run `vsim -version` N times for N
2
+ tools each to figure out if it's full-Questa, Modelsim, QuestaFSE, or Riviera.
3
+
4
+ Instead, eda.py can call this once, and then query if this tool exists when running
5
+ opencos.eda.auto_tool_setup(..)
6
+ '''
7
+
8
+ import shutil
9
+ import subprocess
10
+
11
+ from opencos.util import debug
12
+
13
+ vsim_path = shutil.which('vsim')
14
+
15
+ INIT_HAS_RUN = False
16
+ TOOL_IS = {
17
+ 'riviera': False,
18
+ 'modelsim_ase': False,
19
+ 'questa' : False,
20
+ 'questa_fse': False
21
+ }
22
+
23
+
24
+ def init() -> None:
25
+ '''Sets INIT_HAS_RUN=True (only runs once) and one of TOOL_IS[tool] = True'''
26
+ global INIT_HAS_RUN # pylint: disable=global-statement
27
+
28
+ if INIT_HAS_RUN:
29
+ return
30
+
31
+ INIT_HAS_RUN = True
32
+
33
+ if not vsim_path:
34
+ return
35
+
36
+ proc = None
37
+ try:
38
+ proc = subprocess.run([vsim_path, '-version'], capture_output=True, check=False)
39
+ except Exception as e:
40
+ debug(f'vsim -version: exception {e}')
41
+
42
+ if proc is None or proc.returncode != 0:
43
+ return
44
+
45
+
46
+ stdout_str_lower = proc.stdout.decode('utf-8', errors='replace').lower()
47
+
48
+ if all(x in stdout_str_lower for x in ('starter', 'modelsim', 'fpga')):
49
+ TOOL_IS['modelsim_ase'] = True
50
+ elif all(x in stdout_str_lower for x in ('starter', 'questa', 'fpga')):
51
+ TOOL_IS['questa_fse'] = True
52
+ elif all(x in stdout_str_lower for x in ('riviera', 'aldec')):
53
+ TOOL_IS['riviera'] = True
54
+ elif 'questa' in stdout_str_lower:
55
+ TOOL_IS['questa'] = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.2.50
3
+ Version: 0.2.51
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
@@ -2,13 +2,14 @@ opencos/__init__.py,sha256=RwJA9oc1uUlvNX7v5zoqwjnSRNq2NZwRlHqtS-ICJkI,122
2
2
  opencos/_version.py,sha256=XiHFZjCofkkZvI9befTFKsRt5zLouUo1CIwhW4srWU0,582
3
3
  opencos/_waves_pkg.sv,sha256=1lbhQOVGc3t_R8czYjP40hssP0I3FlZOpHTkI7yKFbI,1251
4
4
  opencos/deps_schema.py,sha256=pdJpKmfXxCYZsfXTxsnACw-s6Mzk55QH06ZSA5uqPV0,15454
5
- opencos/eda.py,sha256=y53bScMokPfpbZfqzpSg9Q08U6GVF_BjW2P0bPiEBE0,20424
6
- opencos/eda_base.py,sha256=h3aeSp0TY5b90r-Hd5dBiRlm2KAhtIsf0aWLJIZ1XfQ,94199
7
- opencos/eda_config.py,sha256=Rnf3PbvlDytg9klQrRcksUizryYwn2pWARYN1KJPj14,11321
8
- opencos/eda_config_defaults.yml,sha256=5EpzyFkEjL7lg3Xrx2M8lvoomkS1lVV18mbhBuym6w8,12607
5
+ opencos/eda.py,sha256=rYBt4Qu2y45zdwD4jDx_p3l2u5LMS1nsaGQCP2ZYdwY,20883
6
+ opencos/eda_base.py,sha256=q0uKEzAMzLmqb4jMBcGv7rTpdKnfrhnMZo7MyB8YN8Y,95706
7
+ opencos/eda_config.py,sha256=Xma8w7kBLCNPCSrCKpceieU7GUBv4i54XZlHjnZ95X4,11353
8
+ opencos/eda_config_defaults.yml,sha256=PdR3xHWQIdGeIyCTr2c3W9Vfq9RC1E9HbqiSydyDYZE,12417
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
+ opencos/eda_deps_sanitize.py,sha256=SQjvrte9Hv9JesRY0wljvbdC6pAmLCikI-Wdzzy-D04,1939
12
13
  opencos/eda_extract_targets.py,sha256=dvBjc2qFBJkwlW6Fm-66Y_vlr0VZL1QwfIosMR_bgbY,4814
13
14
  opencos/eda_tool_helper.py,sha256=_YgobDLEWW6Fzdr976LxaCDZ4DKRyuMs5CrYQHaTPrU,2558
14
15
  opencos/export_helper.py,sha256=ArsM8qxBc08gj9S-UholGU417POfBYb_cHkBQZEfqfI,22046
@@ -17,18 +18,18 @@ opencos/files.py,sha256=aoq0O2KfISzZb-Vi_q_0TTGBER9xJc--FkVZf0ga7pA,1549
17
18
  opencos/names.py,sha256=iC37PV7Pz0PicTDg09vbQ9NXAj-5m6RKrWHkkcHB8As,1145
18
19
  opencos/peakrdl_cleanup.py,sha256=vHNGtalTrIVP335PhRjPt9RhoccgpK1HJAi-E4M8Kc8,736
19
20
  opencos/seed.py,sha256=IL9Yg-r9SLSRseMVWaEHmuw2_DNi_eyut11EafoNTsU,942
20
- opencos/util.py,sha256=yGjyhQoy06A7d-UocMvRKqYkbyZ-FOoTR7F83XBHBR8,33325
21
+ opencos/util.py,sha256=hnE66Mu7Uu-OGdAE5gkJ8nsKc_zUNMptXcuBFkm30wU,33975
21
22
  opencos/commands/__init__.py,sha256=DtOA56oWJu68l-_1_7Gdv0N-gtXVB3-p9IhGzAYex8U,1014
22
23
  opencos/commands/build.py,sha256=jI5ul53qfwn6X-yfSdSQIcLBhGtzZUk7r_wKBBmKJI0,1425
23
24
  opencos/commands/elab.py,sha256=m6Gk03wSzX8UkcmReooK7turF7LpqO0IcdOZwJ8XiyI,1596
24
25
  opencos/commands/export.py,sha256=juzxJL5-RpEnU5DmwF0fiG5pUrB2BbUbvCp2OasEd88,3494
25
26
  opencos/commands/flist.py,sha256=OUu_ewTDLkZqdW4547iRrwOhT4ghm8oMYHsA63yChvo,8551
26
- opencos/commands/lec.py,sha256=gN6nQ4GURhPC8nu8Zuj08s1fmNzuiuaS9vJgtNZyX28,3647
27
+ opencos/commands/lec.py,sha256=mIrMKFEZrFYIzvBdffcIyVULx04Cu4-480FB4Y1EiTA,3863
27
28
  opencos/commands/multi.py,sha256=dCo4rMIkGT3BtlBhUIAd7r31w8qxeJvybpl4H7DR77o,27225
28
29
  opencos/commands/open.py,sha256=unrpGckzg0FE5W3oARq8x0jX7hhV_uM9Oh5FgISHFAg,724
29
30
  opencos/commands/proj.py,sha256=MdHTOtQYG93_gT97dWuSyAgUxX2vi9FRhL0dtc-rM98,1096
30
- opencos/commands/shell.py,sha256=senuqSGOc5nVGU5voZNJO4_hzVAK0ELtu0wmRZgwv3k,7463
31
- opencos/commands/sim.py,sha256=6_WiHnh6_xaEi4T-wlOyWxjCPdVcL-bWppPXy3-sIRw,14291
31
+ opencos/commands/shell.py,sha256=0SNxiNRPD1BO6dK0yxU_iV-8S9UyF4GOWGLL1A_KeVs,7583
32
+ opencos/commands/sim.py,sha256=WPkn_lZqgMPSQTxxT4Qa0qHKMmdHAkt1GXCDw4iD6kI,14723
32
33
  opencos/commands/sweep.py,sha256=ni4XFgnFF8HLXtwPhETyLWfvc2kgtm4bcxFcKzUhkf0,9343
33
34
  opencos/commands/synth.py,sha256=quB-HWS4LKYTiFBHiYarQi4pMnRmt12wQTZpi14VvlE,4355
34
35
  opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1472
@@ -37,7 +38,7 @@ opencos/commands/waves.py,sha256=dsWwtjpDgH-YsiIjJgqTvteY3OZ48UnEAWc3blV2Fog,705
37
38
  opencos/deps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
39
  opencos/deps/defaults.py,sha256=4VgTRXf0hSJrj4tMk0t-mmvgEaaQQFDIYrxWrcKfYWk,1241
39
40
  opencos/deps/deps_commands.py,sha256=OlqueYFK8U83loasok3xJGzUDpNcj2DPk37332DfmRo,17039
40
- opencos/deps/deps_file.py,sha256=QvJ1fMyoQOXNbvHjWevkYF2kx0zvbRgFZ3GAPWw8jqk,12997
41
+ opencos/deps/deps_file.py,sha256=29UkOT-UYw5XYYRK7DySmZkgdTj4wnxVQ5SgZVnrrds,15199
41
42
  opencos/deps/deps_processor.py,sha256=NYGBrBAmk7ltrvxsEhv76Kpp76GBRJqeNNY_ckWf5mE,33028
42
43
  opencos/hw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
44
  opencos/hw/oc_cli.py,sha256=QBmf05VooB9kQFzgliak7PEvqVLTSEI3v6deV1OZQEs,132252
@@ -67,7 +68,7 @@ opencos/tools/invio_yosys.py,sha256=asSjbdPjBXB76KxNZIhoDRn2DoXKsZEQ1YDX_WBzKiA,
67
68
  opencos/tools/iverilog.py,sha256=KQV5tiRdM0ZuJOO0q3ZeUmhRyEc-oJggOc6RKIjoH84,6482
68
69
  opencos/tools/modelsim_ase.py,sha256=rW5YrzLIhZKdbIT2UM60DGmDEHDK3XMlndpBz906cmA,14229
69
70
  opencos/tools/questa.py,sha256=bXH_ajMUBBHNUeWTBNWDp2gGyza2vf8xIC6vz5EWiz8,7619
70
- opencos/tools/questa_fse.py,sha256=vgrimIwJ1REFkQuKadWaS-UKcEB3wnumX-xN-91gqiE,1790
71
+ opencos/tools/questa_fse.py,sha256=WLNvkD57ggI7lQgxYr6h_xmXenh50J_m8o8amhECmpk,1855
71
72
  opencos/tools/riviera.py,sha256=0FKAUvgyUHvJ5z0LOjHV9p3dGF8LI5kj5WLOZXgCDas,10695
72
73
  opencos/tools/slang.py,sha256=mFw58vhnCTRR9yaQ2zHPlNB5HKSf3Y078XcaVnpLaAc,7798
73
74
  opencos/tools/slang_yosys.py,sha256=3fyLRRdTXhSppNtUhhUl00oG-cT9TyyPTH6JvasS9ZE,9804
@@ -75,15 +76,17 @@ opencos/tools/surelog.py,sha256=dtj3ApAKoQasnGdg42n9DPgeqoJ5nCuurIkIO3G5ZCY,5011
75
76
  opencos/tools/tabbycad_yosys.py,sha256=2LePPgYXBVdsy7YcffPIWN-I0B7queLQ_f_pme2SCGw,7803
76
77
  opencos/tools/verilator.py,sha256=h5VScZyJYxk2cXDRrTTD47C_yAVHuyXv9_p7UL1o5mk,19486
77
78
  opencos/tools/vivado.py,sha256=roM0MxrHoNIT_DsUiql1vQFlez2ql4qhwAfB-rGH4Qg,40485
78
- opencos/tools/yosys.py,sha256=5zn9CeJc6UUuBI1Gk7HD8Y_l4GPa4KcLB7NNYxIQBjY,25648
79
+ opencos/tools/yosys.py,sha256=UKWzvc2rSi3J9U2TROqRbmePdBmjskap664giumeRBk,26323
79
80
  opencos/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
- opencos/utils/markup_helpers.py,sha256=tDaDtV4INEg04Diks1ZXlfOwzyCOGnWqc5aeHnwLoOk,3727
81
+ opencos/utils/markup_helpers.py,sha256=A8Ev5UJ4EVKjdcF2g85SQbjdPZR4jGpNqCLaBy_4v7Q,4569
82
+ opencos/utils/status_constants.py,sha256=aQui30ohPCEaWNDh2iujJQ_KQa3plry_rk7uDzS3vWk,603
81
83
  opencos/utils/str_helpers.py,sha256=DOkwfKJR6aENM3U2BkJ41ELDU5Uj_zyhEfxuaQEcpEY,3352
82
84
  opencos/utils/subprocess_helpers.py,sha256=xemAGPey6M0sWY_FElvr-Z0phCfdjaC-znP8FKihPaE,3535
83
- opencos_eda-0.2.50.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
84
- opencos_eda-0.2.50.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
85
- opencos_eda-0.2.50.dist-info/METADATA,sha256=a7ZzQPyvJ1e5ZYWhIs1Gt39wvoa9PWPi27rJxKiMMd8,604
86
- opencos_eda-0.2.50.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
87
- opencos_eda-0.2.50.dist-info/entry_points.txt,sha256=hi6SZHiGGgLrI3KnBCOXDpYOGXYpw-zWBWe5Di39OCw,167
88
- opencos_eda-0.2.50.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
89
- opencos_eda-0.2.50.dist-info/RECORD,,
85
+ opencos/utils/vsim_helper.py,sha256=2voGRZI2iAQ2Pv2ZI5g2why6xpgig-To8im-LVXtuDU,1517
86
+ opencos_eda-0.2.51.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
87
+ opencos_eda-0.2.51.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
88
+ opencos_eda-0.2.51.dist-info/METADATA,sha256=XAX9j35DEWh0nLU7Gt8unosqGzK9dGeqHtGcj8IgK-8,604
89
+ opencos_eda-0.2.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
90
+ opencos_eda-0.2.51.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
91
+ opencos_eda-0.2.51.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
92
+ opencos_eda-0.2.51.dist-info/RECORD,,
@@ -1,5 +1,6 @@
1
1
  [console_scripts]
2
2
  eda = opencos.eda:main_cli
3
+ eda_deps_sanitize = opencos.eda_deps_sanitize:main
3
4
  eda_deps_schema = opencos.deps_schema:main
4
5
  eda_targets = opencos.eda_extract_targets:main
5
6
  oc_cli = opencos.hw.oc_cli:main