opencos-eda 0.3.10__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 (53) hide show
  1. opencos/commands/deps_help.py +63 -113
  2. opencos/commands/export.py +7 -2
  3. opencos/commands/multi.py +3 -3
  4. opencos/commands/sim.py +14 -15
  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_processor.py +129 -50
  10. opencos/docs/Architecture.md +45 -0
  11. opencos/docs/ConnectingApps.md +29 -0
  12. opencos/docs/DEPS.md +199 -0
  13. opencos/docs/Debug.md +77 -0
  14. opencos/docs/DirectoryStructure.md +22 -0
  15. opencos/docs/Installation.md +117 -0
  16. opencos/docs/OcVivadoTcl.md +63 -0
  17. opencos/docs/OpenQuestions.md +7 -0
  18. opencos/docs/README.md +13 -0
  19. opencos/docs/RtlCodingStyle.md +54 -0
  20. opencos/docs/eda.md +147 -0
  21. opencos/docs/oc_cli.md +135 -0
  22. opencos/eda.py +132 -35
  23. opencos/eda_base.py +173 -47
  24. opencos/eda_config.py +56 -17
  25. opencos/eda_config_defaults.yml +21 -4
  26. opencos/files.py +26 -1
  27. opencos/tools/cocotb.py +5 -5
  28. opencos/tools/invio.py +2 -2
  29. opencos/tools/invio_yosys.py +2 -1
  30. opencos/tools/iverilog.py +3 -3
  31. opencos/tools/quartus.py +113 -115
  32. opencos/tools/questa_common.py +2 -2
  33. opencos/tools/riviera.py +3 -3
  34. opencos/tools/slang.py +11 -7
  35. opencos/tools/slang_yosys.py +1 -0
  36. opencos/tools/surelog.py +4 -3
  37. opencos/tools/verilator.py +4 -4
  38. opencos/tools/vivado.py +307 -176
  39. opencos/tools/yosys.py +4 -4
  40. opencos/util.py +6 -3
  41. opencos/utils/dict_helpers.py +31 -0
  42. opencos/utils/markup_helpers.py +2 -2
  43. opencos/utils/subprocess_helpers.py +3 -3
  44. opencos/utils/vscode_helper.py +2 -2
  45. opencos/utils/vsim_helper.py +16 -5
  46. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
  47. opencos_eda-0.3.11.dist-info/RECORD +94 -0
  48. opencos_eda-0.3.10.dist-info/RECORD +0 -81
  49. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
  50. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +0 -0
  51. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
  52. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
  53. {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.11.dist-info}/top_level.txt +0 -0
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,7 +429,7 @@ 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(user_exe)
432
+ user_exe = safe_shutil_which(user_exe)
394
433
 
395
434
  if update_config:
396
435
  if isinstance(config_exe, list):
@@ -399,7 +438,7 @@ def tool_try_add_to_path( # pylint: disable=too-many-branches
399
438
  # update all entries, if we can, if the value is also in 'path'
400
439
  # from our set --tool=Name=path/exe
401
440
  new_value = os.path.join(path, os.path.split(value)[1])
402
- if os.path.exists(new_value) and shutil.which(new_value) and \
441
+ if os.path.exists(new_value) and safe_shutil_which(new_value) and \
403
442
  os.access(new_value, os.X_OK):
404
443
  config['auto_tools_order'][0][name]['exe'][index] = new_value
405
444
  else:
@@ -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
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
7
  import subprocess
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:
@@ -128,7 +128,7 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
128
128
  util.warning('--cocotb-simulator is not set, a simulation cannot be run with'
129
129
  'this arg value')
130
130
  return
131
- exe = shutil.which(simulator)
131
+ exe = safe_shutil_which(simulator)
132
132
  if not exe:
133
133
  util.warning(f'--cocotb-simulator={simulator}, {simulator} is not present in PATH',
134
134
  'a simulation cannot be run with this arg value')
@@ -330,7 +330,7 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
330
330
  def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
331
331
  '''Generate the content for the Python runner script'''
332
332
 
333
- if shutil.which('verilator'):
333
+ if safe_shutil_which('verilator'):
334
334
  # TODO(drew): this shortcuts if verilator is truly usable,
335
335
  # consider using eda_tool_helper to get "tools_loaded", which
336
336
  # is not set in self.config['tools_loaded'] when --tool=cocotb.
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: