opencos-eda 0.3.7__py3-none-any.whl → 0.3.8__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/sim.py CHANGED
@@ -44,7 +44,7 @@ def parameters_dict_get_command_list(params: dict, arg_prefix: str = '-G') -> li
44
44
  return ret_list
45
45
 
46
46
 
47
- class CommandSim(CommandDesign):
47
+ class CommandSim(CommandDesign): # pylint: disable=too-many-public-methods
48
48
  '''Base class command handler for: eda sim ...'''
49
49
 
50
50
  CHECK_REQUIRES = [Tool] # Used by check_command_handler_cls()
@@ -72,6 +72,9 @@ class CommandSim(CommandDesign):
72
72
  'optimize': False,
73
73
  'log-bad-strings': ['ERROR: ', 'FATAL: ', 'Error: ', 'Fatal: '],
74
74
  'log-must-strings': [],
75
+ 'log-warning-strings': ['WARNING: ', 'Warning: '],
76
+ 'uvm': False,
77
+ 'uvm-version': '1.2',
75
78
  # verilate-args: list of args you can only pass to Verilator,
76
79
  # not used by other simulators, so these can go in DEPS files for custom things
77
80
  # like -CFLAGS -O0, etc.
@@ -91,6 +94,8 @@ class CommandSim(CommandDesign):
91
94
  'log-must-strings': ('strings that are required by the log to not-fail the simulation.'
92
95
  ' Some tools use these at only certain phases'
93
96
  ' (compile/elab/sim).'),
97
+ 'log-warning-strings': ('strings that if present will be counted in the total tool'
98
+ ' warnings at end of simulation'),
94
99
  'pass-pattern': ('Additional string required to pass a simulation, appends to'
95
100
  ' log-must-strings'),
96
101
  'sim-args': 'args added to final "simulation" step',
@@ -111,12 +116,22 @@ class CommandSim(CommandDesign):
111
116
  'waves': 'Include waveforms, if possible for tool',
112
117
  'waves-start': 'Starting time of waveform capture, if possible for tool',
113
118
  'test-mode': ('stops the command early without executing, if --gui is present will'
114
- ' instead test without spawning gui')
119
+ ' instead test without spawning gui'),
120
+ 'uvm': (
121
+ 'Attempts to support UVM (aka 1.2 if --uvm-version=1.2) for the'
122
+ ' the simulation tool. May add libraries for uvm, tool dependent.'
123
+ ),
124
+ 'uvm-version': (
125
+ 'Used if --uvm is set, for example --uvm-version=1.2'
126
+ ),
127
+
128
+ })
115
129
 
130
+ self.args_kwargs.update({
131
+ 'uvm-version': { 'choices': ['1.2'] },
116
132
  })
117
133
 
118
134
 
119
- self.args['verilate-args'] = []
120
135
 
121
136
  def process_parameters_get_list(self, arg_prefix: str = '-G') -> list:
122
137
  '''Returns list (suitable command list for shell or for tool) from self.parameters'''
@@ -182,14 +197,14 @@ class CommandSim(CommandDesign):
182
197
 
183
198
  # Collect (overwrite CommandSim) the bad and must strings, if present,
184
199
  # from our config.tools.verilator:
185
- for tool_config_key in ['log-bad-strings', 'log-must-strings']:
200
+ for tool_config_key in ('log-bad-strings', 'log-must-strings', 'log-warning-strings'):
186
201
  if len(self.tool_config.get(tool_config_key, [])) > 0:
187
202
  self.args[tool_config_key] = self.tool_config.get(tool_config_key, [])
188
203
 
189
204
 
190
205
  # Methods that derived classes may override:
191
206
 
