opencos-eda 0.2.52__py3-none-any.whl → 0.2.54__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 (44) hide show
  1. opencos/commands/__init__.py +2 -0
  2. opencos/commands/build.py +1 -1
  3. opencos/commands/deps_help.py +259 -0
  4. opencos/commands/export.py +1 -1
  5. opencos/commands/flist.py +4 -1
  6. opencos/commands/lec.py +1 -1
  7. opencos/commands/open.py +2 -0
  8. opencos/commands/proj.py +1 -1
  9. opencos/commands/shell.py +1 -1
  10. opencos/commands/sim.py +76 -8
  11. opencos/commands/synth.py +1 -1
  12. opencos/commands/upload.py +3 -0
  13. opencos/commands/waves.py +1 -0
  14. opencos/deps/defaults.py +1 -0
  15. opencos/deps/deps_file.py +30 -4
  16. opencos/deps/deps_processor.py +72 -2
  17. opencos/deps_schema.py +3 -0
  18. opencos/eda.py +50 -26
  19. opencos/eda_base.py +177 -33
  20. opencos/eda_config.py +1 -1
  21. opencos/eda_config_defaults.yml +49 -3
  22. opencos/eda_extract_targets.py +1 -58
  23. opencos/tests/helpers.py +16 -0
  24. opencos/tests/test_eda.py +14 -3
  25. opencos/tests/test_tools.py +159 -132
  26. opencos/tools/cocotb.py +15 -14
  27. opencos/tools/iverilog.py +4 -24
  28. opencos/tools/modelsim_ase.py +70 -57
  29. opencos/tools/quartus.py +680 -0
  30. opencos/tools/questa.py +158 -90
  31. opencos/tools/questa_fse.py +10 -0
  32. opencos/tools/riviera.py +1 -0
  33. opencos/tools/verilator.py +9 -15
  34. opencos/tools/vivado.py +30 -23
  35. opencos/util.py +89 -15
  36. opencos/utils/status_constants.py +1 -0
  37. opencos/utils/str_helpers.py +85 -0
  38. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/METADATA +1 -1
  39. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/RECORD +44 -42
  40. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/WHEEL +0 -0
  41. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/entry_points.txt +0 -0
  42. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE +0 -0
  43. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE.spdx +0 -0
  44. {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/top_level.txt +0 -0
@@ -103,6 +103,37 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
103
103
  self.command_design_ref.incdirs.append(abspath)
104
104
  debug(f'Added include dir {abspath} from {self.caller_info}')
105
105
 
106
+
107
+ def _apply_args_check_tools(self, tokens: list) -> list:
108
+ '''Helper for apply_args(list), returns list strips --tool args under certain conditions'''
109
+
110
+ _orig_tool = self.args.get('tool', '')
111
+ if not self.command_design_ref.auto_tool_applied and \
112
+ any(x.startswith('--tool') for x in tokens) and _orig_tool:
113
+ warn_tool = ''
114
+ for i, item in enumerate(list(tokens)):
115
+ if item == '--tool':
116
+ if tokens[i + 1] != _orig_tool:
117
+ warn_tool = tokens[i + 1]
118
+ tokens[i : i+2] = ['', ''] # remove this and next arg
119
+ elif item.startswith('--tool='):
120
+ if item[7:] != _orig_tool:
121
+ warn_tool = item
122
+ tokens[i] = '' # remove just this arg.
123
+
124
+ if warn_tool:
125
+ warning(
126
+ f'Attempting to set --tool {warn_tool} from DEPS',
127
+ f'(file={self.deps_file}:{self.target_node})',
128
+ f'however the tool was already chosen as: {_orig_tool}. The --tool arg will',
129
+ f'not be applied from: {tokens}'
130
+ )
131
+
132
+ tokens = [item for item in tokens if item != ''] # remove blanks
133
+
134
+ return tokens
135
+
136
+
106
137
  def apply_args(self, args_list:list) -> list:
107
138
  '''Given args_list, applies them to our self.command_design_ref obj
108
139
 
@@ -114,9 +145,20 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
114
145
  self.error(f"{args_list=} is not type str/list, can't apply args",
115
146
  f"in {self.caller_info}")
116
147
  tokens = dep_str2list(args_list)
148
+
117
149
  # We're going to run an ArgumentParser here, which is not the most efficient
118
150
  # thing to do b/c it runs on all of self.command_design_ref.args (dict) even
119
151
  # if we're applying a single token.
152
+
153
+ # We have to special-case anything with --tool[=value] in tokens, otherwise
154
+ # the user may think they were allowed to set --tool, but in our flow the Command handler
155
+ # (self.command_design_ref) has already been chosen, so setting the tool can have
156
+ # strange side-effects.
157
+ _orig_tool = self.args.get('tool', '')
158
+ tokens = self._apply_args_check_tools(tokens=tokens)
159
+ if not tokens:
160
+ return []
161
+
120
162
  debug(f'deps_processor - custom apply_args with {tokens=}',
121
163
  f'from {self.caller_info}')
122
164
  _, unparsed = self.command_design_ref.run_argparser_on_list(
@@ -135,6 +177,16 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
135
177
  # flist from that target.
136
178
  warning(f'For {self.command_design_ref.command_name}:' \
137
179
  + f' in {self.caller_info} has unknown args {unparsed=}')
180
+
181
+ if self.command_design_ref.auto_tool_applied and _orig_tool != self.args.get('tool', ''):
182
+ debug(f'deps_processor.apply_args: tool changed, {self.args["tool"]=}, will attempt',
183
+ f'to respawn the job using original args: {self.config["eda_original_args"]}')
184
+ self.command_design_ref.tool_changed_respawn = {
185
+ 'tool': self.args['tool'],
186
+ 'orig_tool': _orig_tool,
187
+ 'from': self.caller_info,
188
+ }
189
+
138
190
  return unparsed
139
191
 
140
192
  def apply_reqs(self, reqs_list:list) -> None:
@@ -213,6 +265,10 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
213
265
  elif key == 'deps':
214
266
  remaining_deps_list += self.process_deps_return_discovered_deps()
215
267
 
268
+ if self.command_design_ref.tool_changed_respawn:
269
+ # Stop now, and have eda.py respawn the command.
270
+ return []
271
+
216
272
  # We return the list of deps that still need to be resolved (['full_path/some_target', ...])
217
273
  return remaining_deps_list
218
274
 
@@ -260,6 +316,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
260
316
 
261
317
 
262
318
  apply_tag_items_tools = False
319
+ apply_tag_items_commands = False
263
320
  apply_tag_items_with_args = False
264
321
 
265
322
  tool = self.args.get('tool', None)
@@ -276,6 +333,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
276
333
  warning(f'{tagname=} in {self.caller_info}:',
277
334
  ' skipped due to with-tools disabled.')
278
335
 
336
+ with_commands = dep_str2list(value.get('with-commands', []))
337
+ if with_commands and not deps_tags_enables.get('with-commands', None):
338
+ with_commands = []
339
+ warning(f'{tagname=} in {self.caller_info}:',
340
+ ' skipped due to with-commands disabled.')
341
+
279
342
  with_args = value.get('with-args', {})
280
343
  if not isinstance(with_args, dict):
281
344
  error(f'{tagname=} in {self.caller_info}:',
@@ -299,6 +362,12 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
299
362
  if tool_full_version and tool_full_version in with_tools:
300
363
  apply_tag_items_tools = True
301
364
 
365
+ # check with-commands?
366
+ if not with_commands:
367
+ apply_tag_items_commands = True # no with-commands present
368
+ elif getattr(self.command_design_ref, 'command_name', '') in with_commands:
369
+ apply_tag_items_commands = True # with-commands present and we matched.
370
+
302
371
  # check with-args?
303
372
  with_args_matched_list = []
304
373
  for k,v in with_args.items():
@@ -336,7 +405,8 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
336
405
  elif enable_tags_matched:
337
406
  apply_tag_items = True
338
407
  else:
339
- apply_tag_items = apply_tag_items_tools and apply_tag_items_with_args
408
+ apply_tag_items = all([apply_tag_items_tools, apply_tag_items_with_args,
409
+ apply_tag_items_commands])
340
410
 
341
411
  if not apply_tag_items:
342
412
  debug(f'process_tags(): {tagname=} in {self.caller_info}',
@@ -370,7 +440,7 @@ class DepsProcessor: # pylint: disable=too-many-instance-attributes
370
440
  if args_list:
371
441
  # This will apply knowns args to the target dep:
372
442
  info(f'{tagname=} in {self.caller_info=}:',
373
- f'applying args b/c {with_tools=} for {args_list=}')
443
+ f'applying args for {args_list=}')
374
444
  self.apply_args(args_list)
375
445
 
376
446
  elif key == 'reqs':
opencos/deps_schema.py CHANGED
@@ -111,6 +111,8 @@ my_target_name:
111
111
  <str>: <---- string user name for this tag
112
112
  with-tools: <---- optional array (or string) of tools this tag requires
113
113
  for the tag to be applied.
114
+ with-commands: <---- optional array (or string) of command this tag requires
115
+ for the tag to be applied.
114
116
  with-args: <---- optional table of args values that must match for
115
117
  this tag to be applied.
116
118
  args: <---- optional array of args to be applied to this target
@@ -217,6 +219,7 @@ TARGET_EDA_COMMAND = {
217
219
  TARGET_TAGS_TABLE = {
218
220
  Optional(str): {
219
221
  Optional('with-tools'): ARRAY_OR_SPACE_SEPARATED_STRING,
222
+ Optional('with-commands'): ARRAY_OR_SPACE_SEPARATED_STRING,
220
223
  Optional('with-args'): dict,
221
224
  Optional('args'): ARRAY_OR_SPACE_SEPARATED_STRING,
222
225
  Optional('deps'): ARRAY_OR_SPACE_SEPARATED_STRING,
opencos/eda.py CHANGED
@@ -20,13 +20,19 @@ from pathlib import Path
20
20
 
21
21
  import opencos
22
22
  from opencos import util, eda_config, eda_base
23
- from opencos.eda_base import Tool, which_tool
23
+ from opencos.eda_base import Tool, which_tool, get_eda_exec
24
24
  from opencos.utils import vsim_helper, vscode_helper
25
+ from opencos.utils.subprocess_helpers import subprocess_run_background
26
+ from opencos.utils import status_constants
25
27
 
26
28
  # Configure util:
27
29
  util.progname = "EDA"
28
30
  util.global_log.default_log_enabled = True
29
31
  util.global_log.default_log_filepath = os.path.join('eda.work', 'eda.log')
32
+ util.global_log.default_log_disable_with_args.extend([
33
+ # avoid default log on certain eda commands
34
+ 'help', 'waves', 'deps-help', 'targets'
35
+ ])
30
36
 
31
37
 
32
38
  # ******************************************************************************
@@ -44,12 +50,12 @@ def init_config(
44
50
  # For key DEFAULT_HANDLERS, we'll update config['command_handler'] with
45
51
  # the actual class using importlib (via opencos.util)
46
52
  config['command_handler'] = {}
47
- for command, str_class in config['DEFAULT_HANDLERS'].items():
53
+ for _cmd, str_class in config['DEFAULT_HANDLERS'].items():
48
54
  cls = util.import_class_from_string(str_class)
49
55
  if not cls:
50
- util.error(f"config DEFAULT_HANDLERS {command=} {str_class=} could not import")
56
+ util.error(f"config DEFAULT_HANDLERS command={_cmd} {str_class=} could not import")
51
57
  else:
52
- config['command_handler'][command] = cls
58
+ config['command_handler'][_cmd] = cls
53
59
 
54
60
  config['auto_tools_found'] = {}
55
61
  config['tools_loaded'] = set()
@@ -58,6 +64,18 @@ def init_config(
58
64
  return config
59
65
 
60
66
 
67
+ def get_all_commands_help_str(config: dict) -> str:
68
+ '''Returns a str of help based on what commands eda supports, from config'''
69
+ all_commands_help = []
70
+ max_command_str_len = max(len(s) for s in config.get('DEFAULT_HANDLERS_HELP', {}).keys())
71
+ for key, value in config.get('DEFAULT_HANDLERS_HELP', {}).items():
72
+ all_commands_help.append(f' {key:<{max_command_str_len}} - {value.strip()}')
73
+ if all_commands_help:
74
+ all_commands_help = [
75
+ 'Where <command> is one of:',
76
+ '',
77
+ ] + all_commands_help
78
+ return '\n'.join(all_commands_help)
61
79
 
62
80
 
63
81
  def usage(tokens: list, config: dict, command="") -> int:
@@ -77,26 +95,11 @@ def usage(tokens: list, config: dict, command="") -> int:
77
95
  """
78
96
  Usage:
79
97
  eda [<options>] <command> [options] <files|targets, ...>
80
-
81
- Where <command> is one of:
82
-
83
- sim - Simulates a DEPS target
84
- elab - Elaborates a DEPS target (sort of sim based LINT)
85
- synth - Synthesizes a DEPS target
86
- flist - Create dependency from a DEPS target
87
- proj - Create a project from a DEPS target for GUI sim/waves/debug
88
- multi - Run multiple DEPS targets, serially or in parallel
89
- tools-multi - Same as 'multi' but run on all available tools, or specfied using --tools
90
- sweep - Sweep one or more arguments across a range, serially or in parallel
91
- build - Build for a board, creating a project and running build flow
92
- waves - Opens waveform from prior simulation
93
- upload - Uploads a finished design into hardware
94
- open - Opens a project
95
- export - Export files related to a target, tool independent
96
- shell - Runs only commands for DEPS target (like sim or elab, but stops prior to tool)
97
- targets - list all possible targets given glob path.
98
- help - This help (without args), or i.e. "eda help sim" for specific help
99
-
98
+ """
99
+ )
100
+ print(get_all_commands_help_str(config=config))
101
+ print(
102
+ """
100
103
  And <files|targets, ...> is one or more source file or DEPS markup file target,
101
104
  such as .v, .sv, .vhd[l], .cpp files, or a target key in a DEPS.[yml|yaml|toml|json].
102
105
  Note that you can prefix source files with `sv@`, `v@`, `vhdl@` or `cpp@` to
@@ -249,7 +252,7 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
249
252
  p = shutil.which(exe)
250
253
  config['auto_tools_found'][name] = exe # populate key-value pairs w/ first exe in list
251
254
  if not quiet:
252
- util.info(f"Detected {name} ({p}), auto-setting up tool {name}")
255
+ util.info(f"Detected {name} ({p})")
253
256
  tool_setup(tool=name, quiet=True, auto_setup=True, warnings=warnings, config=config)
254
257
 
255
258
  return config
@@ -317,7 +320,7 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
317
320
  config['tools_loaded'].add(tool)
318
321
 
319
322
 
320
- def process_tokens( # pylint: disable=too-many-branches,too-many-statements
323
+ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-many-locals
321
324
  tokens: list, original_args: list, config: dict, is_interactive=False
322
325
  ) -> int:
323
326
  '''Returns bash/sh style return code int (0 pass, non-zero fail).
@@ -335,6 +338,7 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements
335
338
  deferred_tokens = []
336
339
  command = ""
337
340
  run_auto_tool_setup = True
341
+ process_tokens_cwd = os.getcwd()
338
342
 
339
343
  parser = eda_base.get_argparser()
340
344
  try:
@@ -409,6 +413,7 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements
409
413
  command not in config.get('command_tool_is_optional', []):
410
414
  use_tool = which_tool(command, config)
411
415
  util.info(f"--tool not specified, using default for {command=}: {use_tool}")
416
+ setattr(sco, 'auto_tool_applied', True)
412
417
 
413
418
  rc = check_command_handler_cls(command_obj=sco, command=command, parsed_args=parsed)
414
419
  if rc > 0:
@@ -426,6 +431,25 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements
426
431
  # rc=1 because that's the python exception rc)
427
432
  rc = getattr(sco, 'status', 2)
428
433
  util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
434
+
435
+ if rc == 0 and not parsed.tool and getattr(sco, 'tool_changed_respawn', False):
436
+ use_tool = sco.args.get('tool', '')
437
+ if not use_tool:
438
+ util.error(f'Unable to change tool from {parsed.tool}, internal eda problem.')
439
+ return status_constants.EDA_DEFAULT_ERROR
440
+
441
+ # close the util.log:
442
+ util.stop_log()
443
+ # respawn the original job, but with --tool=<use_tool> applied:
444
+ _command_list = [get_eda_exec(command), f"--tool={use_tool}"] + original_args
445
+ util.info(f'eda: respawn for tool change: {" ".join(_command_list)};',
446
+ f' (running from: {process_tokens_cwd})')
447
+ subprocess_run_background(
448
+ work_dir=process_tokens_cwd,
449
+ command_list=_command_list,
450
+ background=util.args.get('quiet', False)
451
+ )
452
+
429
453
  return rc
430
454
 
431
455