opencos-eda 0.3.15__py3-none-any.whl → 0.3.17__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 (38) hide show
  1. opencos/commands/flist.py +143 -88
  2. opencos/commands/shell.py +1 -1
  3. opencos/commands/sim.py +21 -8
  4. opencos/commands/waves.py +3 -1
  5. opencos/deps/defaults.py +6 -2
  6. opencos/deps/deps_file.py +30 -9
  7. opencos/deps/deps_processor.py +99 -65
  8. opencos/deps_schema.py +8 -0
  9. opencos/docs/DEPS.md +6 -0
  10. opencos/eda.py +30 -5
  11. opencos/eda_base.py +21 -9
  12. opencos/eda_config.py +2 -1
  13. opencos/eda_config_defaults.yml +9 -1
  14. opencos/eda_tool_helper.py +84 -9
  15. opencos/files.py +41 -0
  16. opencos/tools/cocotb.py +1 -1
  17. opencos/tools/invio.py +1 -1
  18. opencos/tools/invio_yosys.py +1 -1
  19. opencos/tools/iverilog.py +1 -1
  20. opencos/tools/quartus.py +1 -1
  21. opencos/tools/questa_common.py +6 -3
  22. opencos/tools/riviera.py +5 -3
  23. opencos/tools/slang.py +1 -1
  24. opencos/tools/slang_yosys.py +36 -8
  25. opencos/tools/surelog.py +1 -1
  26. opencos/tools/verilator.py +209 -20
  27. opencos/tools/vivado.py +1 -1
  28. opencos/tools/yosys.py +155 -30
  29. opencos/util.py +5 -1
  30. opencos/utils/docker_checks.py +224 -0
  31. opencos/utils/subprocess_helpers.py +3 -1
  32. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/METADATA +1 -1
  33. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/RECORD +38 -37
  34. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/WHEEL +0 -0
  35. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/entry_points.txt +0 -0
  36. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE +0 -0
  37. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/licenses/LICENSE.spdx +0 -0
  38. {opencos_eda-0.3.15.dist-info → opencos_eda-0.3.17.dist-info}/top_level.txt +0 -0
@@ -14,12 +14,25 @@ from importlib import import_module
14
14
 
15
15
  from opencos import eda_config, util
16
16
  from opencos.util import Colors
17
- from opencos.utils import str_helpers
17
+ from opencos.utils import str_helpers, docker_checks
18
+
19
+ # Used by pytest, so we can skip tests if tools aren't present, also used by
20
+ # opencos.eda itself
21
+
22
+ DOCKER_TOOL_NOTE = ' [via docker] '
23
+
24
+ def tool_loaded_but_flagged_as_docker_only(tool: str, config: dict) -> bool:
25
+ '''Checks if a tool was flagged in config as only being able to run
26
+
27
+ via docker (and wasn't availble in PATH, shutil.which(..))
28
+ '''
29
+ exe = config.get('auto_tools_found', {}).get(tool, '')
30
+ return exe and exe == DOCKER_TOOL_NOTE
18
31
 
19
- # Used by pytest, so we can skip tests if tools aren't present.
20
32
 
21
33
  def get_config_and_tools_loaded( # pylint: disable=dangerous-default-value
22
- quiet: bool = False, args: list = []
34
+ quiet: bool = False, args: list = [],
35
+ remove_tools_with_docker_note: bool = True
23
36
  ) -> (dict, set):
24
37
  '''Returns config dict and list tools_loaded, given the found config.
25
38
 
@@ -36,14 +49,34 @@ def get_config_and_tools_loaded( # pylint: disable=dangerous-default-value
36
49
  config = eda.init_config(config=config, quiet=quiet)
37
50
  tools_loaded = config.get('tools_loaded', []).copy()
38
51
 
52
+ # Remove any tools from tools_loaded that had their exe flagged with
53
+ # str DOCKER_TOOL_NOTE. This is also for pytests so that heplers.can_run_eda_sim()
54
+ # returns False if it were to otherwise appear that we can run verilator
55
+ # (but only with --docker flag set, that majority of test do not)
56
+ if remove_tools_with_docker_note:
57
+ for _tool in list(tools_loaded):
58
+ if tool_loaded_but_flagged_as_docker_only(tool=_tool, config=config):
59
+ tools_loaded.remove(_tool)
60
+
39
61
  return config, tools_loaded
40
62
 
41
63
 
42
- def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
64
+ def get_all_handler_commands( # pylint: disable=too-many-branches
65
+ config=None, tools_loaded=None, auto_tools_only: bool = False
66
+ ) -> dict:
43
67
  '''Given a config and tools_loaded (or if not supplied uses defaults) returns a dict
