opencos-eda 0.3.9__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 +89 -120
- opencos/commands/export.py +7 -2
- opencos/commands/multi.py +3 -3
- opencos/commands/sim.py +14 -16
- 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_file.py +82 -79
- 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 +175 -41
- opencos/eda_base.py +180 -50
- opencos/eda_config.py +62 -16
- opencos/eda_config_defaults.yml +21 -4
- opencos/eda_deps_bash_completion.bash +37 -15
- 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 +3 -4
- 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 +5 -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/str_helpers.py +7 -0
- opencos/utils/subprocess_helpers.py +3 -3
- opencos/utils/vscode_helper.py +2 -2
- opencos/utils/vsim_helper.py +58 -22
- {opencos_eda-0.3.9.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.9.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +1 -0
- opencos/tests/__init__.py +0 -0
- opencos/tests/custom_config.yml +0 -13
- opencos/tests/deps_files/command_order/DEPS.yml +0 -44
- opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
- opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
- opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
- opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
- opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
- opencos/tests/helpers.py +0 -354
- opencos/tests/test_build.py +0 -12
- opencos/tests/test_deps_helpers.py +0 -207
- opencos/tests/test_deps_schema.py +0 -30
- opencos/tests/test_eda.py +0 -921
- opencos/tests/test_eda_elab.py +0 -110
- opencos/tests/test_eda_synth.py +0 -150
- opencos/tests/test_oc_cli.py +0 -25
- opencos/tests/test_tools.py +0 -404
- opencos_eda-0.3.9.dist-info/RECORD +0 -99
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/top_level.txt +0 -0
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
|
|
|
@@ -45,6 +46,9 @@ def print_base_help() -> None:
|
|
|
45
46
|
def print_eda_usage_line(no_targets: bool = False, command_name='COMMAND') -> None:
|
|
46
47
|
'''Prints line for eda [options] COMMAND [options] FILES|TARGETS,...'''
|
|
47
48
|
print(f'{safe_emoji("🔦 ")}Usage:')
|
|
49
|
+
print(f' {Colors.bold}{Colors.yellow}eda_targets PATTERN')
|
|
50
|
+
print(f' {Colors.bold}{Colors.yellow}eda_show_autocomplete{Colors.normal}')
|
|
51
|
+
print()
|
|
48
52
|
if no_targets:
|
|
49
53
|
print(
|
|
50
54
|
(f' {Colors.bold}{Colors.yellow}eda {Colors.cyan}[options]'
|
|
@@ -111,7 +115,7 @@ def get_eda_exec(command: str = '') -> str:
|
|
|
111
115
|
# packages cannot be run standalone, they need to be called as: python3 -m opencos.eda,
|
|
112
116
|
# and do not work with relative paths. This only works if env OC_ROOT is set or can be found.
|
|
113
117
|
# 3. If you ran 'source bin/addpath' then you are always using the local (opencos repo)/bin/eda
|
|
114
|
-
eda_path =
|
|
118
|
+
eda_path = safe_shutil_which('eda')
|
|
115
119
|
if not eda_path:
|
|
116
120
|
# Can we run from OC_ROOT/bin/eda?
|
|
117
121
|
oc_root = util.get_oc_root()
|
|
@@ -208,11 +212,12 @@ class Tool:
|
|
|
208
212
|
def set_exe(self, config: dict) -> None:
|
|
209
213
|
'''Sets self._EXE based on config'''
|
|
210
214
|
if self._TOOL and self._TOOL in config.get('auto_tools_order', [{}])[0]:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
exe = exe[0] # pick first
|
|
215
|
+
# config['auto_tools_found'] has the first exe full path:
|
|
216
|
+
exe = config.get('auto_tools_found', {}).get(self._TOOL)
|
|
214
217
|
if exe and exe != self._EXE:
|
|
215
|
-
|
|
218
|
+
# Note that safe_shutil_which() on the exe leaf may not match, this does NOT
|
|
219
|
+
# modify os.environ['PATH'].
|
|
220
|
+
util.debug(f'{self._TOOL} using exe: {exe}')
|
|
216
221
|
self._EXE = exe
|
|
217
222
|
|
|
218
223
|
def get_tool_name(self) -> str:
|
|
@@ -236,14 +241,18 @@ class Tool:
|
|
|
236
241
|
return
|
|
237
242
|
|
|
238
243
|
info_color = Colors.green
|
|
244
|
+
error_color = Colors.bgreen
|
|
239
245
|
start = ''
|
|
240
|
-
if self.
|
|
241
|
-
start = safe_emoji('🔶 ')
|
|
246
|
+
if self.tool_warning_count:
|
|
242
247
|
info_color = Colors.yellow
|
|
248
|
+
if self.tool_error_count:
|
|
249
|
+
info_color = Colors.yellow
|
|
250
|
+
error_color = Colors.byellow
|
|
251
|
+
start = safe_emoji('🔶 ')
|
|
243
252
|
util.info(
|
|
244
|
-
f"{start}Tool - {tool_name}, total counts:",
|
|
253
|
+
f"{start}Tool - {Colors.bold}{tool_name}{Colors.normal}{info_color}, total counts:",
|
|
245
254
|
f"{Colors.bold}{self.tool_warning_count} tool warnings{Colors.normal}{info_color},",
|
|
246
|
-
f"{
|
|
255
|
+
f"{error_color}{self.tool_error_count} tool errors",
|
|
247
256
|
color=info_color
|
|
248
257
|
)
|
|
249
258
|
|
|
@@ -268,6 +277,8 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
268
277
|
self.args_help = {}
|
|
269
278
|
if getattr(self, 'args_kwargs', None) is None:
|
|
270
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.
|
|
271
282
|
self.args.update({
|
|
272
283
|
"keep" : False,
|
|
273
284
|
"force" : False,
|
|
@@ -354,6 +365,7 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
354
365
|
self.errors_log_f = None
|
|
355
366
|
self.auto_tool_applied = False
|
|
356
367
|
self.tool_changed_respawn = {}
|
|
368
|
+
self.top_details = {}
|
|
357
369
|
|
|
358
370
|
|
|
359
371
|
def get_info_job_name(self) -> str:
|
|
@@ -554,8 +566,23 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
554
566
|
# Set the util.artifacts path with our work-dir:
|
|
555
567
|
util.artifacts.set_artifacts_json_dir(self.args['work-dir'])
|
|
556
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
|
+
|
|
557
574
|
return self.args['work-dir']
|
|
558
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
|
+
|
|
559
586
|
def artifacts_add(self, name: str, typ: str, description: str) -> None:
|
|
560
587
|
'''Adds a file to util.artifacts, derived classes may override'''
|
|
561
588
|
util.artifacts.add(name=name, typ=typ, description=description)
|
|
@@ -706,6 +733,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
706
733
|
arguments = [] # list supplied to parser.add_argument(..) so one liner supports both.
|
|
707
734
|
for this_key in keys:
|
|
708
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}')
|
|
709
739
|
|
|
710
740
|
if self.args_help.get(key, ''):
|
|
711
741
|
help_kwargs = {'help': self.args_help[key] + f' (default={value})'}
|
|
@@ -834,6 +864,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
834
864
|
'''
|
|
835
865
|
|
|
836
866
|
_, unparsed = self.run_argparser_on_list(tokens)
|
|
867
|
+
|
|
868
|
+
self._apply_deps_file_defaults_if_present()
|
|
869
|
+
|
|
837
870
|
if process_all and unparsed:
|
|
838
871
|
self.warning_show_known_args()
|
|
839
872
|
self.error_ifarg(
|
|
@@ -979,37 +1012,39 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
979
1012
|
color.disable() # strip our color object if < 3.14
|
|
980
1013
|
|
|
981
1014
|
|
|
1015
|
+
# Include -G, +incdir+, +define+ help if this is a CommanDesign class:
|
|
982
1016
|
lines.append('')
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
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
|
-
|
|
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('')
|
|
1013
1048
|
|
|
1014
1049
|
for line in lines:
|
|
1015
1050
|
print(line)
|
|
@@ -1078,6 +1113,13 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1078
1113
|
else:
|
|
1079
1114
|
util.warning(*msg)
|
|
1080
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
|
+
|
|
1081
1123
|
|
|
1082
1124
|
def update_tool_warn_err_counts_from_log_lines(
|
|
1083
1125
|
self, log_lines: list, bad_strings: list, warning_strings: list
|
|
@@ -1099,6 +1141,48 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1099
1141
|
setattr(self, 'tool_warning_count', getattr(self, 'tool_warning_count', 0) + 1)
|
|
1100
1142
|
|
|
1101
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
|
+
|
|
1102
1186
|
class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
1103
1187
|
'''CommandDesign is the eda base class for command handlers that need to track files.
|
|
1104
1188
|
|
|
@@ -1157,11 +1241,22 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1157
1241
|
# keys 'data' and 'line_numbers'
|
|
1158
1242
|
self.cached_deps = {}
|
|
1159
1243
|
self.targets_dict = {} # key = targets that we've already processed in DEPS files
|
|
1160
|
-
self.last_added_source_file_inferred_top = ''
|
|
1161
1244
|
|
|
1162
1245
|
self.has_pre_compile_dep_shell_commands = False
|
|
1163
1246
|
self.has_post_tool_dep_shell_commands = False
|
|
1164
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
|
+
|
|
1165
1260
|
|
|
1166
1261
|
def run_dep_commands(self) -> None:
|
|
1167
1262
|
'''Run shell/peakrdl style commands from DEPS files, this is peformed before
|
|
@@ -1342,13 +1437,43 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1342
1437
|
else:
|
|
1343
1438
|
os.symlink(src=fname, dst=destfile)
|
|
1344
1439
|
|
|
1345
|
-
@staticmethod
|
|
1346
|
-
def get_top_name(name: str) -> str:
|
|
1347
|
-
'''Attempt to get the 'top' module name from a file, such as path/to/mine.sv will
|
|
1348
1440
|
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
+
|
|
1352
1477
|
|
|
1353
1478
|
def set_parameter(
|
|
1354
1479
|
self, name: str, value, caller_info: str = '(CLI)',
|
|
@@ -1723,7 +1848,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1723
1848
|
|
|
1724
1849
|
if not add_to_non_sources and \
|
|
1725
1850
|
file_ext in known_file_ext_dict.get('inferred_top', []):
|
|
1726
|
-
self.
|
|
1851
|
+
self.top_details['last_added_source_file'] = file_abspath
|
|
1727
1852
|
|
|
1728
1853
|
if add_to_non_sources:
|
|
1729
1854
|
self.files_non_source.append(file_abspath)
|
|
@@ -1928,9 +2053,10 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1928
2053
|
self.args['top'], top_path = last_potential_top_target
|
|
1929
2054
|
util.info("--top not specified, inferred from target:",
|
|
1930
2055
|
f"{self.args['top']} ({top_path})")
|
|
2056
|
+
self.top_details['inferred_from_target_name'] = top_path
|
|
1931
2057
|
|
|
1932
2058
|
else:
|
|
1933
|
-
best_top_fname = self.
|
|
2059
|
+
best_top_fname = self.top_details['last_added_source_file']
|
|
1934
2060
|
if best_top_fname:
|
|
1935
2061
|
last_potential_top_file = (self.get_top_name(best_top_fname), best_top_fname)
|
|
1936
2062
|
|
|
@@ -1940,7 +2066,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1940
2066
|
top_path = last_potential_top_file[1] # from tuple: (top, fpath)
|
|
1941
2067
|
self.args['top'] = util.get_inferred_top_module_name(
|
|
1942
2068
|
module_guess=last_potential_top_file[0],
|
|
1943
|
-
module_fpath=
|
|
2069
|
+
module_fpath=top_path
|
|
1944
2070
|
)
|
|
1945
2071
|
if self.args['top']:
|
|
1946
2072
|
util.info("--top not specified, inferred from final source file:",
|
|
@@ -1949,6 +2075,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1949
2075
|
# (not from DEPS.yml) we need to override self.target if that was set. Otherwise
|
|
1950
2076
|
# it won't save to the correct work-dir:
|
|
1951
2077
|
self.target = self.args['top']
|
|
2078
|
+
self.top_details['inferred_from_last_added_source_file'] = top_path
|
|
1952
2079
|
|
|
1953
2080
|
|
|
1954
2081
|
util.info(f'{self.command_name}: top-most target name: {self.target}')
|
|
@@ -2170,7 +2297,10 @@ class CommandParallel(Command):
|
|
|
2170
2297
|
if w.proc:
|
|
2171
2298
|
util.info(f"need to KILL WORKER_{w.n}, probably needs manual cleanup, check 'ps'")
|
|
2172
2299
|
if w.pid:
|
|
2173
|
-
|
|
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))
|
|
2174
2304
|
util.stop_log()
|
|
2175
2305
|
subprocess.Popen(['stty', 'sane']).wait() # pylint: disable=consider-using-with
|
|
2176
2306
|
|
opencos/eda_config.py
CHANGED
|
@@ -10,11 +10,11 @@ Order of precedence for default value of eda arg: --config-yaml:
|
|
|
10
10
|
import copy
|
|
11
11
|
import os
|
|
12
12
|
import argparse
|
|
13
|
-
import shutil
|
|
14
13
|
|
|
15
14
|
import mergedeep
|
|
16
15
|
|
|
17
16
|
from opencos import util
|
|
17
|
+
from opencos.files import safe_shutil_which
|
|
18
18
|
from opencos.util import safe_emoji
|
|
19
19
|
from opencos.utils.markup_helpers import yaml_safe_load, yaml_safe_writer
|
|
20
20
|
|
|
@@ -24,12 +24,11 @@ class Defaults:
|
|
|
24
24
|
Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
|
|
25
25
|
'''
|
|
26
26
|
|
|
27
|
-
environ_override_config_yml =
|
|
28
|
-
home_override_config_yml =
|
|
29
|
-
os.environ.get('HOME', ''), '.opencos-eda', 'EDA_CONFIG.yml'
|
|
30
|
-
)
|
|
27
|
+
environ_override_config_yml = ''
|
|
28
|
+
home_override_config_yml = ''
|
|
31
29
|
opencos_config_yml = 'eda_config_defaults.yml'
|
|
32
30
|
config_yml = ''
|
|
31
|
+
config_yml_set_from = ''
|
|
33
32
|
|
|
34
33
|
supported_config_keys = set([
|
|
35
34
|
'DEFAULT_HANDLERS', 'DEFAULT_HANDLERS_HELP',
|
|
@@ -39,11 +38,13 @@ class Defaults:
|
|
|
39
38
|
'deps_markup_supported',
|
|
40
39
|
'deps_subprocess_shell',
|
|
41
40
|
'bare_plusarg_supported',
|
|
41
|
+
'deps_expandvars_enable',
|
|
42
42
|
'dep_sub',
|
|
43
43
|
'vars',
|
|
44
44
|
'file_extensions',
|
|
45
45
|
'command_determines_tool',
|
|
46
46
|
'command_tool_is_optional',
|
|
47
|
+
'command_has_subcommands',
|
|
47
48
|
'tools',
|
|
48
49
|
'auto_tools_order',
|
|
49
50
|
])
|
|
@@ -74,12 +75,36 @@ class Defaults:
|
|
|
74
75
|
|
|
75
76
|
EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
Defaults
|
|
79
|
-
|
|
80
|
-
Defaults.
|
|
81
|
-
|
|
78
|
+
def set_defaults() -> None:
|
|
79
|
+
'''Updates Defaults *config_yml members, sets Defaults.config_yml'''
|
|
80
|
+
|
|
81
|
+
Defaults.environ_override_config_yml = os.environ.get(
|
|
82
|
+
'EDA_CONFIG_YML', os.environ.get('EDA_CONFIG_YAML', '')
|
|
83
|
+
)
|
|
84
|
+
if Defaults.environ_override_config_yml and \
|
|
85
|
+
os.path.isfile(Defaults.environ_override_config_yml):
|
|
86
|
+
Defaults.config_yml = Defaults.environ_override_config_yml
|
|
87
|
+
Defaults.config_yml_set_from = 'env EDA_CONFIG_YML'
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
home = os.environ.get('HOME', os.environ.get('HOMEPATH', ''))
|
|
91
|
+
if home and os.path.isdir(os.path.join(home, '.opencos-eda')):
|
|
92
|
+
Defaults.home_override_config_yml = [
|
|
93
|
+
os.path.join(home, '.opencos-eda', 'EDA_CONFIG.yml'),
|
|
94
|
+
os.path.join(home, '.opencos-eda', 'EDA_CONFIG.yaml')
|
|
95
|
+
]
|
|
96
|
+
for x in Defaults.home_override_config_yml:
|
|
97
|
+
if os.path.isfile(x):
|
|
98
|
+
Defaults.config_yml = x
|
|
99
|
+
Defaults.config_yml_set_from = 'file [$HOME|$HOMEPATH]/.opencos-eda'
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# else default:
|
|
82
103
|
Defaults.config_yml = Defaults.opencos_config_yml
|
|
104
|
+
Defaults.config_yml_set_from = ''
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
set_defaults()
|
|
83
108
|
|
|
84
109
|
|
|
85
110
|
def find_eda_config_yml_fpath(
|
|
@@ -232,12 +257,21 @@ def get_config_merged_with_defaults(config:dict) -> dict:
|
|
|
232
257
|
|
|
233
258
|
def get_argparser() -> argparse.ArgumentParser:
|
|
234
259
|
'''Returns an ArgumentParser, handles --config-yml=<filename> arg'''
|
|
260
|
+
|
|
261
|
+
# re-run set_defaults() in case a --env-file overwrote Defaults.config_yml
|
|
262
|
+
set_defaults()
|
|
263
|
+
|
|
235
264
|
parser = argparse.ArgumentParser(
|
|
236
265
|
prog=f'{safe_emoji("🔎 ")}opencos eda config options', add_help=False, allow_abbrev=False
|
|
237
266
|
)
|
|
238
|
-
parser.add_argument(
|
|
239
|
-
|
|
240
|
-
|
|
267
|
+
parser.add_argument(
|
|
268
|
+
'--config-yml', type=str, default=Defaults.config_yml,
|
|
269
|
+
help=(
|
|
270
|
+
f'YAML filename to use for configuration (default {Defaults.config_yml}).'
|
|
271
|
+
' Can be overriden using environmnet var EDA_CONFIG_YML=FILE, or from file'
|
|
272
|
+
' [$HOME|$HOMEPATH]/.opencos-eda/EDA_CONFIG.yml'
|
|
273
|
+
)
|
|
274
|
+
)
|
|
241
275
|
return parser
|
|
242
276
|
|
|
243
277
|
|
|
@@ -255,7 +289,6 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
|
|
|
255
289
|
|
|
256
290
|
This will merge the result with the default config (if overriden)
|
|
257
291
|
'''
|
|
258
|
-
|
|
259
292
|
parser = get_argparser()
|
|
260
293
|
try:
|
|
261
294
|
parsed, unparsed = parser.parse_known_args(args + [''])
|
|
@@ -267,7 +300,13 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
|
|
|
267
300
|
|
|
268
301
|
if parsed.config_yml:
|
|
269
302
|
if not quiet:
|
|
270
|
-
|
|
303
|
+
if parsed.config_yml != Defaults.config_yml:
|
|
304
|
+
# It was set on CLI:
|
|
305
|
+
util.info(f'eda_config: --config-yml={parsed.config_yml} observed')
|
|
306
|
+
elif Defaults.config_yml_set_from:
|
|
307
|
+
# It was picked up via env or HOME/.opencos-eda/ override:
|
|
308
|
+
util.info(f'eda_config: --config-yml={parsed.config_yml} observed, from',
|
|
309
|
+
f'{Defaults.config_yml_set_from}')
|
|
271
310
|
fullpath = find_eda_config_yml_fpath(parsed.config_yml)
|
|
272
311
|
config = get_config(fullpath)
|
|
273
312
|
if not quiet:
|
|
@@ -390,11 +429,18 @@ def tool_try_add_to_path( # pylint: disable=too-many-branches
|
|
|
390
429
|
util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
|
|
391
430
|
return name
|
|
392
431
|
|
|
393
|
-
user_exe =
|
|
432
|
+
user_exe = safe_shutil_which(user_exe)
|
|
394
433
|
|
|
395
434
|
if update_config:
|
|
396
435
|
if isinstance(config_exe, list):
|
|
397
436
|
config['auto_tools_order'][0][name]['exe'][0] = user_exe
|
|
437
|
+
for index,value in enumerate(config_exe[1:]):
|
|
438
|
+
# update all entries, if we can, if the value is also in 'path'
|
|
439
|
+
# from our set --tool=Name=path/exe
|
|
440
|
+
new_value = os.path.join(path, os.path.split(value)[1])
|
|
441
|
+
if os.path.exists(new_value) and safe_shutil_which(new_value) and \
|
|
442
|
+
os.access(new_value, os.X_OK):
|
|
443
|
+
config['auto_tools_order'][0][name]['exe'][index] = new_value
|
|
398
444
|
else:
|
|
399
445
|
config['auto_tools_order'][0][name]['exe'] = user_exe
|
|
400
446
|
util.debug(f'For {tool=}, auto_tools_order config updated')
|
opencos/eda_config_defaults.yml
CHANGED
|
@@ -12,7 +12,6 @@ DEFAULT_HANDLERS:
|
|
|
12
12
|
synth : opencos.commands.CommandSynth
|
|
13
13
|
proj : opencos.commands.CommandProj
|
|
14
14
|
build : opencos.commands.CommandBuild
|
|
15
|
-
upload : opencos.commands.CommandUpload
|
|
16
15
|
open : opencos.commands.CommandOpen
|
|
17
16
|
lec : opencos.commands.CommandLec
|
|
18
17
|
# These commands don't necessarily require a tool
|
|
@@ -22,6 +21,7 @@ DEFAULT_HANDLERS:
|
|
|
22
21
|
flist : opencos.commands.CommandFList
|
|
23
22
|
# These commands (waves, export, targets) do not require a tool, or
|
|
24
23
|
# will self determine the tool. See command_tool_is_optional in this config file.
|
|
24
|
+
upload : opencos.commands.CommandUpload
|
|
25
25
|
waves : opencos.commands.CommandWaves
|
|
26
26
|
export : opencos.commands.CommandExport
|
|
27
27
|
shell : opencos.commands.CommandShell
|
|
@@ -73,6 +73,7 @@ dep_tags_enables:
|
|
|
73
73
|
deps_markup_supported: true # Support DEPS.yml files (also .yaml, .toml, .json)
|
|
74
74
|
deps_subprocess_shell: true # Support subprocess commands using shell=True
|
|
75
75
|
bare_plusarg_supported: true # Support for CLI, DEPS args or deps: +<string> that isn't +define or +incdirs
|
|
76
|
+
deps_expandvars_enable : true # Support env $VAR replacement on targets or files in DEPS.yml
|
|
76
77
|
|
|
77
78
|
dep_sub: [ ] # Legacy, no longer supported.
|
|
78
79
|
vars: { } # Legacy, no longer supported.
|
|
@@ -110,6 +111,7 @@ file_extensions:
|
|
|
110
111
|
command_determines_tool:
|
|
111
112
|
# eda commands that will self-determine the tool to use
|
|
112
113
|
- waves
|
|
114
|
+
- upload
|
|
113
115
|
|
|
114
116
|
command_tool_is_optional:
|
|
115
117
|
# eda commands that may not need to use a tool at all, will skip auto_tools_order if --tool=None (default)
|
|
@@ -118,6 +120,12 @@ command_tool_is_optional:
|
|
|
118
120
|
- targets
|
|
119
121
|
- deps-help
|
|
120
122
|
|
|
123
|
+
command_has_subcommands:
|
|
124
|
+
- multi
|
|
125
|
+
- tools-multi
|
|
126
|
+
- sweep
|
|
127
|
+
|
|
128
|
+
|
|
121
129
|
|
|
122
130
|
tools:
|
|
123
131
|
|
|
@@ -440,17 +448,20 @@ auto_tools_order:
|
|
|
440
448
|
exe: code
|
|
441
449
|
requires_vscode_extension:
|
|
442
450
|
- lramseyer.vaporview
|
|
443
|
-
handlers:
|
|
451
|
+
handlers:
|
|
452
|
+
waves: opencos.commands.waves.CommandWaves
|
|
444
453
|
|
|
445
454
|
surfer:
|
|
446
455
|
exe: code
|
|
447
456
|
requires_vscode_extension:
|
|
448
457
|
- surfer-project.surfer
|
|
449
|
-
handlers:
|
|
458
|
+
handlers:
|
|
459
|
+
waves: opencos.commands.waves.CommandWaves
|
|
450
460
|
|
|
451
461
|
gtkwave:
|
|
452
462
|
exe: gtkwave
|
|
453
|
-
handlers:
|
|
463
|
+
handlers:
|
|
464
|
+
waves: opencos.commands.waves.CommandWaves
|
|
454
465
|
|
|
455
466
|
quartus:
|
|
456
467
|
exe: quartus_sh
|
|
@@ -474,6 +485,7 @@ auto_tools_order:
|
|
|
474
485
|
open: opencos.tools.vivado.CommandOpenVivado
|
|
475
486
|
flist: opencos.tools.vivado.CommandFListVivado
|
|
476
487
|
build: opencos.tools.vivado.CommandBuildVivado
|
|
488
|
+
waves: opencos.commands.waves.CommandWaves
|
|
477
489
|
|
|
478
490
|
slang_yosys:
|
|
479
491
|
exe: yosys
|
|
@@ -517,6 +529,7 @@ auto_tools_order:
|
|
|
517
529
|
elab: opencos.tools.questa.CommandElabQuesta
|
|
518
530
|
sim: opencos.tools.questa.CommandSimQuesta
|
|
519
531
|
flist: opencos.tools.questa.CommandFListQuesta
|
|
532
|
+
waves: opencos.commands.waves.CommandWaves
|
|
520
533
|
|
|
521
534
|
riviera:
|
|
522
535
|
exe: vsim
|
|
@@ -527,6 +540,7 @@ auto_tools_order:
|
|
|
527
540
|
lint: opencos.tools.riviera.CommandLintRiviera
|
|
528
541
|
elab: opencos.tools.riviera.CommandElabRiviera
|
|
529
542
|
sim: opencos.tools.riviera.CommandSimRiviera
|
|
543
|
+
waves: opencos.commands.waves.CommandWaves
|
|
530
544
|
|
|
531
545
|
questa_fe:
|
|
532
546
|
exe: vsim
|
|
@@ -536,6 +550,7 @@ auto_tools_order:
|
|
|
536
550
|
elab: opencos.tools.questa_fe.CommandElabQuestaFe
|
|
537
551
|
sim: opencos.tools.questa_fe.CommandSimQuestaFe
|
|
538
552
|
flist: opencos.tools.questa_fe.CommandFListQuestaFe
|
|
553
|
+
waves: opencos.commands.waves.CommandWaves
|
|
539
554
|
|
|
540
555
|
questa_fse: # free student edition, works similar to modelsim_ase
|
|
541
556
|
exe: vsim
|
|
@@ -545,6 +560,7 @@ auto_tools_order:
|
|
|
545
560
|
elab: opencos.tools.questa_fse.CommandElabQuestaFse
|
|
546
561
|
sim: opencos.tools.questa_fse.CommandSimQuestaFse
|
|
547
562
|
flist: opencos.tools.questa_fse.CommandFListQuestaFse
|
|
563
|
+
waves: opencos.commands.waves.CommandWaves
|
|
548
564
|
|
|
549
565
|
modelsim_ase:
|
|
550
566
|
exe: vsim
|
|
@@ -553,6 +569,7 @@ auto_tools_order:
|
|
|
553
569
|
lint: opencos.tools.modelsim_ase.CommandLintModelsimAse
|
|
554
570
|
elab: opencos.tools.modelsim_ase.CommandElabModelsimAse
|
|
555
571
|
sim: opencos.tools.modelsim_ase.CommandSimModelsimAse
|
|
572
|
+
waves: opencos.commands.waves.CommandWaves
|
|
556
573
|
|
|
557
574
|
iverilog:
|
|
558
575
|
exe: iverilog
|