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
@@ -1,14 +1,34 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  # How to use?
4
- # 1) copy this script locally and source it.
4
+ # 1) run:
5
+ # eda_show_autocomplete
6
+ # for instructions, will likely show you the location of this file,
7
+ # eda_deps_bash_completion.bash, for you to source. Does not require uv.
8
+ #
9
+ # 2) Given the result from (1), and if you use uv, add the following to your
10
+ # ~/.bashrc:
11
+ # # Make sure 'eda' is a valid executable when not in a venv:
12
+ # if ! type -P "eda" &>/dev/null; then
13
+ # uv tool install --python 3.14 opencos-eda >/dev/null 2>&1
14
+ # echo "uv tool installed opencos-eda"
15
+ # fi
16
+ # if [ -f PATH-FROM-STEP-1 ]; then
17
+ # . PATH-FROM-STEP-1
18
+ # fi
19
+ #
20
+ # 3) copy this script locally and source it.
5
21
  # For example:
6
22
  # > source ~/sh/eda_deps_bash_completion.bash
7
- # You can put this in your .bashrc.
8
- # 2) From you venv activate script:
9
- # (bottom of activate script, assuming python3.10):
23
+ # You can put this in your .bashrc. Note you will need a venv active or
24
+ # "eda" isn't in your path yet.
25
+ #
26
+ # 4) Have it sourced when you start your venv. Note this doesn't play as nicely
27
+ # with "uv" due to having a less stable .venv, but you can add this to your
28
+ # VENV_NAME/bin/activate script:
29
+ # (bottom of activate script, assuming python3.XX):
10
30
  # script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
11
- # . $script_dir/../lib/python3.10/site-packages/opencos/eda_deps_bash_completion.bash
31
+ # . $script_dir/../lib/python3.XX/site-packages/opencos/eda_deps_bash_completion.bash
12
32
 
13
33
 
14
34
  # scripts via pyproject.toml:
@@ -17,6 +37,15 @@ SCRIPT_NAME="eda"
17
37
  # how we get the completion targets:
18
38
  EXTRACTION_SCRIPT_NAME="eda_targets"
19
39
 