44
68
 
45
69
  of { <command>: [list of tools that run that command, in auto-tool-order] }.
46
70
 
71
+ If you use auto_tools_only=True, then you will only get command: tools that exist in
72
+ config['auto_tools_order'] (and are loaded tools). This would be what you'd get if you
73
+ ran `eda [command]` and did not set the --tool=TOOL arg, for what can run that command.
74
+
75
+ If you use auto_tools_only=False (default), you will get the auto tool order +
76
+ tool handlers (loaded tools only) that can service the command. For example, eda waves,
77
+ eda upload, and others may not be in the auto tool list for an `eda [command]`, because
78
+ those command have to self determine their tool.
79
+
47
80
  For example:
48
81
  { "sim": ["verilator", "vivado"],
49
82
  "elab": ["slang", "verilator", ...], ...
@@ -51,9 +84,14 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
51
84
  '''
52
85
  all_handler_commands = {}
53
86
 
54
- if config is None or tools_loaded is None:
87
+
88
+ if config and tools_loaded is None:
89
+ tools_loaded = config.get('tools_loaded', [])
90
+ elif config is None or tools_loaded is None:
91
+ # This will re-run eda.init_config(..), b/c you didn't bring a config.
55
92
  config, tools_loaded = get_config_and_tools_loaded()
56
93
 
94
+
57
95
  assert isinstance(config, dict)
58
96
  assert isinstance(tools_loaded, list)
59
97
 
@@ -80,6 +118,19 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
80
118
  else:
81
119
  all_handler_commands[command].append(tool)
82
120
 
121
+ if auto_tools_only:
122
+ # skip the next step and return what we've got.
123
+ return all_handler_commands
124
+
125
+ # Note that the above will miss commands (waves, upload) that are not in auto_tools_order
126
+ for tool in tools_loaded:
127
+ handlers = config.get('tools', {}).get(tool, {}).get('handlers', {})
128
+ for command, _ in handlers.items():
129
+ if command not in all_handler_commands:
130
+ all_handler_commands[command] = list([tool])
131
+ elif tool not in all_handler_commands.get(command, []):
132
+ all_handler_commands[command].append(tool)
133
+
83
134
  return all_handler_commands
84
135
 
85
136
 
@@ -90,6 +141,9 @@ def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
90
141
  if not entry:
91
142
  return ''
92
143
 
144
+ # TODO(drew): maybe if there are no handlers, then do something to get version?
145
+ # like, offer tool_class?
146
+
93
147
  handler_name = entry.get('handlers', {}).get(eda_command, '')
94
148
  if not handler_name:
95
149
  return ''
@@ -246,13 +300,34 @@ def pretty_info_handler_tools( # pylint: disable=dangerous-default-value
246
300
  else:
247
301
  tools_rows.append([_tool, path])
248
302
 
303
+ # Since Docker isn't technically a tool, and only lives in opencos.utils.docker_checks,
304
+ # we would like to still list it here (as "other") to at least show path/version information.
305
+ # This also makes it less surprising if an actual tool has their PATH set with ' [via docker] '
306
+ # (DOCKER_TOOL_NOTE str)
307
+ other_rows = [
308
+ ['--Detected other--', '--Path--']
309
+ ]
310
+ if show_versions:
311
+ other_rows[0].append('--Version--')
312
+ if docker_checks.DOCKER_EXE:
313
+ other_rows.append([
314
+ 'docker',
315
+ docker_checks.DOCKER_EXE,
316
+ docker_checks.get_docker_version()
317
+ ])
318
+
319
+ # Finally, print everything
320
+ pretty_info_tool_rows(tools_rows + other_rows)
321
+
322
+
323
+ def pretty_info_tool_rows(rows: list) -> None:
324
+ '''Pretty print a 2-d list, called by pretty_info_handler_tools'''
249
325
 
250
- # Finally, print detected tools:
251
326
  for rownum, row in enumerate(str_helpers.pretty_2dlist_columns(
252
- tools_rows, return_as_2d_list=True, header_row_centered=False)):
253
- if rownum == 0:
327
+ rows, return_as_2d_list=True, header_row_centered=False)):
328
+ if rownum == 0 or row[0].startswith('--'):
254
329
  util.info(f'{Colors.bgreen}{"".join(row)}')
255
330
  else:
256
- # get the results in a padded 2D list so we can colorize the tool (index 0)
331
+ # get the results in a padded 2D list so we can colorize the name (index 0)
257
332
  util.info(f'{Colors.bgreen}{row[0]}{Colors.normal}{Colors.cyan}' \
258
333
  + ''.join(row[1:]))
opencos/files.py CHANGED
@@ -13,6 +13,7 @@ as part of a verilog $readmemh, etc)
13
13
  '''
14
14
 
15
15
  import os
16
+ from pathlib import Path
16
17
  import shutil
17
18
 
18
19
  # Ways to force files not ending in .sv to be systemverilog (for tools
@@ -68,6 +69,46 @@ def safe_shutil_which(path: str) -> str:
68
69
  return found
69
70
  return ''
70
71
 
72
+
73
+ def get_source_files_paths(
74
+ cmd_des_obj, minimal_root_paths: bool = False
75
+ ) -> list:
76
+ '''Given a CommandDesign object, returns list of Path objs for all source files
77
+
78
+ If minimal_root_paths=True, attempts to return the minimal set of root paths for
79
+ docker read-only volume mapping
80
+
81
+ will look at members for all source files that (any member list type in cmd_des_obj.files_)
82
+ '''
83
+
84
+ # Iterate over lists and add to our set, we'll include cmd_des_obj.incdirs (list)
85
+ # here as well.
86
+ file_dirs = set()
87
+ for name in [x for x in dir(cmd_des_obj) if x.startswith('files_')]:
88
+ member = getattr(cmd_des_obj, name, None)
89
+ if callable(member) or not isinstance(member, list):
90
+ continue
91
+ _ = [file_dirs.add(Path(x).resolve().parent) for x in member]
92
+
93
+ # also incdirs, a path not a file, so don't use .parent
94
+ _ = [file_dirs.add(Path(incdir).resolve()) for incdir in getattr(cmd_des_obj, 'incdirs', [])]
95
+
96
+ if not minimal_root_paths:
97
+ # return them all:
98
+ return list(file_dirs)
99
+
100
+ for x in list(file_dirs):
101
+ # - Using list(file_dirs) as iterator as cheap copy b/c we're altering set file_dirs
102
+ # - We don't want to add '/' as a root path, so make sure dirs have at least
103
+ # 2 parts if we're going to trim items from file_dirs.
104
+ # - Also this is a n^2 traversal, make sure we aren't matching ourselves (x != y)
105
+ # in check. Using childPath.is_relative_to(parentPath).
106
+ if any(x != y and x.is_relative_to(y) and len(y.parts) >= 2 for y in file_dirs):
107
+ file_dirs.remove(x)
108
+
109
+ return list(file_dirs)
110
+
111
+
71
112
  # Note that in Windows, 'python' will return the venv or uv version, 'python3' will
72
113
  # return the installed version (which may not be what you want), so we'll prefer
73
114
  # 'python':
opencos/tools/cocotb.py CHANGED
@@ -25,7 +25,7 @@ class ToolCocotb(Tool):
25
25
  cocotb_version = ''
26
26
  python_exe = ''
27
27
 
28
- def get_versions(self) -> str:
28
+ def get_versions(self, **kwargs) -> str:
29
29
  if self._VERSION:
30
30
  return self._VERSION
31
31
 
opencos/tools/invio.py CHANGED
@@ -17,7 +17,7 @@ class ToolInvio(Tool):
17
17
  _TOOL = 'invio'
18
18
  _URL = 'https://www.verific.com/products/invio/'
19
19
 
20
- def get_versions(self) -> str:
20
+ def get_versions(self, **kwargs) -> str:
21
21
  if self._VERSION:
22
22
  return self._VERSION
23
23
 
@@ -19,7 +19,7 @@ class ToolInvioYosys(ToolYosys):
19
19
  _URL = 'https://www.verific.com/products/invio/'
20
20
  _EXE = 'yosys'
21
21
 
22
- def get_versions(self) -> str:
22
+ def get_versions(self, **kwargs) -> str:
23
23
  if self._VERSION:
24
24
  return self._VERSION
25
25
 
opencos/tools/iverilog.py CHANGED
@@ -23,7 +23,7 @@ class ToolIverilog(Tool):
23
23
  self.iverilog_exe = ''
24
24
  super().__init__(config=config) # calls self.get_versions()
25
25
 
26
- def get_versions(self) -> str:
26
+ def get_versions(self, **kwargs) -> str:
27
27
  self.iverilog_exe = ''
28
28
  if self._VERSION:
29
29
  return self._VERSION
opencos/tools/quartus.py CHANGED
@@ -91,7 +91,7 @@ class ToolQuartus(Tool):
91
91
  util.debug("Could not determine Quartus version from: quartus_sh --version")
92
92
 
93
93
 
94
- def get_versions(self) -> str:
94
+ def get_versions(self, **kwargs) -> str:
95
95
  if self._VERSION:
96
96
  return self._VERSION
97
97
 
@@ -53,14 +53,16 @@ class ToolQuesta(Tool):
53
53
  if line.strip().startswith('Release Notes For'):
54
54
  m = re.search(r'(\d+)\.(\d+)', line)
55
55
  if m:
56
+ _maj, _min = int(m.group(1)), int(m.group(2))
57
+ self._VERSION = f'{_maj}.{_min}'
56
58
  if self._TOOL.startswith('questa'):
57
59
  # don't set these for ModelsimASE:
58
- self.questa_major = int(m.group(1))
60
+ self.questa_major = int(m.group(2))
59
61
  self.questa_minor = int(m.group(2))
60
- self._VERSION = str(self.questa_major) + '.' + str(self.questa_minor)
61
62
  util.debug(f'version {self._VERSION} ({release_notes_txt_filepath})')
62
63
  break
63
64
 
65
+
64
66
  def _try_set_version_from_path(self) -> None:
65
67
  '''Attempts to use portions of exe path to get version info
66
68
 
@@ -75,7 +77,7 @@ class ToolQuesta(Tool):
75
77
  util.warning("Questa path doesn't specificy version, expecting (d+.d+)")
76
78
 
77
79
 
78
- def get_versions(self) -> str:
80
+ def get_versions(self, **kwargs) -> str:
79
81
  if self._VERSION:
80
82
  return self._VERSION
81
83
  path = safe_shutil_which(self._EXE)
@@ -103,6 +105,7 @@ class ToolQuesta(Tool):
103
105
 
104
106
  return self._VERSION
105
107
 
108
+
106
109
  def set_tool_defines(self) -> None:
107
110
  '''Override from class Tool, which handles picking up config['tools'][self._TOOL]
108
111
 
opencos/tools/riviera.py CHANGED
@@ -27,7 +27,7 @@ class ToolRiviera(ToolQuesta):
27
27
  use_vopt = False
28
28
  uvm_versions = set()
29
29
 
30
- def get_versions(self) -> str:
30
+ def get_versions(self, **kwargs) -> str:
31
31
  if self._VERSION:
32
32
  return self._VERSION
33
33
  path = safe_shutil_which(self._EXE)
@@ -102,8 +102,10 @@ class CommandSimRiviera(CommonSimQuesta, ToolRiviera):
102
102
  ' tcl steps are (from tool config in --config-yml): '
103
103
  ) + '; '.join(self.tool_config.get('simulate-coverage-tcl', [])),
