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
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):
@@ -44,50 +42,88 @@ class ToolQuartus(Tool):
44
42
  'family': 'FPGA family for Quartus (e.g., Stratix IV, Arria 10, etc.)',
45
43
  })
46
44
 
45
+ def _try_set_version_from_version_txt(self) -> None:
46
+ '''Attempts to use VSIM_PATH/../version.txt to get version info
47
+
48
+ Return None, may set self._VERSION
49
+ '''
50
+ util.debug(f"quartus path = {self.quartus_exe}")
51
+ version_txt_filepath = os.path.join(self.quartus_base_path, '..', 'version.txt')
52
+ if os.path.isfile(version_txt_filepath):
53
+ with open(version_txt_filepath, encoding='utf-8') as f:
54
+ for line in f.readlines():
55
+ if line.startswith('Version='):
56
+ _, version = line.strip().split('=')
57
+ self._VERSION = version
58
+ break
59
+
60
+ def _try_set_version_from_path(self) -> None:
61
+ '''Attempts to use portions of VSIM_PATH to get version info
62
+
63
+ Return None, may set self._VERSION
64
+ '''
65
+ m = re.search(r'(\d+)\.(\d+)', self.quartus_exe)
66
+ if m:
67
+ version = m.group(1) + '.' + m.group(2)
68
+ self._VERSION = version
69
+
70
+ def _try_set_version_from_exe(self) -> None:
71
+ '''Attempts to run: vsim --version; (max timeout 3 sec) to get version info
72
+
73
+ Return None, may set self._VERSION
74
+
75
+ Since this may fail if we don't have a valid license, we will not error if the version
76
+ is not determined.
77
+ '''
78
+ try:
79
+ result = subprocess.run(
80
+ [self.quartus_exe, '--version'],
81
+ capture_output=True, text=True, timeout=3, check=False
82
+ )
83
+ version_match = re.search(r'Version (\d+\.\d+)', result.stdout)
84
+ if version_match:
85
+ self._VERSION = version_match.group(1)
86
+ else:
87
+ util.debug("Could not determine Quartus version from: quartus_sh --version")
88
+ except (
89
+ subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError
90
+ ):
91
+ util.debug("Could not determine Quartus version from: quartus_sh --version")
92
+
93
+
47
94
  def get_versions(self) -> str:
48
95
  if self._VERSION:
49
96
  return self._VERSION
50
97
 
51
- path = shutil.which(self._EXE)
98
+ path = safe_shutil_which(self._EXE)
52
99
  if not path:
53
100
  self.error("Quartus not in path, need to install or add to $PATH",
54
101
  f"(looked for '{self._EXE}')")
55
102
  else:
56
103
  self.quartus_exe = path
57
104
  self.quartus_base_path, _ = os.path.split(path)
58
- self.quartus_gui_exe = shutil.which('quartus') # vs quartus_sh
59
105
 
106
+ self.quartus_gui_exe = safe_shutil_which(
107
+ os.path.join(self.quartus_base_path, 'quartus') # vs quartus_sh
108
+ )
60
109
 
61
-
62
- # Get version based on install path name or by running quartus_sh --version
63
- util.debug(f"quartus path = {self.quartus_exe}")
64
- m = re.search(r'(\d+)\.(\d+)', self.quartus_exe)
65
- if m:
66
- version = m.group(1) + '.' + m.group(2)
67
- self._VERSION = version
68
- else:
69
- # Try to get version by running quartus_sh --version
70
- try:
71
- result = subprocess.run(
72
- [self.quartus_exe, '--version'],
73
- capture_output=True, text=True, timeout=10, check=False
74
- )
75
- version_match = re.search(r'Version (\d+\.\d+)', result.stdout)
76
- if version_match:
77
- self._VERSION = version_match.group(1)
78
- else:
79
- self.error("Could not determine Quartus version")
80
- except (
81
- subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError
82
- ):
83
- self.error("Could not determine Quartus version")
110
+ # Get version based on install path name:
111
+ # 1. if ../version.txt exists, use that
112
+ # 2. Use the path name if it has version information
113
+ # 3. or by running quartus_sh --version (max timeout=3)
114
+ # If we cannot find the version return '' and warn, do not error.
115
+ self._try_set_version_from_version_txt()
116
+ if not self._VERSION:
117
+ self._try_set_version_from_path()
118
+ if not self._VERSION:
119
+ self._try_set_version_from_exe()
84
120
 
