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.
Files changed (59) hide show
  1. opencos/commands/deps_help.py +63 -113
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +4 -4
  4. opencos/commands/sim.py +14 -15
  5. opencos/commands/sweep.py +1 -1
  6. opencos/commands/synth.py +1 -2
  7. opencos/commands/upload.py +192 -4
  8. opencos/commands/waves.py +52 -8
  9. opencos/deps/deps_commands.py +6 -6
  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 +358 -155
  24. opencos/eda_base.py +187 -60
  25. opencos/eda_config.py +70 -35
  26. opencos/eda_config_defaults.yml +310 -186
  27. opencos/eda_config_reduced.yml +19 -39
  28. opencos/eda_tool_helper.py +190 -21
  29. opencos/files.py +26 -1
  30. opencos/tools/cocotb.py +11 -23
  31. opencos/tools/invio.py +2 -2
  32. opencos/tools/invio_yosys.py +2 -1
  33. opencos/tools/iverilog.py +3 -3
  34. opencos/tools/modelsim_ase.py +1 -1
  35. opencos/tools/quartus.py +172 -137
  36. opencos/tools/questa_common.py +50 -9
  37. opencos/tools/riviera.py +5 -4
  38. opencos/tools/slang.py +14 -10
  39. opencos/tools/slang_yosys.py +1 -0
  40. opencos/tools/surelog.py +7 -6
  41. opencos/tools/verilator.py +9 -7
  42. opencos/tools/vivado.py +315 -180
  43. opencos/tools/yosys.py +5 -5
  44. opencos/util.py +6 -3
  45. opencos/utils/dict_helpers.py +31 -0
  46. opencos/utils/markup_helpers.py +2 -2
  47. opencos/utils/str_helpers.py +38 -0
  48. opencos/utils/subprocess_helpers.py +3 -3
  49. opencos/utils/vscode_helper.py +2 -2
  50. opencos/utils/vsim_helper.py +16 -5
  51. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/METADATA +1 -1
  52. opencos_eda-0.3.12.dist-info/RECORD +93 -0
  53. opencos/eda_config_max_verilator_waivers.yml +0 -39
  54. opencos_eda-0.3.10.dist-info/RECORD +0 -81
  55. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/WHEEL +0 -0
  56. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/entry_points.txt +0 -0
  57. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE +0 -0
  58. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE.spdx +0 -0
  59. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/top_level.txt +0 -0
@@ -10,8 +10,11 @@ Example uses:
10
10
 