40
+ EDA_WORDS="sim lint elab synth flist proj multi tools-multi sweep build \
41
+ waves upload open export shell targets lec \
42
+ +define+ +incdirs+ \
43
+ --help --quiet --verbose --debug \
44
+ --tool --seed --top --keep --force --fake --lint --work-dir \
45
+ --stop-before-compile --stop-after-compile --stop-before-elaborate \
46
+ --export --export-run --export-json \
47
+ "
48
+
20
49
  _eda_script_completion() {
21
50
 
22
51
  # Set up for additional completions
@@ -28,22 +57,15 @@ _eda_script_completion() {
28
57
  if [[ $(type -P "$EXTRACTION_SCRIPT_NAME") ]]; then
29
58
  keys=$("$EXTRACTION_SCRIPT_NAME" "$cur")
30
59
  if [[ -n "$keys" ]]; then
31
- completions=($(compgen -W "$keys" -- "$cur"))
60
+ completions=($(compgen -W "$keys $EDA_WORDS" -- "$cur"))
32
61
  fi
33
62
  fi
34
63
 
35
64
  if [ -z "${completions}" ]; then
36
65
  # If we didn't find anything in a DEPS.[yml|yaml|toml|json], then use:
37
- # 1. a bunch of known eda words or args.
38
- eda_words="multi sim elab flist build synth waves proj waves targets \
39
- +define+ +incdirs+ \
40
- --help --quiet --verbose --debug \
41
- --tool --seed --top --keep --force --fake --lint --work-dir \
42
- --stop-before-compile --stop-after-compile --stop-before-elaborate \
43
- --export --export-run --export-json \
44
- "
66
+ # -- a bunch of known eda words or args.
45
67
  # 2. a glob the current word to mimic normal bash:
46
- completions=($(compgen -W "${eda_words}" -G "${cur}*" -- "$cur"))
68
+ completions=($(compgen -W "$EDA_WORDS" -G "${cur}*" -- "$cur"))
47
69
  fi
48
70
 
49
71
  COMPREPLY=("${completions[@]}")
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:
opencos/tools/quartus.py CHANGED
@@ -9,16 +9,14 @@ Used for Intel FPGA synthesis, place & route, and bitstream generation.
9
9
  import os
10
10
  import re
11
11
  import shlex
12
- import shutil
13
12
  import subprocess
14
-
15
13
  from pathlib import Path
16
14
 
17
- from opencos import util, eda_base
18
- from opencos.eda_base import Tool
19
- from opencos.commands import (
20
- CommandSynth, CommandBuild, CommandFList, CommandProj, CommandUpload, CommandOpen
21
- )
15
+ from opencos import util
16
+ from opencos.commands import CommandSynth, CommandBuild, CommandFList, CommandProj, \
17
+ CommandUpload, CommandOpen
18
+ from opencos.eda_base import Tool, get_eda_exec
19
+ from opencos.files import safe_shutil_which
22
20
  from opencos.utils.str_helpers import sanitize_defines_for_sh, strip_outer_quotes
23
21
 
24
22
  class ToolQuartus(Tool):
@@ -48,16 +46,17 @@ class ToolQuartus(Tool):
48
46
  if self._VERSION:
49
47
  return self._VERSION
50
48
 
51
- path = shutil.which(self._EXE)
49
+ path = safe_shutil_which(self._EXE)
52
50
  if not path:
53
51
  self.error("Quartus not in path, need to install or add to $PATH",
54
52
  f"(looked for '{self._EXE}')")
55
53
  else:
56
54
  self.quartus_exe = path
57
55
  self.quartus_base_path, _ = os.path.split(path)
58
- self.quartus_gui_exe = shutil.which('quartus') # vs quartus_sh
59
-
60
56
 
57
+ self.quartus_gui_exe = safe_shutil_which(
58
+ os.path.join(self.quartus_base_path, 'quartus') # vs quartus_sh
59
+ )
61
60
 
62
61
  # Get version based on install path name or by running quartus_sh --version
63
62
  util.debug(f"quartus path = {self.quartus_exe}")
@@ -151,7 +150,6 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
151
150
  self.write_tcl_file(tcl_file=tcl_file)
152
151
 
153
152
  # execute Quartus synthesis
154
- command_list_gui = [self.quartus_gui_exe, '-t', tcl_file]
155
153
  command_list = [
156
154
  self.quartus_exe, '-t', tcl_file
157
155
  ]
@@ -172,11 +170,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
172
170
  typ='text', description='Quartus Synthesis Report'
173
171
  )
174
172
 
175
- if self.args['gui'] and self.quartus_gui_exe:
176
- self.exec(self.args['work-dir'], command_list_gui)
177
- else:
178
- self.exec(self.args['work-dir'], command_list)
179
-
173
+ self.exec(self.args['work-dir'], command_list)
180
174
 
181
175
  saved_qpf_filename = self.args["top"] + '.qpf'
182
176
  if not os.path.isfile(os.path.join(self.args['work-dir'], saved_qpf_filename)):
@@ -185,8 +179,13 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
185
179
 
186
180
  util.info(f"Synthesis done, results are in: {self.args['work-dir']}")
187
181
 
188
- # Note: in GUI mode, if you ran: quaruts -t build.tcl, it will exit on completion,
189
- # so we'll re-open the project.
182
+ # Note: in GUI mode, if we were to run:
183
+ # ran: quaruts -t build.tcl
184
+ # it treats the tcl script as running "headless" as a pre-script, and won't open the
185
+ # GUI anyway, and will exit on completion,
186
+ # Instead we:
187
+ # 1. always run with quartus_sh, so text goes to stdout
188
+ # 2. we'll re-open the project in GUI mode, here:
190
189
  if self.args['gui'] and self.quartus_gui_exe:
191
190
  self.exec(
192
191
  work_dir=self.args['work-dir'],
@@ -212,19 +211,22 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
212
211
  # Add source files (convert to relative paths and use forward slashes)
213
212
  # Note that default of self.args['all-sv'] is False so we should have added
214
213
  # all files to self.files_sv instead of files_v:
214
+ # Note that tcl uses POSIX paths, so \\ -> /
215
215
  for f in self.files_v:
216
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
216
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
217
217
  tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
218
218
  for f in self.files_sv:
219
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
219
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
220
220
  tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
221
221
  for f in self.files_vhd:
222
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
222
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
223
223
  tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
224
224
 
225
225
  # Add include directories - Quartus needs the base directory where "lib/" can be found
226
226
  for incdir in self.incdirs:
227
- tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
227
+ tcl_lines.append(
228
+ f"set_global_assignment -name SEARCH_PATH \"{Path(incdir).as_posix()}\""
229
+ )
228
230
 
229
231
  # Parameters --> set_parameter -name <Parameter_Name> <Value>
230
232
  for k,v in self.parameters.items():
@@ -242,7 +244,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
242
244
  for incdir in self.incdirs:
243
245
  if os.path.exists(incdir):
244
246
  tcl_lines.append(
245
- f"set_global_assignment -name USER_LIBRARIES \"{incdir}\""
247
+ f"set_global_assignment -name USER_LIBRARIES \"{Path(incdir).as_posix()}\""
246
248
  )
247
249
 
248
250
  # Add defines
@@ -268,7 +270,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
268
270
  for f in sdc_files:
269
271
  for attr in ('SDC_FILE', 'SYN_SDC_FILE', 'RTL_SDC_FILE'):
270
272
  tcl_lines.extend([
271
- f"set_global_assignment -name {attr} \"{f}\""
273
+ f"set_global_assignment -name {attr} \"{Path(f).as_posix()}\""
272
274
  ])
273
275
  tcl_lines.append("set_global_assignment -name SYNTH_TIMING_DRIVEN_SYNTHESIS ON")
274
276
 
@@ -331,7 +333,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
331
333
  f"design={self.args['design']}")
332
334
 
333
335
  command_list = [
334
- eda_base.get_eda_exec('flist'), 'flist',
336
+ get_eda_exec('flist'), 'flist',
335
337
  '--no-default-log',
336
338
  '--tool=' + self.args['tool'],
337
339
  '--force',
@@ -411,7 +413,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
411
413
  fname_abs = os.path.abspath(fname)
412
414
  if not os.path.isfile(fname_abs):
413
415
  self.error(f'add-tcl-files: "{fname_abs}"; does not exist')
414
- build_tcl_lines.append(f'source {fname_abs}')
416
+ build_tcl_lines.append(f'source {Path(fname_abs).as_posix()}')
415
417
  build_tcl_lines.append('')
416
418
 
417
419
  # If we don't have any args for --flow-tcl-files, then use a default flow:
@@ -428,7 +430,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
428
430
  fname_abs = os.path.abspath(fname)
429
431
  if not os.path.isfile(fname_abs):
430
432
  self.error(f'flow-tcl-files: "{fname_abs}"; does not exist')
431
- build_tcl_lines.append(f'source {fname_abs}')
433
+ build_tcl_lines.append(f'source {Path(fname_abs).as_posix()}')
432
434
  build_tcl_lines.append('')
433
435
 
434
436
  with open(build_tcl_file, 'w', encoding='utf-8') as ftcl:
@@ -552,20 +554,22 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
552
554
  f"set_global_assignment -name TOP_LEVEL_ENTITY {top}",
553
555
  ]
554
556
 
555
- # Add source files
557
+ # Add source files, tcl prefers POSIX paths even in Windows Powershell.
556
558
  for f in self.files_v:
557
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
559
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
558
560
  tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
559
561
  for f in self.files_sv:
560
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
562
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
561
563
  tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
562
564
  for f in self.files_vhd:
563
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
565
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
564
566
  tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
565
567
 
566
568
  # Add include directories
567
569
  for incdir in self.incdirs:
568
- tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
570
+ tcl_lines.append(
571
+ f"set_global_assignment -name SEARCH_PATH \"{Path(incdir).as_posix()}\""
572
+ )
569
573
 
570
574
  # Add defines
571
575
  for key, value in self.defines.items():
@@ -575,9 +579,10 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
575
579
  tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}={value}\"")