85
121
  if self._VERSION:
86
122
  numbers_list = self._VERSION.split('.')
87
123
  self.quartus_year = int(numbers_list[0])
88
124
  self.quartus_release = int(numbers_list[1])
89
125
  else:
90
- self.error(f"Quartus version not found, quartus path = {self.quartus_exe}")
126
+ util.warning(f"Quartus version not found, quartus path = {self.quartus_exe}")
91
127
  return self._VERSION
92
128
 
93
129
  def set_tool_defines(self) -> None:
@@ -151,7 +187,6 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
151
187
  self.write_tcl_file(tcl_file=tcl_file)
152
188
 
153
189
  # execute Quartus synthesis
154
- command_list_gui = [self.quartus_gui_exe, '-t', tcl_file]
155
190
  command_list = [
156
191
  self.quartus_exe, '-t', tcl_file
157
192
  ]
@@ -172,11 +207,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
172
207
  typ='text', description='Quartus Synthesis Report'
173
208
  )
174
209
 
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
-
210
+ self.exec(self.args['work-dir'], command_list)
180
211
 
181
212
  saved_qpf_filename = self.args["top"] + '.qpf'
182
213
  if not os.path.isfile(os.path.join(self.args['work-dir'], saved_qpf_filename)):
@@ -185,8 +216,13 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
185
216
 
186
217
  util.info(f"Synthesis done, results are in: {self.args['work-dir']}")
187
218
 
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.
219
+ # Note: in GUI mode, if we were to run:
220
+ # ran: quaruts -t build.tcl
221
+ # it treats the tcl script as running "headless" as a pre-script, and won't open the
222
+ # GUI anyway, and will exit on completion,
223
+ # Instead we:
224
+ # 1. always run with quartus_sh, so text goes to stdout
225
+ # 2. we'll re-open the project in GUI mode, here:
190
226
  if self.args['gui'] and self.quartus_gui_exe:
191
227
  self.exec(
192
228
  work_dir=self.args['work-dir'],
@@ -212,19 +248,22 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
212
248
  # Add source files (convert to relative paths and use forward slashes)
213
249
  # Note that default of self.args['all-sv'] is False so we should have added
214
250
  # all files to self.files_sv instead of files_v:
251
+ # Note that tcl uses POSIX paths, so \\ -> /
215
252
  for f in self.files_v:
216
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
253
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
217
254
  tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
218
255
  for f in self.files_sv:
219
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
256
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
220
257
  tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
221
258
  for f in self.files_vhd:
222
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
259
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
223
260
  tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
224
261
 
225
262
  # Add include directories - Quartus needs the base directory where "lib/" can be found
226
263
  for incdir in self.incdirs:
227
- tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
264
+ tcl_lines.append(
265
+ f"set_global_assignment -name SEARCH_PATH \"{Path(incdir).as_posix()}\""
266
+ )
228
267
 
229
268
  # Parameters --> set_parameter -name <Parameter_Name> <Value>
230
269
  for k,v in self.parameters.items():
@@ -242,7 +281,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
242
281
  for incdir in self.incdirs:
243
282
  if os.path.exists(incdir):
244
283
  tcl_lines.append(
245
- f"set_global_assignment -name USER_LIBRARIES \"{incdir}\""
284
+ f"set_global_assignment -name USER_LIBRARIES \"{Path(incdir).as_posix()}\""
246
285
  )
247
286
 
248
287
  # Add defines
@@ -268,7 +307,7 @@ class CommandSynthQuartus(CommandSynth, ToolQuartus):
268
307
  for f in sdc_files:
269
308
  for attr in ('SDC_FILE', 'SYN_SDC_FILE', 'RTL_SDC_FILE'):
270
309
  tcl_lines.extend([
271
- f"set_global_assignment -name {attr} \"{f}\""
310
+ f"set_global_assignment -name {attr} \"{Path(f).as_posix()}\""
272
311
  ])
273
312
  tcl_lines.append("set_global_assignment -name SYNTH_TIMING_DRIVEN_SYNTHESIS ON")
274
313
 
@@ -331,7 +370,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
331
370
  f"design={self.args['design']}")
