opencos-eda 0.3.10__py3-none-any.whl → 0.3.11__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/deps_help.py +63 -113
- opencos/commands/export.py +7 -2
- opencos/commands/multi.py +3 -3
- opencos/commands/sim.py +14 -15
- opencos/commands/synth.py +1 -2
- opencos/commands/upload.py +192 -4
- opencos/commands/waves.py +8 -8
- opencos/deps/deps_commands.py +6 -6
- opencos/deps/deps_processor.py +129 -50
- opencos/docs/Architecture.md +45 -0
- opencos/docs/ConnectingApps.md +29 -0
- opencos/docs/DEPS.md +199 -0
- opencos/docs/Debug.md +77 -0
- opencos/docs/DirectoryStructure.md +22 -0
- opencos/docs/Installation.md +117 -0
- opencos/docs/OcVivadoTcl.md +63 -0
- opencos/docs/OpenQuestions.md +7 -0
- opencos/docs/README.md +13 -0
- opencos/docs/RtlCodingStyle.md +54 -0
- opencos/docs/eda.md +147 -0
- opencos/docs/oc_cli.md +135 -0
- opencos/eda.py +132 -35
- opencos/eda_base.py +173 -47
- opencos/eda_config.py +56 -17
- opencos/eda_config_defaults.yml +21 -4
- opencos/files.py +26 -1
- opencos/tools/cocotb.py +5 -5
- opencos/tools/invio.py +2 -2
- opencos/tools/invio_yosys.py +2 -1
- opencos/tools/iverilog.py +3 -3
- opencos/tools/quartus.py +113 -115
- opencos/tools/questa_common.py +2 -2
- opencos/tools/riviera.py +3 -3
- opencos/tools/slang.py +11 -7
- opencos/tools/slang_yosys.py +1 -0
- opencos/tools/surelog.py +4 -3
- opencos/tools/verilator.py +4 -4
- opencos/tools/vivado.py +307 -176
- opencos/tools/yosys.py +4 -4
- opencos/util.py +6 -3
- opencos/utils/dict_helpers.py +31 -0
- opencos/utils/markup_helpers.py +2 -2
- opencos/utils/subprocess_helpers.py +3 -3
- opencos/utils/vscode_helper.py +2 -2
- opencos/utils/vsim_helper.py +16 -5
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
- opencos_eda-0.3.11.dist-info/RECORD +94 -0
- opencos_eda-0.3.10.dist-info/RECORD +0 -81
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/top_level.txt +0 -0
opencos/eda.py
CHANGED
|
@@ -9,7 +9,6 @@ markup files
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
|
-
import shutil
|
|
13
12
|
import re
|
|
14
13
|
import signal
|
|
15
14
|
import argparse
|
|
@@ -20,10 +19,10 @@ from pathlib import Path
|
|
|
20
19
|
|
|
21
20
|
import opencos
|
|
22
21
|
from opencos import util, eda_config, eda_base
|
|
22
|
+
from opencos.eda_base import Command, Tool, which_tool, print_eda_usage_line
|
|
23
|
+
from opencos.files import safe_shutil_which
|
|
23
24
|
from opencos.util import safe_emoji, Colors
|
|
24
|
-
from opencos.eda_base import Tool, which_tool, get_eda_exec, print_eda_usage_line
|
|
25
25
|
from opencos.utils import vsim_helper, vscode_helper
|
|
26
|
-
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
27
26
|
from opencos.utils import status_constants, str_helpers, subprocess_helpers
|
|
28
27
|
|
|
29
28
|
# Configure util:
|
|
@@ -44,7 +43,8 @@ util.global_log.default_log_disable_with_args.extend([
|
|
|
44
43
|
# These are also overriden depending on the tool, for example --tool verilator sets
|
|
45
44
|
# "sim": CommandSimVerilator.
|
|
46
45
|
def init_config(
|
|
47
|
-
config: dict, quiet: bool = False, tool=None,
|
|
46
|
+
config: dict, quiet: bool = False, tool=None, command: str = '',
|
|
47
|
+
run_auto_tool_setup: bool = True
|
|
48
48
|
) -> dict:
|
|
49
49
|
'''Sets or clears entries in config (dict) so tools can be re-loaded.'''
|
|
50
50
|
|
|
@@ -64,7 +64,7 @@ def init_config(
|
|
|
64
64
|
config['auto_tools_found'] = {}
|
|
65
65
|
config['tools_loaded'] = set()
|
|
66
66
|
if run_auto_tool_setup:
|
|
67
|
-
config = auto_tool_setup(config=config, quiet=quiet, tool=tool)
|
|
67
|
+
config = auto_tool_setup(config=config, quiet=quiet, tool=tool, command=command)
|
|
68
68
|
return config
|
|
69
69
|
|
|
70
70
|
|
|
@@ -137,7 +137,11 @@ def interactive(config: dict) -> int:
|
|
|
137
137
|
'''
|
|
138
138
|
rc = 0
|
|
139
139
|
while True:
|
|
140
|
-
|
|
140
|
+
try:
|
|
141
|
+
line = input('EDA->')
|
|
142
|
+
except EOFError:
|
|
143
|
+
util.info('End of input reached unexpectedly, exiting')
|
|
144
|
+
return 0
|
|
141
145
|
m = re.match(r'^([^\#]*)\#.*$', line)
|
|
142
146
|
if m:
|
|
143
147
|
line = m.group(1)
|
|
@@ -154,7 +158,8 @@ def interactive(config: dict) -> int:
|
|
|
154
158
|
|
|
155
159
|
|
|
156
160
|
def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
157
|
-
warnings: bool = True, config = None, quiet: bool = False, tool: str = ''
|
|
161
|
+
warnings: bool = True, config = None, quiet: bool = False, tool: str = '',
|
|
162
|
+
command: str = ''
|
|
158
163
|
) -> dict:
|
|
159
164
|
'''Returns an updated config, uses config['auto_tools_order'][0] dict, calls tool_setup(..)
|
|
160
165
|
|
|
@@ -172,9 +177,26 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
172
177
|
assert isinstance(config['auto_tools_order'], list)
|
|
173
178
|
assert isinstance(config['auto_tools_order'][0], dict)
|
|
174
179
|
|
|
180
|
+
if command:
|
|
181
|
+
util.info(f'Auto tool setup for command: {Colors.byellow}{command}')
|
|
182
|
+
|
|
175
183
|
for name, value in config['auto_tools_order'][0].items():
|
|
176
184
|
if tool and tool != name:
|
|
177
|
-
|
|
185
|
+
# if called with tool=(some_name), then only load that tool (which is not
|
|
186
|
+
# this one)
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
if command and command not in value.get('handlers', {}) and \
|
|
190
|
+
command not in config.get('command_has_subcommands', []):
|
|
191
|
+
# Skip tool_setup(..) if the tool handlers can't support command,
|
|
192
|
+
# this is a time-saving feature, but if the comman is multi, tools-multi,
|
|
193
|
+
# sweep, then don't skip this (we don't know what tools we need so load them
|
|
194
|
+
# all.
|
|
195
|
+
# We could figure this out if we went looking for all command(s)
|
|
196
|
+
# multi + sub-command, but that's slightly dangerous if we grab a 'command'
|
|
197
|
+
# from another arg.
|
|
198
|
+
util.debug(f"Skipping tool {name} because it cannot handle {command=}")
|
|
199
|
+
continue
|
|
178
200
|
|
|
179
201
|
util.debug(f"Checking for ability to run tool: {name}")
|
|
180
202
|
exe = value.get('exe', str())
|
|
@@ -207,7 +229,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
207
229
|
exe_path = None
|
|
208
230
|
for exe in exe_list:
|
|
209
231
|
assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
|
|
210
|
-
p =
|
|
232
|
+
p = safe_shutil_which(exe)
|
|
211
233
|
if not exe_path:
|
|
212
234
|
exe_path = p # set on first required exe
|
|
213
235
|
if not p:
|
|
@@ -261,18 +283,22 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
261
283
|
if exe_path:
|
|
262
284
|
p = exe_path
|
|
263
285
|
else:
|
|
264
|
-
p =
|
|
286
|
+
p = safe_shutil_which(exe_list[0])
|
|
265
287
|
config['auto_tools_found'][name] = p # populate key-value pairs w/ first exe in list
|
|
266
288
|
if not quiet:
|
|
267
289
|
util.info(f"Detected {name} ({p})")
|
|
268
290
|
tool_setup(
|
|
269
|
-
tool=name, quiet=True, auto_setup=
|
|
291
|
+
tool=name, quiet=True, auto_setup=(not tool), warnings=warnings, config=config
|
|
270
292
|
)
|
|
293
|
+
else:
|
|
294
|
+
util.debug(f'Tool {name} is missing one of: {has_all_py=} {has_all_env=}',
|
|
295
|
+
f'{has_all_exe=} {has_all_in_exe_path=} {has_vsim_helper=}',
|
|
296
|
+
f'{has_vscode_helper=}')
|
|
271
297
|
|
|
272
298
|
return config
|
|
273
299
|
|
|
274
300
|
|
|
275
|
-
def tool_setup(
|
|
301
|
+
def tool_setup( # pylint: disable=too-many-branches
|
|
276
302
|
tool: str, config: dict, quiet: bool = False, auto_setup: bool = False,
|
|
277
303
|
warnings: bool = True
|
|
278
304
|
):
|
|
@@ -332,10 +358,22 @@ def tool_setup(
|
|
|
332
358
|
|
|
333
359
|
cls = util.import_class_from_string(str_class_name)
|
|
334
360
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
361
|
+
if command in config.get('command_determines_tool', []) + \
|
|
362
|
+
config.get('command_tool_is_optional', []):
|
|
363
|
+
# we don't need to confirm the handler parent is a Tool class.
|
|
364
|
+
pass
|
|
365
|
+
else:
|
|
366
|
+
assert issubclass(cls, Tool), \
|
|
367
|
+
f'{str_class_name=} is does not have Tool class associated with it'
|
|
368
|
+
|
|
369
|
+
if not auto_setup or \
|
|
370
|
+
command not in config.get('command_determines_tool', []):
|
|
371
|
+
# If not auto_setup - then someone called this --tool by name on the command line,
|
|
372
|
+
# then update the command handler
|
|
373
|
+
# otherwise, if --tool was not set, and command determines tool, leave it with
|
|
374
|
+
# the default handler.
|
|
375
|
+
util.debug(f'Setting {cls=} for {command=} in config.command_handler')
|
|
376
|
+
config['command_handler'][command] = cls
|
|
339
377
|
|
|
340
378
|
config['tools_loaded'].add(tool)
|
|
341
379
|
|
|
@@ -358,7 +396,6 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
358
396
|
deferred_tokens = []
|
|
359
397
|
command = ""
|
|
360
398
|
run_auto_tool_setup = True
|
|
361
|
-
process_tokens_cwd = os.getcwd()
|
|
362
399
|
|
|
363
400
|
parser = eda_base.get_argparser()
|
|
364
401
|
try:
|
|
@@ -385,6 +422,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
385
422
|
|
|
386
423
|
# Attempt to get the 'command' in the unparsed args before we've even
|
|
387
424
|
# set the command handlers (some commands don't use tools).
|
|
425
|
+
# Note that we only grab the first command, and for multi, tools-multi,
|
|
426
|
+
# or sweep we do NOT get the subcommand.
|
|
388
427
|
for value in unparsed:
|
|
389
428
|
if value in config['DEFAULT_HANDLERS'].keys():
|
|
390
429
|
command = value
|
|
@@ -400,7 +439,7 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
400
439
|
# This will handle any --tool=<name>=/path/to/bin also, so don't have to
|
|
401
440
|
# run tool_setup(..) on its own.
|
|
402
441
|
config = init_config(
|
|
403
|
-
config, tool=parsed.tool,
|
|
442
|
+
config, tool=parsed.tool, command=command,
|
|
404
443
|
run_auto_tool_setup=run_auto_tool_setup
|
|
405
444
|
)
|
|
406
445
|
if not config:
|
|
@@ -467,26 +506,82 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
467
506
|
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
|
|
468
507
|
|
|
469
508
|
if rc == 0 and not parsed.tool and getattr(sco, 'tool_changed_respawn', False):
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return status_constants.EDA_DEFAULT_ERROR
|
|
474
|
-
|
|
475
|
-
# close the util.log:
|
|
476
|
-
util.stop_log()
|
|
477
|
-
# respawn the original job, but with --tool=<use_tool> applied:
|
|
478
|
-
_command_list = [get_eda_exec(command), f"--tool={use_tool}"] + original_args
|
|
479
|
-
util.info(f'eda: respawn for tool change: {" ".join(_command_list)};',
|
|
480
|
-
f' (running from: {process_tokens_cwd})')
|
|
481
|
-
subprocess_run_background(
|
|
482
|
-
work_dir=process_tokens_cwd,
|
|
483
|
-
command_list=_command_list,
|
|
484
|
-
background=util.args.get('quiet', False)
|
|
509
|
+
return respawn_new_sub_command_object(
|
|
510
|
+
sco=sco, parsed=parsed, config=config, command=command, tokens=tokens,
|
|
511
|
+
deferred_tokens=deferred_tokens
|
|
485
512
|
)
|
|
486
513
|
|
|
487
514
|
return rc
|
|
488
515
|
|
|
489
516
|
|
|
517
|
+
def respawn_new_sub_command_object(
|
|
518
|
+
sco: Command, parsed: argparse.Namespace, config: dict, command: str,
|
|
519
|
+
tokens: list, deferred_tokens: list
|
|
520
|
+
) -> int:
|
|
521
|
+
'''Returns retcode (int). Creates a new Command object, presumably using a different tool,
|
|
522
|
+
|
|
523
|
+
due to args changes from DEPS parsing that led to --tool=<different value> vs the automatic
|
|
524
|
+
value if --tool was not originally set. Will run process_tokens(..) on the new sub commmand
|
|
525
|
+
object.
|
|
526
|
+
'''
|
|
527
|
+
|
|
528
|
+
use_tool = sco.args.get('tool', '')
|
|
529
|
+
|
|
530
|
+
if not use_tool:
|
|
531
|
+
util.error(f'Unable to change tool from {parsed.tool}, internal eda problem.')
|
|
532
|
+
return status_constants.EDA_DEFAULT_ERROR
|
|
533
|
+
|
|
534
|
+
util.info(f'Changing {Colors.bcyan}--tool{Colors.normal}{Colors.green} --->',
|
|
535
|
+
f'{Colors.bcyan}{use_tool}{Colors.normal}{Colors.green} for command:',
|
|
536
|
+
f'{Colors.byellow}{command}')
|
|
537
|
+
|
|
538
|
+
# Update the command handler(s) with this new tool. We don't really respawn, just
|
|
539
|
+
# try to swap out the sco (Command obj handle)
|
|
540
|
+
entry = config['auto_tools_order'][0].get(use_tool, {})
|
|
541
|
+
tool_cmd_handler_dict = entry.get('handlers', {})
|
|
542
|
+
|
|
543
|
+
for _command, str_class_name in tool_cmd_handler_dict.items():
|
|
544
|
+
if _command and command and _command != command:
|
|
545
|
+
# This isn't the command we care about (it's just one of the commands
|
|
546
|
+
# this tool supports, so don't bother loading a handler for it:
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
cls = util.import_class_from_string(str_class_name)
|
|
550
|
+
if _command in config.get('command_determines_tool', []) + \
|
|
551
|
+
config.get('command_tool_is_optional', []):
|
|
552
|
+
# we don't need to confirm the handler parent is a Tool class.
|
|
553
|
+
pass
|
|
554
|
+
else:
|
|
555
|
+
assert issubclass(cls, Tool), \
|
|
556
|
+
f'command {_command} {str_class_name=} does not have Tool class associated with it'
|
|
557
|
+
|
|
558
|
+
util.debug(f'Setting {cls=} for command={_command} in config.command_handler')
|
|
559
|
+
config['command_handler'][_command] = cls
|
|
560
|
+
|
|
561
|
+
old_sco = sco
|
|
562
|
+
sco = config['command_handler'][command](config=config) # sub command object
|
|
563
|
+
util.debug(f'No longer using handler: {type(old_sco)}; now using: {type(sco)}')
|
|
564
|
+
sco.config['eda_original_args'] = old_sco.config['eda_original_args']
|
|
565
|
+
del old_sco
|
|
566
|
+
|
|
567
|
+
rc = check_command_handler_cls(command_obj=sco, command=command, parsed_args=parsed)
|
|
568
|
+
if rc > 0:
|
|
569
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=},'
|
|
570
|
+
f'unparsed={deferred_tokens}')
|
|
571
|
+
return rc
|
|
572
|
+
|
|
573
|
+
setattr(sco, 'command_name', command) # as a safeguard, 'command' set in 'sco'
|
|
574
|
+
util.info(f'--tool={use_tool}: running command: {Colors.byellow}eda {command} ',
|
|
575
|
+
' '.join(deferred_tokens))
|
|
576
|
+
unparsed = sco.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
|
|
577
|
+
|
|
578
|
+
# query the status from the Command object (0 is pass, > 0 is fail, but we'd prefer to
|
|
579
|
+
# avoid rc=1 because that's the python exception rc)
|
|
580
|
+
rc = getattr(sco, 'status', 2)
|
|
581
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
|
|
582
|
+
return rc
|
|
583
|
+
|
|
584
|
+
|
|
490
585
|
def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> int:
|
|
491
586
|
'''Returns bash/sh return code, checks that a command handling class has all
|
|
492
587
|
|
|
@@ -524,7 +619,7 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
|
|
|
524
619
|
if k == 'exe' or k.startswith('requires_cmd'):
|
|
525
620
|
util.warning(
|
|
526
621
|
f"tool '{parsed_tool}' has requirements that may not have been met --",
|
|
527
|
-
f"{k}: {v}"
|
|
622
|
+
f"{k}: {v} (Perhaps not in PATH?)"
|
|
528
623
|
)
|
|
529
624
|
if k == 'requires_vsim_helper':
|
|
530
625
|
if found_tool := vsim_helper.found():
|
|
@@ -595,7 +690,8 @@ def main(*args):
|
|
|
595
690
|
if not util.args['quiet']:
|
|
596
691
|
util.info(f'eda: version {opencos.__version__}', color=Colors.bcyan)
|
|
597
692
|
# And show the command that was run (all args):
|
|
598
|
-
util.info(f"main: eda {' '.join(args)}
|
|
693
|
+
util.info(f"main: {Colors.byellow}eda {' '.join(args)}{Colors.normal}{Colors.green};",
|
|
694
|
+
f"(run from {os.getcwd()})")
|
|
599
695
|
|
|
600
696
|
# Handle --config-yml= arg
|
|
601
697
|
config, unparsed = eda_config.get_eda_config(unparsed)
|
|
@@ -657,6 +753,7 @@ def main_cli() -> None:
|
|
|
657
753
|
util.global_exit_allowed = True
|
|
658
754
|
# Strip eda or eda.py from sys.argv, we know who we are if called from __main__:
|
|
659
755
|
rc = main()
|
|
756
|
+
subprocess_helpers.cleanup_all()
|
|
660
757
|
util.exit(rc)
|
|
661
758
|
|
|
662
759
|
|
opencos/eda_base.py
CHANGED
|
@@ -30,6 +30,7 @@ from opencos.utils.str_helpers import sprint_time, strip_outer_quotes, string_or
|
|
|
30
30
|
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
31
31
|
from opencos.utils import status_constants
|
|
32
32
|
|
|
33
|
+
from opencos.files import safe_shutil_which
|
|
33
34
|
from opencos.deps.deps_file import DepsFile, deps_data_get_all_targets
|
|
34
35
|
from opencos.deps.deps_processor import DepsProcessor
|
|
35
36
|
|
|
@@ -114,7 +115,7 @@ def get_eda_exec(command: str = '') -> str:
|
|
|
114
115
|
# packages cannot be run standalone, they need to be called as: python3 -m opencos.eda,
|
|
115
116
|
# and do not work with relative paths. This only works if env OC_ROOT is set or can be found.
|
|
116
117
|
# 3. If you ran 'source bin/addpath' then you are always using the local (opencos repo)/bin/eda
|
|
117
|
-
eda_path =
|
|
118
|
+
eda_path = safe_shutil_which('eda')
|
|
118
119
|
if not eda_path:
|
|
119
120
|
# Can we run from OC_ROOT/bin/eda?
|
|
120
121
|
oc_root = util.get_oc_root()
|
|
@@ -214,7 +215,7 @@ class Tool:
|
|
|
214
215
|
# config['auto_tools_found'] has the first exe full path:
|
|
215
216
|
exe = config.get('auto_tools_found', {}).get(self._TOOL)
|
|
216
217
|
if exe and exe != self._EXE:
|
|
217
|
-
# Note that
|
|
218
|
+
# Note that safe_shutil_which() on the exe leaf may not match, this does NOT
|
|
218
219
|
# modify os.environ['PATH'].
|
|
219
220
|
util.debug(f'{self._TOOL} using exe: {exe}')
|
|
220
221
|
self._EXE = exe
|
|
@@ -240,14 +241,18 @@ class Tool:
|
|
|
240
241
|
return
|
|
241
242
|
|
|
242
243
|
info_color = Colors.green
|
|
244
|
+
error_color = Colors.bgreen
|
|
243
245
|
start = ''
|
|
244
|
-
if self.
|
|
245
|
-
|
|
246
|
+
if self.tool_warning_count:
|
|
247
|
+
info_color = Colors.yellow
|
|
248
|
+
if self.tool_error_count:
|
|
246
249
|
info_color = Colors.yellow
|
|
250
|
+
error_color = Colors.byellow
|
|
251
|
+
start = safe_emoji('🔶 ')
|
|
247
252
|
util.info(
|
|
248
|
-
f"{start}Tool - {tool_name}, total counts:",
|
|
253
|
+
f"{start}Tool - {Colors.bold}{tool_name}{Colors.normal}{info_color}, total counts:",
|
|
249
254
|
f"{Colors.bold}{self.tool_warning_count} tool warnings{Colors.normal}{info_color},",
|
|
250
|
-
f"{
|
|
255
|
+
f"{error_color}{self.tool_error_count} tool errors",
|
|
251
256
|
color=info_color
|
|
252
257
|
)
|
|
253
258
|
|
|
@@ -272,6 +277,8 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
272
277
|
self.args_help = {}
|
|
273
278
|
if getattr(self, 'args_kwargs', None) is None:
|
|
274
279
|
self.args_kwargs = {}
|
|
280
|
+
if getattr(self, 'args_args', None) is None:
|
|
281
|
+
self.args_args = {} # support for multiple named --arg that map to same self.args key.
|
|
275
282
|
self.args.update({
|
|
276
283
|
"keep" : False,
|
|
277
284
|
"force" : False,
|
|
@@ -358,6 +365,7 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
358
365
|
self.errors_log_f = None
|
|
359
366
|
self.auto_tool_applied = False
|
|
360
367
|
self.tool_changed_respawn = {}
|
|
368
|
+
self.top_details = {}
|
|
361
369
|
|
|
362
370
|
|
|
363
371
|
def get_info_job_name(self) -> str:
|
|
@@ -558,8 +566,23 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
558
566
|
# Set the util.artifacts path with our work-dir:
|
|
559
567
|
util.artifacts.set_artifacts_json_dir(self.args['work-dir'])
|
|
560
568
|
|
|
569
|
+
# Since work-dir has now safely been created (or error flagged) we can override the
|
|
570
|
+
# inferred 'top' if top was not specified (aka, we guessed based on target name or
|
|
571
|
+
# file/module name)
|
|
572
|
+
self.update_top_if_inferred()
|
|
573
|
+
|
|
561
574
|
return self.args['work-dir']
|
|
562
575
|
|
|
576
|
+
|
|
577
|
+
def update_top_if_inferred(self) -> None:
|
|
578
|
+
'''Child classes can override (CommandDesign does)
|
|
579
|
+
|
|
580
|
+
Idea is to overwrite self.args['top'] if it was inferred, but Command.args
|
|
581
|
+
does not initially set self.args['top'], so this method takes no action.
|
|
582
|
+
'''
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
|
|
563
586
|
def artifacts_add(self, name: str, typ: str, description: str) -> None:
|
|
564
587
|
'''Adds a file to util.artifacts, derived classes may override'''
|
|
565
588
|
util.artifacts.add(name=name, typ=typ, description=description)
|
|
@@ -710,6 +733,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
710
733
|
arguments = [] # list supplied to parser.add_argument(..) so one liner supports both.
|
|
711
734
|
for this_key in keys:
|
|
712
735
|
arguments.append(f'--{this_key}')
|
|
736
|
+
for arg in self.args_args.get(this_key, []):
|
|
737
|
+
if arg not in arguments:
|
|
738
|
+
arguments.append(f'--{arg}')
|
|
713
739
|
|
|
714
740
|
if self.args_help.get(key, ''):
|
|
715
741
|
help_kwargs = {'help': self.args_help[key] + f' (default={value})'}
|
|
@@ -838,6 +864,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
838
864
|
'''
|
|
839
865
|
|
|
840
866
|
_, unparsed = self.run_argparser_on_list(tokens)
|
|
867
|
+
|
|
868
|
+
self._apply_deps_file_defaults_if_present()
|
|
869
|
+
|
|
841
870
|
if process_all and unparsed:
|
|
842
871
|
self.warning_show_known_args()
|
|
843
872
|
self.error_ifarg(
|
|
@@ -983,37 +1012,39 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
983
1012
|
color.disable() # strip our color object if < 3.14
|
|
984
1013
|
|
|
985
1014
|
|
|
1015
|
+
# Include -G, +incdir+, +define+ help if this is a CommanDesign class:
|
|
986
1016
|
lines.append('')
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
+
if isinstance(self, CommandDesign):
|
|
1018
|
+
lines.append(
|
|
1019
|
+
f" {color.cyan}-G{color.byellow}<parameterName>{color.normal}=" \
|
|
1020
|
+
+ f"{color.yellow}<value>{color.normal}"
|
|
1021
|
+
)
|
|
1022
|
+
lines.append(indent_me((
|
|
1023
|
+
" Add parameter to top level, support bit/int/string types only."
|
|
1024
|
+
" Example: -GDEPTH=8 (DEPTH treated as SV int/integer)."
|
|
1025
|
+
" -GENABLE=1 (ENABLED treated as SV bit/int/integer)."
|
|
1026
|
+
" -GName=eda (Name treated as SV string \"eda\")."
|
|
1027
|
+
)))
|
|
1028
|
+
|
|
1029
|
+
lines.append(f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}")
|
|
1030
|
+
lines.append(indent_me((
|
|
1031
|
+
" Add define w/out value to tool ahead of SV sources"
|
|
1032
|
+
" Example: +define+SIM_SPEEDUP"
|
|
1033
|
+
)))
|
|
1034
|
+
lines.append(
|
|
1035
|
+
f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}=" \
|
|
1036
|
+
+ f"{color.yellow}<value>{color.normal}")
|
|
1037
|
+
lines.append(indent_me((
|
|
1038
|
+
" Add define w/ value to tool ahead of SV sources"
|
|
1039
|
+
" Example: +define+TECH_LIB=2 +define+FULL_NAME=\"E D A\""
|
|
1040
|
+
)))
|
|
1041
|
+
lines.append(f" {color.cyan}+incdir+{color.byellow}PATH{color.normal}")
|
|
1042
|
+
lines.append(indent_me((
|
|
1043
|
+
" Add path (absolute or relative) for include directories"
|
|
1044
|
+
" for SystemVerilog `include \"<some-file>.svh\""
|
|
1045
|
+
" Example: +incdir+../lib"
|
|
1046
|
+
)))
|
|
1047
|
+
lines.append('')
|
|
1017
1048
|
|
|
1018
1049
|
for line in lines:
|
|
1019
1050
|
print(line)
|
|
@@ -1082,6 +1113,13 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1082
1113
|
else:
|
|
1083
1114
|
util.warning(*msg)
|
|
1084
1115
|
|
|
1116
|
+
@staticmethod
|
|
1117
|
+
def get_top_name(name: str) -> str:
|
|
1118
|
+
'''Attempt to get the 'top' module name from a file, such as path/to/mine.sv will
|
|
1119
|
+
|
|
1120
|
+
return "mine"'''
|
|
1121
|
+
return os.path.splitext(os.path.basename(name))[0]
|
|
1122
|
+
|
|
1085
1123
|
|
|
1086
1124
|
def update_tool_warn_err_counts_from_log_lines(
|
|
1087
1125
|
self, log_lines: list, bad_strings: list, warning_strings: list
|
|
@@ -1103,6 +1141,48 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1103
1141
|
setattr(self, 'tool_warning_count', getattr(self, 'tool_warning_count', 0) + 1)
|
|
1104
1142
|
|
|
1105
1143
|
|
|
1144
|
+
def _apply_deps_file_defaults_if_present(self) -> None:
|
|
1145
|
+
'''Only runs if this is not a CommandDesign class.
|
|
1146
|
+
|
|
1147
|
+
Looks at DEPS file in current directory, and will attempt to apply 'DEFAULTS' if present
|
|
1148
|
+
'''
|
|
1149
|
+
|
|
1150
|
+
if isinstance(self, CommandDesign):
|
|
1151
|
+
return
|
|
1152
|
+
|
|
1153
|
+
# Try to fish out DEFAULTS from ./DEPS if present, so we can apply args to self.
|
|
1154
|
+
_cache_none = {}
|
|
1155
|
+
data = None
|
|
1156
|
+
if self.config['deps_markup_supported']:
|
|
1157
|
+
deps = DepsFile(
|
|
1158
|
+
command_design_ref=self, target_path=str(Path('.')), cache=_cache_none
|
|
1159
|
+
)
|
|
1160
|
+
data = deps.data
|
|
1161
|
+
|
|
1162
|
+
entry = None
|
|
1163
|
+
if data is not None and 'DEFAULTS' in data:
|
|
1164
|
+
entry = deps.get_entry(target_node=str(Path('./DEFAULTS')))
|
|
1165
|
+
if not entry:
|
|
1166
|
+
return
|
|
1167
|
+
|
|
1168
|
+
target = str(Path('./DEFAULTS'))
|
|
1169
|
+
_, target_node = os.path.split(target)
|
|
1170
|
+
caller_info = deps.gen_caller_info(target)
|
|
1171
|
+
|
|
1172
|
+
deps_processor = DepsProcessor(
|
|
1173
|
+
command_design_ref=self,
|
|
1174
|
+
deps_entry=entry,
|
|
1175
|
+
target=target,
|
|
1176
|
+
target_path=str(Path('.')),
|
|
1177
|
+
target_node=target_node,
|
|
1178
|
+
deps_file = deps.deps_file,
|
|
1179
|
+
caller_info = caller_info
|
|
1180
|
+
)
|
|
1181
|
+
_ = deps_processor.process_deps_entry()
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
|
|
1106
1186
|
class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
1107
1187
|
'''CommandDesign is the eda base class for command handlers that need to track files.
|
|
1108
1188
|
|
|
@@ -1161,11 +1241,22 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1161
1241
|
# keys 'data' and 'line_numbers'
|
|
1162
1242
|
self.cached_deps = {}
|
|
1163
1243
|
self.targets_dict = {} # key = targets that we've already processed in DEPS files
|
|
1164
|
-
self.last_added_source_file_inferred_top = ''
|
|
1165
1244
|
|
|
1166
1245
|
self.has_pre_compile_dep_shell_commands = False
|
|
1167
1246
|
self.has_post_tool_dep_shell_commands = False
|
|
1168
1247
|
|
|
1248
|
+
self.top_details = {
|
|
1249
|
+
# used by CommandDesign to track last file added.
|
|
1250
|
+
'last_added_source_file': '',
|
|
1251
|
+
# implies top was not set, we inferred from the final command line
|
|
1252
|
+
# DEPS target name
|
|
1253
|
+
'inferred_from_target_name': False,
|
|
1254
|
+
# implies top was not set, we inferred from the final source file added,
|
|
1255
|
+
# based on a 'module' name we parsed.
|
|
1256
|
+
# also implies self.target was set from inferred top:
|
|
1257
|
+
'inferred_from_last_added_source_file': False,
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1169
1260
|
|
|
1170
1261
|
def run_dep_commands(self) -> None:
|
|
1171
1262
|
'''Run shell/peakrdl style commands from DEPS files, this is peformed before
|
|
@@ -1346,13 +1437,43 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1346
1437
|
else:
|
|
1347
1438
|
os.symlink(src=fname, dst=destfile)
|
|
1348
1439
|
|
|
1349
|
-
@staticmethod
|
|
1350
|
-
def get_top_name(name: str) -> str:
|
|
1351
|
-
'''Attempt to get the 'top' module name from a file, such as path/to/mine.sv will
|
|
1352
1440
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1441
|
+
def update_top_if_inferred(self) -> None:
|
|
1442
|
+
'''Overridden from Command, uses self.top_details - attempts to overwrite
|
|
1443
|
+
|
|
1444
|
+
self.args['top'] if it was previously set based on the first DEPS target name,
|
|
1445
|
+
so it more accurately reflects the top-module-name. You should only do this if the
|
|
1446
|
+
self.args['work-dir'] has already been determined.
|
|
1447
|
+
'''
|
|
1448
|
+
|
|
1449
|
+
if not self.args['top']:
|
|
1450
|
+
return
|
|
1451
|
+
|
|
1452
|
+
if not (self.top_details and \
|
|
1453
|
+
self.top_details['inferred_from_target_name'] and \
|
|
1454
|
+
self.top_details['last_added_source_file']):
|
|
1455
|
+
return
|
|
1456
|
+
|
|
1457
|
+
# since work-dir should be set, we will change self.args['top'] if there
|
|
1458
|
+
# was a last-added source file.
|
|
1459
|
+
best_top_fname = self.top_details['last_added_source_file']
|
|
1460
|
+
|
|
1461
|
+
if not best_top_fname:
|
|
1462
|
+
return
|
|
1463
|
+
|
|
1464
|
+
best_top_name = self.get_top_name(best_top_fname)
|
|
1465
|
+
best_top = util.get_inferred_top_module_name(
|
|
1466
|
+
module_guess=best_top_name,
|
|
1467
|
+
module_fpath=best_top_fname
|
|
1468
|
+
)
|
|
1469
|
+
if not best_top:
|
|
1470
|
+
return
|
|
1471
|
+
|
|
1472
|
+
util.info("--top was previously inferred from target name",
|
|
1473
|
+
f"({self.top_details['inferred_from_target_name']}), overriding with:",
|
|
1474
|
+
f"{best_top} (from file: {best_top_fname})")
|
|
1475
|
+
self.args['top'] = best_top
|
|
1476
|
+
|
|
1356
1477
|
|
|
1357
1478
|
def set_parameter(
|
|
1358
1479
|
self, name: str, value, caller_info: str = '(CLI)',
|
|
@@ -1727,7 +1848,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1727
1848
|
|
|
1728
1849
|
if not add_to_non_sources and \
|
|
1729
1850
|
file_ext in known_file_ext_dict.get('inferred_top', []):
|
|
1730
|
-
self.
|
|
1851
|
+
self.top_details['last_added_source_file'] = file_abspath
|
|
1731
1852
|
|
|
1732
1853
|
if add_to_non_sources:
|
|
1733
1854
|
self.files_non_source.append(file_abspath)
|
|
@@ -1932,9 +2053,10 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1932
2053
|
self.args['top'], top_path = last_potential_top_target
|
|
1933
2054
|
util.info("--top not specified, inferred from target:",
|
|
1934
2055
|
f"{self.args['top']} ({top_path})")
|
|
2056
|
+
self.top_details['inferred_from_target_name'] = top_path
|
|
1935
2057
|
|
|
1936
2058
|
else:
|
|
1937
|
-
best_top_fname = self.
|
|
2059
|
+
best_top_fname = self.top_details['last_added_source_file']
|
|
1938
2060
|
if best_top_fname:
|
|
1939
2061
|
last_potential_top_file = (self.get_top_name(best_top_fname), best_top_fname)
|
|
1940
2062
|
|
|
@@ -1944,7 +2066,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1944
2066
|
top_path = last_potential_top_file[1] # from tuple: (top, fpath)
|
|
1945
2067
|
self.args['top'] = util.get_inferred_top_module_name(
|
|
1946
2068
|
module_guess=last_potential_top_file[0],
|
|
1947
|
-
module_fpath=
|
|
2069
|
+
module_fpath=top_path
|
|
1948
2070
|
)
|
|
1949
2071
|
if self.args['top']:
|
|
1950
2072
|
util.info("--top not specified, inferred from final source file:",
|
|
@@ -1953,6 +2075,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1953
2075
|
# (not from DEPS.yml) we need to override self.target if that was set. Otherwise
|
|
1954
2076
|
# it won't save to the correct work-dir:
|
|
1955
2077
|
self.target = self.args['top']
|
|
2078
|
+
self.top_details['inferred_from_last_added_source_file'] = top_path
|
|
1956
2079
|
|
|
1957
2080
|
|
|
1958
2081
|
util.info(f'{self.command_name}: top-most target name: {self.target}')
|
|
@@ -2174,7 +2297,10 @@ class CommandParallel(Command):
|
|
|
2174
2297
|
if w.proc:
|
|
2175
2298
|
util.info(f"need to KILL WORKER_{w.n}, probably needs manual cleanup, check 'ps'")
|
|
2176
2299
|
if w.pid:
|
|
2177
|
-
|
|
2300
|
+
# Windows compatible: signal.SIGKILL is not available, so we could use
|
|
2301
|
+
# os.kill(PID, 9) but we'll be nice and do another SIGTERM
|
|
2302
|
+
# os.kill(PID, 9)
|
|
2303
|
+
os.kill(w.pid, getattr(signal, 'SIGKILL', signal.SIGTERM))
|
|
2178
2304
|
util.stop_log()
|
|
2179
2305
|
subprocess.Popen(['stty', 'sane']).wait() # pylint: disable=consider-using-with
|
|
2180
2306
|
|