104
104
  'uvm': (
105
- 'Attempts to support UVM. Adds to vlog: -l uvm +incdir+PATH for the PATH to'
106
- ' uvm_macros.svh for the installed version of Riviera used.'
105
+ 'Attempts to support UVM. For Riviera, this adds "-uvmver NUMBER -dbg" to vlog.f.'
106
+ ' You can choose your uvmver using eda arg --uvm-version=NUMBER.'
107
+ ' Also adds +access +r to vopt/vsim. There is no -l or -L library modifications,'
108
+ ' we rely on Riviera to handle this internally based on running: vlog -uvm'
107
109
  ),
108
110
  'license-queue': (
109
111
  'Set to enable env vars (if unset) LICENSE_QUEUE=1, ALDEC_LICENSE_QUEUE=1,'
opencos/tools/slang.py CHANGED
@@ -27,7 +27,7 @@ class ToolSlang(Tool):
27
27
  slang_tidy_exe = ''
28
28
  slang_hier_exe = ''
29
29
 
30
- def get_versions(self) -> str:
30
+ def get_versions(self, **kwargs) -> str:
31
31
  if self._VERSION:
32
32
  return self._VERSION
33
33
  path = safe_shutil_which(self._EXE)
@@ -8,10 +8,10 @@ Contains classes for ToolSlangYosys, CommandSynthSlangYosys
8
8
  import os
9
9
 
10
10
  from opencos import util
11
- from opencos.tools.yosys import ToolYosys, CommonSynthYosys, CommandLecYosys
12
-
11
+ from opencos.tools.yosys import ToolYosys, CommonSynthYosys, CommandLecYosys, CommonFListYosys
13
12
  from opencos.commands.sim import parameters_dict_get_command_list
14
13
 
14
+
15
15
  class ToolSlangYosys(ToolYosys):
16
16
  '''Uses slang.so in yosys plugins directory, called via yosys > plugin -i slang'''
17
17
  _TOOL = 'slang_yosys'
@@ -30,6 +30,15 @@ class CommandSynthSlangYosys(CommonSynthYosys, ToolSlangYosys):
30
30
  CommonSynthYosys.__init__(self, config)
31
31
  ToolSlangYosys.__init__(self, config=self.config)
32
32
 
33
+ self.args.update({
34
+ 'yosys-read-slang-args': []
35
+ })
36
+ self.args_help.update({
37
+ 'yosys-read-slang-args': (
38
+ 'list-style arg, these are applied to the "read_slang" command'
39
+ )
40
+ })
41
+
33
42
  self.slang_out_dir = ''
34
43
  self.slang_v_path = ''
35
44
 
@@ -98,11 +107,10 @@ class CommandSynthSlangYosys(CommonSynthYosys, ToolSlangYosys):
98
107
 
99
108
  def _get_read_slang_cmd_str(self) -> str:
100
109
 
101
- read_slang_cmd = [
102
- 'read_slang',
103
- '--ignore-unknown-modules',
104
- '--best-effort-hierarchy',
105
- ]
110
+ read_slang_cmd = ['read_slang'] + \
111
+ self.args.get('yosys-read-slang-args', []) + \
112
+ self.config.get('tools', {}).get(self._TOOL, {}).get(
113
+ 'tcl-command-args', {}).get('read_slang', [])
106
114
 
107
115
  read_slang_cmd += self.get_yosys_read_verilog_defines_incdirs_files()
108
116
 
@@ -113,7 +121,6 @@ class CommandSynthSlangYosys(CommonSynthYosys, ToolSlangYosys):
113
121
  )