332
371
 
333
372
  command_list = [
334
- eda_base.get_eda_exec('flist'), 'flist',
373
+ get_eda_exec('flist'), 'flist',
335
374
  '--no-default-log',
336
375
  '--tool=' + self.args['tool'],
337
376
  '--force',
@@ -411,7 +450,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
411
450
  fname_abs = os.path.abspath(fname)
412
451
  if not os.path.isfile(fname_abs):
413
452
  self.error(f'add-tcl-files: "{fname_abs}"; does not exist')
414
- build_tcl_lines.append(f'source {fname_abs}')
453
+ build_tcl_lines.append(f'source {Path(fname_abs).as_posix()}')
415
454
  build_tcl_lines.append('')
416
455
 
417
456
  # If we don't have any args for --flow-tcl-files, then use a default flow:
@@ -428,7 +467,7 @@ class CommandBuildQuartus(CommandBuild, ToolQuartus):
428
467
  fname_abs = os.path.abspath(fname)
429
468
  if not os.path.isfile(fname_abs):
430
469
  self.error(f'flow-tcl-files: "{fname_abs}"; does not exist')
431
- build_tcl_lines.append(f'source {fname_abs}')
470
+ build_tcl_lines.append(f'source {Path(fname_abs).as_posix()}')
432
471
  build_tcl_lines.append('')
433
472
 
434
473
  with open(build_tcl_file, 'w', encoding='utf-8') as ftcl:
@@ -552,20 +591,22 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
552
591
  f"set_global_assignment -name TOP_LEVEL_ENTITY {top}",
553
592
  ]
554
593
 
555
- # Add source files
594
+ # Add source files, tcl prefers POSIX paths even in Windows Powershell.
556
595
  for f in self.files_v:
557
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
596
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
558
597
  tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
559
598
  for f in self.files_sv:
560
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
599
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
561
600
  tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
562
601
  for f in self.files_vhd:
563
- rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
602
+ rel_path = Path(os.path.relpath(f, self.args['work-dir'])).as_posix()
564
603
  tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
565
604
 
566
605
  # Add include directories
567
606
  for incdir in self.incdirs:
568
- tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
607
+ tcl_lines.append(
608
+ f"set_global_assignment -name SEARCH_PATH \"{Path(incdir).as_posix()}\""
609
+ )
569
610
 
570
611
  # Add defines
571
612
  for key, value in self.defines.items():
@@ -575,9 +616,10 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
575
616
  tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}={value}\"")
576
617
 
577
618
  # 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}\"")
619
+ for sdc_file in self.files_sdc:
620
+ tcl_lines.append(
621
+ f"set_global_assignment -name SDC_FILE \"{Path(sdc_file).as_posix()}\""
622
+ )
581
623
 
