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.
- opencos/commands/deps_help.py +63 -113
- opencos/commands/export.py +7 -2
- opencos/commands/multi.py +4 -4
- opencos/commands/sim.py +14 -15
- opencos/commands/sweep.py +1 -1
- opencos/commands/synth.py +1 -2
- opencos/commands/upload.py +192 -4
- opencos/commands/waves.py +52 -8
- opencos/deps/deps_commands.py +6 -6
- opencos/deps/deps_processor.py +129 -50
- opencos/docs/Architecture.md +45 -0
- opencos/docs/ConnectingApps.md +29 -0
- opencos/docs/DEPS.md +199 -0
- opencos/docs/Debug.md +77 -0
- opencos/docs/DirectoryStructure.md +22 -0
- opencos/docs/Installation.md +117 -0
- opencos/docs/OcVivadoTcl.md +63 -0
- opencos/docs/OpenQuestions.md +7 -0
- opencos/docs/README.md +13 -0
- opencos/docs/RtlCodingStyle.md +54 -0
- opencos/docs/eda.md +147 -0
- opencos/docs/oc_cli.md +135 -0
- opencos/eda.py +358 -155
- opencos/eda_base.py +187 -60
- opencos/eda_config.py +70 -35
- opencos/eda_config_defaults.yml +310 -186
- opencos/eda_config_reduced.yml +19 -39
- opencos/eda_tool_helper.py +190 -21
- opencos/files.py +26 -1
- opencos/tools/cocotb.py +11 -23
- opencos/tools/invio.py +2 -2
- opencos/tools/invio_yosys.py +2 -1
- opencos/tools/iverilog.py +3 -3
- opencos/tools/modelsim_ase.py +1 -1
- opencos/tools/quartus.py +172 -137
- opencos/tools/questa_common.py +50 -9
- opencos/tools/riviera.py +5 -4
- opencos/tools/slang.py +14 -10
- opencos/tools/slang_yosys.py +1 -0
- opencos/tools/surelog.py +7 -6
- opencos/tools/verilator.py +9 -7
- opencos/tools/vivado.py +315 -180
- opencos/tools/yosys.py +5 -5
- opencos/util.py +6 -3
- opencos/utils/dict_helpers.py +31 -0
- opencos/utils/markup_helpers.py +2 -2
- opencos/utils/str_helpers.py +38 -0
- opencos/utils/subprocess_helpers.py +3 -3
- opencos/utils/vscode_helper.py +2 -2
- opencos/utils/vsim_helper.py +16 -5
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/METADATA +1 -1
- opencos_eda-0.3.12.dist-info/RECORD +93 -0
- opencos/eda_config_max_verilator_waivers.yml +0 -39
- opencos_eda-0.3.10.dist-info/RECORD +0 -81
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.10.dist-info → opencos_eda-0.3.12.dist-info}/licenses/LICENSE.spdx +0 -0
- {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
|
|
18
|
-
from opencos.
|
|
19
|
-
|
|
20
|
-
|
|
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 =
|
|
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
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
189
|
-
#
|
|
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']).
|
|
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']).
|
|
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']).
|
|
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(
|
|
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
|
-
|
|
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']).
|
|
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']).
|
|
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']).
|
|
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(
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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['
|
|
636
|
-
if os.path.isfile(self.args['
|
|
637
|
-
sof_file = self.args['
|
|
638
|
-
|
|
639
|
-
|
|
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
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
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
|
|
opencos/tools/questa_common.py
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|