labview-fpga-hdl-tools 2.0.0.dev2__tar.gz → 2.0.0.dev3__tar.gz
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.
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/PKG-INFO +1 -1
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/check_syntax.py +17 -24
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/common.py +67 -22
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/compile_modelsim_lib.py +19 -23
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/compile_project.py +21 -28
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/create_lvbitx.py +3 -2
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/create_modelsim_project.py +3 -5
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/create_vivado_project.py +35 -37
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/gen_labview_target_plugin.py +19 -33
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/generate_vhdl.py +15 -13
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/get_window_netlist.py +15 -13
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/install_dependencies.py +13 -3
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/install_labview_target_plugin.py +7 -12
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/launch_modelsim.py +9 -8
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/launch_vivado.py +14 -9
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/migrate_clip.py +20 -21
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/sim_modelsim.py +3 -4
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/pyproject.toml +1 -1
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/LICENSE +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/README.md +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/.gitignore +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/__init__.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/__main__.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/command_config.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/command_hooks.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/nihdlsettings_default.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/process_constraints.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev2 → labview_fpga_hdl_tools-2.0.0.dev3}/labview_fpga_hdl_tools/reporting.py +0 -0
|
@@ -83,12 +83,9 @@ def _validate_ini(config):
|
|
|
83
83
|
if invalid_path:
|
|
84
84
|
invalid_paths.append(invalid_path)
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if missing_settings or invalid_paths:
|
|
90
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
91
|
-
raise ValueError(error_msg)
|
|
86
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
87
|
+
if error:
|
|
88
|
+
raise ValueError(error)
|
|
92
89
|
|
|
93
90
|
|
|
94
91
|
def _get_check_syntax_status_from_log(log_contents):
|
|
@@ -109,16 +106,7 @@ def _get_check_syntax_status_from_log(log_contents):
|
|
|
109
106
|
|
|
110
107
|
def _run_check_syntax(config, generated_tcl_path):
|
|
111
108
|
"""Run the generated check-syntax TCL script in Vivado batch mode."""
|
|
112
|
-
|
|
113
|
-
if not vivado_executable:
|
|
114
|
-
raise ValueError("VivadoToolsFolder setting is missing from configuration")
|
|
115
|
-
|
|
116
|
-
vivado_abs = os.path.abspath(vivado_executable)
|
|
117
|
-
if not os.path.exists(vivado_abs):
|
|
118
|
-
raise FileNotFoundError(
|
|
119
|
-
f"Vivado executable not found at: {vivado_abs}\n"
|
|
120
|
-
f"Please check your --vivado argument or VivadoToolsFolder setting in nihdlsettings.py"
|
|
121
|
-
)
|
|
109
|
+
vivado_abs = common.resolve_vivado_executable_abs(config)
|
|
122
110
|
|
|
123
111
|
vivado_project_dir = os.path.join(os.getcwd(), config.vivado_project_folder)
|
|
124
112
|
log_path = os.path.join(vivado_project_dir, "check_syntax.log")
|
|
@@ -128,19 +116,24 @@ def _run_check_syntax(config, generated_tcl_path):
|
|
|
128
116
|
if os.path.exists(path):
|
|
129
117
|
os.remove(path)
|
|
130
118
|
|
|
131
|
-
command =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
119
|
+
command = [
|
|
120
|
+
vivado_abs,
|
|
121
|
+
"-mode",
|
|
122
|
+
"batch",
|
|
123
|
+
"-source",
|
|
124
|
+
generated_tcl_path,
|
|
125
|
+
"-log",
|
|
126
|
+
log_path,
|
|
127
|
+
"-journal",
|
|
128
|
+
journal_path,
|
|
129
|
+
]
|
|
137
130
|
|
|
138
131
|
reporter.detail(f"Generated TCL script: {generated_tcl_path}")
|
|
139
132
|
reporter.detail(f"Vivado executable: {vivado_abs}")
|
|
140
133
|
reporter.detail(f"Working directory: {vivado_project_dir}")
|
|
141
|
-
reporter.detail(f"Running command: {command}")
|
|
134
|
+
reporter.detail(f"Running command: {' '.join(command)}")
|
|
142
135
|
|
|
143
|
-
result = subprocess.run(command, cwd=vivado_project_dir,
|
|
136
|
+
result = subprocess.run(command, cwd=vivado_project_dir, check=False)
|
|
144
137
|
|
|
145
138
|
if not os.path.exists(log_path):
|
|
146
139
|
raise RuntimeError("Vivado check-syntax log file was not created.")
|
|
@@ -237,7 +237,8 @@ def _parse_vhdl_entity(vhdl_path):
|
|
|
237
237
|
|
|
238
238
|
except Exception as e:
|
|
239
239
|
reporter.error(f"Error parsing VHDL file: {str(e)}")
|
|
240
|
-
|
|
240
|
+
if reporter.verbose:
|
|
241
|
+
traceback.print_exc()
|
|
241
242
|
return None, []
|
|
242
243
|
|
|
243
244
|
|
|
@@ -406,10 +407,16 @@ def apply_hdl_excludes(file_list, excluded_abspaths):
|
|
|
406
407
|
|
|
407
408
|
|
|
408
409
|
def run_command(cmd, cwd=None, capture_output=True, check=False):
|
|
409
|
-
"""Run a
|
|
410
|
+
"""Run a command and return its output.
|
|
411
|
+
|
|
412
|
+
The command is given as a pre-tokenized argument list (for example
|
|
413
|
+
``[vivado_abs, "-mode", "batch", ...]``) and is executed directly without a
|
|
414
|
+
shell. Passing an argument list means arguments containing spaces or
|
|
415
|
+
shell-significant characters are passed through verbatim (no quoting bugs)
|
|
416
|
+
and there is no shell-injection surface.
|
|
410
417
|
|
|
411
418
|
Args:
|
|
412
|
-
cmd:
|
|
419
|
+
cmd: The command to run, as a list of arguments.
|
|
413
420
|
cwd: Working directory to run the command in, if any.
|
|
414
421
|
capture_output: When True, capture stdout and return it as a string.
|
|
415
422
|
When False, let the command's output stream to the console and
|
|
@@ -421,7 +428,7 @@ def run_command(cmd, cwd=None, capture_output=True, check=False):
|
|
|
421
428
|
The captured stdout (stripped) when ``capture_output`` is True, otherwise
|
|
422
429
|
an empty string.
|
|
423
430
|
"""
|
|
424
|
-
reporter.detail(f"Running command: {cmd}")
|
|
431
|
+
reporter.detail(f"Running command: {' '.join(str(arg) for arg in cmd)}")
|
|
425
432
|
|
|
426
433
|
kwargs = {}
|
|
427
434
|
if cwd:
|
|
@@ -429,14 +436,12 @@ def run_command(cmd, cwd=None, capture_output=True, check=False):
|
|
|
429
436
|
|
|
430
437
|
if capture_output:
|
|
431
438
|
# Capture and return output
|
|
432
|
-
result = subprocess.run(
|
|
433
|
-
cmd, shell=True, text=True, capture_output=True, check=check, **kwargs
|
|
434
|
-
)
|
|
439
|
+
result = subprocess.run(cmd, text=True, capture_output=True, check=check, **kwargs)
|
|
435
440
|
# Check if stdout is None before calling strip()
|
|
436
441
|
return result.stdout.strip() if result.stdout is not None else ""
|
|
437
442
|
else:
|
|
438
443
|
# Don't capture output (let it go to console)
|
|
439
|
-
subprocess.run(cmd,
|
|
444
|
+
subprocess.run(cmd, check=check, **kwargs)
|
|
440
445
|
return "" # Return empty string instead of None
|
|
441
446
|
|
|
442
447
|
|
|
@@ -495,6 +500,48 @@ def get_invalid_paths_error(invalid_paths):
|
|
|
495
500
|
return error_msg
|
|
496
501
|
|
|
497
502
|
|
|
503
|
+
def build_settings_error(missing_settings, invalid_paths):
|
|
504
|
+
"""Return the standard missing-settings / invalid-paths message, or "" if none.
|
|
505
|
+
|
|
506
|
+
Combines the missing-settings and invalid-path messages with the shared hint
|
|
507
|
+
used by every ``_validate_ini``. Returns an empty string when there is
|
|
508
|
+
nothing to report, so callers can do ``if error: raise ValueError(error)``
|
|
509
|
+
(or report-and-return) without repeating the concatenation and hint text.
|
|
510
|
+
"""
|
|
511
|
+
if not (missing_settings or invalid_paths):
|
|
512
|
+
return ""
|
|
513
|
+
return (
|
|
514
|
+
get_missing_settings_error(missing_settings)
|
|
515
|
+
+ get_invalid_paths_error(invalid_paths)
|
|
516
|
+
+ "\nPlease update your configuration file and try again."
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def resolve_vivado_executable_abs(config):
|
|
521
|
+
"""Resolve the Vivado executable to an absolute path.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
config: CommandConfiguration providing ``vivado_tools_folder``.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
str: Absolute path to the Vivado executable.
|
|
528
|
+
|
|
529
|
+
Raises:
|
|
530
|
+
ValueError: if the executable cannot be resolved from the configuration.
|
|
531
|
+
FileNotFoundError: if the resolved executable does not exist on disk.
|
|
532
|
+
"""
|
|
533
|
+
vivado_executable = get_vivado_executable(config.vivado_tools_folder)
|
|
534
|
+
if not vivado_executable:
|
|
535
|
+
raise ValueError("VivadoToolsFolder setting is missing from configuration")
|
|
536
|
+
vivado_abs = os.path.abspath(vivado_executable)
|
|
537
|
+
if not os.path.exists(vivado_abs):
|
|
538
|
+
raise FileNotFoundError(
|
|
539
|
+
f"Vivado executable not found at: {vivado_abs}\n"
|
|
540
|
+
"Please check your VivadoToolsFolder setting in nihdlsettings.py"
|
|
541
|
+
)
|
|
542
|
+
return vivado_abs
|
|
543
|
+
|
|
544
|
+
|
|
498
545
|
def collect_missing_settings(config, required):
|
|
499
546
|
"""Return the labels of required settings that are unset (falsy) on config.
|
|
500
547
|
|
|
@@ -517,23 +564,21 @@ def collect_missing_settings(config, required):
|
|
|
517
564
|
def raise_for_missing_settings(missing_settings, invalid_paths):
|
|
518
565
|
"""Raise ``ValueError`` for missing settings or invalid paths, if any.
|
|
519
566
|
|
|
520
|
-
Missing settings take precedence over invalid paths
|
|
521
|
-
|
|
522
|
-
|
|
567
|
+
Missing settings take precedence over invalid paths (only one message is
|
|
568
|
+
raised). Uses the shared ``get_missing_settings_error`` /
|
|
569
|
+
``get_invalid_paths_error`` builders so the wording matches the
|
|
570
|
+
``_validate_ini`` validators. Does nothing when both lists are empty.
|
|
523
571
|
"""
|
|
524
572
|
if missing_settings:
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
raise ValueError(error_msg)
|
|
530
|
-
|
|
573
|
+
raise ValueError(
|
|
574
|
+
get_missing_settings_error(missing_settings)
|
|
575
|
+
+ "\nPlease update your configuration file and try again."
|
|
576
|
+
)
|
|
531
577
|
if invalid_paths:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
raise ValueError(error_msg)
|
|
578
|
+
raise ValueError(
|
|
579
|
+
get_invalid_paths_error(invalid_paths)
|
|
580
|
+
+ "\nPlease update your configuration file and try again."
|
|
581
|
+
)
|
|
537
582
|
|
|
538
583
|
|
|
539
584
|
def generate_guid():
|
|
@@ -69,11 +69,9 @@ def _validate_ini(config):
|
|
|
69
69
|
f"{config.modelsim_tools_folder}"
|
|
70
70
|
)
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
76
|
-
raise ValueError(error_msg)
|
|
72
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
73
|
+
if error:
|
|
74
|
+
raise ValueError(error)
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
def _libraries_already_built(out_folder, libraries):
|
|
@@ -134,16 +132,7 @@ def _is_progress_line(line):
|
|
|
134
132
|
|
|
135
133
|
def _run_compile_simlib(config, tcl_path, out_folder, libraries):
|
|
136
134
|
"""Run the generated compile_simlib TCL script in Vivado batch mode."""
|
|
137
|
-
|
|
138
|
-
if not vivado_executable:
|
|
139
|
-
raise ValueError("VivadoToolsFolder setting is missing from configuration")
|
|
140
|
-
|
|
141
|
-
vivado_abs = os.path.abspath(vivado_executable)
|
|
142
|
-
if not os.path.exists(vivado_abs):
|
|
143
|
-
raise FileNotFoundError(
|
|
144
|
-
f"Vivado executable not found at: {vivado_abs}\n"
|
|
145
|
-
f"Please check your VivadoToolsFolder setting in nihdlsettings.py"
|
|
146
|
-
)
|
|
135
|
+
vivado_abs = common.resolve_vivado_executable_abs(config)
|
|
147
136
|
|
|
148
137
|
os.makedirs(out_folder, exist_ok=True)
|
|
149
138
|
# Use a distinct name for Vivado's session log/journal. compile_simlib
|
|
@@ -165,12 +154,19 @@ def _run_compile_simlib(config, tcl_path, out_folder, libraries):
|
|
|
165
154
|
"try again."
|
|
166
155
|
) from exc
|
|
167
156
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
# Build the argument list explicitly and run without a shell so paths that
|
|
158
|
+
# contain spaces or shell-significant characters are passed through verbatim.
|
|
159
|
+
command = [
|
|
160
|
+
vivado_abs,
|
|
161
|
+
"-mode",
|
|
162
|
+
"batch",
|
|
163
|
+
"-source",
|
|
164
|
+
tcl_path,
|
|
165
|
+
"-log",
|
|
166
|
+
log_path,
|
|
167
|
+
"-journal",
|
|
168
|
+
journal_path,
|
|
169
|
+
]
|
|
174
170
|
|
|
175
171
|
# Run with the MODELSIM environment variable unset so compile_simlib does
|
|
176
172
|
# not read or modify a global modelsim.ini; it writes a fresh one into the
|
|
@@ -181,7 +177,7 @@ def _run_compile_simlib(config, tcl_path, out_folder, libraries):
|
|
|
181
177
|
reporter.detail(f"Vivado executable: {vivado_abs}")
|
|
182
178
|
reporter.detail(f"Output directory: {out_folder}")
|
|
183
179
|
reporter.detail(f"Libraries: {', '.join(libraries)}")
|
|
184
|
-
reporter.detail(f"Running command: {command}")
|
|
180
|
+
reporter.detail(f"Running command: {' '.join(command)}")
|
|
185
181
|
reporter.success("")
|
|
186
182
|
reporter.success("=" * 78)
|
|
187
183
|
reporter.success("Compiling the Xilinx ModelSim simulation libraries with Vivado.")
|
|
@@ -201,7 +197,7 @@ def _run_compile_simlib(config, tcl_path, out_folder, libraries):
|
|
|
201
197
|
process = subprocess.Popen(
|
|
202
198
|
command,
|
|
203
199
|
cwd=out_folder,
|
|
204
|
-
shell=
|
|
200
|
+
shell=False,
|
|
205
201
|
env=env,
|
|
206
202
|
stdout=subprocess.PIPE,
|
|
207
203
|
stderr=subprocess.STDOUT,
|
|
@@ -25,12 +25,12 @@ def _generate_compile_project_tcl(config, output_path):
|
|
|
25
25
|
|
|
26
26
|
def _validate_ini(config):
|
|
27
27
|
"""Validate required settings and paths for compile-project."""
|
|
28
|
-
missing_settings =
|
|
28
|
+
missing_settings = common.collect_missing_settings(
|
|
29
|
+
config,
|
|
30
|
+
[("vivado_project_folder", "VivadoProjectSettings.VivadoProjectFolder")],
|
|
31
|
+
)
|
|
29
32
|
invalid_paths = []
|
|
30
33
|
|
|
31
|
-
if not config.vivado_project_folder:
|
|
32
|
-
missing_settings.append("VivadoProjectSettings.VivadoProjectFolder")
|
|
33
|
-
|
|
34
34
|
if not config.vivado_tcl_scripts_folder:
|
|
35
35
|
missing_settings.append("VivadoProjectSettings.VivadoTclScriptsFolder")
|
|
36
36
|
else:
|
|
@@ -72,12 +72,9 @@ def _validate_ini(config):
|
|
|
72
72
|
if invalid_path:
|
|
73
73
|
invalid_paths.append(invalid_path)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if missing_settings or invalid_paths:
|
|
79
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
80
|
-
raise ValueError(error_msg)
|
|
75
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
76
|
+
if error:
|
|
77
|
+
raise ValueError(error)
|
|
81
78
|
|
|
82
79
|
|
|
83
80
|
def _get_compile_status_from_log(log_contents):
|
|
@@ -103,16 +100,7 @@ def _get_compile_status_from_log(log_contents):
|
|
|
103
100
|
|
|
104
101
|
def _run_compile_project(config, generated_tcl_path):
|
|
105
102
|
"""Run the generated compile-project TCL script in Vivado batch mode."""
|
|
106
|
-
|
|
107
|
-
if not vivado_executable:
|
|
108
|
-
raise ValueError("VivadoToolsFolder setting is missing from configuration")
|
|
109
|
-
|
|
110
|
-
vivado_abs = os.path.abspath(vivado_executable)
|
|
111
|
-
if not os.path.exists(vivado_abs):
|
|
112
|
-
raise FileNotFoundError(
|
|
113
|
-
f"Vivado executable not found at: {vivado_abs}\n"
|
|
114
|
-
f"Please check your --vivado argument or VivadoToolsFolder setting in nihdlsettings.py"
|
|
115
|
-
)
|
|
103
|
+
vivado_abs = common.resolve_vivado_executable_abs(config)
|
|
116
104
|
|
|
117
105
|
current_dir = os.getcwd()
|
|
118
106
|
vivado_project_dir = os.path.join(current_dir, config.vivado_project_folder)
|
|
@@ -123,18 +111,23 @@ def _run_compile_project(config, generated_tcl_path):
|
|
|
123
111
|
if os.path.exists(path):
|
|
124
112
|
os.remove(path)
|
|
125
113
|
|
|
126
|
-
command =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
114
|
+
command = [
|
|
115
|
+
vivado_abs,
|
|
116
|
+
"-mode",
|
|
117
|
+
"batch",
|
|
118
|
+
"-source",
|
|
119
|
+
generated_tcl_path,
|
|
120
|
+
"-log",
|
|
121
|
+
log_path,
|
|
122
|
+
"-journal",
|
|
123
|
+
journal_path,
|
|
124
|
+
]
|
|
132
125
|
|
|
133
126
|
reporter.detail(f"Vivado executable: {vivado_abs}")
|
|
134
127
|
reporter.detail(f"Working directory: {vivado_project_dir}")
|
|
135
|
-
reporter.detail(f"Running command: {command}")
|
|
128
|
+
reporter.detail(f"Running command: {' '.join(command)}")
|
|
136
129
|
|
|
137
|
-
result = subprocess.run(command, cwd=vivado_project_dir,
|
|
130
|
+
result = subprocess.run(command, cwd=vivado_project_dir, check=False)
|
|
138
131
|
|
|
139
132
|
if not os.path.exists(log_path):
|
|
140
133
|
raise RuntimeError("Vivado compile-project log file was not created.")
|
|
@@ -211,7 +211,8 @@ def create_lv_bitx(config=None):
|
|
|
211
211
|
return result # Return the result code
|
|
212
212
|
except Exception as e:
|
|
213
213
|
reporter.error(f"Unhandled exception: {str(e)}")
|
|
214
|
-
|
|
214
|
+
if reporter.verbose:
|
|
215
|
+
import traceback
|
|
215
216
|
|
|
216
|
-
|
|
217
|
+
traceback.print_exc()
|
|
217
218
|
return 1 # Return error code on exception
|
|
@@ -61,11 +61,9 @@ def _validate_ini(config):
|
|
|
61
61
|
f"{config.modelsim_tools_folder}"
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
68
|
-
raise ValueError(error_msg)
|
|
64
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
65
|
+
if error:
|
|
66
|
+
raise ValueError(error)
|
|
69
67
|
|
|
70
68
|
|
|
71
69
|
def _get_vsim_executable(modelsim_path):
|
|
@@ -18,6 +18,7 @@ The tool supports:
|
|
|
18
18
|
import glob
|
|
19
19
|
import os
|
|
20
20
|
import shutil
|
|
21
|
+
import stat
|
|
21
22
|
import subprocess
|
|
22
23
|
from collections import defaultdict
|
|
23
24
|
from enum import Enum
|
|
@@ -290,7 +291,7 @@ def _copy_long_path_files(file_list, vivado_project_dir, tcl_base_dir):
|
|
|
290
291
|
if vivado_path_len > path_len_limit:
|
|
291
292
|
target_path = os.path.join(target_folder_long, os.path.basename(file))
|
|
292
293
|
if os.path.exists(target_path):
|
|
293
|
-
os.chmod(target_path,
|
|
294
|
+
os.chmod(target_path, os.stat(target_path).st_mode | stat.S_IWUSR) # writable
|
|
294
295
|
try:
|
|
295
296
|
shutil.copy2(file, target_path)
|
|
296
297
|
new_file_list.append(target_path)
|
|
@@ -471,19 +472,16 @@ def _validate_ini(config):
|
|
|
471
472
|
Raises:
|
|
472
473
|
ValueError: If any required settings are missing or invalid
|
|
473
474
|
"""
|
|
474
|
-
missing_settings =
|
|
475
|
+
missing_settings = common.collect_missing_settings(
|
|
476
|
+
config,
|
|
477
|
+
[
|
|
478
|
+
("vivado_project_folder", "VivadoProjectSettings.VivadoProjectFolder"),
|
|
479
|
+
("top_level_entity", "VivadoProjectSettings.TopLevelEntity"),
|
|
480
|
+
("fpga_part", "VivadoProjectSettings.FPGAPart"),
|
|
481
|
+
],
|
|
482
|
+
)
|
|
475
483
|
invalid_paths = []
|
|
476
484
|
|
|
477
|
-
# Check VivadoProjectSettings
|
|
478
|
-
if not config.vivado_project_folder:
|
|
479
|
-
missing_settings.append("VivadoProjectSettings.VivadoProjectFolder")
|
|
480
|
-
|
|
481
|
-
if not config.top_level_entity:
|
|
482
|
-
missing_settings.append("VivadoProjectSettings.TopLevelEntity")
|
|
483
|
-
|
|
484
|
-
if not config.fpga_part:
|
|
485
|
-
missing_settings.append("VivadoProjectSettings.FPGAPart")
|
|
486
|
-
|
|
487
485
|
# Don't validate Vivado path if skip_vivado is set
|
|
488
486
|
if not config.skip_vivado:
|
|
489
487
|
if not config.vivado_tools_folder:
|
|
@@ -541,14 +539,9 @@ def _validate_ini(config):
|
|
|
541
539
|
if invalid_path:
|
|
542
540
|
invalid_paths.append(invalid_path)
|
|
543
541
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
# If any issues found, raise an error with the helpful message
|
|
549
|
-
if missing_settings or invalid_paths:
|
|
550
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
551
|
-
raise ValueError(error_msg)
|
|
542
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
543
|
+
if error:
|
|
544
|
+
raise ValueError(error)
|
|
552
545
|
|
|
553
546
|
|
|
554
547
|
def _validate_constraints_files(config):
|
|
@@ -563,13 +556,9 @@ def _validate_constraints_files(config):
|
|
|
563
556
|
if invalid_path:
|
|
564
557
|
invalid_paths.append(invalid_path)
|
|
565
558
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
# If any issues found, raise an error with the helpful message
|
|
570
|
-
if invalid_paths:
|
|
571
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
572
|
-
raise ValueError(error_msg)
|
|
559
|
+
error = common.build_settings_error([], invalid_paths)
|
|
560
|
+
if error:
|
|
561
|
+
raise ValueError(error)
|
|
573
562
|
|
|
574
563
|
|
|
575
564
|
def _create_project(mode: ProjectMode, config):
|
|
@@ -703,14 +692,21 @@ def _create_project(mode: ProjectMode, config):
|
|
|
703
692
|
|
|
704
693
|
if mode == ProjectMode.NEW:
|
|
705
694
|
# Create a new project
|
|
706
|
-
command =
|
|
695
|
+
command = [vivado_abs, "-mode", "batch", "-source", new_proj_path]
|
|
707
696
|
elif mode == ProjectMode.UPDATE:
|
|
708
697
|
# Update the existing project
|
|
709
|
-
command =
|
|
698
|
+
command = [
|
|
699
|
+
vivado_abs,
|
|
700
|
+
f"{project_name}.xpr",
|
|
701
|
+
"-mode",
|
|
702
|
+
"batch",
|
|
703
|
+
"-source",
|
|
704
|
+
update_proj_path,
|
|
705
|
+
]
|
|
710
706
|
else:
|
|
711
707
|
raise ValueError(f"Unsupported mode: {mode}")
|
|
712
708
|
|
|
713
|
-
reporter.detail(f"Running command: {command}")
|
|
709
|
+
reporter.detail(f"Running command: {' '.join(command)}")
|
|
714
710
|
|
|
715
711
|
# In skip_vivado mode, create a mock project file and skip Vivado execution
|
|
716
712
|
if config.skip_vivado:
|
|
@@ -723,23 +719,25 @@ def _create_project(mode: ProjectMode, config):
|
|
|
723
719
|
return 0
|
|
724
720
|
|
|
725
721
|
try:
|
|
726
|
-
output =
|
|
722
|
+
# Stream Vivado output live (capture_output=False) so the user sees
|
|
723
|
+
# progress during the slow compile and so the failure message below
|
|
724
|
+
# actually has output "above" to point at.
|
|
725
|
+
common.run_command(
|
|
727
726
|
command,
|
|
728
727
|
cwd=os.getcwd(),
|
|
728
|
+
capture_output=False,
|
|
729
729
|
check=True,
|
|
730
730
|
)
|
|
731
731
|
except subprocess.CalledProcessError as e:
|
|
732
|
-
os.chdir(current_dir)
|
|
733
732
|
action = "updating" if mode == ProjectMode.UPDATE else "creating"
|
|
734
733
|
raise RuntimeError(
|
|
735
734
|
f"Vivado exited with a non-zero status ({e.returncode}) while {action} the "
|
|
736
735
|
f"project. Review the Vivado output above and vivado.log for details."
|
|
737
736
|
) from e
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
reporter.detail(output)
|
|
737
|
+
finally:
|
|
738
|
+
# Always restore the working directory, even if Vivado raised, so later
|
|
739
|
+
# relative-path resolution in the same run is not corrupted.
|
|
740
|
+
os.chdir(current_dir)
|
|
743
741
|
|
|
744
742
|
|
|
745
743
|
def _create_project_handler(config, overwrite=False, update=False):
|
|
@@ -20,6 +20,7 @@ import csv # For reading signal definitions from CSV
|
|
|
20
20
|
import os # For file and directory operations
|
|
21
21
|
import re
|
|
22
22
|
import shutil # For file copying operations
|
|
23
|
+
import stat
|
|
23
24
|
import xml.etree.ElementTree as ET # For XML generation and manipulation # noqa: N817
|
|
24
25
|
from xml.dom.minidom import parseString # For pretty-formatted XML output
|
|
25
26
|
|
|
@@ -616,7 +617,7 @@ def _copy_fpgafiles(
|
|
|
616
617
|
target_path = os.path.join(dest_deps_folder, base_filename)
|
|
617
618
|
|
|
618
619
|
if os.path.exists(target_path):
|
|
619
|
-
os.chmod(target_path,
|
|
620
|
+
os.chmod(target_path, os.stat(target_path).st_mode | stat.S_IWUSR) # writable
|
|
620
621
|
# Check file paths before copy
|
|
621
622
|
if file and target_path:
|
|
622
623
|
shutil.copy2(file, target_path)
|
|
@@ -646,7 +647,7 @@ def _copy_menu_files(plugin_folder, menus_folder):
|
|
|
646
647
|
dst_file = os.path.join(dest_dir, file)
|
|
647
648
|
# Make destination writable if it exists
|
|
648
649
|
if os.path.exists(dst_file):
|
|
649
|
-
os.chmod(dst_file,
|
|
650
|
+
os.chmod(dst_file, os.stat(dst_file).st_mode | stat.S_IWUSR)
|
|
650
651
|
# Add null check before copy
|
|
651
652
|
if src_file is not None and dst_file is not None:
|
|
652
653
|
shutil.copy2(src_file, dst_file)
|
|
@@ -688,29 +689,19 @@ def _validate_ini(config):
|
|
|
688
689
|
# generation.
|
|
689
690
|
missing_settings, invalid_paths = generate_vhdl._validate_generated_vhdl_settings(config)
|
|
690
691
|
|
|
691
|
-
# Required
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if not config.lv_target_guid:
|
|
706
|
-
missing_settings.append("LVFPGATargetSettings.LVTargetGUID")
|
|
707
|
-
|
|
708
|
-
# Validate input files and folders
|
|
709
|
-
if not config.boardio_output:
|
|
710
|
-
missing_settings.append("LVFPGATargetSettings.BoardIOXML")
|
|
711
|
-
|
|
712
|
-
if not config.clock_output:
|
|
713
|
-
missing_settings.append("LVFPGATargetSettings.ClockXML")
|
|
692
|
+
# Required presence-only settings (list settings with path validation follow).
|
|
693
|
+
missing_settings += common.collect_missing_settings(
|
|
694
|
+
config,
|
|
695
|
+
[
|
|
696
|
+
("target_family", "GeneralSettings.TargetFamily"),
|
|
697
|
+
("base_target", "GeneralSettings.BaseTarget"),
|
|
698
|
+
("lv_target_plugin_output_folder", "LVFPGATargetSettings.LVTargetPluginOutputFolder"),
|
|
699
|
+
("lv_target_name", "LVFPGATargetSettings.LVTargetName"),
|
|
700
|
+
("lv_target_guid", "LVFPGATargetSettings.LVTargetGUID"),
|
|
701
|
+
("boardio_output", "LVFPGATargetSettings.BoardIOXML"),
|
|
702
|
+
("clock_output", "LVFPGATargetSettings.ClockXML"),
|
|
703
|
+
],
|
|
704
|
+
)
|
|
714
705
|
|
|
715
706
|
# Check list settings
|
|
716
707
|
if not config.hdl_file_lists:
|
|
@@ -750,14 +741,9 @@ def _validate_ini(config):
|
|
|
750
741
|
if invalid_path:
|
|
751
742
|
invalid_paths.append(invalid_path)
|
|
752
743
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
# If any issues found, raise an error with the helpful message
|
|
758
|
-
if missing_settings or invalid_paths:
|
|
759
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
760
|
-
raise ValueError(error_msg)
|
|
744
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
745
|
+
if error:
|
|
746
|
+
raise ValueError(error)
|
|
761
747
|
|
|
762
748
|
|
|
763
749
|
def gen_lv_target_support(config=None):
|
|
@@ -40,8 +40,13 @@ def _map_datatype_to_vhdl(data_type):
|
|
|
40
40
|
return "std_logic"
|
|
41
41
|
|
|
42
42
|
elif data_type.startswith(("U", "I")):
|
|
43
|
-
# Handle U8, U16, U32, U64, I8, I16, I32, I64
|
|
44
|
-
|
|
43
|
+
# Handle U8, U16, U32, U64, I8, I16, I32, I64. Degrade gracefully to a
|
|
44
|
+
# sentinel (like the FXP/Array branches) instead of crashing the whole
|
|
45
|
+
# flow when the CSV holds a malformed U*/I* data type.
|
|
46
|
+
try:
|
|
47
|
+
bit_width = int(data_type[1:])
|
|
48
|
+
except ValueError:
|
|
49
|
+
return "INVALID_DATA_TYPE"
|
|
45
50
|
return f"std_logic_vector({bit_width - 1} downto 0)"
|
|
46
51
|
|
|
47
52
|
elif data_type.startswith("FXP"):
|
|
@@ -220,7 +225,7 @@ def _render_generated_vhdl(template_paths, output_folder, context):
|
|
|
220
225
|
context (dict): kwargs passed to ``Template.render``.
|
|
221
226
|
|
|
222
227
|
Raises:
|
|
223
|
-
|
|
228
|
+
RuntimeError: If an error occurs during VHDL generation.
|
|
224
229
|
"""
|
|
225
230
|
try:
|
|
226
231
|
os.makedirs(output_folder, exist_ok=True)
|
|
@@ -259,7 +264,10 @@ def _validate_generated_vhdl_settings(config):
|
|
|
259
264
|
tuple: ``(missing_settings, invalid_paths)`` lists describing any
|
|
260
265
|
problems found. Both empty means the VHDL-generation settings are valid.
|
|
261
266
|
"""
|
|
262
|
-
missing_settings =
|
|
267
|
+
missing_settings = common.collect_missing_settings(
|
|
268
|
+
config,
|
|
269
|
+
[("generated_vhdl_output_folder", "LVFPGATargetSettings.GeneratedVhdlOutputFolder")],
|
|
270
|
+
)
|
|
263
271
|
invalid_paths = []
|
|
264
272
|
|
|
265
273
|
# Validate input CSV if custom IO is included
|
|
@@ -277,10 +285,6 @@ def _validate_generated_vhdl_settings(config):
|
|
|
277
285
|
if invalid_path:
|
|
278
286
|
invalid_paths.append(invalid_path)
|
|
279
287
|
|
|
280
|
-
# Validate output folder setting
|
|
281
|
-
if not config.generated_vhdl_output_folder:
|
|
282
|
-
missing_settings.append("LVFPGATargetSettings.GeneratedVhdlOutputFolder")
|
|
283
|
-
|
|
284
288
|
return missing_settings, invalid_paths
|
|
285
289
|
|
|
286
290
|
|
|
@@ -307,11 +311,9 @@ def gen_generated_vhdl(config=None):
|
|
|
307
311
|
|
|
308
312
|
# Validate that required settings for generation are present
|
|
309
313
|
missing_settings, invalid_paths = _validate_generated_vhdl_settings(config)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
314
|
-
reporter.error(f"Error: {error_msg}")
|
|
314
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
315
|
+
if error:
|
|
316
|
+
reporter.error(f"Error: {error}")
|
|
315
317
|
return 1
|
|
316
318
|
|
|
317
319
|
context = _build_generated_vhdl_context(config)
|
|
@@ -75,7 +75,14 @@ def _get_window_netlist(config):
|
|
|
75
75
|
|
|
76
76
|
try:
|
|
77
77
|
common.run_command(
|
|
78
|
-
|
|
78
|
+
[
|
|
79
|
+
vivado_abs,
|
|
80
|
+
f"{project_name}.xpr",
|
|
81
|
+
"-mode",
|
|
82
|
+
"batch",
|
|
83
|
+
"-source",
|
|
84
|
+
get_netlist_tcl_path,
|
|
85
|
+
],
|
|
79
86
|
cwd=os.getcwd(),
|
|
80
87
|
capture_output=False,
|
|
81
88
|
check=True,
|
|
@@ -248,7 +255,10 @@ def _validate_ini(config):
|
|
|
248
255
|
Raises:
|
|
249
256
|
ValueError: If any required settings are missing or paths are invalid
|
|
250
257
|
"""
|
|
251
|
-
missing_settings =
|
|
258
|
+
missing_settings = common.collect_missing_settings(
|
|
259
|
+
config,
|
|
260
|
+
[("lv_window_netlist_output_folder", "LVWindowNetlistSettings.LVWindowFolder")],
|
|
261
|
+
)
|
|
252
262
|
invalid_paths = []
|
|
253
263
|
|
|
254
264
|
# Check required paths for window netlist generation
|
|
@@ -274,9 +284,6 @@ def _validate_ini(config):
|
|
|
274
284
|
f"Vivado has issues with long paths. Please use a shorter path for your project."
|
|
275
285
|
)
|
|
276
286
|
|
|
277
|
-
if not config.lv_window_netlist_output_folder:
|
|
278
|
-
missing_settings.append("LVWindowNetlistSettings.LVWindowFolder")
|
|
279
|
-
|
|
280
287
|
# Check for Vivado tools path
|
|
281
288
|
if not config.vivado_tools_folder:
|
|
282
289
|
missing_settings.append("VivadoProjectSettings.VivadoToolsFolder")
|
|
@@ -289,14 +296,9 @@ def _validate_ini(config):
|
|
|
289
296
|
if invalid_path:
|
|
290
297
|
invalid_paths.append(invalid_path)
|
|
291
298
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# If any issues found, raise an error with the helpful message
|
|
297
|
-
if missing_settings or invalid_paths:
|
|
298
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
299
|
-
raise ValueError(error_msg)
|
|
299
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
300
|
+
if error:
|
|
301
|
+
raise ValueError(error)
|
|
300
302
|
|
|
301
303
|
|
|
302
304
|
def get_window(config=None):
|
|
@@ -106,8 +106,8 @@ def _write_dep_marker(repo, repo_name, tag, requested, repo_url, repo_path, deps
|
|
|
106
106
|
for stale in deps_dir.glob(f"{repo_name}-*{_DEP_INFO_SUFFIX}"):
|
|
107
107
|
try:
|
|
108
108
|
stale.unlink()
|
|
109
|
-
except OSError:
|
|
110
|
-
|
|
109
|
+
except OSError as e:
|
|
110
|
+
reporter.detail(f" [INFO] Could not remove stale marker {stale.name}: {e}")
|
|
111
111
|
|
|
112
112
|
commit = _get_commit_hash(repo_path) or "unknown"
|
|
113
113
|
installed = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -360,7 +360,17 @@ def _clone_repo_at_tag(repo, tag_or_spec, base_dir, delete_allowed=False, allow_
|
|
|
360
360
|
response = "y"
|
|
361
361
|
reporter.detail(f" Auto-deleting and re-cloning (--delete flag set)")
|
|
362
362
|
else:
|
|
363
|
-
|
|
363
|
+
try:
|
|
364
|
+
response = input(f" Delete and re-clone? (y/N): ").strip().lower()
|
|
365
|
+
except EOFError:
|
|
366
|
+
# Non-interactive/CI run: there is no stdin to prompt on. Default
|
|
367
|
+
# to the safe "keep existing clone" choice instead of crashing;
|
|
368
|
+
# pass --delete to force a re-clone in automated runs.
|
|
369
|
+
reporter.detail(
|
|
370
|
+
" No interactive input available; keeping existing clone "
|
|
371
|
+
"(pass --delete to force a re-clone)."
|
|
372
|
+
)
|
|
373
|
+
response = "n"
|
|
364
374
|
|
|
365
375
|
if response in ["y", "yes"]:
|
|
366
376
|
reporter.detail(f" Deleting {repo_path}...")
|
|
@@ -72,7 +72,10 @@ def _validate_ini(config):
|
|
|
72
72
|
Raises:
|
|
73
73
|
ValueError: If any required settings are missing or paths are invalid
|
|
74
74
|
"""
|
|
75
|
-
missing_settings =
|
|
75
|
+
missing_settings = common.collect_missing_settings(
|
|
76
|
+
config,
|
|
77
|
+
[("lv_target_name", "LVFPGATargetSettings.LVTargetName")],
|
|
78
|
+
)
|
|
76
79
|
invalid_paths = []
|
|
77
80
|
|
|
78
81
|
# Check required settings for installation
|
|
@@ -88,9 +91,6 @@ def _validate_ini(config):
|
|
|
88
91
|
if invalid_path:
|
|
89
92
|
invalid_paths.append(invalid_path)
|
|
90
93
|
|
|
91
|
-
if not config.lv_target_name:
|
|
92
|
-
missing_settings.append("LVFPGATargetSettings.LVTargetName")
|
|
93
|
-
|
|
94
94
|
if not config.lv_target_plugin_output_folder:
|
|
95
95
|
missing_settings.append("LVFPGATargetSettings.LVTargetPluginOutputFolder")
|
|
96
96
|
else:
|
|
@@ -103,14 +103,9 @@ def _validate_ini(config):
|
|
|
103
103
|
if invalid_path:
|
|
104
104
|
invalid_paths.append(invalid_path)
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# If any issues found, raise an error with the helpful message
|
|
111
|
-
if missing_settings or invalid_paths:
|
|
112
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
113
|
-
raise ValueError(error_msg)
|
|
106
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
107
|
+
if error:
|
|
108
|
+
raise ValueError(error)
|
|
114
109
|
|
|
115
110
|
|
|
116
111
|
def install_lv_target_support(config=None):
|
|
@@ -30,9 +30,7 @@ def _validate_ini(config):
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
if not common.get_modelsim_entity(config):
|
|
33
|
-
missing_settings.append(
|
|
34
|
-
"ModelSimSettings.ModelSimEntity (or VivadoProjectSettings.TopLevelEntity)"
|
|
35
|
-
)
|
|
33
|
+
missing_settings.append("ModelSimSettings.ModelSimEntity (set via set_modelsim_top_entity)")
|
|
36
34
|
|
|
37
35
|
common.raise_for_missing_settings(missing_settings, invalid_paths)
|
|
38
36
|
|
|
@@ -95,14 +93,17 @@ def launch_modelsim(batch=False, config=None):
|
|
|
95
93
|
return result.returncode
|
|
96
94
|
reporter.success("Simulation completed successfully")
|
|
97
95
|
else:
|
|
98
|
-
# GUI mode: launch vsim
|
|
96
|
+
# GUI mode: launch vsim in its own window and return immediately.
|
|
99
97
|
if platform.system() == "Windows":
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
# Use cmd's "start" (as an argument list, shell=False) to open vsim
|
|
99
|
+
# in its own window with no shell-injection surface. This also runs
|
|
100
|
+
# vsim launchers that are batch files, which CreateProcess cannot
|
|
101
|
+
# execute directly.
|
|
102
|
+
cmd = ["cmd", "/c", "start", "", vsim_exe, "-do", do_file]
|
|
103
|
+
return_code = subprocess.call(cmd, cwd=project_dir)
|
|
102
104
|
else:
|
|
103
|
-
cmd = [vsim_exe, "-do", do_file]
|
|
104
105
|
subprocess.Popen(
|
|
105
|
-
|
|
106
|
+
[vsim_exe, "-do", do_file],
|
|
106
107
|
cwd=project_dir,
|
|
107
108
|
stdout=subprocess.DEVNULL,
|
|
108
109
|
stderr=subprocess.DEVNULL,
|
|
@@ -26,7 +26,10 @@ def _validate_ini(config):
|
|
|
26
26
|
Raises:
|
|
27
27
|
ValueError: If any required settings are missing
|
|
28
28
|
"""
|
|
29
|
-
missing_settings =
|
|
29
|
+
missing_settings = common.collect_missing_settings(
|
|
30
|
+
config,
|
|
31
|
+
[("vivado_project_folder", "VivadoProjectSettings.VivadoProjectFolder")],
|
|
32
|
+
)
|
|
30
33
|
invalid_paths = []
|
|
31
34
|
|
|
32
35
|
# Check required Vivado settings
|
|
@@ -40,9 +43,6 @@ def _validate_ini(config):
|
|
|
40
43
|
if invalid_path:
|
|
41
44
|
invalid_paths.append(invalid_path)
|
|
42
45
|
|
|
43
|
-
if not config.vivado_project_folder:
|
|
44
|
-
missing_settings.append("VivadoProjectSettings.VivadoProjectFolder")
|
|
45
|
-
|
|
46
46
|
common.raise_for_missing_settings(missing_settings, invalid_paths)
|
|
47
47
|
|
|
48
48
|
|
|
@@ -102,13 +102,18 @@ def launch_vivado(config=None):
|
|
|
102
102
|
reporter.success("SKIP VIVADO: Validation successful, skipping Vivado launch")
|
|
103
103
|
return 0
|
|
104
104
|
|
|
105
|
-
# Launch Vivado
|
|
105
|
+
# Launch Vivado. project_arg (an existing .xpr) is optional.
|
|
106
106
|
if platform.system() == "Windows":
|
|
107
|
-
#
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
# Use cmd's "start" to open Vivado in its own window and return
|
|
108
|
+
# immediately. Passed as an argument list (shell=False) so there is no
|
|
109
|
+
# shell-injection surface, and so Vivado's vivado.bat launcher -- a batch
|
|
110
|
+
# file that CreateProcess cannot run directly -- is executed by cmd.
|
|
111
|
+
cmd = ["cmd", "/c", "start", "", vivado_abs]
|
|
112
|
+
if project_arg:
|
|
113
|
+
cmd.append(project_arg)
|
|
114
|
+
return_code = subprocess.call(cmd, cwd=vivado_project_dir)
|
|
110
115
|
else:
|
|
111
|
-
# On Linux/macOS, launch directly
|
|
116
|
+
# On Linux/macOS, launch directly (blocks until Vivado exits).
|
|
112
117
|
cmd = [vivado_abs]
|
|
113
118
|
if project_arg:
|
|
114
119
|
cmd.append(project_arg)
|
|
@@ -289,7 +289,8 @@ def _generate_board_io_csv_from_clip_xml(input_xml_path, clip_output_csv):
|
|
|
289
289
|
|
|
290
290
|
except Exception as e:
|
|
291
291
|
reporter.error(f"Error processing XML: {str(e)}")
|
|
292
|
-
|
|
292
|
+
if reporter.verbose:
|
|
293
|
+
traceback.print_exc()
|
|
293
294
|
|
|
294
295
|
|
|
295
296
|
def _process_constraint_file(input_xml_path, output_folder, instance_path):
|
|
@@ -340,7 +341,8 @@ def _process_constraint_file(input_xml_path, output_folder, instance_path):
|
|
|
340
341
|
|
|
341
342
|
except Exception as e:
|
|
342
343
|
reporter.error(f"Error processing XDC file {os.path.basename(input_xml_path)}: {str(e)}")
|
|
343
|
-
|
|
344
|
+
if reporter.verbose:
|
|
345
|
+
traceback.print_exc()
|
|
344
346
|
|
|
345
347
|
|
|
346
348
|
def _generate_clip_to_window_signals(input_xml_path, output_vhdl_path):
|
|
@@ -430,7 +432,8 @@ def _generate_clip_to_window_signals(input_xml_path, output_vhdl_path):
|
|
|
430
432
|
|
|
431
433
|
except Exception as e:
|
|
432
434
|
reporter.error(f"Error generating CLIP to Window signals: {e}")
|
|
433
|
-
|
|
435
|
+
if reporter.verbose:
|
|
436
|
+
traceback.print_exc()
|
|
434
437
|
return False, [f"Critical error: {str(e)}"]
|
|
435
438
|
|
|
436
439
|
|
|
@@ -573,7 +576,17 @@ def _validate_ini(config):
|
|
|
573
576
|
Raises:
|
|
574
577
|
ValueError: If any required settings are missing or paths are invalid
|
|
575
578
|
"""
|
|
576
|
-
missing_settings =
|
|
579
|
+
missing_settings = common.collect_missing_settings(
|
|
580
|
+
config,
|
|
581
|
+
[
|
|
582
|
+
("clip_output_csv", "CLIPMigrationSettings.LVTargetBoardIO"),
|
|
583
|
+
("clip_inst_example", "CLIPMigrationSettings.CLIPInstantiationExample"),
|
|
584
|
+
(
|
|
585
|
+
"clip_to_window_signal_definitions",
|
|
586
|
+
"CLIPMigrationSettings.CLIPtoWindowSignalDefinitions",
|
|
587
|
+
),
|
|
588
|
+
],
|
|
589
|
+
)
|
|
577
590
|
invalid_paths = []
|
|
578
591
|
|
|
579
592
|
# Check required paths
|
|
@@ -587,9 +600,6 @@ def _validate_ini(config):
|
|
|
587
600
|
if invalid_path:
|
|
588
601
|
invalid_paths.append(invalid_path)
|
|
589
602
|
|
|
590
|
-
if not config.clip_output_csv:
|
|
591
|
-
missing_settings.append("CLIPMigrationSettings.LVTargetBoardIO")
|
|
592
|
-
|
|
593
603
|
if not config.clip_top_hdl:
|
|
594
604
|
missing_settings.append("CLIPMigrationSettings.CLIPHDLTop")
|
|
595
605
|
else:
|
|
@@ -600,9 +610,6 @@ def _validate_ini(config):
|
|
|
600
610
|
if invalid_path:
|
|
601
611
|
invalid_paths.append(invalid_path)
|
|
602
612
|
|
|
603
|
-
if not config.clip_inst_example:
|
|
604
|
-
missing_settings.append("CLIPMigrationSettings.CLIPInstantiationExample")
|
|
605
|
-
|
|
606
613
|
# Note: CLIPXDCIn is optional, so we don't check if it's missing -
|
|
607
614
|
# but if they are present, validate them
|
|
608
615
|
# Only check for CLIPEntityPath and XDC output folder if there are XDC paths
|
|
@@ -621,17 +628,9 @@ def _validate_ini(config):
|
|
|
621
628
|
if invalid_path:
|
|
622
629
|
invalid_paths.append(invalid_path)
|
|
623
630
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
# Construct error message
|
|
628
|
-
error_msg = common.get_missing_settings_error(missing_settings)
|
|
629
|
-
error_msg += common.get_invalid_paths_error(invalid_paths)
|
|
630
|
-
|
|
631
|
-
# If any issues found, raise an error with the helpful message
|
|
632
|
-
if missing_settings or invalid_paths:
|
|
633
|
-
error_msg += "\nPlease update your configuration file and try again."
|
|
634
|
-
raise ValueError(error_msg)
|
|
631
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
632
|
+
if error:
|
|
633
|
+
raise ValueError(error)
|
|
635
634
|
|
|
636
635
|
|
|
637
636
|
def migrate_clip(config=None):
|
|
@@ -33,10 +33,9 @@ def _validate_ini(config):
|
|
|
33
33
|
if not common.get_modelsim_entity(config):
|
|
34
34
|
missing_settings.append("ModelSimSettings.ModelSimEntity (set via set_modelsim_top_entity)")
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
raise ValueError(error_msg)
|
|
36
|
+
error = common.build_settings_error(missing_settings, invalid_paths)
|
|
37
|
+
if error:
|
|
38
|
+
raise ValueError(error)
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
def sim_modelsim(do_file=None, config=None):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|