582
624
  tcl_lines += [
583
625
  "project_close",
@@ -601,116 +643,109 @@ class CommandProjQuartus(CommandProj, ToolQuartus):
601
643
  class CommandUploadQuartus(CommandUpload, ToolQuartus):
602
644
  '''CommandUploadQuartus is a command handler for: eda upload --tool=quartus'''
603
645
 
646
+ SUPPORTED_BIT_EXT = ['.sof']
647
+
604
648
  def __init__(self, config: dict):
605
649
  CommandUpload.__init__(self, config)
606
650
  ToolQuartus.__init__(self, config=self.config)
607
651
  # add args specific to this tool
608
652
  self.args.update({
609
- 'sof-file': "",
610
653
  'cable': "1",
611
654
  'device': "1",
612
655
  'list-cables': False,
613
656
  'list-devices': False,
614
- 'list-sof-files': False,
615
- 'tcl-file': "upload.tcl",
616
- 'log-file': "upload.log",
617
657
  })
618
658
  self.args_help.update({
619
- 'sof-file': 'SOF file to upload (auto-detected if not specified)',
620
659
  'cable': 'Cable number to use for programming',
621
660
  'device': 'Device number on the cable',
622
661
  'list-cables': 'List available programming cables',
623
662
  '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
663
  })
628
664
 
665
+ # Support mulitple arg keys for bitfile and list-bitfiles, so
666
+ # --sof-file and --list-sof-files work the same.
667
+ self.args_args.update({
668
+ 'bitfile': ['sof-file'],
669
+ 'list-bitfiles': ['list-sof-files'],
670
+ })
671
+ self.args_help.update({
672
+ 'bitfile': 'SOF file to upload (auto-detected if not specified)',
673
+ 'list-bitfiles': 'List available SOF files',
674
+ })
675
+
676
+
629
677
  def do_it(self): # pylint: disable=too-many-branches,too-many-statements,too-many-locals
678
+ '''
679
+ Note this is called directly by opencos.commands.CommandUpload, based
680
+ on which bitfile(s) were found, or if --tool=quartus was set
681
+
682
+ We do not need to handle --list-bitfiles, was handled by CommandUpload.
683
+ '''
684
+
630
685
  # add defines for this job
631
686
  self.set_tool_defines()
632
687
  self.write_eda_config_and_args()
633
688
 
689
+ # Find quartus_pgm executable, we'll want the one from the same path
690
+ # that was used for our self._EXE (ToolQuartus).
691
+ quartus_pgm = safe_shutil_which(os.path.join(self.quartus_base_path, 'quartus_pgm'))
692
+ if not quartus_pgm:
693
+ self.error("quartus_pgm not found in PATH")
694
+ return
695
+
696
+ # Handle --list-cables
697
+ if self.args['list-cables']:
698
+ util.info("Listing available cables...")
699
+ command_list = [quartus_pgm, '--auto']
700
+ _, stdout, _ = self.exec(self.args['work-dir'], command_list)
701
+ util.info("Available cables listed above")
702
+ return
703
+
634
704
  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']}")
705
+ if self.args['bitfile']:
706
+ if os.path.isfile(self.args['bitfile']):
707
+ sof_file = self.args['bitfile']
708
+
709
+ # self.bitfiles was already set by CommandUpload.process_tokens()
710
+ if len(self.bitfiles) == 1:
711
+ sof_file = self.bitfiles[0]
640
712
 
641
713
  # 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'])
714
+ if not sof_file:
715
+ # CommandUpload already displayed them, and exited on --list-bitfiles.
716
+ if len(self.bitfiles) > 1:
717
+ self.error("Multiple SOF files found, please specify --sof-file or --bitfile",
718
+ "or use a different search pattern")
719
+ return
720
+
721
+ self.error("No SOF files found")
722
+ return
723
+
724
+ util.info(f"Programming with SOF file: {sof_file}")
669
725
 
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")
705
726
 
706
727
  # Execute Quartus programmer
728
+ # Format: quartus_pgm -c <cable> -m jtag -o "p;<sof_file>@<device>"
729
+ cable = self.args['cable']
730
+ device = self.args['device']
731
+ operation = f"p;{sof_file}@{device}"
732
+
707
733
  command_list = [
708
- self.quartus_exe, '-t', str(script_file)
734
+ quartus_pgm, '-c', cable, '-m', 'jtag', '-o', operation
709
735
  ]
710
- if not util.args['verbose']:
711
- command_list.append('-q')
712
736
 
713
- self.exec(self.args['work-dir'], command_list)
737
+ _, stdout, _ = self.exec(self.args['work-dir'], command_list)
738
+
739
+ # Do some log scraping
740
+ for line in stdout.split('\n'):
741
+ if any(x in line for x in ('Warning', 'WARNING')):
742
+ self.tool_warning_count += 1
743
+ elif any(x in line for x in ('Error', 'ERROR')):
744
+ self.tool_error_count += 1
745
+
746
+ self.report_tool_warn_error_counts()
747
+ self.report_pass_fail()
748
+
714
749
  util.info("Upload operation completed")
715
750
 
716
751
 
@@ -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):
@@ -34,10 +34,49 @@ class ToolQuesta(Tool):
34
34
  def __init__(self, config: dict):
35
35
  super().__init__(config=config)
36
36
 
