opencos-eda 0.3.6__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/flist.py +2 -2
- opencos/commands/multi.py +4 -3
- opencos/commands/sim.py +73 -14
- opencos/eda.py +22 -20
- opencos/eda_base.py +227 -82
- opencos/eda_config.py +1 -0
- opencos/eda_config_defaults.yml +28 -3
- opencos/tests/deps_files/command_order/DEPS.yml +13 -0
- opencos/tests/helpers.py +9 -5
- opencos/tests/test_eda.py +1 -1
- opencos/tests/test_tools.py +62 -33
- opencos/tools/iverilog.py +2 -2
- opencos/tools/riviera.py +33 -2
- opencos/tools/slang.py +24 -0
- opencos/tools/surelog.py +22 -0
- opencos/tools/verilator.py +12 -7
- opencos/tools/vivado.py +4 -0
- opencos/tools/yosys.py +3 -3
- opencos/util.py +29 -6
- opencos/utils/str_helpers.py +4 -4
- opencos/utils/subprocess_helpers.py +23 -6
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/METADATA +6 -4
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/RECORD +28 -28
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/top_level.txt +0 -0
opencos/eda_base.py
CHANGED
|
@@ -26,7 +26,7 @@ from opencos import eda_config
|
|
|
26
26
|
|
|
27
27
|
from opencos.util import Colors, safe_emoji
|
|
28
28
|
from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or_space, \
|
|
29
|
-
indent_wrap_long_text, pretty_list_columns_manual
|
|
29
|
+
indent_wrap_long_text, pretty_list_columns_manual, get_terminal_columns
|
|
30
30
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
31
31
|
from opencos.utils import status_constants
|
|
32
32
|
|
|
@@ -42,6 +42,24 @@ def print_base_help() -> None:
|
|
|
42
42
|
print(get_argparser_short_help())
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
def print_eda_usage_line(no_targets: bool = False, command_name='COMMAND') -> None:
|
|
46
|
+
'''Prints line for eda [options] COMMAND [options] FILES|TARGETS,...'''
|
|
47
|
+
print(f'{safe_emoji("🔦 ")}Usage:')
|
|
48
|
+
if no_targets:
|
|
49
|
+
print(
|
|
50
|
+
(f' {Colors.bold}{Colors.yellow}eda {Colors.cyan}[options]'
|
|
51
|
+
f' {Colors.yellow}{command_name} {Colors.cyan}[options]{Colors.normal}')
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
print(
|
|
55
|
+
(f' {Colors.bold}{Colors.yellow}eda {Colors.cyan}[options]'
|
|
56
|
+
f' {Colors.yellow}{command_name} {Colors.cyan}[options]'
|
|
57
|
+
f' {Colors.yellow}FILES|TARGETS,...{Colors.normal}')
|
|
58
|
+
)
|
|
59
|
+
print()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
45
63
|
def get_argparser() -> argparse.ArgumentParser:
|
|
46
64
|
'''Returns the ArgumentParser for general eda CLI'''
|
|
47
65
|
parser = argparse.ArgumentParser(
|
|
@@ -82,7 +100,7 @@ def get_argparsers_args_list() -> list:
|
|
|
82
100
|
|
|
83
101
|
|
|
84
102
|
def get_eda_exec(command: str = '') -> str:
|
|
85
|
-
'''Returns the full path of `eda` executable to be used for a given eda
|
|
103
|
+
'''Returns the full path of `eda` executable to be used for a given eda COMMAND'''
|
|
86
104
|
# NOTE(drew): This is kind of flaky. 'eda multi' reinvokes 'eda'. But the executable for 'eda'
|
|
87
105
|
# is one of:
|
|
88
106
|
# 1. pip3 install opencos-eda
|
|
@@ -133,6 +151,17 @@ def which_tool(command: str, config: dict) -> str:
|
|
|
133
151
|
return tool
|
|
134
152
|
|
|
135
153
|
|
|
154
|
+
def get_class_tool_name(command_obj: object) -> str:
|
|
155
|
+
'''Attempts to return command_obj._TOOL via command_obj.get_tool_name()'''
|
|
156
|
+
if f := getattr(command_obj, 'get_tool_name', None):
|
|
157
|
+
if callable(f):
|
|
158
|
+
ret = f()
|
|
159
|
+
if ret and isinstance(ret, str):
|
|
160
|
+
return ret
|
|
161
|
+
return ''
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
136
165
|
class Tool:
|
|
137
166
|
'''opencos.eda_base.Tool is a base class used by opencos.tools.<name>.
|
|
138
167
|
|
|
@@ -149,6 +178,10 @@ class Tool:
|
|
|
149
178
|
# a Command object's self.args instead of the class Tool.args. Safely create it
|
|
150
179
|
# if it doesn't exist:
|
|
151
180
|
self._VERSION = None
|
|
181
|
+
|
|
182
|
+
self.tool_warning_count = 0
|
|
183
|
+
self.tool_error_count = 0
|
|
184
|
+
|
|
152
185
|
if getattr(self, 'args', None) is None:
|
|
153
186
|
self.args = {}
|
|
154
187
|
if getattr(self, 'args_help', None) is None:
|
|
@@ -182,6 +215,10 @@ class Tool:
|
|
|
182
215
|
util.info(f'Override for {self._TOOL} using exe {exe}')
|
|
183
216
|
self._EXE = exe
|
|
184
217
|
|
|
218
|
+
def get_tool_name(self) -> str:
|
|
219
|
+
'''Returns _TOOL'''
|
|
220
|
+
return str(self._TOOL)
|
|
221
|
+
|
|
185
222
|
def get_full_tool_and_versions(self) -> str:
|
|
186
223
|
'''Returns tool:version, such as: verilator:5.033'''
|
|
187
224
|
if not self._VERSION:
|
|
@@ -192,13 +229,31 @@ class Tool:
|
|
|
192
229
|
'''Sets and returns self._VERSION'''
|
|
193
230
|
return self._VERSION
|
|
194
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
|
+
|
|
195
250
|
def set_tool_defines(self) -> None:
|
|
196
251
|
'''Derived classes may override, sets any additional defines based on tool.'''
|
|
197
252
|
return
|
|
198
253
|
|
|
199
254
|
|
|
200
|
-
class Command: # pylint: disable=too-many-public-methods
|
|
201
|
-
'''Base class for all: eda
|
|
255
|
+
class Command: # pylint: disable=too-many-public-methods,too-many-instance-attributes
|
|
256
|
+
'''Base class for all: eda COMMAND
|
|
202
257
|
|
|
203
258
|
The Command class should be used when you don't require files, otherwise consider
|
|
204
259
|
CommandDesign.
|
|
@@ -211,6 +266,8 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
211
266
|
self.args = {}
|
|
212
267
|
if getattr(self, 'args_help', None) is None:
|
|
213
268
|
self.args_help = {}
|
|
269
|
+
if getattr(self, 'args_kwargs', None) is None:
|
|
270
|
+
self.args_kwargs = {}
|
|
214
271
|
self.args.update({
|
|
215
272
|
"keep" : False,
|
|
216
273
|
"force" : False,
|
|
@@ -235,21 +292,54 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
235
292
|
'error-unknown-args': True,
|
|
236
293
|
})
|
|
237
294
|
self.args_help.update({
|
|
238
|
-
'
|
|
239
|
-
|
|
240
|
-
|
|
295
|
+
'keep': (
|
|
296
|
+
'Determined by eda <tool>, generally prevents jobs from overwriting artifacts'
|
|
297
|
+
),
|
|
298
|
+
'force': 'Determined by eda <tool>, force override',
|
|
299
|
+
'fake': (
|
|
300
|
+
'Determined by eda <tool>, generally is a dry-run that does not execute tool'
|
|
301
|
+
),
|
|
302
|
+
'stop-before-compile': (
|
|
303
|
+
'stop this run before any compile steps (if possible for tool) and'
|
|
304
|
+
' save .sh scripts in eda-dir/'
|
|
305
|
+
),
|
|
306
|
+
'stop-after-compile': (
|
|
307
|
+
'stop this run after any compile steps (if possible for tool) and'
|
|
308
|
+
' save .sh scripts in eda-dir/'
|
|
309
|
+
),
|
|
310
|
+
'stop-after-elaborate': (
|
|
311
|
+
'stop this run after any elaborate steps (if possible for tool) and'
|
|
312
|
+
' save .sh scripts in eda-dir/'
|
|
313
|
+
),
|
|
314
|
+
'lint': 'tool dependent, run with linting options',
|
|
315
|
+
"job-name" : 'Optional, used to create a sub directory under base work-dir (eda-dir)',
|
|
316
|
+
"sub-work-dir" : (
|
|
317
|
+
'Optional, similar to job-name, can be used to name the directory created under'
|
|
318
|
+
' eda-dir'
|
|
319
|
+
),
|
|
320
|
+
'eda-dir': 'Optional, relative base directory where eda logs are saved',
|
|
241
321
|
'export': 'export results for these targets in eda-dir',
|
|
242
322
|
'export-run': 'export, and run, results for these targets in eda-dir',
|
|
243
323
|
'export-json': 'export, and save a JSON file per target',
|
|
244
|
-
'work-dir': ('Optional override for working directory, often
|
|
245
|
-
' ./eda.work
|
|
324
|
+
'work-dir': ('Optional override for working directory, if unset will often use '
|
|
325
|
+
' ./eda.work/[TOP|TARGET].COMMAND'),
|
|
246
326
|
"work-dir-use-target-dir": ('Set the work-dir to be the same as the in-place location'
|
|
247
327
|
' where the target (DEPS) exists'),
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
328
|
+
"suffix": (
|
|
329
|
+
"Optional, determined by eda COMMAND, used by 'multi' jobs information logging"
|
|
330
|
+
),
|
|
331
|
+
"design": (
|
|
332
|
+
"Optional, used to override both the work-dir, and if unset the top DEPS target"
|
|
333
|
+
),
|
|
334
|
+
'enable-tags': (
|
|
335
|
+
'DEPS markup tag names to be force enabled for this'
|
|
336
|
+
' command (mulitple appends to list).'
|
|
337
|
+
),
|
|
338
|
+
'disable-tags': (
|
|
339
|
+
'DEPS markup tag names to be disabled (even if they match the criteria) for this'
|
|
340
|
+
' command (mulitple appends to list). --disable-tags has higher precedence than'
|
|
341
|
+
' --enable-tags.'
|
|
342
|
+
),
|
|
253
343
|
'test-mode': ('command and tool dependent, usually stops the command early without'
|
|
254
344
|
' executing.'),
|
|
255
345
|
'error-unknown-args': (
|
|
@@ -266,6 +356,13 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
266
356
|
self.tool_changed_respawn = {}
|
|
267
357
|
|
|
268
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
|
+
|
|
269
366
|
def error(self, *args, **kwargs) -> None:
|
|
270
367
|
'''Returns None, child classes can call self.error(..) instead of util.error,
|
|
271
368
|
|
|
@@ -293,7 +390,6 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
293
390
|
f'ERROR: [eda] ({self.command_name}) {" ".join(list(args))}',
|
|
294
391
|
file=self.errors_log_f
|
|
295
392
|
)
|
|
296
|
-
|
|
297
393
|
self.status = util.error(*args, **kwargs) # error_code passed and returned via kwargs
|
|
298
394
|
|
|
299
395
|
def stop_process_tokens_before_do_it(self) -> bool:
|
|
@@ -308,24 +404,28 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
308
404
|
return True
|
|
309
405
|
return False
|
|
310
406
|
|
|
311
|
-
def status_any_error(self, report=True) -> bool:
|
|
407
|
+
def status_any_error(self, report: bool = True) -> bool:
|
|
312
408
|
'''Used by derived classes process_tokens() to know an error was reached
|
|
313
|
-
and to not perform the command. Necessary for pytests that use eda.main()
|
|
314
|
-
|
|
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:
|
|
315
421
|
util.error(f"command '{self.command_name}' has previous errors")
|
|
316
|
-
return
|
|
422
|
+
return any_err
|
|
317
423
|
|
|
318
424
|
def report_pass_fail(self) -> None:
|
|
319
425
|
'''Reports an INFO line with pass/fail information'''
|
|
320
|
-
job_name =
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
)
|
|
324
|
-
if self.status_any_error():
|
|
325
|
-
util.info(f'{safe_emoji("❌ ")}{job_name}: Errors observed.', color=Colors.red)
|
|
326
|
-
else:
|
|
327
|
-
util.info(f'{safe_emoji("✅ ")}{job_name}: No errors observed.')
|
|
328
|
-
|
|
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.')
|
|
329
429
|
|
|
330
430
|
def which_tool(self, command:str) -> str:
|
|
331
431
|
'''Returns a str for the tool name used for the requested command'''
|
|
@@ -356,7 +456,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
356
456
|
) -> str:
|
|
357
457
|
'''Creates the working directory and populates self.args['work-dir']
|
|
358
458
|
|
|
359
|
-
Generally uses ./ self.args['eda-dir'] /
|
|
459
|
+
Generally uses ./ self.args['eda-dir'] / TARGET-NAME.COMMAND /
|
|
360
460
|
however, self.args['job-name'] or ['sub-work-dir'] can override that.
|
|
361
461
|
|
|
362
462
|
Additionally, the work-dir is attempted to be deleted if it already exists
|
|
@@ -491,16 +591,17 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
491
591
|
if return_code > 0:
|
|
492
592
|
if return_code == 1:
|
|
493
593
|
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE1
|
|
494
|
-
|
|
594
|
+
elif return_code == 255:
|
|
495
595
|
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE255
|
|
496
596
|
else:
|
|
497
597
|
self.status = status_constants.EDA_EXEC_NONZERO_RETURN_CODE2
|
|
598
|
+
|
|
498
599
|
if stop_on_error:
|
|
499
600
|
self.error(f"exec: returned with error (return code: {return_code})",
|
|
500
601
|
error_code=self.status)
|
|
501
602
|
else:
|
|
502
|
-
util.
|
|
503
|
-
|
|
603
|
+
util.info(f"{safe_emoji('❌ ')}exec: returned with error (return code:",
|
|
604
|
+
f"{return_code})")
|
|
504
605
|
else:
|
|
505
606
|
util.debug(f"exec: returned without error (return code: {return_code})")
|
|
506
607
|
return stderr, stdout, return_code
|
|
@@ -614,25 +715,42 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
614
715
|
help_kwargs = {'help': f'{type(value).__name__} default={value}'}
|
|
615
716
|
help_kwargs['help'] = help_kwargs['help'].replace('%', '%%')
|
|
616
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
|
+
|
|
617
722
|
|
|
618
723
|
# It's important to set the default=None on these, except for list types where default
|
|
619
724
|
# is []. If the parsed Namespace has values set to None or [], we do not update. This
|
|
620
725
|
# means that as deps are processed that have args set, they cannot override the top
|
|
621
726
|
# level args that were already set, nor be overriden by defaults.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
727
|
+
try:
|
|
728
|
+
if isinstance(value, bool):
|
|
729
|
+
# For bool, support --key and --no-key with
|
|
730
|
+
# action=argparse.BooleanOptionalAction. Note, this means you cannot use
|
|
731
|
+
# --some-bool=True, or --some-bool=False, has to be --some-bool or
|
|
732
|
+
# --no-some-bool.
|
|
733
|
+
# Also we cannot have self.args.keys() that start with 'no-', which is
|
|
734
|
+
# why this entire thing is wrapped with try/except.
|
|
735
|
+
parser.add_argument(
|
|
736
|
+
*arguments, default=None, **bool_action_kwargs, **help_kwargs)
|
|
737
|
+
elif isinstance(value, (list, set)):
|
|
738
|
+
parser.add_argument(*arguments, default=value, action='append', **help_kwargs)
|
|
739
|
+
elif isinstance(value, (int, str)):
|
|
740
|
+
parser.add_argument(*arguments, default=value, type=type(value), **help_kwargs)
|
|
741
|
+
elif value is None:
|
|
742
|
+
parser.add_argument(*arguments, default=None, **help_kwargs)
|
|
743
|
+
else:
|
|
744
|
+
assert False, f'{key=} {value=} how do we do argparse for this type of value?'
|
|
745
|
+
except Exception as e:
|
|
746
|
+
if isinstance(value, bool):
|
|
747
|
+
self.error(f'Could not add argument: {key=} {value=} {type(value)=}',
|
|
748
|
+
f'{arguments=} {bool_action_kwargs=} {help_kwargs=} {e=}')
|
|
749
|
+
else:
|
|
750
|
+
self.error(f'Could not add argument: {key=} {value=} {type(value)=}',
|
|
751
|
+
f'{arguments=} {help_kwargs=} {e=}')
|
|
752
|
+
|
|
753
|
+
|
|
636
754
|
|
|
637
755
|
return parser
|
|
638
756
|
|
|
@@ -730,7 +848,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
730
848
|
def get_command_from_unparsed_args(
|
|
731
849
|
self, tokens: list, error_if_no_command: bool = True
|
|
732
850
|
) -> str:
|
|
733
|
-
'''Given a list of unparsed args, try to fish out the eda
|
|
851
|
+
'''Given a list of unparsed args, try to fish out the eda COMMAND value.
|
|
734
852
|
|
|
735
853
|
This will remove the value from the tokens list.
|
|
736
854
|
'''
|
|
@@ -742,7 +860,7 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
742
860
|
break
|
|
743
861
|
|
|
744
862
|
if not ret and error_if_no_command:
|
|
745
|
-
self.error(f"Looking for a valid eda {self.command_name}
|
|
863
|
+
self.error(f"Looking for a valid eda {self.command_name} COMMAND",
|
|
746
864
|
f"but didn't find one in {tokens=}",
|
|
747
865
|
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
748
866
|
return ret
|
|
@@ -808,19 +926,16 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
808
926
|
if self.args_help has additional help information.
|
|
809
927
|
'''
|
|
810
928
|
|
|
811
|
-
# Indent long lines (>100) to indent=
|
|
812
|
-
def indent_me(text:str):
|
|
813
|
-
return indent_wrap_long_text(
|
|
929
|
+
# Indent long lines (>100) to indent=24 (this is where usual argparse help leaves off)
|
|
930
|
+
def indent_me(text: str, initial_indent: int = 23):
|
|
931
|
+
return indent_wrap_long_text(
|
|
932
|
+
' ' * initial_indent + text, width=get_terminal_columns(), indent=24
|
|
933
|
+
)
|
|
814
934
|
|
|
815
|
-
util.info('Help:')
|
|
935
|
+
util.info('Help:', color=Colors.cyan)
|
|
816
936
|
# using bare 'print' here, since help was requested, avoids --color and --quiet
|
|
817
937
|
print()
|
|
818
|
-
|
|
819
|
-
if no_targets:
|
|
820
|
-
print(f' eda [options] {self.command_name} [options]')
|
|
821
|
-
else:
|
|
822
|
-
print(f' eda [options] {self.command_name} [options] [files|targets, ...]')
|
|
823
|
-
print()
|
|
938
|
+
print_eda_usage_line(no_targets=no_targets, command_name=self.command_name)
|
|
824
939
|
|
|
825
940
|
print_base_help()
|
|
826
941
|
lines = []
|
|
@@ -829,10 +944,18 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
829
944
|
return
|
|
830
945
|
|
|
831
946
|
if self.command_name:
|
|
832
|
-
|
|
833
|
-
|
|
947
|
+
line = (
|
|
948
|
+
f"{safe_emoji('🔧 ')}{Colors.cyan}Generic help for"
|
|
949
|
+
f" command={Colors.byellow}{self.command_name}{Colors.cyan}"
|
|
950
|
+
)
|
|
951
|
+
if tool := get_class_tool_name(self):
|
|
952
|
+
line += f" tool={Colors.byellow}{tool}"
|
|
953
|
+
line += f" {Colors.normal}(using '{self.__class__.__name__}')"
|
|
954
|
+
lines.append(line)
|
|
834
955
|
else:
|
|
835
|
-
lines.append(
|
|
956
|
+
lines.append(
|
|
957
|
+
f"{safe_emoji('🔧 ')}{Colors.cyan}Generic help:{Colors.normal}"
|
|
958
|
+
)
|
|
836
959
|
|
|
837
960
|
# Attempt to run argparser on args, but don't error if it fails.
|
|
838
961
|
unparsed = []
|
|
@@ -842,44 +965,46 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
842
965
|
except Exception:
|
|
843
966
|
pass
|
|
844
967
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
vstr = "'" + v + "'"
|
|
859
|
-
lines.append(indent_me(f" --{k:20} : string : {vstr:12}{khelp}"))
|
|
860
|
-
else:
|
|
861
|
-
lines.append(indent_me(f" --{k:20} : <unknown> : {vstr:12}{khelp}"))
|
|
968
|
+
short_help_lines = util.get_argparser_short_help(
|
|
969
|
+
parser=self.get_argparser(support_underscores=False)
|
|
970
|
+
).split('\n')
|
|
971
|
+
short_help_lines.pop(0) # Strip first line w/ argparser prog name
|
|
972
|
+
lines.extend(short_help_lines)
|
|
973
|
+
|
|
974
|
+
# For these custom args, -GParameter=Value, +define+Name[=value] +incdir+path
|
|
975
|
+
# make colors to look like python >= 3.14, if that is our version.
|
|
976
|
+
color = Colors()
|
|
977
|
+
if sys.version_info.major < 3 or \
|
|
978
|
+
(sys.version_info.major == 3 and sys.version_info.minor < 14):
|
|
979
|
+
color.disable() # strip our color object if < 3.14
|
|
980
|
+
|
|
862
981
|
|
|
863
982
|
lines.append('')
|
|
983
|
+
lines.append(
|
|
984
|
+
f" {color.cyan}-G{color.byellow}<parameterName>{color.normal}=" \
|
|
985
|
+
+ f"{color.yellow}<value>{color.normal}"
|
|
986
|
+
)
|
|
864
987
|
lines.append(indent_me((
|
|
865
|
-
" -G<parameterName>=<value> "
|
|
866
988
|
" Add parameter to top level, support bit/int/string types only."
|
|
867
989
|
" Example: -GDEPTH=8 (DEPTH treated as SV int/integer)."
|
|
868
990
|
" -GENABLE=1 (ENABLED treated as SV bit/int/integer)."
|
|
869
991
|
" -GName=eda (Name treated as SV string \"eda\")."
|
|
870
992
|
)))
|
|
993
|
+
|
|
994
|
+
lines.append(f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}")
|
|
871
995
|
lines.append(indent_me((
|
|
872
|
-
" +define+<defineName> "
|
|
873
996
|
" Add define w/out value to tool ahead of SV sources"
|
|
874
997
|
" Example: +define+SIM_SPEEDUP"
|
|
875
998
|
)))
|
|
999
|
+
lines.append(
|
|
1000
|
+
f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}=" \
|
|
1001
|
+
+ f"{color.yellow}<value>{color.normal}")
|
|
876
1002
|
lines.append(indent_me((
|
|
877
|
-
" +define+<defineName>=<value> "
|
|
878
1003
|
" Add define w/ value to tool ahead of SV sources"
|
|
879
1004
|
" Example: +define+TECH_LIB=2 +define+FULL_NAME=\"E D A\""
|
|
880
1005
|
)))
|
|
1006
|
+
lines.append(f" {color.cyan}+incdir+{color.byellow}PATH{color.normal}")
|
|
881
1007
|
lines.append(indent_me((
|
|
882
|
-
" +incdir+<path> "
|
|
883
1008
|
" Add path (absolute or relative) for include directories"
|
|
884
1009
|
" for SystemVerilog `include \"<some-file>.svh\""
|
|
885
1010
|
" Example: +incdir+../lib"
|
|
@@ -954,6 +1079,26 @@ class Command: # pylint: disable=too-many-public-methods
|
|
|
954
1079
|
util.warning(*msg)
|
|
955
1080
|
|
|
956
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
|
+
|
|
957
1102
|
class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
958
1103
|
'''CommandDesign is the eda base class for command handlers that need to track files.
|
|
959
1104
|
|
opencos/eda_config.py
CHANGED
opencos/eda_config_defaults.yml
CHANGED
|
@@ -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
|
|
@@ -29,3 +29,16 @@ target_test_with_post_tool_commands:
|
|
|
29
29
|
run-after-tool: true
|
|
30
30
|
- target_echo_hi_bye
|
|
31
31
|
top: foo
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
test_run_from_work_dir:
|
|
35
|
+
deps:
|
|
36
|
+
- commands:
|
|
37
|
+
- shell: echo "pwd=$PWD"
|
|
38
|
+
run-from-work-dir: true
|
|
39
|
+
|
|
40
|
+
test_run_from_work_dir_false:
|
|
41
|
+
deps:
|
|
42
|
+
- commands:
|
|
43
|
+
- shell: echo "pwd=$PWD"
|
|
44
|
+
run-from-work-dir: false
|
opencos/tests/helpers.py
CHANGED
|
@@ -10,8 +10,8 @@ from pathlib import Path
|
|
|
10
10
|
from contextlib import redirect_stdout, redirect_stderr
|
|
11
11
|
|
|
12
12
|
from opencos import eda, eda_tool_helper, deps_schema
|
|
13
|
-
from opencos.utils.markup_helpers import yaml_safe_load
|
|
14
13
|
from opencos.utils import status_constants
|
|
14
|
+
from opencos.utils.markup_helpers import yaml_safe_load
|
|
15
15
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
16
16
|
|
|
17
17
|
# Figure out what tools the system has available, without calling eda.main(..)
|
|
@@ -238,7 +238,7 @@ class Helpers:
|
|
|
238
238
|
|
|
239
239
|
def log_it(
|
|
240
240
|
self, command_str: str, logfile=None, use_eda_wrap: bool = True,
|
|
241
|
-
run_in_subprocess: bool = False,
|
|
241
|
+
run_in_subprocess: bool = False, include_default_log: bool = False,
|
|
242
242
|
preserve_env: bool = False
|
|
243
243
|
) -> int:
|
|
244
244
|
'''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
|
|
@@ -257,13 +257,17 @@ class Helpers:
|
|
|
257
257
|
logfile = self._resolve_logfile(logfile)
|
|
258
258
|
rc = 50
|
|
259
259
|
|
|
260
|
+
eda_log_arg = '--no-default-log'
|
|
261
|
+
if include_default_log:
|
|
262
|
+
eda_log_arg = ''
|
|
263
|
+
|
|
260
264
|
# TODO(drew): There are some issues with log_it redirecting stdout from vivado
|
|
261
265
|
# and modelsim_ase. So this may not work for all tools, you may have to directly
|
|
262
266
|
# look at eda.work/{target}.sim/sim.log or xsim.log.
|
|
263
267
|
print(f'{os.getcwd()=}')
|
|
264
268
|
print(f'{command_str=}')
|
|
265
269
|
if run_in_subprocess or self.RUN_IN_SUBPROCESS:
|
|
266
|
-
command_list = ['eda',
|
|
270
|
+
command_list = ['eda', eda_log_arg] + command_str.split()
|
|
267
271
|
_, _, rc = subprocess_run_background(
|
|
268
272
|
work_dir=self.DEFAULT_DIR,
|
|
269
273
|
command_list=command_list,
|
|
@@ -275,9 +279,9 @@ class Helpers:
|
|
|
275
279
|
with open(logfile, 'w', encoding='utf-8') as f:
|
|
276
280
|
with redirect_stdout(f), redirect_stderr(f):
|
|
277
281
|
if use_eda_wrap or self.USE_EDA_WRAP:
|
|
278
|
-
rc = eda_wrap(
|
|
282
|
+
rc = eda_wrap(eda_log_arg, *(command_str.split()))
|
|
279
283
|
else:
|
|
280
|
-
rc = eda.main(
|
|
284
|
+
rc = eda.main(eda_log_arg, *(command_str.split()))
|
|
281
285
|
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
282
286
|
|
|
283
287
|
if self.PRESERVE_ENV or preserve_env:
|
opencos/tests/test_eda.py
CHANGED
|
@@ -648,7 +648,7 @@ class TestsRequiresIVerilog(Helpers):
|
|
|
648
648
|
print(f'{rc=}')
|
|
649
649
|
assert rc == 0
|
|
650
650
|
assert self.is_in_log('Detected iverilog')
|
|
651
|
-
assert self.is_in_log("Generic help for command=
|
|
651
|
+
assert self.is_in_log("Generic help for command=sim tool=iverilog")
|
|
652
652
|
|
|
653
653
|
def test_iverilog_sim(self):
|
|
654
654
|
'''Test for command sim'''
|