11
11
  '''
12
12
 
13
+ from importlib import import_module
13
14
 
14
- from opencos import eda, eda_config, util
15
+ from opencos import eda_config, util
16
+ from opencos.util import Colors
17
+ from opencos.utils import str_helpers
15
18
 
16
19
  # Used by pytest, so we can skip tests if tools aren't present.
17
20
 
@@ -26,8 +29,13 @@ def get_config_and_tools_loaded( # pylint: disable=dangerous-default-value
26
29
  # We have to figure out what tools are avaiable w/out calling eda.main,
27
30
  # so we can get some of these using eda_config.get_eda_config()
28
31
  config, _ = eda_config.get_eda_config(args=args, quiet=quiet)
32
+
33
+ # only import 'eda' here so that other methods in this pymodule can be used
34
+ # within eda, etc, if you already have a valid config or tools_loaded.
35
+ eda = import_module("opencos.eda")
29
36
  config = eda.init_config(config=config, quiet=quiet)
30
37
  tools_loaded = config.get('tools_loaded', set()).copy()
38
+
31
39
  return config, tools_loaded
32
40
 
33
41
 
@@ -50,22 +58,27 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
50
58
  assert isinstance(tools_loaded, set)
51
59
 
52
60
  # Let's re-walk auto_tools_order to get this ordered per eda command:
53
- for tool, table in config.get('auto_tools_order', [{}])[0].items():
54
- if tool not in tools_loaded:
55
- continue
56
-
57
- if table.get('disable-tools-multi', False):
58
- # Flagged as do-not-add when running eda command: tools-multi
59
- util.debug(f'eda_tool_helper.py -- skipping {tool=} it is set with flag',
60
- 'disable-tools-multi in config')
61
- continue
62
-
63
- for command in table.get('handlers', {}).keys():
64
- if command not in all_handler_commands:
65
- # create ordered list from config.
66
- all_handler_commands[command] = list([tool])
67
- else:
68
- all_handler_commands[command].append(tool)
61
+ for command, tools_list in config.get('auto_tools_order', {}).items():
62
+
63
+ for tool in tools_list:
64
+ entry = config.get('tools', {}).get(tool, {})
65
+ assert entry, f'{command=} in auto_tools_order {tool=} not present in tools'
66
+
67
+ if tool not in tools_loaded:
68
+ continue
69
+
70
+ if entry.get('disable-tools-multi', False):
71
+ # Flagged as do-not-add when running eda command: tools-multi
72
+ util.debug(f'eda_tool_helper.py -- skipping {tool=} it is set with flag',
73
+ 'disable-tools-multi in config')
74
+ continue
75
+
76
+ if command in entry.get('handlers', {}):
77
+ if command not in all_handler_commands:
78
+ # create ordered list from config.
79
+ all_handler_commands[command] = list([tool])
80
+ else:
81
+ all_handler_commands[command].append(tool)
69
82
 
70
83
  return all_handler_commands
71
84
 
@@ -73,7 +86,7 @@ def get_all_handler_commands(config=None, tools_loaded=None) -> dict:
73
86
  def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
74
87
  '''Attempts to get a Command Handler's version given tool + eda_command'''
75
88
 
76
- entry = config['auto_tools_order'][0].get(tool, {})
89
+ entry = config['tools'].get(tool, {})
77
90
  if not entry:
78
91
  return ''
79
92
 
@@ -83,7 +96,163 @@ def get_handler_tool_version(tool: str, eda_command: str, config: dict) -> str:
83
96
 
84
97
  module = util.import_class_from_string(handler_name)
85
98
  obj = module(config=config)
86
- if not getattr(obj, 'get_versions', None):
87
- return ''
88
99
 