192
- def run_commands_check_logs( # pylint: disable=dangerous-default-value
207
+ def run_commands_check_logs( # pylint: disable=dangerous-default-value,too-many-locals
193
208
  self, commands: list , check_logs: bool = True, log_filename=None,
194
209
  bad_strings: list = [],
195
210
  must_strings: list = [],
@@ -197,7 +212,10 @@ class CommandSim(CommandDesign):
197
212
  ) -> None:
198
213
  '''Returns None, runs all commands (each element is a list) and checks logs
199
214
 
200
- for bad-strings and must-strings (args or class member vars)
215
+ for bad-strings and must-strings (args or class member vars).
216
+
217
+ Will add any bad_strings or tool log-warning-strings to self, so tool
218
+ related warning/error counts can be reported later.
201
219
  '''
202
220
 
203
221
  for obj in commands:
@@ -212,7 +230,7 @@ class CommandSim(CommandDesign):
212
230
  if not work_dir:
213
231
  work_dir = self.args['work-dir']
214
232
 
215
- util.debug(f'run_commands_check_logs: {clist=}, {tee_fpath=}')
233
+ util.debug(f'run_commands_check_logs: {clist=}, {tee_fpath=}, {log_filename=}')
216
234
 
217
235
  log_fname = None
218
236
  if tee_fpath:
@@ -220,11 +238,18 @@ class CommandSim(CommandDesign):
220
238
  if log_filename:
221
239
  log_fname = log_filename
222
240
 
223
-
224
- _, stdout, _ = self.exec(
225
- work_dir=work_dir, command_list=clist, tee_fpath=tee_fpath
241
+ # track the retcode because we run stop_on_error=False (to count warnings/errors)
242
+ _, stdout, retcode = self.exec(
243
+ work_dir=work_dir, command_list=clist, tee_fpath=tee_fpath,
244
+ stop_on_error=False,
226
245
  )
227
246
 
247
+ if check_logs and not log_fname and bad_strings:
248
+ util.warning(
249
+ f'{self.get_info_job_name()}: check_logs=True but no log file set',
250
+ f'(run --debug for more info), for command: {" ".join(clist)}'
251
+ )
252
+
228
253
  if check_logs and log_fname:
229
254
  # Note this call will check on stdout if not GUI, not opening the log_fname,
230
255
  # but if this is GUI we normally lose stdout and have to open the log.
@@ -234,6 +259,7 @@ class CommandSim(CommandDesign):
234
259
  file_contents_str = stdout
235
260
 
236
261
  self.check_logs_for_errors(
262
+ sim_retcode=retcode,
237
263
  filename=os.path.join(work_dir, log_fname),
238
264
  file_contents_str=file_contents_str,
239
265
  bad_strings=bad_strings, must_strings=must_strings,
@@ -244,6 +270,10 @@ class CommandSim(CommandDesign):
244
270
  name=os.path.join(work_dir, log_fname),
245
271
  typ='text', description='Simulator stdout/stderr log file'
246
272
  )
273
+ if retcode:
274
+ self.error(
275
+ f"{self.get_info_job_name()}: exec returned with error code: {retcode}"
276
+ )
247
277
 
248
278
  def do_export(self) -> None:
249
279
  '''CommandSim helper for handling args --export*
@@ -337,9 +367,13 @@ class CommandSim(CommandDesign):
337
367
  '''
338
368
  return
339
369
 
370
+
340
371
  def check_logs_for_errors( # pylint: disable=dangerous-default-value,too-many-locals,too-many-branches
341
- self, filename: str = '', file_contents_str: str = '',
372
+ self,
373
+ sim_retcode: int = 0,
374
+ filename: str = '', file_contents_str: str = '',
342
375
  bad_strings: list = [], must_strings: list = [],
376
+ warning_strings: list = [],
343
377
  use_bad_strings: bool = True, use_must_strings: bool = True
344
378
  ) -> None:
345
379
  '''Returns None, checks logs using args bad_strings, must_strings,
@@ -358,6 +392,8 @@ class CommandSim(CommandDesign):
358
392
  if use_must_strings:
359
393
  _must_strings = must_strings + self.args.get('log-must-strings', [])
360
394
 
395
+ _warning_strings = warning_strings + self.args.get('log-warning-strings', [])
396
+
361
397
  if self.args['pass-pattern'] != "":
362
398
  _must_strings.append(self.args['pass-pattern'])
363
399
 
@@ -389,6 +425,20 @@ class CommandSim(CommandDesign):
389
425
  self.error(f'sim.check_logs_for_errors: {log_fpath=} does not exist, and no',
390
426
  'file_contents_str exists to check')
391
427
 
428
+ self.update_tool_warn_err_counts_from_log_lines(
429
+ log_lines=lines, bad_strings=_bad_strings, warning_strings=_warning_strings
430
+ )
431
+
432
+ if isinstance(self, Tool):
433
+ self.report_tool_warn_error_counts()
434
+
435
+ if sim_retcode > 0:
436
+ # We have to update artifacts first, have the caller set the error.
437
+ # Skip the checking for bad strings or must strings, because we've already
438
+ # failed, but we did the important counting of Error and Warning lines above,
439
+ # and reported it.
440
+ return
441
+
392
442
  if lines:
393
443
  for lineno, line in enumerate(lines):
394
444
  if any(must_str in line for must_str in _must_strings):
opencos/eda_base.py CHANGED
@@ -178,6 +178,10 @@ class Tool:
178
178
  # a Command object's self.args instead of the class Tool.args. Safely create it
179
179
  # if it doesn't exist:
180
180
  self._VERSION = None
181
+
182
+ self.tool_warning_count = 0
183
+ self.tool_error_count = 0
184
+
181
185
  if getattr(self, 'args', None) is None:
182
186
  self.args = {}
183
187
  if getattr(self, 'args_help', None) is None:
@@ -225,12 +229,30 @@ class Tool:
225
229
  '''Sets and returns self._VERSION'''
226
230
  return self._VERSION
227
231
 
232
+ def report_tool_warn_error_counts(self) -> None:
233
+ '''Reports info line based on self.tool_error_count and self.tool_warning_count.'''
234
+ tool_name = get_class_tool_name(self)
235
+ if not tool_name:
236
+ return
237
+
238
+ info_color = Colors.green
239
+ start = ''
240
+ if self.tool_error_count or self.tool_warning_count:
241
+ start = safe_emoji('🔶 ')
242
+ info_color = Colors.yellow
243
+ util.info(
244
+ f"{start}Tool - {tool_name}, total counts:",
245
+ f"{Colors.bold}{self.tool_warning_count} tool warnings{Colors.normal}{info_color},",
246
+ f"{Colors.bold}{self.tool_error_count} tool errors",
247
+ color=info_color
248
+ )
249
+
228
250
  def set_tool_defines(self) -> None:
229
251
  '''Derived classes may override, sets any additional defines based on tool.'''
230
252
  return
231
253
 
232
254
 
233
- class Command: # pylint: disable=too-many-public-methods
255
+ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attributes
234
256
  '''Base class for all: eda COMMAND
235
257
 
236
258
  The Command class should be used when you don't require files, otherwise consider
@@ -244,6 +266,8 @@ class Command: # pylint: disable=too-many-public-methods
244
266
  self.args = {}
245
267
  if getattr(self, 'args_help', None) is None:
246
268
  self.args_help = {}
269
+ if getattr(self, 'args_kwargs', None) is None:
270
+ self.args_kwargs = {}
247
271
  self.args.update({
248
272
  "keep" : False,
249
273
  "force" : False,
@@ -332,6 +356,13 @@ class Command: # pylint: disable=too-many-public-methods
332
356
  self.tool_changed_respawn = {}
333
357
 
334
358
 
359
+ def get_info_job_name(self) -> str:
360
+ '''Returns an informational string of the job name, using: command - tool - top'''
361
+ return ' - '.join(
362
+ x for x in (self.command_name, self.args.get('tool', ''),
363
+ self.args.get('top', '')) if x
364
+ )
365
+
335
366
  def error(self, *args, **kwargs) -> None:
336
367
  '''Returns None, child classes can call self.error(..) instead of util.error,
337
368
 
@@ -359,7 +390,6 @@ class Command: # pylint: disable=too-many-public-methods
359
390
  f'ERROR: [eda] ({self.command_name}) {" ".join(list(args))}',
360
391
  file=self.errors_log_f
361
392
  )
362
-
363
393
  self.status = util.error(*args, **kwargs) # error_code passed and returned via kwargs
364
394
 
365
395
  def stop_process_tokens_before_do_it(self) -> bool:
@@ -374,24 +404,28 @@ class Command: # pylint: disable=too-many-public-methods
374
404
  return True
375
405
  return False
376
406
 
377
- def status_any_error(self, report=True) -> bool:
407
+ def status_any_error(self, report: bool = True) -> bool:
378
408
  '''Used by derived classes process_tokens() to know an error was reached
379
- and to not perform the command. Necessary for pytests that use eda.main()'''
380
- if report and self.status > 0:
409
+ and to not perform the command. Necessary for pytests that use eda.main()
410
+
411
+ Note we also check any parent Tool class for tool_error_count > 0
412
+ '''
413
+ any_err = False
414
+ if self.status > 0:
415
+ any_err = True
416
+ elif isinstance(self, Tool) and getattr(self, 'tool_error_count', 0):
417
+ util.warning(f'eda_base.py internal: Command status={self.status}, but',
418
+ f'Tool tool_error_count={getattr(self, "tool_error_count", 0)}')
419
+ any_err = True
420
+ if report and any_err:
381
421
  util.error(f"command '{self.command_name}' has previous errors")
382
- return self.status > 0
422
+ return any_err
383
423
 
384
424
  def report_pass_fail(self) -> None:
385
425
  '''Reports an INFO line with pass/fail information'''
386
- job_name = ' - '.join(
387
- x for x in (self.command_name, self.args.get('tool', ''),
388
- self.args.get('top', '')) if x
389
- )
390
- if self.status_any_error():
391
- util.info(f'{safe_emoji("❌ ")}{job_name}: Errors observed.', color=Colors.red)
392
- else:
393
- util.info(f'{safe_emoji("✅ ")}{job_name}: No errors observed.')
394
-
426
+ job_name = self.get_info_job_name()
427
+ if not self.status_any_error():
428
+ util.info(f'{safe_emoji("✅ ")}{job_name}: {Colors.bold}No errors observed.')
395
429
 
396
430
  def which_tool(self, command:str) -> str:
397
431
  '''Returns a str for the tool name used for the requested command'''
@@ -557,16 +591,17 @@ class Command: # pylint: disable=too-many-public-methods
557
591
  if return_code > 0:
558
592
  if return_code == 1:
559
593
  self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE1
560
- if return_code == 255:
594
+ elif return_code == 255:
561
595
  self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE255
562
596
  else:
563
597
  self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE2
598
+
564
599
  if stop_on_error:
565
600
  self.error(f"exec: returned with error (return code: {return_code})",
566
601
  error_code=self.status)
567
602
  else:
568
- util.debug(f"{safe_emoji('❌ ')}exec: returned with error (return code:",
569
- f"{return_code})")
603
+ util.info(f"{safe_emoji('❌ ')}exec: returned with error (return code:",
604
+ f"{return_code})")
570
605
  else:
571
606
  util.debug(f"exec: returned without error (return code: {return_code})")
572
607
  return stderr, stdout, return_code
@@ -680,6 +715,10 @@ class Command: # pylint: disable=too-many-public-methods
680
715
  help_kwargs = {'help': f'{type(value).__name__} default={value}'}
681
716
  help_kwargs['help'] = help_kwargs['help'].replace('%', '%%')
682
717
 
718
+ # Update with any self.args_kwargs for this key
719
+ if self.args_kwargs.get(key, {}):
720
+ help_kwargs.update(self.args_kwargs.get(key, {}))
721
+
683
722
 
684
723
  # It's important to set the default=None on these, except for list types where default
685
724
  # is []. If the parsed Namespace has values set to None or [], we do not update. This
@@ -1040,6 +1079,26 @@ class Command: # pylint: disable=too-many-public-methods
1040
1079
  util.warning(*msg)
1041
1080
 
1042
1081
 
1082
+ def update_tool_warn_err_counts_from_log_lines(
1083
+ self, log_lines: list, bad_strings: list, warning_strings: list
1084
+ ) -> None:
1085
+ '''Given lines (list of str) from a log, update self.tool_[error|warning]_count values
1086
+
1087
+ Since Tool class is not always a parent in Command/CommandDesign/CommandSim, we
1088
+ have to modfiy these member names safely for pylint.
1089
+
1090
+ Derived classes may override this, especially if the Tool is known and reports
1091
+ the complete error/warning summary counts in a certain way.
1092
+ '''
1093
+ if not isinstance(self, Tool):
1094
+ return
1095
+ for line in log_lines:
1096
+ if any(bad_str in line for bad_str in bad_strings):
1097
+ setattr(self, 'tool_error_count', getattr(self, 'tool_error_count', 0) + 1)
1098
+ if any(warn_str in line for warn_str in warning_strings):
1099
+ setattr(self, 'tool_warning_count', getattr(self, 'tool_warning_count', 0) + 1)
1100
+
1101
+
1043
1102
  class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1044
1103
  '''CommandDesign is the eda base class for command handlers that need to track files.
1045
1104
 
opencos/eda_config.py CHANGED
@@ -57,6 +57,7 @@ class Defaults:
57
57
  'defines',
58
58
  'log-bad-strings',
59
59
  'log-must-strings',
60
+ 'log-warning-strings',
60
61
  'sim-libraries',
61
62
  'compile-args',
62
63
  'compile-waves-args',
@@ -154,6 +154,8 @@ tools:
154
154
 
155
155
  verilator:
156
156
  defines: { }
157
+ log-warning-strings:
158
+ - "%Warning"
157
159
  log-bad-strings:
158
160
  - "%Error"
159
161
  - "%Fatal"
@@ -239,8 +241,11 @@ tools:
239
241
  defines:
240
242
  OC_TOOL_RIVIERA: 1
241
243
  RIVIERA: 1
244
+ log-warning-strings:
245
+ - "Warning: "
242
246
  log-bad-strings:
243
- - "Error:"
247
+ - "Error: "
248
+ - "Fatal: "
244
249
  log-must-strings:
245
250
  - "VSIM: Simulation has finished"
246
251
  compile-args: |
@@ -261,8 +266,11 @@ tools:
261
266
  defines:
262
267
  OC_ASSERT_PROPERTY_NOT_SUPPORTED: 1
263
268
  OC_TOOL_MODELSIM_ASE: 1
269
+ log-warning-strings:
270
+ - "Warning: "
264
271
  log-bad-strings:
265
- - "Error:"
272
+ - "Error: "
273
+ - "Fatal: "
266
274
  log-must-strings:
267
275
  - " vsim "
268
276
  - "Errors: 0"
@@ -286,8 +294,11 @@ tools:
286
294
  questa_fse:
287
295
  defines:
288
296
  OC_TOOL_QUESTA_FSE: 1
297
+ log-warning-strings:
298
+ - "Warning: "
289
299
  log-bad-strings:
290
- - "Error:"
300
+ - "Error: "
301
+ - "Fatal: "
291
302
  log-must-strings:
292
303
  - " vsim "
293
304
  - "Errors: 0"
@@ -309,6 +320,9 @@ tools:
309
320
 
310
321
 
311
322
  iverilog:
323
+ log-warning-strings:
324
+ - "Warning:"
325
+ - "WARNING:"
312
326
  log-bad-strings:
313
327
  - "Error:"
314
328
  - "ERROR:"
@@ -328,6 +342,9 @@ tools:
328
342
  cocotb:
329
343
  defines:
330
344
  OC_TOOL_COCOTB: null
345
+ log-warning-strings:
346
+ - "Warning:"
347
+ - "WARNING:"
331
348
  log-bad-strings:
332
349
  - "ERROR"
333
350
  - "FAILED"
@@ -344,6 +361,14 @@ tools:
344
361
  OC_TOOL_QUARTUS: null
345
362
 
346
363
  vivado:
364
+ log-warning-strings:
365
+ - "WARNING: "
366
+ - "Warning: "
367
+ log-bad-strings:
368
+ - "FATAL: "
369
+ - "Fatal: "
370
+ - "ERROR: "
371
+ - "Error: "
347
372
  sim-libraries:
348
373
  - xil_defaultlib
349
374
  - unisims_ver
@@ -7,13 +7,12 @@ import shutil
7
7
  import sys
8
8
  import pytest
9
9
 
10
- from opencos import eda, eda_base
11
-
10
+ from opencos import eda_base
12
11
  from opencos.tools.verilator import ToolVerilator
13
12
  from opencos.tools.vivado import ToolVivado
14
13
  from opencos.tools.cocotb import ToolCocotb
15
14
  from opencos.tests import helpers
16
- from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail, config, tools_loaded
15
+ from opencos.tests.helpers import Helpers, eda_wrap, eda_wrap_is_sim_fail, config, tools_loaded
17
16
  from opencos.utils.markup_helpers import yaml_safe_load
18
17
  from opencos.utils import status_constants
19
18
 
@@ -24,6 +23,10 @@ def chdir_remove_work_dir(relpath):
24
23
  '''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
25
24
  return helpers.chdir_remove_work_dir(THISPATH, relpath)
26
25
 
26
+ def filter_tools(tools: list) -> list:
27
+ '''Given a list of tool l, filters to return a list of tools that are loaded'''
28
+ return [x for x in tools if x in tools_loaded]
29
+
27
30
 
28
31
  def test_tools_loaded():
29
32
  '''Does not directly call 'eda.main' instead create a few Tool
@@ -107,40 +110,60 @@ list_of_added_sim_args = [
107
110
  '--gui --test-mode',
108
111
  ]
109
112
 
113
+ list_of_loaded_tools = filter_tools(list_of_tools)
114
+
110
115
  cannot_use_cocotb = 'cocotb' not in tools_loaded or \
111
116
  ('iverilog' not in tools_loaded and \
112
117
  'verilator' not in tools_loaded)
113
118
  CANNOT_USE_COCOTB_REASON = 'requires cocotb in tools_loaded, and one of (iverilog, verilator) too'
114
119
 
115
- @pytest.mark.parametrize("command", list_of_commands)
116
- @pytest.mark.parametrize("tool", list_of_tools)
117
- @pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
118
- @pytest.mark.parametrize("added_sim_args_str", list_of_added_sim_args)
119
- def test_sim_elab_tools_pass_or_fail(command, tool, target, sim_expect_pass, added_sim_args_str):
120
- '''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-args> <parameter-target>
121
120
 
122
- will correctly pass or fail depending on if it is supported or not.
121
+ class TestSimElabTools(Helpers):
122
+ '''Tests for eda sim|elab for various tools with various args'''
123
123
 
124
- Also tests for: non-gui, or --gui --test-mode (runs non-gui, but most python args will
125
- be for --gui mode, signal logging, etc).
126
- '''
127
- if tool not in tools_loaded:
128
- pytest.skip(f"{tool=} skipped, {tools_loaded=}")
129
- return # skip/pass
130
-
131
- added_args = []
132
- if command == 'sim':
133
- added_args = added_sim_args_str.split()
134
-
135
- relative_dir = "deps_files/test_err_fatal"
136
- os.chdir(os.path.join(THISPATH, relative_dir))
137
- rc = eda.main(command, '--tool', tool, *(added_args), target)
138
- print(f'{rc=}')
139
- if command != 'sim' or sim_expect_pass:
140
- # command='elab' should pass.
141
- assert rc == 0
142
- else:
143
- assert eda_wrap_is_sim_fail(rc)
124
+ DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'test_err_fatal')
125
+
126
+ @pytest.mark.parametrize("command", list_of_commands)
127
+ @pytest.mark.parametrize("tool", list_of_loaded_tools)
128
+ @pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
129
+ @pytest.mark.parametrize("added_sim_args_str", list_of_added_sim_args)
130
+ def test_pass_or_fail(
131
+ self, command, tool, target, sim_expect_pass, added_sim_args_str
132
+ ):
133
+ '''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-args> <parameter-target>
134
+
135
+ will correctly pass or fail depending on if it is supported or not.
136
+
137
+ Also tests for: non-gui, or --gui --test-mode (runs non-gui, but most python args will
138
+ be for --gui mode, signal logging, etc).
139
+ '''
140
+ added_args_str = ''
141
+ if command == 'sim':
142
+ added_args_str = added_sim_args_str
143
+
144
+ rc = self.log_it(f'{command} --tool {tool} {added_args_str} {target}')
145
+ print(f'{rc=}')
146
+ tool_error_count_lines = self.get_log_lines_with('tool errors')
147
+ if command != 'sim' or sim_expect_pass:
148
+ # command='elab' should pass.
149
+ assert rc == 0
150
+ assert tool_error_count_lines
151
+ assert all('tool warnings' in line for line in tool_error_count_lines)
152
+ assert all(' 0 tool errors' in line for line in tool_error_count_lines)
153
+
154
+ else:
155
+ assert eda_wrap_is_sim_fail(rc)
156
+ assert tool_error_count_lines
157
+ assert all('tool warnings' in line for line in tool_error_count_lines)
158
+ # May or may not have reported tool errors.
159
+ assert all('tool errors' in line for line in tool_error_count_lines)
160
+ # The final line of tool warnings/errors should have > 0 tool errors,
161
+ # since we were checking SV $error and $fatal
162
+ assert ' 0 tool errors' not in tool_error_count_lines[-1]
163
+ # The line should end with ' X tool errors'
164
+ parts = tool_error_count_lines[-1].split()
165
+ assert parts[-3].isdigit()
166
+ assert int(parts[-3]) in (1, 2, 3) # we should have at least 1, but some tools dup.
144
167
 
145
168
 
146
169
  @pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
opencos/tools/iverilog.py CHANGED
@@ -93,7 +93,7 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
93
93
  def compile(self):
94
94
  if self.args['stop-before-compile']:
95
95
  return
96
- self.run_commands_check_logs(self.iverilog_command_lists, check_logs=False)
96
+ self.run_commands_check_logs(self.iverilog_command_lists)
97
97
 
98
98
  def elaborate(self):
99
99
  pass
@@ -148,7 +148,7 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
148
148
 
149
149
  command_list += list(self.files_sv) + list(self.files_v)
150
150
 
151
- return [ util.ShellCommandList(command_list) ]
151
+ return [ util.ShellCommandList(command_list, tee_fpath='compile.log') ]
152
152
 
153
153
  def get_elaborate_command_lists(self, **kwargs) -> list:
154
154
  return []
opencos/tools/riviera.py CHANGED
@@ -21,6 +21,7 @@ class ToolRiviera(ToolModelsimAse):
21
21
  _TOOL = 'riviera'
22
22
  _EXE = 'vsim'
23
23
  use_vopt = False
24
+ uvm_versions = set()
24
25
 
25
26
  def get_versions(self) -> str:
26
27
  if self._VERSION:
@@ -40,7 +41,17 @@ class ToolRiviera(ToolModelsimAse):
40
41
  )
41
42
  stdout = version_ret.stdout.decode('utf-8', errors='replace').rstrip()
42
43
 
43
- # Expect:
44
+ # Get the UVM versions in the install directory. Note this may run
45
+ # more than once, so only do this if self.uvm_versions not yet set:
46
+ riviera_path, _ = os.path.split(self.sim_exe_base_path)
47
+ vlib_path = os.path.join(riviera_path, 'vlib')
48
+ if not self.uvm_versions and os.path.isdir(vlib_path):
49
+ for item in os.listdir(vlib_path):
50
+ # uvm-1.1, uvm-1.1d - so don't pick anything > 9 chars (uvm-M.mRr)
51
+ if item.startswith('uvm-') and '1800' not in item and len(item) <= 9:
52
+ self.uvm_versions.add(item[4:])
53
+
54
+ # For Version, expect:
44
55
  # Aldec, Inc. Riviera-PRO version 2025.04.139.9738 built for Linux64 on May 30, 2025
45
56
  left, right = stdout.split('version')
46
57
  if 'Riviera' not in left:
@@ -76,6 +87,18 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
76
87
  'bring your own .tcl file to run in Riviera (vsim) for coverage. The default'
77
88
  ' tcl steps are (from tool config in --config-yml): '
78
89
  ) + '; '.join(self.tool_config.get('simulate-coverage-tcl', [])),
90
+ 'uvm': (
91
+ 'Attempts to support UVM. Adds to vlog: -l uvm +incdir+PATH for the PATH to'
92
+ ' uvm_macros.svh for the installed version of Riviera used.'
93
+ ),
94
+ })
95
+
96
+ if self.uvm_versions:
97
+ # set default to latest version:
98
+ self.args['uvm-version'] = sorted(self.uvm_versions)[-1]
99
+
100
+ self.args_kwargs.update({
101
+ 'uvm-version': { 'choices': list(self.uvm_versions) }
79
102
  })
80
103
 
81
104
  def set_tool_defines(self):
@@ -136,7 +159,9 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
136
159
  return []
137
160
 
138
161
 
139
- def write_vlog_dot_f(self, filename='vlog.f') -> None:
162
+ def write_vlog_dot_f( # pylint: disable=too-many-branches
163
+ self, filename: str = 'vlog.f'
164
+ ) -> None:
140
165
  '''Returns none, creates filename (str) for a vlog.f'''
141
166
  vlog_dot_f_lines = []
142
167
 
@@ -153,6 +178,12 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
153
178
  vlog_dot_f_fname = filename
154
179
  vlog_dot_f_fpath = os.path.join(self.args['work-dir'], vlog_dot_f_fname)
155
180
 
181
+ if self.args['uvm']:
182
+ vlog_dot_f_lines.extend([
183
+ f'-uvmver {self.args["uvm-version"]}',
184
+ '-dbg'
185
+ ])
186
+
156
187
  for value in self.incdirs:
157
188
  vlog_dot_f_lines += [ f"+incdir+{value}" ]
158
189
 
opencos/tools/slang.py CHANGED
@@ -176,6 +176,30 @@ class CommandElabSlang(CommandElab, ToolSlang):
176
176
  def get_post_simulate_command_lists(self, **kwargs) -> list:
177
177
  return []
178
178
 
179
+ def update_tool_warn_err_counts_from_log_lines(
180
+ self, log_lines: list, bad_strings: list, warning_strings: list
181
+ ) -> None:
182
+ '''
183
+ Overriden from Command, we ignore bad_strings/warning_strings and use a custom
184
+ checker.
185
+ '''
186
+ for line in log_lines:
187
+ if not line.startswith('Build failed: '):
188
+ continue
189
+ if not all(x in line for x in ('errors', 'warnings')):
190
+ continue
191
+
192
+ parts = line.strip().split()
193
+ if len(parts) < 6:
194
+ continue
195
+
196
+ errs = parts[2]
197
+ warns = parts[4]
198
+ if errs.isdigit():
199
+ self.tool_error_count += int(errs)
200
+ if warns.isdigit():
201
+ self.tool_warning_count += int(warns)
202
+
179
203
  def _get_slang_command_list_start(self) -> list:
180
204
  command_list = [self.slang_exe]
181
205
 
opencos/tools/surelog.py CHANGED
@@ -147,6 +147,28 @@ class CommandElabSurelog(CommandElab, ToolSurelog):
147
147
  command_lists=self.surelog_command_lists, line_breaks=True
148
148
  )
149
149
 
150
+ def update_tool_warn_err_counts_from_log_lines(
151
+ self, log_lines: list, bad_strings: list, warning_strings: list
152
+ ) -> None:
153
+ '''
154
+ Overriden from Command, we ignore bad_strings/warning_strings and use a custom
155
+ checker.
156
+ '''
157
+ for line in log_lines:
158
+ line = line.strip()
159
+ if line.endswith(' 0'):
160
+ continue
161
+ if line.startswith('[ FATAL] : ') or \
162
+ line.startswith('[ SYNTAX] : ') or \
163
+ line.startswith('[ ERROR] : '):
164
+ parts = line.split()
165
+ if parts[-1].isdigit():
166
+ self.tool_error_count += int(parts[-1])
167
+ if line.startswith('[WARNING] : '):
168
+ parts = line.split()
169
+ if parts[-1].isdigit():
170
+ self.tool_warning_count += int(parts[-1])
171
+
150
172
 
151
173
  class CommandLintSurelog(CommandElabSurelog):
152
174
  '''CommandLintSurelog is a command handler for: eda lint --tool=surelog.'''
@@ -82,7 +82,6 @@ class VerilatorSim(CommandSim, ToolVerilator):
82
82
  'lint-only': False,
83
83
  'cc-mode': False,
84
84
  'verilator-coverage-args': [],
85
- 'uvm': False,
86
85
  'x-assign': '',
87
86
  'x-initial': '',
88
87
  })
@@ -117,9 +116,9 @@ class VerilatorSim(CommandSim, ToolVerilator):
117
116
  ' Also conditinally adds to verilated exe call:'
118
117
  ' +verilator+rand+reset+[0,2] for arg values 0, unique|fast'),
119
118
  'uvm': (
120
- 'Warns on Verilator < 5.042, or missing $UVM_HOME environment var set (or in'
121
- ' .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator with args:'
122
- ' -Wno-fatal +define+UVM_NO_DPI'
119
+ 'Enables UVM. Warns on Verilator < 5.042, or missing $UVM_HOME environment'
120
+ ' var set (or in .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator'
121
+ ' with args: -Wno-fatal +define+UVM_NO_DPI'
123
122
  ),
124
123
  'verilator-coverage-args': (
125
124
  'Requires --coverage, args to be applied to verilator_coverage, which runs'
@@ -127,6 +126,12 @@ class VerilatorSim(CommandSim, ToolVerilator):
127
126
  ),
128
127
  })
129
128
 
129
+
130
+ self.args_kwargs.update({
131
+ 'x-assign': { 'choices': ['0', '1', 'unique', 'fast'] },
132
+ 'x-initial': { 'choices': ['0', 'unique', 'fast'] },
133
+ })
134
+
130
135
  self.verilate_command_lists = []
131
136
  self.lint_only_command_lists = []
132
137
  self.verilated_exec_command_lists = []
@@ -562,7 +567,8 @@ class VerilatorSim(CommandSim, ToolVerilator):
562
567
  'version > v5.042')
563
568
 
564
569
  if not os.environ.get('UVM_HOME', ''):
565
- util.warning('--uvm set, however env (or .env or --env-file) $UVM_HOME is not set')
570
+ util.warning('--uvm set, however env (or .env or --env-file)',
571
+ '$UVM_HOME is not set')
566
572
 
567
573
  uvm_pkg_found = self._verilator_support_uvm_pkg_fpath(add_if_found=add_uvm_pkg_if_found)
568
574
  if warnings and not uvm_pkg_found:
opencos/tools/vivado.py CHANGED
@@ -225,6 +225,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
225
225
 
226
226
  if self.tool_config.get('elab-waves-args', ''):
227
227
  command_list += self.tool_config.get('elab-waves-args', '').split()
228
+ if self.args['uvm']:
229
+ command_list.extend(['-L', 'uvm'])
228
230
  elif self.args['gui'] and self.args['waves']:
229
231
  command_list += ['-debug', 'all']
230
232
  elif self.args['gui']:
@@ -305,6 +307,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
305
307
  command_list[0] += ".bat"
306
308
  if typ == 'sv':
307
309
  command_list.append('-sv')
310
+ if self.args['uvm']:
311
+ command_list.extend(['-L', 'uvm'])
308
312
  command_list += self.tool_config.get('compile-args', '').split()
309
313
  if util.args['verbose']:
310
314
  command_list += ['-v', '2']
opencos/util.py CHANGED
@@ -901,7 +901,8 @@ def exit( # pylint: disable=redefined-builtin
901
901
  elif args['warnings']:
902
902
  info_color = Colors.yellow
903
903
  info(
904
- f"{start}Exiting with {args['warnings']} warnings, {args['errors']} errors",
904
+ f"{start}Exiting with {Colors.bold}{args['warnings']} warnings{info_color},",
905
+ f"{Colors.bold}{args['errors']} errors",
905
906
  color=info_color
906
907
  )
907
908
  sys.exit(error_code)
@@ -132,11 +132,11 @@ def get_terminal_columns():
132
132
 
133
133
  Returns:
134
134
  int: The number of columns in the terminal, or a default value (e.g., 80)
135
- if the terminal size cannot be determined.
135
+ if the terminal size cannot be determined. Min value of 40 is returned.
136
136
  """
137
137
  try:
138
138
  size = os.get_terminal_size()
139
- return size.columns
139
+ return max(40, size.columns)
140
140
  except OSError:
141
141
  # Handle cases where the terminal size cannot be determined (e.g., not in a TTY)
142
142
  return 80 # Default to 80 columns
@@ -166,13 +166,13 @@ def pretty_list_columns_manual(data: list, num_columns: int = 4, auto_columns: b
166
166
  max_line_len = 0
167
167
  for x in max_lengths:
168
168
  max_line_len += x + _spacing
169
- if max_line_len > window_cols:
169
+ if max_line_len >= window_cols:
170
170
  # subtract a column (already >= 2):
171
171
  ret_lines.extend(
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opencos-eda
3
- Version: 0.3.7
3
+ Version: 0.3.8
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
@@ -3,9 +3,9 @@ opencos/_version.py,sha256=KaWIjS0c08g-C0fgYY1kXwSPqhOFxaq5pYEeoZhOR_I,617
3
3
  opencos/_waves_pkg.sv,sha256=TL5YT9lT-fn2FD54MbVVZROmZ7vtW3ScA_rM2eRzKmU,2068
4
4
  opencos/deps_schema.py,sha256=fx1_IJhsDYkUciwwCPTXHP6ftFjTsPVjO4xg12twIjw,17384
5
5
  opencos/eda.py,sha256=tmDF1TZAa_i3CSWdc92GL1iYM86Fz1p_wdsfy88aPco,23933
6
- opencos/eda_base.py,sha256=wdjfm79q7jlXgbn1PTRe-4Ewe9h2itJRuZ7zsk_SdGQ,115259
7
- opencos/eda_config.py,sha256=cKAUKguoFEvFgvi2c36Osf6W767Y4KYjoagflqVhUSw,14168
8
- opencos/eda_config_defaults.yml,sha256=tbu7hyfPM0AetW0CTMu4J3umGMpHLn5bs5bkWDyFM3w,16260
6
+ opencos/eda_base.py,sha256=J_al8-oQS5HCTJyiNSFVaNosESPctLWNpKG7FVGJXks,117815
7
+ opencos/eda_config.py,sha256=d7gMx9ZJTcnUwVhqUkQtY4PI3UGD0ZvH8D_GXvc5Erc,14199
8
+ opencos/eda_config_defaults.yml,sha256=BbBJqahrec5UmnnpsM9AaQVh3NM0wyGd5V5QKCMmNsA,16780
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
@@ -18,7 +18,7 @@ opencos/files.py,sha256=4fomXM5vyA5FUAImSeAjrPchQPysPSD07c_TLYg5cd8,1617
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=7KR8cxjdLYoQIAMbEpbBHjYqMThbMWo1HKt33zfocn0,44395
21
+ opencos/util.py,sha256=rJnXWOy_5vlb_qC1O5LmeOwJaJZrV46ZM9tYAcQKqAw,44453
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
@@ -31,7 +31,7 @@ opencos/commands/multi.py,sha256=kC21JYJU5E-rP0v690YsgR2rV9WikoeUTwGC4gzYK3M,275
31
31
  opencos/commands/open.py,sha256=XckvKUNwvc5KHbYGV-eQ2i0WG4X-yckroDaMC610MB4,804
32
32
  opencos/commands/proj.py,sha256=cExW9ZZkw6nkpVyNfeQzJADzmPtbYgBgWml82tqO6jY,1158
33
33
  opencos/commands/shell.py,sha256=upHpFs8Gdtzi-boVXwsC-QzEsnvtoZNMAu4oN10kdxw,7801
34
- opencos/commands/sim.py,sha256=JkrlVHqzFAzAMCEjJ_fzRxDqbFt-DzWunzCUapUaJBw,21870
34
+ opencos/commands/sim.py,sha256=a8fsf_Yw5igGiLhJGszlsKxTuQndfrFdStexmA4gwgE,24142
35
35
  opencos/commands/sweep.py,sha256=ni4XFgnFF8HLXtwPhETyLWfvc2kgtm4bcxFcKzUhkf0,9343
36
36
  opencos/commands/synth.py,sha256=m4ZwqHgOF5We0XP94F7TQli11WCPlkzhamI4fDfFR1o,4573
37
37
  opencos/commands/targets.py,sha256=_jRNhm2Fqj0fmMvTw6Ba39DCsRHf_r_uZCy_R064kpA,1472
@@ -55,7 +55,7 @@ opencos/tests/test_eda.py,sha256=n76XUSfaPNnTZeS8u5z6f9MHKPJIls4o-S4ftlGxuNg,377
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
58
- opencos/tests/test_tools.py,sha256=6uGZQaR7znpfjOuhRiLTLY0XdDiGFSTruOEDphsZfDg,13749
58
+ opencos/tests/test_tools.py,sha256=xzxlHSgqZNN9urxzNfQU4vjexmxRF6bsKvXijU35z78,15046
59
59
  opencos/tests/deps_files/command_order/DEPS.yml,sha256=jFce1gErT8XJpovYJj7t7X6Lu-Up_4yNRj9L8wOCAbI,1046
60
60
  opencos/tests/deps_files/error_msgs/DEPS.yml,sha256=fYvHouIscOlr8V28bqx9SoxRBpDBLX4AG-AkVXh8qbo,717
61
61
  opencos/tests/deps_files/iverilog_test/DEPS.yml,sha256=vDylEuLt642lhRSvOr3F5ziB5lhPSwkaUGN4_mWJw-c,40
@@ -68,30 +68,30 @@ opencos/tools/cocotb.py,sha256=bR97Mb87D0HPdoV82rLT7oszPLmdIPzYU1IEBaGnuXg,20544
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=3IQIZVDioChKEJIVVJki-q7NlvBg0k3n61oM7ltG9c8,6551
71
+ opencos/tools/iverilog.py,sha256=PH_mvFU3LbMAPNskxMi1tD4DsMWRcsy27V_M0La9TNY,6558
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=_-vsN7TD6WdW4PVsSJaEhJls3RgXGRowhY_QV1hdFqE,13678
77
- opencos/tools/slang.py,sha256=S_vODMT5Zl5vi9FMGHfahp5B0oMNyDIRJXtRAldVCwY,8625
76
+ opencos/tools/riviera.py,sha256=Llvd7jwOvy8rApqFOTnGpHSAhV3vFUsdjgMUuW4rsOE,15003
77
+ opencos/tools/slang.py,sha256=nDB9eK_DNAgewsXP4Sc1wh_bQiOr5q3-3ccpTbdWr1Y,9423
78
78
  opencos/tools/slang_yosys.py,sha256=MKh13eAmLJDkynZiezyT8E2gI4CKnXipzgFCZppaMXo,10230
79
- opencos/tools/surelog.py,sha256=S2RAZJyjdISm_tRvAhXbla7_z_tJfotZih5f9Y3m7DQ,5648
79
+ opencos/tools/surelog.py,sha256=uqUOpDq9Vs8Inj4m3nlMbag47LrJB4QHUIRes7dFbQY,6520
80
80
  opencos/tools/tabbycad_yosys.py,sha256=2LePPgYXBVdsy7YcffPIWN-I0B7queLQ_f_pme2SCGw,7803
81
- opencos/tools/verilator.py,sha256=mUNGZZB0P8NiF_zJW4wJHRQvwyiV_YwYNo3H_GYDFFc,24736
82
- opencos/tools/vivado.py,sha256=I9yVFK5wnFl1268l8WCk5Xh8wZBlSCODVTg4qStVF8c,41475
81
+ opencos/tools/verilator.py,sha256=oIZpqiawohf21JZ_ib0NSAbVpVMYCwPswQ315Y6nU7g,24941
82
+ opencos/tools/vivado.py,sha256=k_7sc9cv69BIkIxnbQKNFjLubS_O-p7HvTH_SkiZDMQ,41643
83
83
  opencos/tools/yosys.py,sha256=2h88u0NA_vqLgZG9I78gIy6nQuWxyT6mirDlyjTHn9w,28290
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=-hR7MAQLOoY2lIfqtxNtnzb3apeJPkh8shEGFzkwQfs,6637
87
+ opencos/utils/str_helpers.py,sha256=mCByhIyZLwDJuyeT70YShepgkkikY0pf7k6_xS93dGg,6675
88
88
  opencos/utils/subprocess_helpers.py,sha256=nmRUe5sPyXomzKEvEQU5231U_vilj8TuXNXLipwqLTM,6579
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.7.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
- opencos_eda-0.3.7.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
- opencos_eda-0.3.7.dist-info/METADATA,sha256=BzLaVDTvMpHFvXBhH7S6ZQJhtfYDoUh_cvR8jGux0fI,1164
94
- opencos_eda-0.3.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
- opencos_eda-0.3.7.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
- opencos_eda-0.3.7.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
- opencos_eda-0.3.7.dist-info/RECORD,,
91
+ opencos_eda-0.3.8.dist-info/licenses/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
92
+ opencos_eda-0.3.8.dist-info/licenses/LICENSE.spdx,sha256=8gn1610RMP6eFgT3Hm6q9VKXt0RvdTItL_oxMo72jII,189
93
+ opencos_eda-0.3.8.dist-info/METADATA,sha256=d4yZ9dj5zoyO3Rf1nA7G5AOSHmJ6RXrd4vedVuEWQJI,1164
94
+ opencos_eda-0.3.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
95
+ opencos_eda-0.3.8.dist-info/entry_points.txt,sha256=6n1T5NwVYDhN5l1h5zmyT197G4pE0SySDreB0QJzJR0,218
96
+ opencos_eda-0.3.8.dist-info/top_level.txt,sha256=J4JDP-LpRyJqPNeh9bSjx6yrLz2Mk0h6un6YLmtqql4,8
97
+ opencos_eda-0.3.8.dist-info/RECORD,,