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.
Files changed (74) hide show
  1. opencos/commands/deps_help.py +89 -120
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +3 -3
  4. opencos/commands/sim.py +14 -16
  5. opencos/commands/synth.py +1 -2
  6. opencos/commands/upload.py +192 -4
  7. opencos/commands/waves.py +8 -8
  8. opencos/deps/deps_commands.py +6 -6
  9. opencos/deps/deps_file.py +82 -79
  10. opencos/deps/deps_processor.py +129 -50
  11. opencos/docs/Architecture.md +45 -0
  12. opencos/docs/ConnectingApps.md +29 -0
  13. opencos/docs/DEPS.md +199 -0
  14. opencos/docs/Debug.md +77 -0
  15. opencos/docs/DirectoryStructure.md +22 -0
  16. opencos/docs/Installation.md +117 -0
  17. opencos/docs/OcVivadoTcl.md +63 -0
  18. opencos/docs/OpenQuestions.md +7 -0
  19. opencos/docs/README.md +13 -0
  20. opencos/docs/RtlCodingStyle.md +54 -0
  21. opencos/docs/eda.md +147 -0
  22. opencos/docs/oc_cli.md +135 -0
  23. opencos/eda.py +175 -41
  24. opencos/eda_base.py +180 -50
  25. opencos/eda_config.py +62 -16
  26. opencos/eda_config_defaults.yml +21 -4
  27. opencos/eda_deps_bash_completion.bash +37 -15
  28. opencos/files.py +26 -1
  29. opencos/tools/cocotb.py +5 -5
  30. opencos/tools/invio.py +2 -2
  31. opencos/tools/invio_yosys.py +2 -1
  32. opencos/tools/iverilog.py +3 -3
  33. opencos/tools/quartus.py +113 -115
  34. opencos/tools/questa_common.py +3 -4
  35. opencos/tools/riviera.py +3 -3
  36. opencos/tools/slang.py +11 -7
  37. opencos/tools/slang_yosys.py +1 -0
  38. opencos/tools/surelog.py +4 -3
  39. opencos/tools/verilator.py +5 -4
  40. opencos/tools/vivado.py +307 -176
  41. opencos/tools/yosys.py +4 -4
  42. opencos/util.py +6 -3
  43. opencos/utils/dict_helpers.py +31 -0
  44. opencos/utils/markup_helpers.py +2 -2
  45. opencos/utils/str_helpers.py +7 -0
  46. opencos/utils/subprocess_helpers.py +3 -3
  47. opencos/utils/vscode_helper.py +2 -2
  48. opencos/utils/vsim_helper.py +58 -22
  49. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
  50. opencos_eda-0.3.11.dist-info/RECORD +94 -0
  51. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +1 -0
  52. opencos/tests/__init__.py +0 -0
  53. opencos/tests/custom_config.yml +0 -13
  54. opencos/tests/deps_files/command_order/DEPS.yml +0 -44
  55. opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
  56. opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
  57. opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
  58. opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
  59. opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
  60. opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
  61. opencos/tests/helpers.py +0 -354
  62. opencos/tests/test_build.py +0 -12
  63. opencos/tests/test_deps_helpers.py +0 -207
  64. opencos/tests/test_deps_schema.py +0 -30
  65. opencos/tests/test_eda.py +0 -921
  66. opencos/tests/test_eda_elab.py +0 -110
  67. opencos/tests/test_eda_synth.py +0 -150
  68. opencos/tests/test_oc_cli.py +0 -25
  69. opencos/tests/test_tools.py +0 -404
  70. opencos_eda-0.3.9.dist-info/RECORD +0 -99
  71. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
  72. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
  73. {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
  74. {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 = shutil.which('eda')
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
- exe = config.get('auto_tools_order', [{}])[0][self._TOOL].get('exe', '')
212
- if exe and isinstance(exe, list):
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
- util.info(f'Override for {self._TOOL} using exe {exe}')
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.tool_error_count or self.tool_warning_count:
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"{Colors.bold}{self.tool_error_count} tool errors",
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
- lines.append(
984
- f" {color.cyan}-G{color.byellow}<parameterName>{color.normal}=" \
985
- + f"{color.yellow}<value>{color.normal}"
986
- )
987
- lines.append(indent_me((
988
- " Add parameter to top level, support bit/int/string types only."
989
- " Example: -GDEPTH=8 (DEPTH treated as SV int/integer)."
990
- " -GENABLE=1 (ENABLED treated as SV bit/int/integer)."
991
- " -GName=eda (Name treated as SV string \"eda\")."
992
- )))
993
-
994
- lines.append(f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}")
995
- lines.append(indent_me((
996
- " Add define w/out value to tool ahead of SV sources"
997
- " Example: +define+SIM_SPEEDUP"
998
- )))
999
- lines.append(
1000
- f" {color.cyan}+define+{color.byellow}<defineName>{color.normal}=" \
1001
- + f"{color.yellow}<value>{color.normal}")
1002
- lines.append(indent_me((
1003
- " Add define w/ value to tool ahead of SV sources"
1004
- " Example: +define+TECH_LIB=2 +define+FULL_NAME=\"E D A\""
1005
- )))
1006
- lines.append(f" {color.cyan}+incdir+{color.byellow}PATH{color.normal}")
1007
- lines.append(indent_me((
1008
- " Add path (absolute or relative) for include directories"
1009
- " for SystemVerilog `include \"<some-file>.svh\""
1010
- " Example: +incdir+../lib"
1011
- )))
1012
- lines.append('')
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
- return "mine"'''
1350
- # TODO(drew): Use the helper method in util for this instead to peek in file contents?
1351
- return os.path.splitext(os.path.basename(name))[0]
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.last_added_source_file_inferred_top = file_abspath
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.last_added_source_file_inferred_top
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=last_potential_top_file[1]
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
- os.kill(w.pid, signal.SIGKILL)
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 = os.environ.get('EDA_CONFIG_YML', '')
28
- home_override_config_yml = os.path.join(
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
- if os.path.isfile(Defaults.environ_override_config_yml):
78
- Defaults.config_yml = Defaults.environ_override_config_yml
79
- elif os.path.isfile(Defaults.home_override_config_yml):
80
- Defaults.config_yml = Defaults.home_override_config_yml
81
- else:
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('--config-yml', type=str, default=Defaults.config_yml,
239
- help=('YAML filename to use for configuration (default'
240
- f' {Defaults.config_yml})'))
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
- util.info(f'eda_config: --config-yml={parsed.config_yml} observed')
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 = shutil.which(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')
@@ -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