89
- return obj.get_versions()
100
+ # Some command classes like CommandWaves, don't have get_versions(), but
101
+ # have get_versions_of_tool():
102
+ if getattr(obj, 'get_versions_of_tool', None):
103
+ return obj.get_versions_of_tool(tool)
104
+
105
+ # Note that Tool.get_versions() is supposed to be 'fast', we don't always
106
+ # run the tool if the 'exe -version' takes too long.
107
+ if getattr(obj, 'get_versions', None):
108
+ return obj.get_versions()
109
+
110
+ return ''
111
+
112
+
113
+
114
+ def get_handler_info_with_versions( # pylint: disable=too-many-branches
115
+ config: dict | None = None,
116
+ include_commands: bool = True,
117
+ sort: bool = True
118
+ ) -> str:
119
+ '''Creates and returns a dict of
120
+
121
+ {'commands': (what tools/versions can run them),
122
+ 'tools': (what version and what commands they can run)
123
+ }
124
+
125
+ for arg 'config' you may use:
126
+
127
+ config, tools_loaded = get_config_and_tools_loaded()
128
+ '''
129
+
130
+ if not config:
131
+ config, tools_loaded = get_config_and_tools_loaded()
132
+ else:
133
+ tools_loaded = list(config.get('tools_loaded', set()))
134
+
135
+ eda_commands = list(config.get('DEFAULT_HANDLERS', {}).keys())
136
+ show_versions = config.get('show_tool_versions', False)
137
+
138
+ info = {
139
+ 'tools': {},
140
+ }
141
+
142
+
143
+ if not show_versions:
144
+ for tool, path in config.get('auto_tools_found', {}).items():
145
+ info['tools'][tool] = {
146
+ 'path': path
147
+ }
148
+ return info
149
+
150
+
151
+ if include_commands:
152
+ info.update({
153
+ 'commands': {}
154
+ })
155
+ for eda_command in eda_commands:
156
+ info['commands'][eda_command] = {} # init
157
+
158
+ for tool in tools_loaded:
159
+
160
+ if include_commands:
161
+ info['tools'][tool] = {
162
+ 'version': '',
163
+ 'commands': [],
164
+ }
165
+ else:
166
+ info['tools'][tool] = {
167
+ 'version': '',
168
+ }
169
+
170
+ for eda_command in eda_commands:
171
+
172
+ if not include_commands and info['tools'][tool]['version']:
173
+ # version is already set, and we're not doing for all commands:
174
+ break
175
+
176
+ # Note that if you have a generic handler, or a tool that has
177
+ # several handlers with more than one Tool class, that all can return a
178
+ # non blank-str version, you may have problems.
179
+ ver = get_handler_tool_version(
180
+ tool=tool, eda_command=eda_command, config=config
181
+ )
182
+
183
+ if not ver:
184
+ continue
185
+
186
+ info['tools'][tool]['version'] = ver
187
+
188
+ if include_commands:
189
+ info['commands'][eda_command][tool] = ver
190
+ info['tools'][tool]['commands'].append(eda_command)
191
+
192
+ for tool, _ in info['tools'].items():
193
+ if tool in config.get('auto_tools_found', {}):
194
+ info['tools'][tool]['path'] = config['auto_tools_found'][tool]
195
+
196
+ # return the info dict with 'tools' and 'commands' entries sorted:
197
+ if sort:
198
+ for key in list(info.keys()):
199
+ if info[key]:
200
+ info[key] = dict(sorted(info[key].items()))
201
+
202
+ return info
203
+
204
+
205
+ def pretty_info_handler_tools(
206
+ info: dict | None = None, config: dict | None = None, command: str | None = ''
207
+ ) -> None:
208
+ '''Pretty print (via util.info) the result from get_handler_info_with_versions()
209
+
210
+ if info is None or empty, will use config to run get_handler_info_with_versions(..)
211
+
212
+ Does not include commands
213
+ '''
214
+
215
+ if not info:
216
+ info = get_handler_info_with_versions(config=config, include_commands=False, sort=True)
217
+
218
+ if not info.get('tools', {}):
219
+ # No tools detected
220
+ if command and command in config.get('command_tool_is_optional', []):
221
+ # but this command doesn't need tools
222
+ return
223
+
224
+ # if command omitted, or command may need tools, print that we don't have any
225
+ util.info('No tools detected!', color=Colors.yellow)
226
+ return
227
+
228
+ show_versions = any('version' in dvalue for dvalue in info['tools'].values())
229
+
230
+ tools_rows = [
231
+ ['--Detected tool--', '--Path--'] # Header row.
232
+ ]
233
+
234
+ if show_versions:
235
+ tools_rows[0].append('--Version--')
236
+
237
+ for _tool, dvalue in info['tools'].items():
238
+ path = dvalue.get("path", "")
239
+ if path:
240
+ path = f'({path})'
241
+
242
+ if show_versions:
243
+ version = dvalue.get("version", "")
244
+ # will defer printing again, so we can put them into aligned columns:
245
+ tools_rows.append([_tool, path, version])
246
+ else:
247
+ tools_rows.append([_tool, path])
248
+
249
+
250
+ # Finally, print detected tools:
251
+ 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:
254
+ util.info(f'{Colors.bgreen}{"".join(row)}')
255
+ else:
256
+ # get the results in a padded 2D list so we can colorize the tool (index 0)
257
+ util.info(f'{Colors.bgreen}{row[0]}{Colors.normal}{Colors.cyan}' \
258
+ + ''.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
+ import shutil
16
17
 
17
18
  # Ways to force files not ending in .sv to be systemverilog (for tools
18
19
  # that require -sv vs Verilog-2001'''
@@ -31,8 +32,12 @@ FORCE_PREFIX_DICT = {
31
32
 
32
33
  ALL_FORCED_PREFIXES = set(list(FORCE_PREFIX_DICT.keys()))
33
34
 
34
- def get_source_file(target:str) -> (bool, str, str):
35
+ def get_source_file(target: str) -> (bool, str, str):
35
36
  '''Returns tuple: bool if file exists, filepath str, and optional forced file type str'''
37
+
38
+ if '$' in target:
39
+ target = os.path.expandvars(target)
40
+
36
41
  if os.path.isfile(target):
37
42
  # target exists as a file, return True w/ original target:
38
43
  return True, target, ''
@@ -47,3 +52,23 @@ def get_source_file(target:str) -> (bool, str, str):
47
52
 
48
53
  # target or fpath didn't exist, return False with the original target:
49
54
  return False, target, ''
55
+
56
+
57
+ def safe_shutil_which(path: str) -> str:
58
+ '''Windows/WSL compatible Wrapper for shutil.which that checks for 'path':
59
+
60
+ - path
61
+ - path.exe
62
+ - path.bat
63
+
64
+ Returns full path str returned by shutil.which
65
+ '''
66
+ for ext in ('', '.exe', 'bat'):
67
+ if found := shutil.which(f'{path}{ext}'):
68
+ return found
69
+ return ''
70
+
71
+ # Note that in Windows, 'python' will return the venv or uv version, 'python3' will
72
+ # return the installed version (which may not be what you want), so we'll prefer
73
+ # 'python':
74
+ PY_EXE = safe_shutil_which('python') or safe_shutil_which('python3')
opencos/tools/cocotb.py CHANGED
@@ -4,12 +4,12 @@ Contains classes for ToolCocotb, CommandSimCocotb.
4
4
  '''
5
5
 
6
6
  import os
7
- import shutil
8
- import subprocess
7
+ from importlib import metadata
9
8
 
10
9
  from opencos import util
11
- from opencos.eda_base import Tool
12
10
  from opencos.commands import CommandSim
11
+ from opencos.eda_base import Tool
12
+ from opencos.files import safe_shutil_which, PY_EXE
13
13
  from opencos.utils import status_constants
14
14
  from opencos.utils.str_helpers import sanitize_defines_for_sh
15
15
  from opencos.tools import verilator # For default waivers.
@@ -30,7 +30,7 @@ class ToolCocotb(Tool):
30
30
  return self._VERSION
31
31
 
32
32
  # Check if python is available
33
- python_path = shutil.which('python') or shutil.which('python3')
33
+ python_path = PY_EXE
34
34
  if not python_path:
35
35
  self.error('"python" or "python3" not in path, required for cocotb')
36
36
  else:
@@ -38,22 +38,16 @@ class ToolCocotb(Tool):
38
38
 
39
39
  # Check if cocotb is installed
40
40
  try:
41
- version_ret = subprocess.run(
42
- [self.python_exe, '-c', 'import cocotb; print(cocotb.__version__)'],
43
- capture_output=True,
44
- check=True,
45
- text=True
46
- )
47
- version = version_ret.stdout.strip()
41
+ version = metadata.version('cocotb')
48
42
  self.cocotb_version = version
49
43
  self._VERSION = version
50
44
  util.debug(f'Found cocotb version: {version}')
51
45
  return self._VERSION
52
- except subprocess.CalledProcessError:
53
- self.error('cocotb package not installed in python environment. '
54
- 'Install with: pip install cocotb')
46
+ except metadata.PackageNotFoundError:
47
+ util.warning('cocotb package not installed in python environment. '
48
+ 'Install with: pip install cocotb; or pypackage: opencos-eda[cocotb]')
55
49
  except Exception as e:
56
- self.error(f'Failed to check cocotb version: {e}')
50
+ util.warning(f'Failed to check cocotb version: {e}')
57
51
 
58
52
  return ''
59
53
 
@@ -128,7 +122,7 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
128
122
  util.warning('--cocotb-simulator is not set, a simulation cannot be run with'
129
123
  'this arg value')
130
124
  return
131
- exe = shutil.which(simulator)
125
+ exe = safe_shutil_which(simulator)
132
126
  if not exe:
133
127
  util.warning(f'--cocotb-simulator={simulator}, {simulator} is not present in PATH',
134
128
  'a simulation cannot be run with this arg value')
@@ -330,13 +324,7 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
330
324
  def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
331
325
  '''Generate the content for the Python runner script'''
332
326
 
333
- if shutil.which('verilator'):
334
- # TODO(drew): this shortcuts if verilator is truly usable,
335
- # consider using eda_tool_helper to get "tools_loaded", which
336
- # is not set in self.config['tools_loaded'] when --tool=cocotb.
337
- # Would need minor refactor for eda.py methods auto_tools_order,
338
- # tool_setup to go into eda_tool_helper.py, and those methods would
339
- # need hooks to be non-destructive to config.
327
+ if safe_shutil_which('verilator'):
340
328
  tmp_verilator_obj = verilator.VerilatorSim(config=self.config)
341
329
  verilator_waivers = tmp_verilator_obj.get_verilator_tool_config_waivers()
342
330
  else:
opencos/tools/invio.py CHANGED
@@ -5,9 +5,9 @@
5
5
  import importlib.util
6
6
 
7
7
  from opencos import util
8
- from opencos.tools import invio_helpers
9
- from opencos.eda_base import Tool
10
8
  from opencos.commands import CommandElab
9
+ from opencos.eda_base import Tool
10
+ from opencos.tools import invio_helpers
11
11
 
12
12
 
13
13
 
@@ -8,6 +8,7 @@ import os
8
8
  import importlib.util
9
9
 
10
10
  from opencos import util
11
+ from opencos.files import PY_EXE
11
12
  from opencos.tools import invio_helpers
12
13
  from opencos.tools.yosys import ToolYosys, CommonSynthYosys
13
14
 
@@ -100,7 +101,7 @@ class CommandSynthInvioYosys(CommonSynthYosys, ToolInvioYosys):
100
101
 
101
102
 
102
103
  invio_command_list = util.ShellCommandList(
103
- ['python3', invio_dict['full_py_filename']], tee_fpath=invio_dict['full_py_filename']
104
+ [PY_EXE, invio_dict['full_py_filename']], tee_fpath=invio_dict['full_py_filename']
104
105
  )
105
106
 
106
107
  # Optinally create and run a sta.f:
opencos/tools/iverilog.py CHANGED
@@ -3,12 +3,12 @@
3
3
  Contains classes for ToolIverilog CommandSimIverilog, CommandElabIverilog.
4
4
  '''
5
5
 
6
- import shutil
7
6
  import subprocess
8
7
 
9
8
  from opencos import util
10
- from opencos.eda_base import Tool
11
9
  from opencos.commands import CommandSim
10
+ from opencos.eda_base import Tool
11
+ from opencos.files import safe_shutil_which
12
12
  from opencos.utils.str_helpers import sanitize_defines_for_sh
13
13
 
14
14
 
@@ -28,7 +28,7 @@ class ToolIverilog(Tool):
28
28
  if self._VERSION:
29
29
  return self._VERSION
30
30
 
31
- iverilog_path = shutil.which(self._EXE)
31
+ iverilog_path = safe_shutil_which(self._EXE)
32
32
  if iverilog_path is None:
33
33
  self.error(f'"{self._EXE}" not in path, need to get it ({self._URL})')
34
34
  else:
@@ -1,6 +1,6 @@
1
1
  ''' opencos.tools.modelsim_ase - Used by opencos.eda for sim/elab commands w/ --tool=modelsim_ase.
2
2
 
3
- Contains classes for ToolModelsimAse, CommandSimModelsimAse, CommandElabModelsimAse.
3
+ Contains classes for CommandSimModelsimAse, CommandElabModelsimAse.
4
4
 
5
5
  Note that this is for 32-bit Modelsim Student Edition. Consider using --tool=questa_fse instead.
6
6
  '''