opencos-eda 0.2.52__py3-none-any.whl → 0.2.54__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/__init__.py +2 -0
- opencos/commands/build.py +1 -1
- opencos/commands/deps_help.py +259 -0
- opencos/commands/export.py +1 -1
- opencos/commands/flist.py +4 -1
- opencos/commands/lec.py +1 -1
- opencos/commands/open.py +2 -0
- opencos/commands/proj.py +1 -1
- opencos/commands/shell.py +1 -1
- opencos/commands/sim.py +76 -8
- opencos/commands/synth.py +1 -1
- opencos/commands/upload.py +3 -0
- opencos/commands/waves.py +1 -0
- opencos/deps/defaults.py +1 -0
- opencos/deps/deps_file.py +30 -4
- opencos/deps/deps_processor.py +72 -2
- opencos/deps_schema.py +3 -0
- opencos/eda.py +50 -26
- opencos/eda_base.py +177 -33
- opencos/eda_config.py +1 -1
- opencos/eda_config_defaults.yml +49 -3
- opencos/eda_extract_targets.py +1 -58
- opencos/tests/helpers.py +16 -0
- opencos/tests/test_eda.py +14 -3
- opencos/tests/test_tools.py +159 -132
- opencos/tools/cocotb.py +15 -14
- opencos/tools/iverilog.py +4 -24
- opencos/tools/modelsim_ase.py +70 -57
- opencos/tools/quartus.py +680 -0
- opencos/tools/questa.py +158 -90
- opencos/tools/questa_fse.py +10 -0
- opencos/tools/riviera.py +1 -0
- opencos/tools/verilator.py +9 -15
- opencos/tools/vivado.py +30 -23
- opencos/util.py +89 -15
- opencos/utils/status_constants.py +1 -0
- opencos/utils/str_helpers.py +85 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/METADATA +1 -1
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/RECORD +44 -42
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.52.dist-info → opencos_eda-0.2.54.dist-info}/top_level.txt +0 -0
opencos/tools/quartus.py
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
''' opencos.tools.quartus - Used by opencos.eda commands with --tool=quartus
|
|
2
|
+
|
|
3
|
+
Contains classes for ToolQuartus, and command handlers for synth, build, flist.
|
|
4
|
+
Used for Intel FPGA synthesis, place & route, and bitstream generation.
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
# pylint: disable=R0801 # (setting similar, but not identical, self.defines key/value pairs)
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
import shutil
|
|
13
|
+
import subprocess
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
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
|
+
)
|
|
22
|
+
|
|
23
|
+
class ToolQuartus(Tool):
|
|
24
|
+
'''ToolQuartus used by opencos.eda for --tool=quartus'''
|
|
25
|
+
|
|
26
|
+
_TOOL = 'quartus'
|
|
27
|
+
_EXE = 'quartus_sh'
|
|
28
|
+
|
|
29
|
+
quartus_year = None
|
|
30
|
+
quartus_release = None
|
|
31
|
+
quartus_base_path = ''
|
|
32
|
+
quartus_exe = ''
|
|
33
|
+
quartus_gui_exe = ''
|
|
34
|
+
|
|
35
|
+
def __init__(self, config: dict):
|
|
36
|
+
super().__init__(config=config)
|
|
37
|
+
self.args.update({
|
|
38
|
+
'part': 'A3CY135BM16AE6S',
|
|
39
|
+
'family': 'Agilex 3',
|
|
40
|
+
})
|
|
41
|
+
self.args_help.update({
|
|
42
|
+
'part': 'Device used for commands: synth, build.',
|
|
43
|
+
'family': 'FPGA family for Quartus (e.g., Stratix IV, Arria 10, etc.)',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
def get_versions(self) -> str:
|
|
47
|
+
if self._VERSION:
|
|
48
|
+
return self._VERSION
|
|
49
|
+
|
|
50
|
+
path = shutil.which(self._EXE)
|
|
51
|
+
if not path:
|
|
52
|
+
self.error("Quartus not in path, need to install or add to $PATH",
|
|
53
|
+
f"(looked for '{self._EXE}')")
|
|
54
|
+
else:
|
|
55
|
+
self.quartus_exe = path
|
|
56
|
+
self.quartus_base_path, _ = os.path.split(path)
|
|
57
|
+
self.quartus_gui_exe = shutil.which('quartus') # vs quartus_sh
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# Get version based on install path name or by running quartus_sh --version
|
|
62
|
+
util.debug(f"quartus path = {self.quartus_exe}")
|
|
63
|
+
m = re.search(r'(\d+)\.(\d+)', self.quartus_exe)
|
|
64
|
+
if m:
|
|
65
|
+
version = m.group(1) + '.' + m.group(2)
|
|
66
|
+
self._VERSION = version
|
|
67
|
+
else:
|
|
68
|
+
# Try to get version by running quartus_sh --version
|
|
69
|
+
try:
|
|
70
|
+
result = subprocess.run(
|
|
71
|
+
[self.quartus_exe, '--version'],
|
|
72
|
+
capture_output=True, text=True, timeout=10, check=False
|
|
73
|
+
)
|
|
74
|
+
version_match = re.search(r'Version (\d+\.\d+)', result.stdout)
|
|
75
|
+
if version_match:
|
|
76
|
+
self._VERSION = version_match.group(1)
|
|
77
|
+
else:
|
|
78
|
+
self.error("Could not determine Quartus version")
|
|
79
|
+
except (
|
|
80
|
+
subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError
|
|
81
|
+
):
|
|
82
|
+
self.error("Could not determine Quartus version")
|
|
83
|
+
|
|
84
|
+
if self._VERSION:
|
|
85
|
+
numbers_list = self._VERSION.split('.')
|
|
86
|
+
self.quartus_year = int(numbers_list[0])
|
|
87
|
+
self.quartus_release = int(numbers_list[1])
|
|
88
|
+
else:
|
|
89
|
+
self.error(f"Quartus version not found, quartus path = {self.quartus_exe}")
|
|
90
|
+
return self._VERSION
|
|
91
|
+
|
|
92
|
+
def set_tool_defines(self) -> None:
|
|
93
|
+
self.defines['OC_TOOL_QUARTUS'] = None
|
|
94
|
+
def_year_release = f'OC_TOOL_QUARTUS_{self.quartus_year:02d}_{self.quartus_release:d}'
|
|
95
|
+
self.defines[def_year_release] = None
|
|
96
|
+
|
|
97
|
+
# Code can be conditional on Quartus versions
|
|
98
|
+
versions = ['20.1', '21.1', '22.1', '23.1', '24.1', '25.1']
|
|
99
|
+
|
|
100
|
+
def version_compare(v1, v2):
|
|
101
|
+
v1_parts = [int(x) for x in v1.split('.')]
|
|
102
|
+
v2_parts = [int(x) for x in v2.split('.')]
|
|
103
|
+
l = max(len(v1_parts), len(v2_parts))
|
|
104
|
+
v1_parts += [0] * (l - len(v1_parts))
|
|
105
|
+
v2_parts += [0] * (l - len(v2_parts))
|
|
106
|
+
return (v1_parts > v2_parts) - (v1_parts < v2_parts)
|
|
107
|
+
|
|
108
|
+
for ver in versions:
|
|
109
|
+
str_ver = ver.replace('.', '_')
|
|
110
|
+
cmp = version_compare(self._VERSION, ver)
|
|
111
|
+
if cmp <= 0:
|
|
112
|
+
self.defines[f'OC_TOOL_QUARTUS_{str_ver}_OR_OLDER'] = None
|
|
113
|
+
if cmp >= 0:
|
|
114
|
+
self.defines[f'OC_TOOL_QUARTUS_{str_ver}_OR_NEWER'] = None
|
|
115
|
+
|
|
116
|
+
util.debug(f"Setup tool defines: {self.defines}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class CommandSynthQuartus(CommandSynth, ToolQuartus):
|
|
120
|
+
'''CommandSynthQuartus is a command handler for: eda synth --tool=quartus'''
|
|
121
|
+
|
|
122
|
+
def __init__(self, config: dict):
|
|
123
|
+
CommandSynth.__init__(self, config)
|
|
124
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
125
|
+
# add args specific to this tool
|
|
126
|
+
self.args.update({
|
|
127
|
+
'gui': False,
|
|
128
|
+
'tcl-file': "synth.tcl",
|
|
129
|
+
'sdc': "",
|
|
130
|
+
'qsf': "",
|
|
131
|
+
})
|
|
132
|
+
self.args_help.update({
|
|
133
|
+
'gui': 'Run Quartus in GUI mode',
|
|
134
|
+
'tcl-file': 'name of TCL file to be created for Quartus',
|
|
135
|
+
'sdc': 'SDC constraints file',
|
|
136
|
+
'qsf': 'Quartus Settings File (.qsf)',
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
def do_it(self) -> None:
|
|
140
|
+
CommandSynth.do_it(self)
|
|
141
|
+
|
|
142
|
+
if self.is_export_enabled():
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# create TCL
|
|
146
|
+
tcl_file = os.path.abspath(
|
|
147
|
+
os.path.join(self.args['work-dir'], self.args['tcl-file'])
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.write_tcl_file(tcl_file=tcl_file)
|
|
151
|
+
|
|
152
|
+
# execute Quartus synthesis
|
|
153
|
+
command_list = [
|
|
154
|
+
self.quartus_exe, '-t', tcl_file
|
|
155
|
+
]
|
|
156
|
+
if not util.args['verbose']:
|
|
157
|
+
command_list.append('-q')
|
|
158
|
+
|
|
159
|
+
# Add artifact tracking
|
|
160
|
+
util.artifacts.add_extension(
|
|
161
|
+
search_paths=self.args['work-dir'], file_extension='qpf',
|
|
162
|
+
typ='tcl', description='Quartus Project File'
|
|
163
|
+
)
|
|
164
|
+
util.artifacts.add_extension(
|
|
165
|
+
search_paths=self.args['work-dir'], file_extension='qsf',
|
|
166
|
+
typ='tcl', description='Quartus Settings File'
|
|
167
|
+
)
|
|
168
|
+
util.artifacts.add_extension(
|
|
169
|
+
search_paths=self.args['work-dir'], file_extension='rpt',
|
|
170
|
+
typ='text', description='Quartus Synthesis Report'
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
self.exec(self.args['work-dir'], command_list)
|
|
174
|
+
util.info(f"Synthesis done, results are in: {self.args['work-dir']}")
|
|
175
|
+
|
|
176
|
+
def write_tcl_file(self, tcl_file: str) -> None: # pylint: disable=too-many-locals,too-many-branches
|
|
177
|
+
'''Writes synthesis capable Quartus tcl file to filepath 'tcl_file'.'''
|
|
178
|
+
|
|
179
|
+
top = self.args['top']
|
|
180
|
+
part = self.args['part']
|
|
181
|
+
family = self.args['family']
|
|
182
|
+
|
|
183
|
+
tcl_lines = [
|
|
184
|
+
"# Quartus Synthesis Script",
|
|
185
|
+
"load_package flow",
|
|
186
|
+
f"project_new {top} -overwrite",
|
|
187
|
+
f"set_global_assignment -name FAMILY \"{family}\"",
|
|
188
|
+
f"set_global_assignment -name DEVICE {part}",
|
|
189
|
+
f"set_global_assignment -name TOP_LEVEL_ENTITY {top}",
|
|
190
|
+
]
|
|
191
|
+
|
|
192
|
+
# Add source files (convert to relative paths and use forward slashes)
|
|
193
|
+
# Note that default of self.args['all-sv'] is False so we should have added
|
|
194
|
+
# all files to self.files_sv instead of files_v:
|
|
195
|
+
for f in self.files_v:
|
|
196
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
197
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
|
|
198
|
+
for f in self.files_sv:
|
|
199
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
200
|
+
tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
|
|
201
|
+
for f in self.files_vhd:
|
|
202
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
203
|
+
tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
|
|
204
|
+
|
|
205
|
+
# Add include directories - Quartus needs the base directory where "lib/" can be found
|
|
206
|
+
for incdir in self.incdirs:
|
|
207
|
+
tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
|
|
208
|
+
|
|
209
|
+
# Add all include directories as user libraries for better include resolution
|
|
210
|
+
for incdir in self.incdirs:
|
|
211
|
+
if os.path.exists(incdir):
|
|
212
|
+
tcl_lines.append(
|
|
213
|
+
f"set_global_assignment -name USER_LIBRARIES \"{incdir}\""
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Add defines
|
|
217
|
+
for key, value in self.defines.items():
|
|
218
|
+
if value is None:
|
|
219
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}\"")
|
|
220
|
+
else:
|
|
221
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}={value}\"")
|
|
222
|
+
|
|
223
|
+
# Add constraints
|
|
224
|
+
if self.args['sdc']:
|
|
225
|
+
tcl_lines.append(f"set_global_assignment -name SDC_FILE \"{self.args['sdc']}\"")
|
|
226
|
+
elif self.files_sdc:
|
|
227
|
+
for sdc_file in self.files_sdc:
|
|
228
|
+
tcl_lines.append(f"set_global_assignment -name SDC_FILE \"{sdc_file}\"")
|
|
229
|
+
|
|
230
|
+
tcl_lines += [
|
|
231
|
+
"# Run synthesis",
|
|
232
|
+
'flng::run_flow_command -flow "compile" -end "dni_synthesis"',
|
|
233
|
+
'flng::run_flow_command -flow "compile" -end "sta_early" -resume',
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
with open(tcl_file, 'w', encoding='utf-8') as ftcl:
|
|
237
|
+
ftcl.write('\n'.join(tcl_lines))
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class CommandBuildQuartus(CommandBuild, ToolQuartus):
|
|
241
|
+
'''CommandBuildQuartus is a command handler for: eda build --tool=quartus'''
|
|
242
|
+
|
|
243
|
+
def __init__(self, config: dict):
|
|
244
|
+
CommandBuild.__init__(self, config)
|
|
245
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
246
|
+
# add args specific to this tool
|
|
247
|
+
self.args.update({
|
|
248
|
+
'gui': False,
|
|
249
|
+
'proj': False,
|
|
250
|
+
'resynth': False,
|
|
251
|
+
'reset': False,
|
|
252
|
+
'add-tcl-files': [],
|
|
253
|
+
'flow-tcl-files': [],
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
def do_it(self) -> None: # pylint: disable=too-many-branches,too-many-statements
|
|
257
|
+
# add defines for this job
|
|
258
|
+
self.set_tool_defines()
|
|
259
|
+
self.write_eda_config_and_args()
|
|
260
|
+
|
|
261
|
+
# create FLIST
|
|
262
|
+
flist_file = os.path.abspath(os.path.join(self.args['work-dir'], 'build.flist'))
|
|
263
|
+
util.debug(f"CommandBuildQuartus: top={self.args['top']} target={self.target}",
|
|
264
|
+
f"design={self.args['design']}")
|
|
265
|
+
|
|
266
|
+
command_list = [
|
|
267
|
+
eda_base.get_eda_exec('flist'), 'flist',
|
|
268
|
+
'--no-default-log',
|
|
269
|
+
'--tool=' + self.args['tool'],
|
|
270
|
+
'--force',
|
|
271
|
+
'--out=' + flist_file,
|
|
272
|
+
'--no-quote-define',
|
|
273
|
+
'--no-quote-define-value',
|
|
274
|
+
'--no-escape-define-value',
|
|
275
|
+
'--equal-define',
|
|
276
|
+
'--bracket-quote-path',
|
|
277
|
+
# Enhanced prefixes for better Quartus integration
|
|
278
|
+
'--prefix-incdir=' + shlex.quote("set_global_assignment -name SEARCH_PATH "),
|
|
279
|
+
'--prefix-define=' + shlex.quote("set_global_assignment -name VERILOG_MACRO "),
|
|
280
|
+
'--prefix-sv=' + shlex.quote("set_global_assignment -name SYSTEMVERILOG_FILE "),
|
|
281
|
+
'--prefix-v=' + shlex.quote("set_global_assignment -name VERILOG_FILE "),
|
|
282
|
+
'--prefix-vhd=' + shlex.quote("set_global_assignment -name VHDL_FILE "),
|
|
283
|
+
'--emit-rel-path', # Use relative paths for better portability
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
# create an eda.flist_input.f that we'll pass to flist:
|
|
287
|
+
with open(os.path.join(self.args['work-dir'], 'eda.flist_input.f'),
|
|
288
|
+
'w', encoding='utf-8') as f:
|
|
289
|
+
f.write('\n'.join(self.files_v + self.files_sv + self.files_vhd + ['']))
|
|
290
|
+
command_list.append('--input-file=eda.flist_input.f')
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
for key,value in self.defines.items():
|
|
294
|
+
if value is None:
|
|
295
|
+
command_list += [ f"+define+{key}" ]
|
|
296
|
+
else:
|
|
297
|
+
command_list += [ shlex.quote(f"+define+{key}={value}") ]
|
|
298
|
+
|
|
299
|
+
# Write out a .sh command for debug
|
|
300
|
+
command_list = util.ShellCommandList(command_list, tee_fpath='run_eda_flist.log')
|
|
301
|
+
util.write_shell_command_file(dirpath=self.args['work-dir'], filename='run_eda_flist.sh',
|
|
302
|
+
command_lists=[command_list], line_breaks=True)
|
|
303
|
+
|
|
304
|
+
self.exec(work_dir=self.args['work-dir'], command_list=command_list,
|
|
305
|
+
tee_fpath=command_list.tee_fpath)
|
|
306
|
+
|
|
307
|
+
if self.args['job-name'] == "":
|
|
308
|
+
self.args['job-name'] = self.args['design']
|
|
309
|
+
project_dir = 'project.' + self.args['job-name']
|
|
310
|
+
|
|
311
|
+
# Create a simple Quartus build TCL script
|
|
312
|
+
build_tcl_file = os.path.abspath(os.path.join(self.args['work-dir'], 'build.tcl'))
|
|
313
|
+
build_tcl_lines = [
|
|
314
|
+
'# Quartus Build Script',
|
|
315
|
+
'',
|
|
316
|
+
f'set Top {self.args["top"]}'
|
|
317
|
+
'',
|
|
318
|
+
'load_package flow',
|
|
319
|
+
f'project_new {self.args["design"]} -overwrite',
|
|
320
|
+
f'set_global_assignment -name FAMILY \"{self.args["family"]}\"',
|
|
321
|
+
f'set_global_assignment -name DEVICE {self.args["part"]}',
|
|
322
|
+
'set_global_assignment -name TOP_LEVEL_ENTITY "$Top"',
|
|
323
|
+
'',
|
|
324
|
+
'# Source the flist file',
|
|
325
|
+
'source build.flist',
|
|
326
|
+
'',
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
# If we have additinal TCL files via --add-tcl-files, then source those too:
|
|
330
|
+
if self.args['add-tcl-files']:
|
|
331
|
+
build_tcl_lines.append('')
|
|
332
|
+
build_tcl_lines.append('# Source TCL files from --add-tcl-files args')
|
|
333
|
+
for fname in self.args['add-tcl-files']:
|
|
334
|
+
fname_abs = os.path.abspath(fname)
|
|
335
|
+
if not os.path.isfile(fname_abs):
|
|
336
|
+
self.error(f'add-tcl-files: "{fname_abs}"; does not exist')
|
|
337
|
+
build_tcl_lines.append(f'source {fname_abs}')
|
|
338
|
+
build_tcl_lines.append('')
|
|
339
|
+
|
|
340
|
+
# If we don't have any args for --flow-tcl-files, then use a default flow:
|
|
341
|
+
if not self.args['flow-tcl-files']:
|
|
342
|
+
build_tcl_lines.extend([
|
|
343
|
+
'# Default flow for compile',
|
|
344
|
+
'flng::run_flow_command -flow "compile"',
|
|
345
|
+
''
|
|
346
|
+
])
|
|
347
|
+
else:
|
|
348
|
+
build_tcl_lines.append('')
|
|
349
|
+
build_tcl_lines.append('# Flow TCL files from --flow-tcl-files args')
|
|
350
|
+
for fname in self.args['flow-tcl-files']:
|
|
351
|
+
fname_abs = os.path.abspath(fname)
|
|
352
|
+
if not os.path.isfile(fname_abs):
|
|
353
|
+
self.error(f'flow-tcl-files: "{fname_abs}"; does not exist')
|
|
354
|
+
build_tcl_lines.append(f'source {fname_abs}')
|
|
355
|
+
build_tcl_lines.append('')
|
|
356
|
+
|
|
357
|
+
with open(build_tcl_file, 'w', encoding='utf-8') as ftcl:
|
|
358
|
+
ftcl.write('\n'.join(build_tcl_lines))
|
|
359
|
+
|
|
360
|
+
# launch Quartus build, from work-dir:
|
|
361
|
+
command_list_gui = [self.quartus_gui_exe, '-t', 'build.tcl', project_dir]
|
|
362
|
+
command_list = [self.quartus_exe, '-t', 'build.tcl', project_dir]
|
|
363
|
+
saved_qpf_filename = self.args["design"] + '.qpf'
|
|
364
|
+
if not util.args['verbose']:
|
|
365
|
+
command_list.append('-q')
|
|
366
|
+
|
|
367
|
+
# Write out a .sh command for debug
|
|
368
|
+
command_list = util.ShellCommandList(command_list, tee_fpath=None)
|
|
369
|
+
util.write_shell_command_file(dirpath=self.args['work-dir'], filename='run_quartus.sh',
|
|
370
|
+
command_lists=[command_list], line_breaks=True)
|
|
371
|
+
util.write_shell_command_file(dirpath=self.args['work-dir'], filename='run_quartus_gui.sh',
|
|
372
|
+
command_lists=[
|
|
373
|
+
command_list_gui,
|
|
374
|
+
['quartus', saved_qpf_filename], # reopen when done.
|
|
375
|
+
], line_breaks=True)
|
|
376
|
+
|
|
377
|
+
# Add artifact tracking for build
|
|
378
|
+
artifacts_search_paths = [
|
|
379
|
+
self.args['work-dir'],
|
|
380
|
+
os.path.join(self.args['work-dir'], 'output_files'),
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
util.artifacts.add_extension(
|
|
384
|
+
search_paths=artifacts_search_paths, file_extension='sof',
|
|
385
|
+
typ='bitstream', description='Quartus SRAM Object File (bitstream)'
|
|
386
|
+
)
|
|
387
|
+
util.artifacts.add_extension(
|
|
388
|
+
search_paths=artifacts_search_paths, file_extension='pof',
|
|
389
|
+
typ='bitstream', description='Quartus Programmer Object File'
|
|
390
|
+
)
|
|
391
|
+
util.artifacts.add_extension(
|
|
392
|
+
search_paths=artifacts_search_paths, file_extension='rpt',
|
|
393
|
+
typ='text', description='Quartus Timing, Fitter, or other report'
|
|
394
|
+
)
|
|
395
|
+
util.artifacts.add_extension(
|
|
396
|
+
search_paths=artifacts_search_paths, file_extension='summary',
|
|
397
|
+
typ='text', description='Quartus Timing, Fitter, or other summary'
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if self.args['stop-before-compile']:
|
|
401
|
+
util.info(f"--stop-before-compile set: scripts in : {self.args['work-dir']}")
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if self.args['gui'] and self.quartus_gui_exe:
|
|
406
|
+
self.exec(
|
|
407
|
+
work_dir=self.args['work-dir'], command_list=command_list_gui
|
|
408
|
+
)
|
|
409
|
+
else:
|
|
410
|
+
self.exec(
|
|
411
|
+
work_dir=self.args['work-dir'], command_list=command_list,
|
|
412
|
+
tee_fpath=command_list.tee_fpath
|
|
413
|
+
)
|
|
414
|
+
if not os.path.isfile(os.path.join(self.args['work-dir'], saved_qpf_filename)):
|
|
415
|
+
self.error('Saved project file does not exist:',
|
|
416
|
+
os.path.join(self.args['work-dir'], saved_qpf_filename))
|
|
417
|
+
|
|
418
|
+
util.info(f"Build done, results are in: {self.args['work-dir']}")
|
|
419
|
+
|
|
420
|
+
# Note: in GUI mode, if you ran: quaruts -t build.tcl, it will exit on completion,
|
|
421
|
+
# so we'll re-open the project.
|
|
422
|
+
if self.args['gui'] and self.quartus_gui_exe:
|
|
423
|
+
self.exec(
|
|
424
|
+
work_dir=self.args['work-dir'],
|
|
425
|
+
command_list=['quartus', saved_qpf_filename]
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class CommandFListQuartus(CommandFList, ToolQuartus):
|
|
430
|
+
'''CommandFListQuartus is a command handler for: eda flist --tool=quartus'''
|
|
431
|
+
|
|
432
|
+
def __init__(self, config: dict):
|
|
433
|
+
CommandFList.__init__(self, config=config)
|
|
434
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class CommandProjQuartus(CommandProj, ToolQuartus):
|
|
438
|
+
'''CommandProjQuartus is a command handler for: eda proj --tool=quartus'''
|
|
439
|
+
|
|
440
|
+
def __init__(self, config: dict):
|
|
441
|
+
CommandProj.__init__(self, config)
|
|
442
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
443
|
+
# add args specific to this tool
|
|
444
|
+
self.args.update({
|
|
445
|
+
'gui': True,
|
|
446
|
+
'tcl-file': "proj.tcl",
|
|
447
|
+
})
|
|
448
|
+
self.args_help.update({
|
|
449
|
+
'gui': 'Open Quartus in GUI mode (always True for proj)',
|
|
450
|
+
'tcl-file': 'name of TCL file to be created for Quartus project',
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
def do_it(self):
|
|
454
|
+
# add defines for this job
|
|
455
|
+
self.set_tool_defines()
|
|
456
|
+
self.write_eda_config_and_args()
|
|
457
|
+
|
|
458
|
+
# create TCL
|
|
459
|
+
tcl_file = os.path.abspath(os.path.join(self.args['work-dir'], self.args['tcl-file']))
|
|
460
|
+
|
|
461
|
+
part = self.args['part']
|
|
462
|
+
family = self.args['family']
|
|
463
|
+
top = self.args['top']
|
|
464
|
+
|
|
465
|
+
tcl_lines = [
|
|
466
|
+
"# Quartus Project Creation Script",
|
|
467
|
+
"load_package flow",
|
|
468
|
+
f"project_new {top}_proj -overwrite",
|
|
469
|
+
f"set_global_assignment -name FAMILY \"{family}\"",
|
|
470
|
+
f"set_global_assignment -name DEVICE {part}",
|
|
471
|
+
f"set_global_assignment -name TOP_LEVEL_ENTITY {top}",
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
# Add source files
|
|
475
|
+
for f in self.files_v:
|
|
476
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
477
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_FILE \"{rel_path}\"")
|
|
478
|
+
for f in self.files_sv:
|
|
479
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
480
|
+
tcl_lines.append(f"set_global_assignment -name SYSTEMVERILOG_FILE \"{rel_path}\"")
|
|
481
|
+
for f in self.files_vhd:
|
|
482
|
+
rel_path = os.path.relpath(f, self.args['work-dir']).replace('\\', '/')
|
|
483
|
+
tcl_lines.append(f"set_global_assignment -name VHDL_FILE \"{rel_path}\"")
|
|
484
|
+
|
|
485
|
+
# Add include directories
|
|
486
|
+
for incdir in self.incdirs:
|
|
487
|
+
tcl_lines.append(f"set_global_assignment -name SEARCH_PATH \"{incdir}\"")
|
|
488
|
+
|
|
489
|
+
# Add defines
|
|
490
|
+
for key, value in self.defines.items():
|
|
491
|
+
if value is None:
|
|
492
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}\"")
|
|
493
|
+
else:
|
|
494
|
+
tcl_lines.append(f"set_global_assignment -name VERILOG_MACRO \"{key}={value}\"")
|
|
495
|
+
|
|
496
|
+
# Add constraints if available
|
|
497
|
+
if self.files_sdc:
|
|
498
|
+
for sdc_file in self.files_sdc:
|
|
499
|
+
tcl_lines.append(f"set_global_assignment -name SDC_FILE \"{sdc_file}\"")
|
|
500
|
+
|
|
501
|
+
tcl_lines += [
|
|
502
|
+
"project_close",
|
|
503
|
+
f"project_open {top}_proj"
|
|
504
|
+
]
|
|
505
|
+
|
|
506
|
+
with open(tcl_file, 'w', encoding='utf-8') as ftcl:
|
|
507
|
+
ftcl.write('\n'.join(tcl_lines))
|
|
508
|
+
|
|
509
|
+
# execute Quartus in GUI mode
|
|
510
|
+
command_list = [
|
|
511
|
+
self.quartus_exe, '-t', tcl_file
|
|
512
|
+
]
|
|
513
|
+
if not util.args['verbose']:
|
|
514
|
+
command_list.append('-q')
|
|
515
|
+
|
|
516
|
+
self.exec(self.args['work-dir'], command_list)
|
|
517
|
+
util.info(f"Project created and opened in: {self.args['work-dir']}")
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class CommandUploadQuartus(CommandUpload, ToolQuartus):
|
|
521
|
+
'''CommandUploadQuartus is a command handler for: eda upload --tool=quartus'''
|
|
522
|
+
|
|
523
|
+
def __init__(self, config: dict):
|
|
524
|
+
CommandUpload.__init__(self, config)
|
|
525
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
526
|
+
# add args specific to this tool
|
|
527
|
+
self.args.update({
|
|
528
|
+
'sof-file': "",
|
|
529
|
+
'cable': "1",
|
|
530
|
+
'device': "1",
|
|
531
|
+
'list-cables': False,
|
|
532
|
+
'list-devices': False,
|
|
533
|
+
'list-sof-files': False,
|
|
534
|
+
'tcl-file': "upload.tcl",
|
|
535
|
+
'log-file': "upload.log",
|
|
536
|
+
})
|
|
537
|
+
self.args_help.update({
|
|
538
|
+
'sof-file': 'SOF file to upload (auto-detected if not specified)',
|
|
539
|
+
'cable': 'Cable number to use for programming',
|
|
540
|
+
'device': 'Device number on the cable',
|
|
541
|
+
'list-cables': 'List available programming cables',
|
|
542
|
+
'list-devices': 'List available devices on cable',
|
|
543
|
+
'list-sof-files': 'List available SOF files',
|
|
544
|
+
'tcl-file': 'name of TCL file to be created for upload',
|
|
545
|
+
'log-file': 'log file for upload operation',
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
def do_it(self): # pylint: disable=too-many-branches,too-many-statements,too-many-locals
|
|
549
|
+
# add defines for this job
|
|
550
|
+
self.set_tool_defines()
|
|
551
|
+
self.write_eda_config_and_args()
|
|
552
|
+
|
|
553
|
+
sof_file = None
|
|
554
|
+
if self.args['sof-file']:
|
|
555
|
+
if os.path.isfile(self.args['sof-file']):
|
|
556
|
+
sof_file = self.args['sof-file']
|
|
557
|
+
else:
|
|
558
|
+
self.error(f"Specified SOF file does not exist: {self.args['sof-file']}")
|
|
559
|
+
|
|
560
|
+
# Auto-discover SOF file if not specified
|
|
561
|
+
if not sof_file and not self.args['list-cables'] and not self.args['list-devices']:
|
|
562
|
+
sof_files = []
|
|
563
|
+
util.debug(f"Looking for SOF files in {os.path.abspath('.')}")
|
|
564
|
+
for root, _, files in os.walk("."):
|
|
565
|
+
for f in files:
|
|
566
|
+
if f.endswith(".sof"):
|
|
567
|
+
fullpath = os.path.abspath(os.path.join(root, f))
|
|
568
|
+
sof_files.append(fullpath)
|
|
569
|
+
util.info(f"Found SOF file: {fullpath}")
|
|
570
|
+
|
|
571
|
+
if len(sof_files) == 1:
|
|
572
|
+
sof_file = sof_files[0]
|
|
573
|
+
elif len(sof_files) > 1:
|
|
574
|
+
if self.args['list-sof-files']:
|
|
575
|
+
util.info("Multiple SOF files found:")
|
|
576
|
+
for sf in sof_files:
|
|
577
|
+
util.info(f" {sf}")
|
|
578
|
+
return
|
|
579
|
+
self.error("Multiple SOF files found, please specify --sof-file")
|
|
580
|
+
elif not sof_files:
|
|
581
|
+
if self.args['list-sof-files']:
|
|
582
|
+
util.info("No SOF files found")
|
|
583
|
+
return
|
|
584
|
+
self.error("No SOF files found")
|
|
585
|
+
|
|
586
|
+
# Generate TCL script
|
|
587
|
+
script_file = Path(self.args['tcl-file'])
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
with script_file.open("w", encoding="utf-8") as fout:
|
|
591
|
+
fout.write('load_package quartus_pgm\n')
|
|
592
|
+
|
|
593
|
+
if self.args['list-cables']:
|
|
594
|
+
fout.write('foreach cable [get_hardware_names] {\n')
|
|
595
|
+
fout.write(' puts "Cable: $cable"\n')
|
|
596
|
+
fout.write('}\n')
|
|
597
|
+
|
|
598
|
+
if self.args['list-devices']:
|
|
599
|
+
cable_idx = int(self.args["cable"]) - 1
|
|
600
|
+
fout.write(f'set cable [lindex [get_hardware_names] {cable_idx}]\n')
|
|
601
|
+
fout.write('foreach device [get_device_names -hardware_name $cable] {\n')
|
|
602
|
+
fout.write(' puts "Device: $device"\n')
|
|
603
|
+
fout.write('}\n')
|
|
604
|
+
|
|
605
|
+
if sof_file:
|
|
606
|
+
cable_idx2 = int(self.args["cable"]) - 1
|
|
607
|
+
device_idx = int(self.args["device"]) - 1
|
|
608
|
+
fout.write(f'set cable [lindex [get_hardware_names] {cable_idx2}]\n')
|
|
609
|
+
device_cmd = (
|
|
610
|
+
f'set device [lindex [get_device_names -hardware_name $cable] {device_idx}]'
|
|
611
|
+
)
|
|
612
|
+
fout.write(device_cmd)
|
|
613
|
+
fout.write('set_global_assignment -name USE_CONFIGURATION_DEVICE OFF\n')
|
|
614
|
+
fout.write('execute_flow -compile\n')
|
|
615
|
+
fout.write(f'quartus_pgm -c $cable -m jtag -o "p;{sof_file}@$device"\n')
|
|
616
|
+
|
|
617
|
+
except Exception as exc:
|
|
618
|
+
self.error(f"Cannot create {script_file}: {exc}")
|
|
619
|
+
|
|
620
|
+
if sof_file:
|
|
621
|
+
util.info(f"Programming with SOF file: {sof_file}")
|
|
622
|
+
else:
|
|
623
|
+
util.info("Listing cables/devices only")
|
|
624
|
+
|
|
625
|
+
# Execute Quartus programmer
|
|
626
|
+
command_list = [
|
|
627
|
+
self.quartus_exe, '-t', str(script_file)
|
|
628
|
+
]
|
|
629
|
+
if not util.args['verbose']:
|
|
630
|
+
command_list.append('-q')
|
|
631
|
+
|
|
632
|
+
self.exec(self.args['work-dir'], command_list)
|
|
633
|
+
util.info("Upload operation completed")
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
class CommandOpenQuartus(CommandOpen, ToolQuartus):
|
|
637
|
+
'''CommandOpenQuartus is a command handler for: eda open --tool=quartus'''
|
|
638
|
+
|
|
639
|
+
def __init__(self, config: dict):
|
|
640
|
+
CommandOpen.__init__(self, config)
|
|
641
|
+
ToolQuartus.__init__(self, config=self.config)
|
|
642
|
+
# add args specific to this tool
|
|
643
|
+
self.args.update({
|
|
644
|
+
'file': "",
|
|
645
|
+
'gui': True,
|
|
646
|
+
})
|
|
647
|
+
self.args_help.update({
|
|
648
|
+
'file': 'Quartus project file (.qpf) to open (auto-detected if not specified)',
|
|
649
|
+
'gui': 'Open Quartus in GUI mode (always True for open)',
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
def do_it(self):
|
|
653
|
+
if not self.args['file']:
|
|
654
|
+
util.info("Searching for Quartus project...")
|
|
655
|
+
found_file = False
|
|
656
|
+
all_files = []
|
|
657
|
+
for root, _, files in os.walk("."):
|
|
658
|
+
for file in files:
|
|
659
|
+
if file.endswith(".qpf"):
|
|
660
|
+
found_file = os.path.abspath(os.path.join(root, file))
|
|
661
|
+
util.info(f"Found project: {found_file}")
|
|
662
|
+
all_files.append(found_file)
|
|
663
|
+
self.args['file'] = found_file
|
|
664
|
+
if len(all_files) > 1:
|
|
665
|
+
all_files.sort(key=os.path.getmtime)
|
|
666
|
+
self.args['file'] = all_files[-1]
|
|
667
|
+
util.info(f"Choosing: {self.args['file']} (newest)")
|
|
668
|
+
|
|
669
|
+
if not self.args['file']:
|
|
670
|
+
self.error("Couldn't find a QPF Quartus project to open")
|
|
671
|
+
|
|
672
|
+
projdir = os.path.dirname(self.args['file'])
|
|
673
|
+
|
|
674
|
+
command_list = [
|
|
675
|
+
self.quartus_exe, self.args['file']
|
|
676
|
+
]
|
|
677
|
+
|
|
678
|
+
self.write_eda_config_and_args()
|
|
679
|
+
self.exec(projdir, command_list)
|
|
680
|
+
util.info(f"Opened Quartus project: {self.args['file']}")
|