37
+
38
+ def _try_set_version_from_release_notes(self) -> None:
39
+ '''Attempts to use a RELEASE_NOTES.txt file to get version info
40
+
41
+ Return None, may set self._VERSION
42
+
43
+ {path}/../docs/rlsnotes/RELEASE_NOTES.txt, where first line
44
+ '''
45
+ release_notes_txt_filepath = os.path.join(
46
+ self.sim_exe_base_path, '..', 'docs', 'rlsnotes', 'RELEASE_NOTES.txt'
47
+ )
48
+ if not os.path.isfile(release_notes_txt_filepath):
49
+ return
50
+
51
+ with open(release_notes_txt_filepath, encoding='utf-8') as f:
52
+ for line in f.readlines():
53
+ if line.strip().startswith('Release Notes For'):
54
+ m = re.search(r'(\d+)\.(\d+)', line)
55
+ if m:
56
+ self.questa_major = int(m.group(1))
57
+ self.questa_minor = int(m.group(2))
58
+ self._VERSION = str(self.questa_major) + '.' + str(self.questa_minor)
59
+ util.debug(f'version {self._VERSION} ({release_notes_txt_filepath})')
60
+ break
61
+
62
+ def _try_set_version_from_path(self) -> None:
63
+ '''Attempts to use portions of exe path to get version info
64
+
65
+ Return None, may set self._VERSION
66
+ '''
67
+ m = re.search(r'(\d+)\.(\d+)', self.sim_exe_base_path)
68
+ if m:
69
+ self.questa_major = int(m.group(1))
70
+ self.questa_minor = int(m.group(2))
71
+ self._VERSION = str(self.questa_major) + '.' + str(self.questa_minor)
72
+ else:
73
+ util.warning("Questa path doesn't specificy version, expecting (d+.d+)")
74
+
75
+
37
76
  def get_versions(self) -> str:
38
77
  if self._VERSION:
39
78
  return self._VERSION
40
- path = shutil.which(self._EXE)
79
+ path = safe_shutil_which(self._EXE)
41
80
  if not path:
42
81
  self.error(f"{self._EXE} not in path, need to setup",
43
82
  "(i.e. source /opt/intelFPGA_pro/23.4/settings64.sh")
@@ -51,13 +90,15 @@ class ToolQuesta(Tool):
51
90
  self.sim_exe = path
52
91
  self.sim_exe_base_path, _ = os.path.split(path)
53
92
 
54
- m = re.search(r'(\d+)\.(\d+)', path)
55
- if m:
56
- self.questa_major = int(m.group(1))
57
- self.questa_minor = int(m.group(2))
58
- self._VERSION = str(self.questa_major) + '.' + str(self.questa_minor)
59
- else:
60
- self.error("Questa path doesn't specificy version, expecting (d+.d+)")
93
+ # For Questa family, we will get the version from the path.
94
+ # 1. (if present): {path}/../docs/rlsnotes/RELEASE_NOTES.txt, where first line
95
+ # shows the full version.
96
+ # 2. else, use the path
97
+ self._try_set_version_from_release_notes()
98
+
99
+ if not self._VERSION:
100
+ self._try_set_version_from_path()
101
+
61
102
  return self._VERSION
62
103
 
63
104
  def set_tool_defines(self):
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=}")
@@ -35,6 +35,7 @@ class ToolRiviera(ToolQuesta):
35
35
  self.sim_exe = path
36
36
  self.sim_exe_base_path, _ = os.path.split(path)
37
37
 
38
+ # vsim -version is very fast and does not require a license:
38
39
  version_ret = subprocess.run(
39
40
  [self.sim_exe, '-version'],
40
41
  capture_output=True,
@@ -56,7 +57,7 @@ class ToolRiviera(ToolQuesta):
56
57
  # Aldec, Inc. Riviera-PRO version 2025.04.139.9738 built for Linux64 on May 30, 2025
57
58
  left, right = stdout.split('version')
58
59
  if 'Riviera' not in left:
59
- self.error(f'{stdout}: does not show Riviera')
60
+ util.warning(f'riviera version parsing: {stdout}: does not show "Riviera"')
60
61
  self._VERSION = right.split()[0]
61
62
  return self._VERSION
62
63