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