114
122
 
115
123
  # In case --top was not set:
116
- # TODO(drew): Can we skip this if it was an inferred top???
117
124
  if not any(x.startswith('--top') for x in read_slang_cmd):
118
125
  read_slang_cmd.append(f'--top {self.args["top"]}')
119
126
 
@@ -249,3 +256,24 @@ class CommandLecSlangYosys(CommandLecYosys, ToolSlangYosys): # pylint: disable=t
249
256
  def __init__(self, config: dict):
250
257
  CommandLecYosys.__init__(self, config)
251
258
  ToolSlangYosys.__init__(self, config=self.config)
259
+
260
+
261
+ class CommandFListSlangYosys(CommonFListYosys, ToolSlangYosys):
262
+ '''
263
+ Handler for: eda flist --tool=slang_yosys
264
+
265
+ This will create a eda.yosys_slang.f flist (or to std-out if --print-to-stdout),
266
+ suitable for args passed to a "read_slang" command in yosys, such as:
267
+
268
+ yosys -i slang
269
+ read_slang -f FILE_CREATED_BY_THIS_HANDLER [other args]
270
+
271
+ '''
272
+ def __init__(self, config: dict):
273
+ CommonFListYosys.__init__(self, config=config)
274
+ ToolSlangYosys.__init__(self, config=self.config)
275
+
276
+ self.args.update({
277
+ 'force': True, # always overwrite the output
278
+ 'out': 'eda_flist.yosys.read_slang.f',
279
+ })
opencos/tools/surelog.py CHANGED
@@ -22,7 +22,7 @@ class ToolSurelog(Tool):
22
22
 
23
23
  surelog_exe = ''
24
24
 
25
- def get_versions(self) -> str:
25
+ def get_versions(self, **kwargs) -> str:
26
26
  if self._VERSION:
27
27
  return self._VERSION
28
28
  path = safe_shutil_which(self._EXE)