labview-fpga-hdl-tools 2.0.0.dev0__tar.gz → 2.0.0.dev1__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.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/PKG-INFO +1 -1
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/create_vivado_project.py +61 -27
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/gen_labview_target_plugin.py +106 -22
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/process_constraints.py +54 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/pyproject.toml +1 -1
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/LICENSE +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/README.md +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/.gitignore +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/__init__.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/__main__.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/check_syntax.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/command_config.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/command_hooks.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/common.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/compile_modelsim_lib.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/compile_project.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/create_lvbitx.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/create_modelsim_project.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/generate_vhdl.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/get_window_netlist.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/install_dependencies.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/install_labview_target_plugin.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/launch_modelsim.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/launch_vivado.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/migrate_clip.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/nihdlsettings_default.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/reporting.py +0 -0
- {labview_fpga_hdl_tools-2.0.0.dev0 → labview_fpga_hdl_tools-2.0.0.dev1}/labview_fpga_hdl_tools/sim_modelsim.py +0 -0
|
@@ -25,6 +25,27 @@ from enum import Enum
|
|
|
25
25
|
from . import common, generate_vhdl, process_constraints
|
|
26
26
|
from .reporting import reporter
|
|
27
27
|
|
|
28
|
+
# Windows MAX_PATH. When the generated TCL adds a source file, Vivado resolves the
|
|
29
|
+
# file's add_files path (which is relative to the TCL folder) against the Vivado
|
|
30
|
+
# project folder -- its working directory -- WITHOUT collapsing the leading ".."
|
|
31
|
+
# segments. On Windows that raw, non-normalized string is bounded by MAX_PATH.
|
|
32
|
+
# Empirically a resolved length of 260 fails with "[Vivado 12-172] File or
|
|
33
|
+
# Directory ... does not exist" even though the file exists; 259 is the longest
|
|
34
|
+
# that works.
|
|
35
|
+
VIVADO_MAX_PATH_LEN = 260
|
|
36
|
+
|
|
37
|
+
# Copy a source file into objects/gatheredfiles once its Vivado-resolved path would
|
|
38
|
+
# reach this length. This sits just below VIVADO_MAX_PATH_LEN so a normal install
|
|
39
|
+
# never trips the hard failure, while leaving as much room as possible so that
|
|
40
|
+
# dependency files shipped at their real paths are added in place rather than
|
|
41
|
+
# copied. The dependency path shortening in the flexrio build (build.py
|
|
42
|
+
# DEPS_MAX_REL_PATH_LEN) is sized to keep normal installs under this limit.
|
|
43
|
+
VIVADO_SAFE_MAX_PATH_LEN = 255
|
|
44
|
+
|
|
45
|
+
# Non-Windows platforms are not affected by MAX_PATH; use a generous limit that
|
|
46
|
+
# effectively only guards against pathological path lengths.
|
|
47
|
+
POSIX_MAX_PATH_LEN = 4096
|
|
48
|
+
|
|
28
49
|
|
|
29
50
|
def _has_spaces(file_path):
|
|
30
51
|
"""Checks if the given file path contains spaces.
|
|
@@ -211,17 +232,25 @@ def _find_and_log_duplicates(file_list):
|
|
|
211
232
|
return unique_file_list
|
|
212
233
|
|
|
213
234
|
|
|
214
|
-
def _copy_long_path_files(file_list):
|
|
215
|
-
"""Copies files
|
|
235
|
+
def _copy_long_path_files(file_list, vivado_project_dir, tcl_base_dir):
|
|
236
|
+
"""Copies files that would exceed Vivado's path limit into "objects/gatheredfiles".
|
|
237
|
+
|
|
238
|
+
Vivado does not use a source file's absolute path to decide whether it can open
|
|
239
|
+
it. The generated TCL adds each file with a path that is relative to the TCL
|
|
240
|
+
folder, and Vivado resolves that relative path against the Vivado project folder
|
|
241
|
+
(its working directory) WITHOUT collapsing the leading ".." segments. On Windows
|
|
242
|
+
that raw, non-normalized string is bounded by the 260-char MAX_PATH limit, so it
|
|
243
|
+
-- not the shorter normalized absolute path -- is what determines whether Vivado
|
|
244
|
+
reports "[Vivado 12-172] File or Directory ... does not exist".
|
|
216
245
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- Handling Windows long paths for deep directory structures
|
|
220
|
-
- Setting proper file permissions
|
|
221
|
-
- Error reporting for failed copy operations
|
|
246
|
+
This function reproduces that exact resolution to decide which files must be
|
|
247
|
+
copied to a short local path before being added to the project.
|
|
222
248
|
|
|
223
249
|
Args:
|
|
224
250
|
file_list (list): Original list of file paths
|
|
251
|
+
vivado_project_dir (str): Absolute path of the Vivado project folder, which
|
|
252
|
+
Vivado uses as its working directory when the generated TCL runs
|
|
253
|
+
tcl_base_dir (str): Folder that the add_files paths are made relative to
|
|
225
254
|
|
|
226
255
|
Returns:
|
|
227
256
|
list: Updated file list with long path files moved to local paths
|
|
@@ -237,29 +266,28 @@ def _copy_long_path_files(file_list):
|
|
|
237
266
|
# Store original file path before modification
|
|
238
267
|
original_file = file
|
|
239
268
|
absolute_file_path = os.path.abspath(original_file)
|
|
240
|
-
try:
|
|
241
|
-
relative_file_path = os.path.relpath(absolute_file_path, os.getcwd())
|
|
242
|
-
except ValueError:
|
|
243
|
-
relative_file_path = original_file
|
|
244
269
|
|
|
245
|
-
#
|
|
270
|
+
# Reproduce the path length Vivado will actually see. On Windows this is the
|
|
271
|
+
# non-normalized "<project folder>\<relative add_files path>" string that is
|
|
272
|
+
# subject to MAX_PATH; elsewhere the generous absolute-path limit applies.
|
|
273
|
+
if os.name == "nt":
|
|
274
|
+
tcl_relative_path = os.path.relpath(absolute_file_path, tcl_base_dir)
|
|
275
|
+
vivado_path_len = len(vivado_project_dir) + len(os.sep) + len(tcl_relative_path)
|
|
276
|
+
path_len_limit = VIVADO_SAFE_MAX_PATH_LEN
|
|
277
|
+
else:
|
|
278
|
+
vivado_path_len = len(absolute_file_path)
|
|
279
|
+
path_len_limit = POSIX_MAX_PATH_LEN
|
|
280
|
+
|
|
281
|
+
# Handle long source paths on Windows so the copy itself can read them.
|
|
246
282
|
if os.name == "nt":
|
|
247
283
|
file = f"\\\\?\\{absolute_file_path}"
|
|
248
284
|
target_folder_long = f"\\\\?\\{os.path.abspath(target_folder)}"
|
|
249
285
|
else:
|
|
250
286
|
target_folder_long = target_folder
|
|
251
287
|
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
|
|
255
|
-
# the absolute path and the relative path because Vivado might be
|
|
256
|
-
# using either one when processing the TCL script, and we want to
|
|
257
|
-
# ensure that we catch all cases where the path length could be
|
|
258
|
-
# an issue.
|
|
259
|
-
max_path = 200 if os.name == "nt" else 4096
|
|
260
|
-
|
|
261
|
-
# Check if the absolute or relative path is longer than max_path characters.
|
|
262
|
-
if len(absolute_file_path) > max_path or len(relative_file_path) > max_path:
|
|
288
|
+
# Copy the file to a short local path when Vivado's resolved path would be
|
|
289
|
+
# too long. Files at short paths are added from their original location.
|
|
290
|
+
if vivado_path_len > path_len_limit:
|
|
263
291
|
target_path = os.path.join(target_folder_long, os.path.basename(file))
|
|
264
292
|
if os.path.exists(target_path):
|
|
265
293
|
os.chmod(target_path, 0o777) # Make the file writable
|
|
@@ -267,7 +295,10 @@ def _copy_long_path_files(file_list):
|
|
|
267
295
|
shutil.copy2(file, target_path)
|
|
268
296
|
new_file_list.append(target_path)
|
|
269
297
|
reporter.warn(f"WARNING: Long path file {original_file}")
|
|
270
|
-
reporter.detail(
|
|
298
|
+
reporter.detail(
|
|
299
|
+
f" (Vivado path length {vivado_path_len} exceeds the safe "
|
|
300
|
+
f"limit of {path_len_limit}) was copied into the objects/gatheredfiles folder."
|
|
301
|
+
)
|
|
271
302
|
reporter.detail(
|
|
272
303
|
f" You must run 'nihdl create-project --update' to pull in any changes to the source file."
|
|
273
304
|
)
|
|
@@ -592,9 +623,12 @@ def _create_project(mode: ProjectMode, config):
|
|
|
592
623
|
|
|
593
624
|
# Copy long path files to the gatheredfiles folder
|
|
594
625
|
# Returns the file list with the files from old long path locations having
|
|
595
|
-
# new locations in gatheredfiles
|
|
596
|
-
|
|
597
|
-
|
|
626
|
+
# new locations in gatheredfiles. The Vivado project folder and TCL folder are
|
|
627
|
+
# needed to reproduce the exact path length Vivado resolves at add_files time.
|
|
628
|
+
tcl_base_dir = os.path.join(current_dir, "TCL")
|
|
629
|
+
vivado_project_dir = os.path.join(current_dir, config.vivado_project_folder)
|
|
630
|
+
file_list = _copy_long_path_files(file_list, vivado_project_dir, tcl_base_dir)
|
|
631
|
+
vhdl2008_file_list = _copy_long_path_files(vhdl2008_file_list, vivado_project_dir, tcl_base_dir)
|
|
598
632
|
|
|
599
633
|
# Override default LV generated files with extracted window files
|
|
600
634
|
if config.lv_window_netlist_folder:
|
|
@@ -38,6 +38,10 @@ DOCUMENT_ROOT_PREFIX = (
|
|
|
38
38
|
|
|
39
39
|
# Data type prototypes mapping - used to map LabVIEW data types to their FPGA representations
|
|
40
40
|
# The {direction} placeholder is replaced with Input/OutputWithoutReadback based on signal direction
|
|
41
|
+
#
|
|
42
|
+
# LV FPGA supported prototypes are documented here -
|
|
43
|
+
# src/lvfpga/source/LabVIEW/resource/RVI/FPGATargetXML/niLvFpgaTargetNecessities.xml
|
|
44
|
+
#
|
|
41
45
|
DATA_TYPE_PROTOTYPES = {
|
|
42
46
|
"FXP": DOCUMENT_ROOT_PREFIX
|
|
43
47
|
+ "FXPDigital{direction}{output_readback}{zero_sync_regs}", # Fixed-point numeric type
|
|
@@ -61,6 +65,49 @@ DATA_TYPE_PROTOTYPES = {
|
|
|
61
65
|
+ "i64Digital{direction}{output_readback}{zero_sync_regs}", # Signed 64-bit integer
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
# The BoardIO prototype name is assembled as:
|
|
69
|
+
# <type>Digital<direction><output_readback><zero_sync_regs>
|
|
70
|
+
# Not every permutation of the CSV Direction / OutputReadback / ZeroSyncRegs
|
|
71
|
+
# columns corresponds to a prototype that LabVIEW FPGA publishes. These sets
|
|
72
|
+
# capture the supported suffixes (everything after "<type>Digital") so that
|
|
73
|
+
# unsupported combinations are reported as validation errors instead of being
|
|
74
|
+
# written out as unusable prototypes.
|
|
75
|
+
#
|
|
76
|
+
# Every supported data type allows these combinations:
|
|
77
|
+
_BASE_PROTOTYPE_SUFFIXES = frozenset(
|
|
78
|
+
{
|
|
79
|
+
"Input",
|
|
80
|
+
"InputZeroDefaultSyncRegisters",
|
|
81
|
+
"OutputWithReadback",
|
|
82
|
+
"OutputWithReadbackZeroDefaultSyncRegisters",
|
|
83
|
+
"OutputWithoutReadback",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Only Boolean and FXP additionally support a zero-default-sync-register form of
|
|
88
|
+
# the without-readback output. The integer port types (U8-U64, I8-I64) do not.
|
|
89
|
+
_TYPES_SUPPORTING_WITHOUT_READBACK_ZERO_SYNC = frozenset({"Boolean", "FXP"})
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _get_supported_prototype_suffixes(data_type_name):
|
|
93
|
+
"""Return the set of supported prototype suffixes for a CSV DataType.
|
|
94
|
+
|
|
95
|
+
The suffix is the portion of the prototype name after ``<type>Digital``
|
|
96
|
+
(i.e. ``<direction><output_readback><zero_sync_regs>``). This encodes which
|
|
97
|
+
Direction / OutputReadback / ZeroSyncRegs permutations map to a real
|
|
98
|
+
LabVIEW FPGA prototype for the given data type.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
data_type_name (str): DataType base name (e.g. ``Boolean``, ``U8``, ``FXP``).
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
set[str]: Supported prototype-name suffixes for the data type.
|
|
105
|
+
"""
|
|
106
|
+
suffixes = set(_BASE_PROTOTYPE_SUFFIXES)
|
|
107
|
+
if data_type_name in _TYPES_SUPPORTING_WITHOUT_READBACK_ZERO_SYNC:
|
|
108
|
+
suffixes.add("OutputWithoutReadbackZeroDefaultSyncRegisters")
|
|
109
|
+
return suffixes
|
|
110
|
+
|
|
64
111
|
|
|
65
112
|
def _parse_register_offset(offset_text):
|
|
66
113
|
"""Parse a register offset string as an integer.
|
|
@@ -286,10 +333,14 @@ def _generate_xml_from_csv(csv_path, boardio_output_path, clock_output_path):
|
|
|
286
333
|
"true": "ZeroDefaultSyncRegisters",
|
|
287
334
|
"false": "",
|
|
288
335
|
}.get(zero_sync_regs.lower())
|
|
289
|
-
if io_zero_sync_regs is None
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
336
|
+
if io_zero_sync_regs is None:
|
|
337
|
+
if zero_sync_regs:
|
|
338
|
+
error = f"Row {row_count}: Invalid ZeroSyncRegs '{zero_sync_regs}' for signal '{lv_name}'. Must be 'TRUE' or 'FALSE'."
|
|
339
|
+
validation_errors.append(error)
|
|
340
|
+
io_zero_sync_regs = "INVALID_SYNC_REGS"
|
|
341
|
+
else:
|
|
342
|
+
# An empty cell is treated as FALSE (no suffix appended).
|
|
343
|
+
io_zero_sync_regs = ""
|
|
293
344
|
|
|
294
345
|
# Validate output readback setting for outputs
|
|
295
346
|
if io_direction == "Output":
|
|
@@ -308,25 +359,54 @@ def _generate_xml_from_csv(csv_path, boardio_output_path, clock_output_path):
|
|
|
308
359
|
data_type_name = data_type.split("(")[0] if "(" in data_type else data_type
|
|
309
360
|
|
|
310
361
|
if data_type_name in DATA_TYPE_PROTOTYPES:
|
|
311
|
-
prototype
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
362
|
+
# The prototype name is assembled from the direction,
|
|
363
|
+
# readback, and sync-register tokens. Not every
|
|
364
|
+
# permutation corresponds to a prototype that LabVIEW
|
|
365
|
+
# FPGA publishes, so reject unsupported combinations
|
|
366
|
+
# instead of emitting an unusable prototype.
|
|
367
|
+
prototype_suffix = f"{io_direction}{io_output_readback}{io_zero_sync_regs}"
|
|
368
|
+
supported_suffixes = _get_supported_prototype_suffixes(data_type_name)
|
|
369
|
+
tokens_valid = (
|
|
370
|
+
io_direction in ("Input", "Output")
|
|
371
|
+
and io_output_readback in ("", "WithReadback", "WithoutReadback")
|
|
372
|
+
and io_zero_sync_regs in ("", "ZeroDefaultSyncRegisters")
|
|
315
373
|
)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
374
|
+
|
|
375
|
+
if tokens_valid and prototype_suffix not in supported_suffixes:
|
|
376
|
+
error = (
|
|
377
|
+
f"Row {row_count}: DataType='{data_type}' with "
|
|
378
|
+
f"Direction='{direction}', OutputReadback='{output_readback}', "
|
|
379
|
+
f"and ZeroSyncRegs='{zero_sync_regs}' for signal '{lv_name}' "
|
|
380
|
+
f"does not map to a supported LabVIEW FPGA prototype. The integer "
|
|
381
|
+
f"port types (U8-U64, I8-I64) have no WithoutReadback output with "
|
|
382
|
+
f"ZeroSyncRegs=TRUE; set OutputReadback=TRUE or ZeroSyncRegs=FALSE. "
|
|
383
|
+
f"See the DataType compatibility table in "
|
|
384
|
+
f"docs/LVTargetCustomIO-Reference.md for all valid combinations."
|
|
385
|
+
)
|
|
386
|
+
validation_errors.append(error)
|
|
387
|
+
io_resource.set("prototype", "UNSUPPORTED_PROTOTYPE_COMBINATION")
|
|
388
|
+
else:
|
|
389
|
+
prototype = DATA_TYPE_PROTOTYPES[data_type_name].format(
|
|
390
|
+
direction=io_direction,
|
|
391
|
+
zero_sync_regs=io_zero_sync_regs,
|
|
392
|
+
output_readback=io_output_readback,
|
|
393
|
+
)
|
|
394
|
+
io_resource.set("prototype", prototype)
|
|
395
|
+
|
|
396
|
+
# Handle FXP attributes
|
|
397
|
+
if data_type_name == "FXP" and "(" in data_type:
|
|
398
|
+
try:
|
|
399
|
+
parts = data_type.split("(")[1].split(")")[0].split(",")
|
|
400
|
+
io_resource.set("wordLength", parts[0])
|
|
401
|
+
io_resource.set("integerWordLength", parts[1])
|
|
402
|
+
io_resource.set(
|
|
403
|
+
"unsigned",
|
|
404
|
+
"true" if "Unsigned" in data_type else "false",
|
|
405
|
+
)
|
|
406
|
+
except Exception as e:
|
|
407
|
+
reporter.error(
|
|
408
|
+
f"Error parsing FXP parameters for {lv_name}: {e}"
|
|
409
|
+
)
|
|
330
410
|
else:
|
|
331
411
|
# Add validation error for invalid signal type
|
|
332
412
|
error = f"Row {row_count}: Invalid signal type '{data_type}' for signal '{lv_name}'. Valid types: {', '.join(DATA_TYPE_PROTOTYPES.keys())}"
|
|
@@ -755,6 +835,10 @@ def gen_lv_target_support(config=None):
|
|
|
755
835
|
fpga_files_folder,
|
|
756
836
|
process_constraints.build_custom_constraints_content(config.custom_constraints),
|
|
757
837
|
)
|
|
838
|
+
process_constraints.wrap_from_to_constraints_macro_in_folder(
|
|
839
|
+
fpga_files_folder,
|
|
840
|
+
config.entity_path_to_window_wrapper,
|
|
841
|
+
)
|
|
758
842
|
|
|
759
843
|
_copy_menu_files(config.lv_target_plugin_output_folder, config.lv_target_menus_folder)
|
|
760
844
|
|
|
@@ -176,6 +176,60 @@ def replace_custom_constraints_in_xdc_folder(folder, custom_constraints_content)
|
|
|
176
176
|
reporter.detail(f"Replaced macro_GitHubCustomConstraints in {filename}")
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
_FROM_TO_CONSTRAINTS_MACRO_RE = re.compile(
|
|
180
|
+
r"^([ \t]*)(#LabVIEWFPGA_Macro[ \t]+macro_fromToConstraints)[ \t]*$",
|
|
181
|
+
re.IGNORECASE | re.MULTILINE,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def wrap_from_to_constraints_macro_in_folder(folder, entity_path_to_window_wrapper):
|
|
186
|
+
"""Wrap the from-to-constraints macro with current_instance scoping in constraint files.
|
|
187
|
+
|
|
188
|
+
In the LabVIEW FPGA target plugin flow, LabVIEW FPGA replaces
|
|
189
|
+
``#LabVIEWFPGA_Macro macro_fromToConstraints`` with the generated window From/To timing
|
|
190
|
+
constraints when a VI is compiled against the custom target. Those constraints
|
|
191
|
+
reference cells relative to the window wrapper instance, so the macro must be wrapped
|
|
192
|
+
with ``current_instance`` scoping (mirroring the FROM_TO handling in
|
|
193
|
+
``process_constraints_template``). Without it, instance-relative constraints fail to
|
|
194
|
+
match cells inside the window when the window VHDL is encrypted.
|
|
195
|
+
|
|
196
|
+
Scans every ``.xdc`` and ``.xdc_template`` file in *folder* and wraps the
|
|
197
|
+
from-to-constraints macro line, leaving the macro token itself intact for LabVIEW FPGA
|
|
198
|
+
to replace later.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
folder (str): Directory containing constraint files to process.
|
|
202
|
+
entity_path_to_window_wrapper (str | None): Hierarchical path to the window wrapper
|
|
203
|
+
instance. If falsy, no wrapping is performed.
|
|
204
|
+
"""
|
|
205
|
+
if not os.path.isdir(folder) or not entity_path_to_window_wrapper:
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
def _wrap(match):
|
|
209
|
+
indent = match.group(1)
|
|
210
|
+
token = match.group(2)
|
|
211
|
+
return (
|
|
212
|
+
f"{indent}set TopInstance0 [current_instance .]\n"
|
|
213
|
+
f"{indent}current_instance {entity_path_to_window_wrapper}\n"
|
|
214
|
+
f"{indent}{token}\n"
|
|
215
|
+
f"{indent}current_instance -quiet\n"
|
|
216
|
+
f"{indent}current_instance $TopInstance0"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
for filename in os.listdir(folder):
|
|
220
|
+
lowered = filename.lower()
|
|
221
|
+
if not (lowered.endswith(".xdc") or lowered.endswith(".xdc_template")):
|
|
222
|
+
continue
|
|
223
|
+
filepath = os.path.join(folder, filename)
|
|
224
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
225
|
+
content = f.read()
|
|
226
|
+
new_content, count = _FROM_TO_CONSTRAINTS_MACRO_RE.subn(_wrap, content)
|
|
227
|
+
if count > 0:
|
|
228
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
229
|
+
f.write(new_content)
|
|
230
|
+
reporter.detail(f"Wrapped macro_fromToConstraints with current_instance in {filename}")
|
|
231
|
+
|
|
232
|
+
|
|
179
233
|
def process_constraints_template(config):
|
|
180
234
|
"""Process XDC constraint template files.
|
|
181
235
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|