576
580
 
577
581
  # Add constraints if available
578
- if self.files_sdc:
579
- for sdc_file in self.files_sdc:
580
- tcl_lines.append(f"set_global_assignment -name SDC_FILE \"{sdc_file}\"")
582
+ for sdc_file in self.files_sdc:
583
+ tcl_lines.append(
584
+ f"set_global_assignment -name SDC_FILE \"{Path(sdc_file).as_posix()}\""
585
+ )
581
586
 
582
587
  tcl_lines += [
583
588
  "project_close",
@@ -601,116 +606,109 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
601
606
  class CommandUploadQuartus(CommandUpload, ToolQuartus):
602
607
  '''CommandUploadQuartus is a command handler for: eda upload --tool=quartus'''
603
608
 
609
+ SUPPORTED_BIT_EXT = ['.sof']
610
+
604
611
  def __init__(self, config: dict):
605
612
  CommandUpload.__init__(self, config)
606
613
  ToolQuartus.__init__(self, config=self.config)
607
614
  # add args specific to this tool
608
615
  self.args.update({
609
- 'sof-file': "",
610
616
  'cable': "1",
611
617
  'device': "1",
612
618
  'list-cables': False,
613
619
  'list-devices': False,
614
- 'list-sof-files': False,
615
- 'tcl-file': "upload.tcl",
616
- 'log-file': "upload.log",
617
620
  })
618
621
  self.args_help.update({
619
- 'sof-file': 'SOF file to upload (auto-detected if not specified)',
620
622
  'cable': 'Cable number to use for programming',
621
623
  'device': 'Device number on the cable',
622
624
  'list-cables': 'List available programming cables',
623
625
  'list-devices': 'List available devices on cable',
624
- 'list-sof-files': 'List available SOF files',
625
- 'tcl-file': 'name of TCL file to be created for upload',
626
- 'log-file': 'log file for upload operation',
627
626
  })
628
627
 
628
+ # Support mulitple arg keys for bitfile and list-bitfiles, so
629
+ # --sof-file and --list-sof-files work the same.
630
+ self.args_args.update({
631
+ 'bitfile': ['sof-file'],
632
+ 'list-bitfiles': ['list-sof-files'],
633
+ })
634
+ self.args_help.update({
635
+ 'bitfile': 'SOF file to upload (auto-detected if not specified)',
636
+ 'list-bitfiles': 'List available SOF files',
637
+ })
638
+
639
+
629
640
  def do_it(self): # pylint: disable=too-many-branches,too-many-statements,too-many-locals
641
+ '''
642
+ Note this is called directly by opencos.commands.CommandUpload, based
643
+ on which bitfile(s) were found, or if --tool=quartus was set
644
+
645
+ We do not need to handle --list-bitfiles, was handled by CommandUpload.
646
+ '''
647
+
630
648
  # add defines for this job
631
649
  self.set_tool_defines()
632
650
  self.write_eda_config_and_args()
633
651
 
652
+ # Find quartus_pgm executable, we'll want the one from the same path
653
+ # that was used for our self._EXE (ToolQuartus).
654
+ quartus_pgm = safe_shutil_which(os.path.join(self.quartus_base_path, 'quartus_pgm'))
655
+ if not quartus_pgm:
656
+ self.error("quartus_pgm not found in PATH")
657
+ return
658
+
659
+ # Handle --list-cables
660
+ if self.args['list-cables']:
661
+ util.info("Listing available cables...")
662
+ command_list = [quartus_pgm, '--auto']
663
+ _, stdout, _ = self.exec(self.args['work-dir'], command_list)
664
+ util.info("Available cables listed above")
665
+ return
666
+
634
667
  sof_file = None
635
- if self.args['sof-file']:
636
- if os.path.isfile(self.args['sof-file']):
637
- sof_file = self.args['sof-file']
638
- else:
639
- self.error(f"Specified SOF file does not exist: {self.args['sof-file']}")
668
+ if self.args['bitfile']:
669
+ if os.path.isfile(self.args['bitfile']):
670
+ sof_file = self.args['bitfile']
671
+
672
+ # self.bitfiles was already set by CommandUpload.process_tokens()
673
+ if len(self.bitfiles) == 1:
674
+ sof_file = self.bitfiles[0]
640
675
 
641
676
  # Auto-discover SOF file if not specified
642
- if not sof_file and not self.args['list-cables'] and not self.args['list-devices']:
643
- sof_files = []
644
- util.debug(f"Looking for SOF files in {os.path.abspath('.')}")
645
- for root, _, files in os.walk("."):
646
- for f in files:
647
- if f.endswith(".sof"):
648
- fullpath = os.path.abspath(os.path.join(root, f))
649
- sof_files.append(fullpath)
650
- util.info(f"Found SOF file: {fullpath}")
651
-
652
- if len(sof_files) == 1:
653
- sof_file = sof_files[0]
654
- elif len(sof_files) > 1:
655
- if self.args['list-sof-files']:
656
- util.info("Multiple SOF files found:")
657
- for sf in sof_files:
658
- util.info(f" {sf}")
659
- return
660
- self.error("Multiple SOF files found, please specify --sof-file")
661
- elif not sof_files:
662
- if self.args['list-sof-files']:
663
- util.info("No SOF files found")
664
- return
665
- self.error("No SOF files found")
666
-
667
- # Generate TCL script
668
- script_file = Path(self.args['tcl-file'])
669
-
670
- try:
671
- with script_file.open("w", encoding="utf-8") as fout:
672
- fout.write('load_package quartus_pgm\n')
673
-
674
- if self.args['list-cables']:
675
- fout.write('foreach cable [get_hardware_names] {\n')
676
- fout.write(' puts "Cable: $cable"\n')
677
- fout.write('}\n')
678
-
679
- if self.args['list-devices']:
680
- cable_idx = int(self.args["cable"]) - 1
681
- fout.write(f'set cable [lindex [get_hardware_names] {cable_idx}]\n')
682
- fout.write('foreach device [get_device_names -hardware_name $cable] {\n')
683
- fout.write(' puts "Device: $device"\n')
684
- fout.write('}\n')
685
-
686
- if sof_file:
687
- cable_idx2 = int(self.args["cable"]) - 1
688
- device_idx = int(self.args["device"]) - 1
689
- fout.write(f'set cable [lindex [get_hardware_names] {cable_idx2}]\n')
690
- device_cmd = (
691
- f'set device [lindex [get_device_names -hardware_name $cable] {device_idx}]'
692
- )
693
- fout.write(device_cmd)
694
- fout.write('set_global_assignment -name USE_CONFIGURATION_DEVICE OFF\n')
695
- fout.write('execute_flow -compile\n')
696
- fout.write(f'quartus_pgm -c $cable -m jtag -o "p;{sof_file}@$device"\n')
697
-
698
- except Exception as exc:
699
- self.error(f"Cannot create {script_file}: {exc}")
700
-
701
- if sof_file:
702
- util.info(f"Programming with SOF file: {sof_file}")
703
- else:
704
- util.info("Listing cables/devices only")
677
+ if not sof_file:
678
+ # CommandUpload already displayed them, and exited on --list-bitfiles.
679
+ if len(self.bitfiles) > 1:
680
+ self.error("Multiple SOF files found, please specify --sof-file or --bitfile",
681
+ "or use a different search pattern")
682
+ return
683
+
684
+ self.error("No SOF files found")
685
+ return
686
+
687
+ util.info(f"Programming with SOF file: {sof_file}")
688
+
705
689
 
706
690
  # Execute Quartus programmer
691
+ # Format: quartus_pgm -c <cable> -m jtag -o "p;<sof_file>@<device>"
692
+ cable = self.args['cable']
693
+ device = self.args['device']
694
+ operation = f"p;{sof_file}@{device}"
695
+
707
696
  command_list = [
708
- self.quartus_exe, '-t', str(script_file)
697
+ quartus_pgm, '-c', cable, '-m', 'jtag', '-o', operation
709
698
  ]
710
- if not util.args['verbose']:
711
- command_list.append('-q')
712
699
 
713
- self.exec(self.args['work-dir'], command_list)
700
+ _, stdout, _ = self.exec(self.args['work-dir'], command_list)
701
+
702
+ # Do some log scraping
703
+ for line in stdout.split('\n'):
704
+ if any(x in line for x in ('Warning', 'WARNING')):
705
+ self.tool_warning_count += 1
706
+ elif any(x in line for x in ('Error', 'ERROR')):
707
+ self.tool_error_count += 1
708
+
709
+ self.report_tool_warn_error_counts()
710
+ self.report_pass_fail()
711
+
714
712
  util.info("Upload operation completed")
715
713
 
716
714
 
@@ -11,11 +11,11 @@ Contains classes for ToolQuesta, and CommonSimQuesta.
11
11
 
12
12
  import os
13
13
  import re
14
- import shutil
15
14
 
16
15
  from opencos import util
17
16
  from opencos.commands import sim, CommandSim, CommandFList
18
17
  from opencos.eda_base import Tool
18
+ from opencos.files import safe_shutil_which
19
19
  from opencos.utils.str_helpers import sanitize_defines_for_sh
20
20
 
21
21
  class ToolQuesta(Tool):
@@ -25,7 +25,7 @@ class ToolQuesta(Tool):
25
25
  _EXE = 'vsim'
26
26
 
27
27
  starter_edition = False
28
- use_vopt = shutil.which('vopt') # vopt exists in qrun/vsim framework, and we'll use it.
28
+ use_vopt = False # set manually or by get_versions() (after __init__ has set self._EXE)
29
29
  sim_exe = '' # vsim or qrun
30
30
  sim_exe_base_path = ''
31
31
  questa_major = None
@@ -33,12 +33,11 @@ class ToolQuesta(Tool):
33
33
 
34
34
  def __init__(self, config: dict):
35
35
  super().__init__(config=config)
36
- self.args['part'] = 'xcu200-fsgd2104-2-e'
37
36
 
38
37
  def get_versions(self) -> str:
39
38
  if self._VERSION:
40
39
  return self._VERSION
41
- path = shutil.which(self._EXE)
40
+ path = safe_shutil_which(self._EXE)
42
41
  if not path:
43
42
  self.error(f"{self._EXE} not in path, need to setup",
44
43
  "(i.e. source /opt/intelFPGA_pro/23.4/settings64.sh")
opencos/tools/riviera.py CHANGED
@@ -7,12 +7,12 @@ Contains classes for ToolRiviera, CommandSimRiviera, CommandElabRiviera.
7
7
  # pylint: disable=R0801 # (duplicate code in derived classes, such as if-condition return.)
8
8
 
9
9
  import os
10
- import shutil
11
10
  import subprocess
12
11
 
13
12
  from opencos import util
14
- from opencos.tools.questa_common import ToolQuesta, CommonSimQuesta
15
13
  from opencos.commands import CommandFList
14
+ from opencos.files import safe_shutil_which
15
+ from opencos.tools.questa_common import ToolQuesta, CommonSimQuesta
16
16
  from opencos.utils.str_helpers import sanitize_defines_for_sh
17
17
  from opencos.utils import status_constants
18
18
 
@@ -27,7 +27,7 @@ class ToolRiviera(ToolQuesta):
27
27
  def get_versions(self) -> str:
28
28
  if self._VERSION:
29
29
  return self._VERSION
30
- path = shutil.which(self._EXE)
30
+ path = safe_shutil_which(self._EXE)
31
31
  if not path:
32
32
  self.error(f"{self._EXE} not in path, need to setup or add to PATH")
33
33
  util.debug(f"{path=}")