opencos-eda 0.3.10__py3-none-any.whl → 0.3.12__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 +4 -4
- opencos/commands/sim.py +14 -15
- opencos/commands/sweep.py +1 -1
- opencos/commands/synth.py +1 -2
- opencos/commands/upload.py +192 -4
- opencos/commands/waves.py +52 -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 +358 -155
- opencos/eda_base.py +187 -60
- opencos/eda_config.py +70 -35
- opencos/eda_config_defaults.yml +310 -186
- opencos/eda_config_reduced.yml +19 -39
- opencos/eda_tool_helper.py +190 -21
- opencos/files.py +26 -1
- opencos/tools/cocotb.py +11 -23
- opencos/tools/invio.py +2 -2
- opencos/tools/invio_yosys.py +2 -1
- opencos/tools/iverilog.py +3 -3
- opencos/tools/modelsim_ase.py +1 -1
- opencos/tools/quartus.py +172 -137
- opencos/tools/questa_common.py +50 -9
- opencos/tools/riviera.py +5 -4
- opencos/tools/slang.py +14 -10
- opencos/tools/slang_yosys.py +1 -0
- opencos/tools/surelog.py +7 -6
- opencos/tools/verilator.py +9 -7
- opencos/tools/vivado.py +315 -180
- opencos/tools/yosys.py +5 -5
- opencos/util.py +6 -3
- opencos/utils/dict_helpers.py +31 -0
- opencos/utils/markup_helpers.py +2 -2
- opencos/utils/str_helpers.py +38 -0
- 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.12.dist-info}/METADATA +1 -1
- opencos_eda-0.3.12.dist-info/RECORD +93 -0
- opencos/eda_config_max_verilator_waivers.yml +0 -39
- opencos_eda-0.3.10.dist-info/RECORD +0 -81
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.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
|
|
|
@@ -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()
|
|
@@ -210,11 +211,11 @@ class Tool:
|
|
|
210
211
|
|
|
211
212
|
def set_exe(self, config: dict) -> None:
|
|
212
213
|
'''Sets self._EXE based on config'''
|
|
213
|
-
if self._TOOL and self._TOOL in config.get('
|
|
214
|
+
if self._TOOL and self._TOOL in config.get('auto_tools_found', {}):
|
|
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
|
|
@@ -230,7 +231,11 @@ class Tool:
|
|
|
230
231
|
return str(self._TOOL) + ':' + str(self._VERSION)
|
|
231
232
|
|
|
232
233
|
def get_versions(self) -> str:
|
|
233
|
-
'''Sets and returns self._VERSION
|
|
234
|
+
'''Sets and returns self._VERSION. Note that this is overriden by nearly all
|
|
235
|
+
|
|
236
|
+
child Tool classes, and should limit info/warning messaging since it is part
|
|
237
|
+
of opencos.eda.init_config(..)
|
|
238
|
+
'''
|
|
234
239
|
return self._VERSION
|
|
235
240
|
|
|
236
241
|
def report_tool_warn_error_counts(self) -> None:
|
|
@@ -240,14 +245,18 @@ class Tool:
|
|
|
240
245
|
return
|
|
241
246
|
|
|
242
247
|
info_color = Colors.green
|
|
248
|
+
error_color = Colors.bgreen
|
|
243
249
|
start = ''
|
|
244
|
-
if self.
|
|
245
|
-
|
|
250
|
+
if self.tool_warning_count:
|
|
251
|
+
info_color = Colors.yellow
|
|
252
|
+
if self.tool_error_count:
|
|
246
253
|
info_color = Colors.yellow
|
|
254
|
+
error_color = Colors.bred
|
|
255
|
+
start = safe_emoji('🔶 ')
|
|
247
256
|
util.info(
|
|
248
|
-
f"{start}Tool - {tool_name}, total counts:",
|
|
257
|
+
f"{start}Tool - {Colors.bold}{tool_name}{Colors.normal}{info_color}, total counts:",
|
|
249
258
|
f"{Colors.bold}{self.tool_warning_count} tool warnings{Colors.normal}{info_color},",
|
|
250
|
-
f"{
|
|
259
|
+
f"{error_color}{self.tool_error_count} tool errors",
|
|
251
260
|
color=info_color
|
|
252
261
|
)
|
|
253
262
|
|
|
@@ -272,6 +281,8 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
272
281
|
self.args_help = {}
|
|
273
282
|
if getattr(self, 'args_kwargs', None) is None:
|
|
274
283
|
self.args_kwargs = {}
|
|
284
|
+
if getattr(self, 'args_args', None) is None:
|
|
285
|
+
self.args_args = {} # support for multiple named --arg that map to same self.args key.
|
|
275
286
|
self.args.update({
|
|
276
287
|
"keep" : False,
|
|
277
288
|
"force" : False,
|
|
@@ -358,6 +369,7 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
358
369
|
self.errors_log_f = None
|
|
359
370
|
self.auto_tool_applied = False
|
|
360
371
|
self.tool_changed_respawn = {}
|
|
372
|
+
self.top_details = {}
|
|
361
373
|
|
|
362
374
|
|
|
363
375
|
def get_info_job_name(self) -> str:
|
|
@@ -558,8 +570,23 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
558
570
|
# Set the util.artifacts path with our work-dir:
|
|
559
571
|
util.artifacts.set_artifacts_json_dir(self.args['work-dir'])
|
|
560
572
|
|
|
573
|
+
# Since work-dir has now safely been created (or error flagged) we can override the
|
|
574
|
+
# inferred 'top' if top was not specified (aka, we guessed based on target name or
|
|
575
|
+
# file/module name)
|
|
576
|
+
self.update_top_if_inferred()
|
|
577
|
+
|
|
561
578
|
return self.args['work-dir']
|
|
562
579
|
|
|
580
|
+
|
|
581
|
+
def update_top_if_inferred(self) -> None:
|
|
582
|
+
'''Child classes can override (CommandDesign does)
|
|
583
|
+
|
|
584
|
+
Idea is to overwrite self.args['top'] if it was inferred, but Command.args
|
|
585
|
+
does not initially set self.args['top'], so this method takes no action.
|
|
586
|
+
'''
|
|
587
|
+
return
|
|
588
|
+
|
|
589
|
+
|
|
563
590
|
def artifacts_add(self, name: str, typ: str, description: str) -> None:
|
|
564
591
|
'''Adds a file to util.artifacts, derived classes may override'''
|
|
565
592
|
util.artifacts.add(name=name, typ=typ, description=description)
|
|
@@ -710,6 +737,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
710
737
|
arguments = [] # list supplied to parser.add_argument(..) so one liner supports both.
|
|
711
738
|
for this_key in keys:
|
|
712
739
|
arguments.append(f'--{this_key}')
|
|
740
|
+
for arg in self.args_args.get(this_key, []):
|
|
741
|
+
if arg not in arguments:
|
|
742
|
+
arguments.append(f'--{arg}')
|
|
713
743
|
|
|
714
744
|
if self.args_help.get(key, ''):
|
|
715
745
|
help_kwargs = {'help': self.args_help[key] + f' (default={value})'}
|
|
@@ -838,6 +868,9 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
838
868
|
'''
|
|
839
869
|
|
|
840
870
|
_, unparsed = self.run_argparser_on_list(tokens)
|
|
871
|
+
|
|
872
|
+
self._apply_deps_file_defaults_if_present()
|
|
873
|
+
|
|
841
874
|
if process_all and unparsed:
|
|
842
875
|
self.warning_show_known_args()
|
|
843
876
|
self.error_ifarg(
|
|
@@ -849,23 +882,20 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
849
882
|
|
|
850
883
|
return unparsed
|
|
851
884
|
|
|
852
|
-
def
|
|
853
|
-
self,
|
|
885
|
+
def get_sub_command_from_config(
|
|
886
|
+
self, error_if_no_command: bool = True,
|
|
854
887
|
) -> str:
|
|
855
|
-
'''
|
|
888
|
+
'''If this is a command with a sub-command (multi, sweep), then opencos.eda would
|
|
856
889
|
|
|
857
|
-
|
|
890
|
+
have already parsed in an place it in config['sub_command']. Get this value and return
|
|
891
|
+
it.
|
|
858
892
|
'''
|
|
859
|
-
ret = ''
|
|
860
|
-
for value in tokens:
|
|
861
|
-
if value in self.config['command_handler'].keys():
|
|
862
|
-
ret = value
|
|
863
|
-
tokens.remove(value)
|
|
864
|
-
break
|
|
893
|
+
ret = self.config.get('sub_command', '')
|
|
865
894
|
|
|
866
895
|
if not ret and error_if_no_command:
|
|
896
|
+
util.debug(f'get_sub_command_from_config: see: {self.config=}')
|
|
867
897
|
self.error(f"Looking for a valid eda {self.command_name} COMMAND",
|
|
868
|
-
|
|
898
|
+
"but didn't find one in config",
|
|
869
899
|
error_code=status_constants.EDA_COMMAND_OR_ARGS_ERROR)
|
|
870
900
|
return ret
|
|
871
901
|
|
|
@@ -983,37 +1013,39 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
983
1013
|
color.disable() # strip our color object if < 3.14
|
|
984
1014
|
|
|
985
1015
|
|
|
1016
|
+
# Include -G, +incdir+, +define+ help if this is a CommanDesign class:
|
|
986
1017
|
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
|
-
|
|
1018
|
+
if isinstance(self, CommandDesign):
|
|
1019
|
+
lines.append(
|
|
1020
|
+
f" {color.cyan}-G{color.byellow}<parameterName>{color.normal}=" \
|
|
1021
|
+
+ f"{color.yellow}<value>{color.normal}"
|
|
1022
|
+
)
|
|
1023
|
+
lines.append(indent_me((
|
|
1024
|
+
" Add parameter to top level, support bit/int/string types only."
|
|
1025
|
+
" Example: -GDEPTH=8 (DEPTH treated as SV int/integer)."
|
|
1026
|
+
" -GENABLE=1 (ENABLED treated as SV bit/int/integer)."
|
|
1027
|
+
" -GName=eda (Name treated as SV string \"eda\")."
|
|
1028
|
+
)))
|
|
1029
|
+
|
|
1030
|
+
lines.append(f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}")
|
|
1031
|
+
lines.append(indent_me((
|
|
1032
|
+
" Add define w/out value to tool ahead of SV sources"
|
|
1033
|
+
" Example: +define+SIM_SPEEDUP"
|
|
1034
|
+
)))
|
|
1035
|
+
lines.append(
|
|
1036
|
+
f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}=" \
|
|
1037
|
+
+ f"{color.yellow}<value>{color.normal}")
|
|
1038
|
+
lines.append(indent_me((
|
|
1039
|
+
" Add define w/ value to tool ahead of SV sources"
|
|
1040
|
+
" Example: +define+TECH_LIB=2 +define+FULL_NAME=\"E D A\""
|
|
1041
|
+
)))
|
|
1042
|
+
lines.append(f" {color.cyan}+incdir+{color.byellow}PATH{color.normal}")
|
|
1043
|
+
lines.append(indent_me((
|
|
1044
|
+
" Add path (absolute or relative) for include directories"
|
|
1045
|
+
" for SystemVerilog `include \"<some-file>.svh\""
|
|
1046
|
+
" Example: +incdir+../lib"
|
|
1047
|
+
)))
|
|
1048
|
+
lines.append('')
|
|
1017
1049
|
|
|
1018
1050
|
for line in lines:
|
|
1019
1051
|
print(line)
|
|
@@ -1082,6 +1114,13 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1082
1114
|
else:
|
|
1083
1115
|
util.warning(*msg)
|
|
1084
1116
|
|
|
1117
|
+
@staticmethod
|
|
1118
|
+
def get_top_name(name: str) -> str:
|
|
1119
|
+
'''Attempt to get the 'top' module name from a file, such as path/to/mine.sv will
|
|
1120
|
+
|
|
1121
|
+
return "mine"'''
|
|
1122
|
+
return os.path.splitext(os.path.basename(name))[0]
|
|
1123
|
+
|
|
1085
1124
|
|
|
1086
1125
|
def update_tool_warn_err_counts_from_log_lines(
|
|
1087
1126
|
self, log_lines: list, bad_strings: list, warning_strings: list
|
|
@@ -1103,6 +1142,48 @@ class Command: # pylint: disable=too-many-public-methods,too-many-instance-attri
|
|
|
1103
1142
|
setattr(self, 'tool_warning_count', getattr(self, 'tool_warning_count', 0) + 1)
|
|
1104
1143
|
|
|
1105
1144
|
|
|
1145
|
+
def _apply_deps_file_defaults_if_present(self) -> None:
|
|
1146
|
+
'''Only runs if this is not a CommandDesign class.
|
|
1147
|
+
|
|
1148
|
+
Looks at DEPS file in current directory, and will attempt to apply 'DEFAULTS' if present
|
|
1149
|
+
'''
|
|
1150
|
+
|
|
1151
|
+
if isinstance(self, CommandDesign):
|
|
1152
|
+
return
|
|
1153
|
+
|
|
1154
|
+
# Try to fish out DEFAULTS from ./DEPS if present, so we can apply args to self.
|
|
1155
|
+
_cache_none = {}
|
|
1156
|
+
data = None
|
|
1157
|
+
if self.config['deps_markup_supported']:
|
|
1158
|
+
deps = DepsFile(
|
|
1159
|
+
command_design_ref=self, target_path=str(Path('.')), cache=_cache_none
|
|
1160
|
+
)
|
|
1161
|
+
data = deps.data
|
|
1162
|
+
|
|
1163
|
+
entry = None
|
|
1164
|
+
if data is not None and 'DEFAULTS' in data:
|
|
1165
|
+
entry = deps.get_entry(target_node=str(Path('./DEFAULTS')))
|
|
1166
|
+
if not entry:
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
target = str(Path('./DEFAULTS'))
|
|
1170
|
+
_, target_node = os.path.split(target)
|
|
1171
|
+
caller_info = deps.gen_caller_info(target)
|
|
1172
|
+
|
|
1173
|
+
deps_processor = DepsProcessor(
|
|
1174
|
+
command_design_ref=self,
|
|
1175
|
+
deps_entry=entry,
|
|
1176
|
+
target=target,
|
|
1177
|
+
target_path=str(Path('.')),
|
|
1178
|
+
target_node=target_node,
|
|
1179
|
+
deps_file = deps.deps_file,
|
|
1180
|
+
caller_info = caller_info
|
|
1181
|
+
)
|
|
1182
|
+
_ = deps_processor.process_deps_entry()
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
|
|
1106
1187
|
class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
1107
1188
|
'''CommandDesign is the eda base class for command handlers that need to track files.
|
|
1108
1189
|
|
|
@@ -1161,11 +1242,22 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1161
1242
|
# keys 'data' and 'line_numbers'
|
|
1162
1243
|
self.cached_deps = {}
|
|
1163
1244
|
self.targets_dict = {} # key = targets that we've already processed in DEPS files
|
|
1164
|
-
self.last_added_source_file_inferred_top = ''
|
|
1165
1245
|
|
|
1166
1246
|
self.has_pre_compile_dep_shell_commands = False
|
|
1167
1247
|
self.has_post_tool_dep_shell_commands = False
|
|
1168
1248
|
|
|
1249
|
+
self.top_details = {
|
|
1250
|
+
# used by CommandDesign to track last file added.
|
|
1251
|
+
'last_added_source_file': '',
|
|
1252
|
+
# implies top was not set, we inferred from the final command line
|
|
1253
|
+
# DEPS target name
|
|
1254
|
+
'inferred_from_target_name': False,
|
|
1255
|
+
# implies top was not set, we inferred from the final source file added,
|
|
1256
|
+
# based on a 'module' name we parsed.
|
|
1257
|
+
# also implies self.target was set from inferred top:
|
|
1258
|
+
'inferred_from_last_added_source_file': False,
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1169
1261
|
|
|
1170
1262
|
def run_dep_commands(self) -> None:
|
|
1171
1263
|
'''Run shell/peakrdl style commands from DEPS files, this is peformed before
|
|
@@ -1346,13 +1438,43 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1346
1438
|
else:
|
|
1347
1439
|
os.symlink(src=fname, dst=destfile)
|
|
1348
1440
|
|
|
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
1441
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1442
|
+
def update_top_if_inferred(self) -> None:
|
|
1443
|
+
'''Overridden from Command, uses self.top_details - attempts to overwrite
|
|
1444
|
+
|
|
1445
|
+
self.args['top'] if it was previously set based on the first DEPS target name,
|
|
1446
|
+
so it more accurately reflects the top-module-name. You should only do this if the
|
|
1447
|
+
self.args['work-dir'] has already been determined.
|
|
1448
|
+
'''
|
|
1449
|
+
|
|
1450
|
+
if not self.args['top']:
|
|
1451
|
+
return
|
|
1452
|
+
|
|
1453
|
+
if not (self.top_details and \
|
|
1454
|
+
self.top_details['inferred_from_target_name'] and \
|
|
1455
|
+
self.top_details['last_added_source_file']):
|
|
1456
|
+
return
|
|
1457
|
+
|
|
1458
|
+
# since work-dir should be set, we will change self.args['top'] if there
|
|
1459
|
+
# was a last-added source file.
|
|
1460
|
+
best_top_fname = self.top_details['last_added_source_file']
|
|
1461
|
+
|
|
1462
|
+
if not best_top_fname:
|
|
1463
|
+
return
|
|
1464
|
+
|
|
1465
|
+
best_top_name = self.get_top_name(best_top_fname)
|
|
1466
|
+
best_top = util.get_inferred_top_module_name(
|
|
1467
|
+
module_guess=best_top_name,
|
|
1468
|
+
module_fpath=best_top_fname
|
|
1469
|
+
)
|
|
1470
|
+
if not best_top:
|
|
1471
|
+
return
|
|
1472
|
+
|
|
1473
|
+
util.info("--top was previously inferred from target name",
|
|
1474
|
+
f"({self.top_details['inferred_from_target_name']}), overriding with:",
|
|
1475
|
+
f"{best_top} (from file: {best_top_fname})")
|
|
1476
|
+
self.args['top'] = best_top
|
|
1477
|
+
|
|
1356
1478
|
|
|
1357
1479
|
def set_parameter(
|
|
1358
1480
|
self, name: str, value, caller_info: str = '(CLI)',
|
|
@@ -1727,7 +1849,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1727
1849
|
|
|
1728
1850
|
if not add_to_non_sources and \
|
|
1729
1851
|
file_ext in known_file_ext_dict.get('inferred_top', []):
|
|
1730
|
-
self.
|
|
1852
|
+
self.top_details['last_added_source_file'] = file_abspath
|
|
1731
1853
|
|
|
1732
1854
|
if add_to_non_sources:
|
|
1733
1855
|
self.files_non_source.append(file_abspath)
|
|
@@ -1932,9 +2054,10 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1932
2054
|
self.args['top'], top_path = last_potential_top_target
|
|
1933
2055
|
util.info("--top not specified, inferred from target:",
|
|
1934
2056
|
f"{self.args['top']} ({top_path})")
|
|
2057
|
+
self.top_details['inferred_from_target_name'] = top_path
|
|
1935
2058
|
|
|
1936
2059
|
else:
|
|
1937
|
-
best_top_fname = self.
|
|
2060
|
+
best_top_fname = self.top_details['last_added_source_file']
|
|
1938
2061
|
if best_top_fname:
|
|
1939
2062
|
last_potential_top_file = (self.get_top_name(best_top_fname), best_top_fname)
|
|
1940
2063
|
|
|
@@ -1944,7 +2067,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1944
2067
|
top_path = last_potential_top_file[1] # from tuple: (top, fpath)
|
|
1945
2068
|
self.args['top'] = util.get_inferred_top_module_name(
|
|
1946
2069
|
module_guess=last_potential_top_file[0],
|
|
1947
|
-
module_fpath=
|
|
2070
|
+
module_fpath=top_path
|
|
1948
2071
|
)
|
|
1949
2072
|
if self.args['top']:
|
|
1950
2073
|
util.info("--top not specified, inferred from final source file:",
|
|
@@ -1953,6 +2076,7 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
|
|
|
1953
2076
|
# (not from DEPS.yml) we need to override self.target if that was set. Otherwise
|
|
1954
2077
|
# it won't save to the correct work-dir:
|
|
1955
2078
|
self.target = self.args['top']
|
|
2079
|
+
self.top_details['inferred_from_last_added_source_file'] = top_path
|
|
1956
2080
|
|
|
1957
2081
|
|
|
1958
2082
|
util.info(f'{self.command_name}: top-most target name: {self.target}')
|
|
@@ -2174,7 +2298,10 @@ class CommandParallel(Command):
|
|
|
2174
2298
|
if w.proc:
|
|
2175
2299
|
util.info(f"need to KILL WORKER_{w.n}, probably needs manual cleanup, check 'ps'")
|
|
2176
2300
|
if w.pid:
|
|
2177
|
-
|
|
2301
|
+
# Windows compatible: signal.SIGKILL is not available, so we could use
|
|
2302
|
+
# os.kill(PID, 9) but we'll be nice and do another SIGTERM
|
|
2303
|
+
# os.kill(PID, 9)
|
|
2304
|
+
os.kill(w.pid, getattr(signal, 'SIGKILL', signal.SIGTERM))
|
|
2178
2305
|
util.stop_log()
|
|
2179
2306
|
subprocess.Popen(['stty', 'sane']).wait() # pylint: disable=consider-using-with
|
|
2180
2307
|
|
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
|
|
|
@@ -22,14 +22,17 @@ class Defaults:
|
|
|
22
22
|
'''Defaults is a global namespace for constants and supported features.
|
|
23
23
|
|
|
24
24
|
Defaults.config_yml is set depending on search order for default eda_config[_defaults].yml
|
|
25
|
+
|
|
26
|
+
Note that opencos.eda and other packages are free to set and add additonal keys to the
|
|
27
|
+
"config", but the "supported_config_keys" are only what's allowed in the initial
|
|
28
|
+
--config-yml=YAML_FILE
|
|
25
29
|
'''
|
|
26
30
|
|
|
27
|
-
environ_override_config_yml =
|
|
28
|
-
home_override_config_yml =
|
|
29
|
-
os.environ.get('HOME', ''), '.opencos-eda', 'EDA_CONFIG.yml'
|
|
30
|
-
)
|
|
31
|
+
environ_override_config_yml = ''
|
|
32
|
+
home_override_config_yml = ''
|
|
31
33
|
opencos_config_yml = 'eda_config_defaults.yml'
|
|
32
34
|
config_yml = ''
|
|
35
|
+
config_yml_set_from = ''
|
|
33
36
|
|
|
34
37
|
supported_config_keys = set([
|
|
35
38
|
'DEFAULT_HANDLERS', 'DEFAULT_HANDLERS_HELP',
|
|
@@ -39,21 +42,23 @@ class Defaults:
|
|
|
39
42
|
'deps_markup_supported',
|
|
40
43
|
'deps_subprocess_shell',
|
|
41
44
|
'bare_plusarg_supported',
|
|
45
|
+
'deps_expandvars_enable',
|
|
46
|
+
'show_tool_versions',
|
|
42
47
|
'dep_sub',
|
|
43
48
|
'vars',
|
|
44
49
|
'file_extensions',
|
|
45
50
|
'command_determines_tool',
|
|
46
51
|
'command_tool_is_optional',
|
|
52
|
+
'command_has_subcommands',
|
|
47
53
|
'tools',
|
|
48
54
|
'auto_tools_order',
|
|
49
55
|
])
|
|
50
|
-
|
|
56
|
+
supported_config_tool_keys = set([
|
|
51
57
|
'exe', 'handlers',
|
|
52
58
|
'requires_env', 'requires_py', 'requires_cmd', 'requires_in_exe_path',
|
|
53
|
-
'requires_vsim_helper',
|
|
59
|
+
'requires_vsim_helper',
|
|
60
|
+
'requires_vscode_extension',
|
|
54
61
|
'disable-tools-multi', 'disable-auto',
|
|
55
|
-
])
|
|
56
|
-
supported_config_tool_keys = set([
|
|
57
62
|
'defines',
|
|
58
63
|
'log-bad-strings',
|
|
59
64
|
'log-must-strings',
|
|
@@ -74,12 +79,36 @@ class Defaults:
|
|
|
74
79
|
|
|
75
80
|
EDA_OUTPUT_CONFIG_FNAME = 'eda_output_config.yml'
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
Defaults
|
|
79
|
-
|
|
80
|
-
Defaults.
|
|
81
|
-
|
|
82
|
+
def set_defaults() -> None:
|
|
83
|
+
'''Updates Defaults *config_yml members, sets Defaults.config_yml'''
|
|
84
|
+
|
|
85
|
+
Defaults.environ_override_config_yml = os.environ.get(
|
|
86
|
+
'EDA_CONFIG_YML', os.environ.get('EDA_CONFIG_YAML', '')
|
|
87
|
+
)
|
|
88
|
+
if Defaults.environ_override_config_yml and \
|
|
89
|
+
os.path.isfile(Defaults.environ_override_config_yml):
|
|
90
|
+
Defaults.config_yml = Defaults.environ_override_config_yml
|
|
91
|
+
Defaults.config_yml_set_from = 'env EDA_CONFIG_YML'
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
home = os.environ.get('HOME', os.environ.get('HOMEPATH', ''))
|
|
95
|
+
if home and os.path.isdir(os.path.join(home, '.opencos-eda')):
|
|
96
|
+
Defaults.home_override_config_yml = [
|
|
97
|
+
os.path.join(home, '.opencos-eda', 'EDA_CONFIG.yml'),
|
|
98
|
+
os.path.join(home, '.opencos-eda', 'EDA_CONFIG.yaml')
|
|
99
|
+
]
|
|
100
|
+
for x in Defaults.home_override_config_yml:
|
|
101
|
+
if os.path.isfile(x):
|
|
102
|
+
Defaults.config_yml = x
|
|
103
|
+
Defaults.config_yml_set_from = 'file [$HOME|$HOMEPATH]/.opencos-eda'
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# else default:
|
|
82
107
|
Defaults.config_yml = Defaults.opencos_config_yml
|
|
108
|
+
Defaults.config_yml_set_from = ''
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
set_defaults()
|
|
83
112
|
|
|
84
113
|
|
|
85
114
|
def find_eda_config_yml_fpath(
|
|
@@ -133,14 +162,6 @@ def check_config(config:dict, filename='') -> None:
|
|
|
133
162
|
util.error(f'eda_config.get_config({filename=}): has unsupported {key=}' \
|
|
134
163
|
+ f' {Defaults.supported_config_keys=}')
|
|
135
164
|
|
|
136
|
-
for row in config.get('auto_tools_order', []):
|
|
137
|
-
for tool, table in row.items():
|
|
138
|
-
for key in table:
|
|
139
|
-
if key not in Defaults.supported_config_auto_tools_order_keys:
|
|
140
|
-
util.error(f'eda_config.get_config({filename=}): has unsupported {key=}' \
|
|
141
|
-
+ f' in auto_tools_order, {tool=},' \
|
|
142
|
-
+ f' {Defaults.supported_config_auto_tools_order_keys=}')
|
|
143
|
-
|
|
144
165
|
for tool,table in config.get('tools', {}).items():
|
|
145
166
|
for key in table:
|
|
146
167
|
if key not in Defaults.supported_config_tool_keys:
|
|
@@ -232,12 +253,21 @@ def get_config_merged_with_defaults(config:dict) -> dict:
|
|
|
232
253
|
|
|
233
254
|
def get_argparser() -> argparse.ArgumentParser:
|
|
234
255
|
'''Returns an ArgumentParser, handles --config-yml=<filename> arg'''
|
|
256
|
+
|
|
257
|
+
# re-run set_defaults() in case a --env-file overwrote Defaults.config_yml
|
|
258
|
+
set_defaults()
|
|
259
|
+
|
|
235
260
|
parser = argparse.ArgumentParser(
|
|
236
261
|
prog=f'{safe_emoji("🔎 ")}opencos eda config options', add_help=False, allow_abbrev=False
|
|
237
262
|
)
|
|
238
|
-
parser.add_argument(
|
|
239
|
-
|
|
240
|
-
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
'--config-yml', type=str, default=Defaults.config_yml,
|
|
265
|
+
help=(
|
|
266
|
+
f'YAML filename to use for configuration (default {Defaults.config_yml}).'
|
|
267
|
+
' Can be overriden using environmnet var EDA_CONFIG_YML=FILE, or from file'
|
|
268
|
+
' [$HOME|$HOMEPATH]/.opencos-eda/EDA_CONFIG.yml'
|
|
269
|
+
)
|
|
270
|
+
)
|
|
241
271
|
return parser
|
|
242
272
|
|
|
243
273
|
|
|
@@ -255,7 +285,6 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
|
|
|
255
285
|
|
|
256
286
|
This will merge the result with the default config (if overriden)
|
|
257
287
|
'''
|
|
258
|
-
|
|
259
288
|
parser = get_argparser()
|
|
260
289
|
try:
|
|
261
290
|
parsed, unparsed = parser.parse_known_args(args + [''])
|
|
@@ -267,7 +296,13 @@ def get_eda_config(args:list, quiet=False) -> (dict, list):
|
|
|
267
296
|
|
|
268
297
|
if parsed.config_yml:
|
|
269
298
|
if not quiet:
|
|
270
|
-
|
|
299
|
+
if parsed.config_yml != Defaults.config_yml:
|
|
300
|
+
# It was set on CLI:
|
|
301
|
+
util.info(f'eda_config: --config-yml={parsed.config_yml} observed')
|
|
302
|
+
elif Defaults.config_yml_set_from:
|
|
303
|
+
# It was picked up via env or HOME/.opencos-eda/ override:
|
|
304
|
+
util.info(f'eda_config: --config-yml={parsed.config_yml} observed, from',
|
|
305
|
+
f'{Defaults.config_yml_set_from}')
|
|
271
306
|
fullpath = find_eda_config_yml_fpath(parsed.config_yml)
|
|
272
307
|
config = get_config(fullpath)
|
|
273
308
|
if not quiet:
|
|
@@ -354,10 +389,10 @@ def tool_try_add_to_path( # pylint: disable=too-many-branches
|
|
|
354
389
|
|
|
355
390
|
name, path_arg = name_path_parts[0:2]
|
|
356
391
|
|
|
357
|
-
if name not in config['
|
|
392
|
+
if name not in config['tools']:
|
|
358
393
|
return name
|
|
359
394
|
|
|
360
|
-
config_exe = config['
|
|
395
|
+
config_exe = config['tools'][name].get('exe', str())
|
|
361
396
|
if isinstance(config_exe, list):
|
|
362
397
|
orig_exe = config_exe[0]
|
|
363
398
|
else:
|
|
@@ -390,21 +425,21 @@ def tool_try_add_to_path( # pylint: disable=too-many-branches
|
|
|
390
425
|
util.error(f'--tool setting for {tool}: {user_exe} is not an executable')
|
|
391
426
|
return name
|
|
392
427
|
|
|
393
|
-
user_exe =
|
|
428
|
+
user_exe = safe_shutil_which(user_exe)
|
|
394
429
|
|
|
395
430
|
if update_config:
|
|
396
431
|
if isinstance(config_exe, list):
|
|
397
|
-
config['
|
|
432
|
+
config['tools'][name]['exe'][0] = user_exe
|
|
398
433
|
for index,value in enumerate(config_exe[1:]):
|
|
399
434
|
# update all entries, if we can, if the value is also in 'path'
|
|
400
435
|
# from our set --tool=Name=path/exe
|
|
401
436
|
new_value = os.path.join(path, os.path.split(value)[1])
|
|
402
|
-
if os.path.exists(new_value) and
|
|
437
|
+
if os.path.exists(new_value) and safe_shutil_which(new_value) and \
|
|
403
438
|
os.access(new_value, os.X_OK):
|
|
404
|
-
config['
|
|
439
|
+
config['tools'][name]['exe'][index] = new_value
|
|
405
440
|
else:
|
|
406
|
-
config['
|
|
407
|
-
util.debug(f'For {tool=},
|
|
441
|
+
config['tools'][name]['exe'] = user_exe
|
|
442
|
+
util.debug(f'For {tool=}, tools config entry updated')
|
|
408
443
|
|
|
409
444
|
util.debug(f'For {tool=}, final {user_exe=}')
|
|
410
445
|
|