Skip to content

Commit 250cce1

Browse files
author
Salvador Santoluctio
committed
tweaks from testing DMA
1 parent c07eec1 commit 250cce1

5 files changed

Lines changed: 213 additions & 27 deletions

File tree

labview_fpga_hdl_tools/command_config.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ class CommandConfiguration:
112112
lv_target_install_folder: Optional[str] = None # Installation folder for target plugins
113113
lv_target_menus_folder: Optional[str] = None # Folder containing target plugin menu files
114114
lv_target_info_ini: Optional[str] = None # Path to TargetInfo.ini file
115-
lv_target_exclude_files: Optional[str] = (
116-
None # Path to Python script with file exclusion patterns
117-
)
115+
lv_target_exclude_files: List[str] = field(
116+
default_factory=list
117+
) # List of paths to exclude file lists
118118
num_hdl_registers: Optional[int] = None # Number of HDL registers
119119
max_hdl_reg_offset: Optional[int] = None # Maximum HDL register byte offset
120120
# ----- CLIP MIGRATION SETTINGS -----
@@ -304,8 +304,19 @@ def set_lv_target_info_ini(self, value):
304304
self.lv_target_info_ini = resolve_path(value)
305305

306306
def set_lv_target_exclude_files(self, value):
307-
"""Set the target exclude files path (resolved to absolute)."""
308-
self.lv_target_exclude_files = resolve_path(value)
307+
"""Set the target exclude files path (resolved to absolute).
308+
309+
For backward compatibility. Clears the list then appends.
310+
Prefer add_lv_target_exclude_files for multiple sources.
311+
"""
312+
self.lv_target_exclude_files.clear()
313+
self.add_lv_target_exclude_files(value)
314+
315+
def add_lv_target_exclude_files(self, value):
316+
"""Append a target exclude files path (resolved to absolute)."""
317+
resolved = resolve_path(value)
318+
if resolved is not None:
319+
self.lv_target_exclude_files.append(resolved)
309320

310321
def set_num_hdl_registers(self, value):
311322
"""Set the number of HDL registers."""

labview_fpga_hdl_tools/gen_labview_target_plugin.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from mako.template import Template # For template-based file generation # type: ignore
2828

2929
from . import common # For shared utilities across tools
30+
from . import process_constraints # For shared constraint macro replacement
3031

3132
# Constants
3233
BOARDIO_WRAPPER_NAME = "BoardIO" # Top-level wrapper name in the BoardIO XML hierarchy
@@ -670,15 +671,26 @@ def _copy_fpgafiles(
670671
dest_deps_folder = os.path.join(plugin_folder, "FpgaFiles")
671672
os.makedirs(dest_deps_folder, exist_ok=True)
672673

673-
# Read the list of files to exclude from the file
674-
exclude_file_list = []
675-
if lv_target_exclude_files and os.path.exists(lv_target_exclude_files):
676-
with open(lv_target_exclude_files, "r", encoding="utf-8") as f:
677-
# Read each line, strip whitespace, and filter out empty lines
678-
exclude_file_list = [line.strip() for line in f if line.strip()]
679-
print(f"Loaded {len(exclude_file_list)} files to exclude from {lv_target_exclude_files}")
674+
# Read the list of files to exclude, merging all provided exclude file lists
675+
exclude_file_set = set()
676+
exclude_sources = (
677+
lv_target_exclude_files
678+
if isinstance(lv_target_exclude_files, list)
679+
else ([lv_target_exclude_files] if lv_target_exclude_files else [])
680+
)
681+
for exclude_path in exclude_sources:
682+
if exclude_path and os.path.exists(exclude_path):
683+
with open(exclude_path, "r", encoding="utf-8") as f:
684+
entries = [line.strip() for line in f if line.strip()]
685+
exclude_file_set.update(entries)
686+
print(f"Loaded {len(entries)} files to exclude from {exclude_path}")
687+
else:
688+
print(f"Exclude file list not found: {exclude_path}")
689+
exclude_file_list = exclude_file_set
690+
if not exclude_file_list:
691+
print("No exclude file lists provided or none found")
680692
else:
681-
print("No exclude file list provided or file does not exist")
693+
print(f"Total unique files to exclude: {len(exclude_file_list)}")
682694

683695
for file in file_list:
684696
# Get the base filename
@@ -820,7 +832,9 @@ def _validate_ini(config, gen_window_only=False):
820832
# Validate each file list path
821833
for i, file_list_path in enumerate(config.hdl_file_lists):
822834
invalid_path = common.validate_path(
823-
file_list_path, f"VivadoProjectSettings.VivadoProjectFilesLists[{i}]", "file"
835+
file_list_path,
836+
f"VivadoProjectSettings.VivadoProjectFilesLists[{i}]",
837+
"file",
824838
)
825839
if invalid_path:
826840
invalid_paths.append(invalid_path)
@@ -831,7 +845,9 @@ def _validate_ini(config, gen_window_only=False):
831845
# Validate each template file path
832846
for i, template_path in enumerate(config.lv_target_xml_templates):
833847
invalid_path = common.validate_path(
834-
template_path, f"LVFPGATargetSettings.TargetXMLTemplates[{i}]", "file"
848+
template_path,
849+
f"LVFPGATargetSettings.TargetXMLTemplates[{i}]",
850+
"file",
835851
)
836852
if invalid_path:
837853
invalid_paths.append(invalid_path)
@@ -840,7 +856,9 @@ def _validate_ini(config, gen_window_only=False):
840856
if config.lv_target_constraints:
841857
for i, constraint_path in enumerate(config.lv_target_constraints):
842858
invalid_path = common.validate_path(
843-
constraint_path, f"LVFPGATargetSettings.LVTargetConstraints[{i}]", "file"
859+
constraint_path,
860+
f"LVFPGATargetSettings.LVTargetConstraints[{i}]",
861+
"file",
844862
)
845863
if invalid_path:
846864
invalid_paths.append(invalid_path)
@@ -957,6 +975,12 @@ def gen_lv_target_support(config=None):
957975
config.lv_target_exclude_files,
958976
)
959977

978+
fpga_files_folder = os.path.join(config.lv_target_plugin_output_folder or "", "FpgaFiles")
979+
process_constraints.replace_custom_constraints_in_xdc_folder(
980+
fpga_files_folder,
981+
config.custom_constraints,
982+
)
983+
960984
_copy_menu_files(config.lv_target_plugin_output_folder, config.lv_target_menus_folder)
961985

962986
_copy_targetinfo_ini(config.lv_target_plugin_output_folder, config.lv_target_info_ini)

labview_fpga_hdl_tools/nihdlsettings_default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def pre_all(context):
5353
set_include_custom_io_on_lv_window, set_lv_target_name, set_lv_target_guid,
5454
set_lv_target_install_folder, add_lv_target_constraints,
5555
set_lv_target_menus_folder, set_lv_target_info_ini,
56-
set_lv_target_exclude_files, set_max_hdl_reg_offset,
56+
set_lv_target_exclude_files, add_lv_target_exclude_files, set_max_hdl_reg_offset,
5757
add_window_vhdl_template, add_lv_target_xml_template,
5858
set_window_vhdl_output_folder,
5959
set_lv_target_plugin_output_folder, set_boardio_output, set_clock_output

labview_fpga_hdl_tools/process_constraints.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,59 @@ def _convert_tg_quoted_lines(content):
105105
return "".join(converted_lines)
106106

107107

108+
def load_custom_constraints(custom_constraints_path):
109+
"""Load custom constraints content from a file.
110+
111+
Args:
112+
custom_constraints_path (str | None): Path to the custom constraints XDC file.
113+
114+
Returns:
115+
str: The file content, or empty string if not specified or not found.
116+
"""
117+
if custom_constraints_path and os.path.exists(custom_constraints_path):
118+
with open(custom_constraints_path, "r", encoding="utf-8") as f:
119+
content = f.read()
120+
print(f"Loaded custom constraints from {custom_constraints_path}")
121+
return content
122+
print("No custom constraints file specified or file not found")
123+
return ""
124+
125+
126+
_CUSTOM_CONSTRAINTS_RE = re.compile(
127+
r"#LabVIEWFPGAHdlTools_Macro\s+macro_GitHubCustomConstraints",
128+
re.IGNORECASE,
129+
)
130+
131+
132+
def replace_custom_constraints_in_xdc_folder(folder, custom_constraints_path):
133+
"""Replace the custom constraints macro in all XDC files in a folder.
134+
135+
Scans every ``.xdc`` file in *folder* and replaces
136+
``#LabVIEWFPGAHDLTools_Macro macro_GitHubCustomConstraints`` with the
137+
content of *custom_constraints_path*.
138+
139+
Args:
140+
folder (str): Directory containing XDC files to process.
141+
custom_constraints_path (str | None): Path to the custom constraints file.
142+
"""
143+
if not os.path.isdir(folder):
144+
return
145+
146+
custom_constraints_content = load_custom_constraints(custom_constraints_path)
147+
148+
for filename in os.listdir(folder):
149+
if not filename.lower().endswith(".xdc"):
150+
continue
151+
filepath = os.path.join(folder, filename)
152+
with open(filepath, "r", encoding="utf-8") as f:
153+
content = f.read()
154+
new_content, count = _CUSTOM_CONSTRAINTS_RE.subn(custom_constraints_content, content)
155+
if count > 0:
156+
with open(filepath, "w", encoding="utf-8") as f:
157+
f.write(new_content)
158+
print(f"Replaced macro_GitHubCustomConstraints in {filename}")
159+
160+
108161
def process_constraints_template(config):
109162
"""Process XDC constraint template files.
110163
@@ -175,13 +228,7 @@ def process_constraints_template(config):
175228
from_to_content = ""
176229

177230
# Read custom constraints file if specified
178-
custom_constraints_content = ""
179-
if config.custom_constraints and os.path.exists(config.custom_constraints):
180-
with open(config.custom_constraints, "r", encoding="utf-8") as f:
181-
custom_constraints_content = f.read()
182-
print(f"Loaded custom constraints from {config.custom_constraints}")
183-
else:
184-
print("No custom constraints file specified or file not found")
231+
custom_constraints_content = load_custom_constraints(config.custom_constraints)
185232

186233
# Get template files from configuration
187234
template_files = config.constraints_templates
@@ -245,11 +292,9 @@ def process_constraints_template(config):
245292
)
246293

247294
# Replace GITHUB_CUSTOM_CONSTRAINTS macro token (case insensitive)
248-
final_content, count = re.subn(
249-
r"#LabVIEWFPGAHdlTools_Macro\s+macro_GitHubCustomConstraints",
295+
final_content, count = _CUSTOM_CONSTRAINTS_RE.subn(
250296
custom_constraints_content,
251297
final_content,
252-
flags=re.IGNORECASE,
253298
)
254299
if count == 0:
255300
raise ValueError(

tests/test_process_constraints.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Unit tests for process_constraints shared functions."""
2+
3+
from labview_fpga_hdl_tools.process_constraints import (
4+
load_custom_constraints,
5+
replace_custom_constraints_in_xdc_folder,
6+
)
7+
8+
9+
class TestLoadCustomConstraints:
10+
"""Tests for load_custom_constraints()."""
11+
12+
def test_given_valid_file__when_loaded__then_returns_content(self, tmp_path):
13+
constraints_file = tmp_path / "custom.xdc"
14+
constraints_file.write_text("set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]")
15+
result = load_custom_constraints(str(constraints_file))
16+
assert result == "set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]"
17+
18+
def test_given_none_path__when_loaded__then_returns_empty_string(self):
19+
result = load_custom_constraints(None)
20+
assert result == ""
21+
22+
def test_given_nonexistent_path__when_loaded__then_returns_empty_string(self):
23+
result = load_custom_constraints("/nonexistent/path/custom.xdc")
24+
assert result == ""
25+
26+
def test_given_empty_string_path__when_loaded__then_returns_empty_string(self):
27+
result = load_custom_constraints("")
28+
assert result == ""
29+
30+
31+
class TestReplaceCustomConstraintsInXdcFolder:
32+
"""Tests for replace_custom_constraints_in_xdc_folder()."""
33+
34+
def test_given_xdc_with_macro__when_replaced__then_macro_is_substituted(self, tmp_path):
35+
xdc_file = tmp_path / "constraints.xdc"
36+
xdc_file.write_text(
37+
"# Header\n" "#LabVIEWFPGAHdlTools_Macro macro_GitHubCustomConstraints\n" "# Footer\n"
38+
)
39+
custom_file = tmp_path / "custom.xdc"
40+
custom_file.write_text("set_property PACKAGE_PIN A1 [get_ports clk]")
41+
42+
replace_custom_constraints_in_xdc_folder(str(tmp_path), str(custom_file))
43+
44+
result = xdc_file.read_text()
45+
assert "set_property PACKAGE_PIN A1 [get_ports clk]" in result
46+
assert "macro_GitHubCustomConstraints" not in result
47+
assert "# Header\n" in result
48+
assert "# Footer\n" in result
49+
50+
def test_given_xdc_with_macro__when_no_custom_file__then_macro_is_removed(self, tmp_path):
51+
xdc_file = tmp_path / "constraints.xdc"
52+
xdc_file.write_text(
53+
"# Header\n" "#LabVIEWFPGAHdlTools_Macro macro_GitHubCustomConstraints\n" "# Footer\n"
54+
)
55+
56+
replace_custom_constraints_in_xdc_folder(str(tmp_path), None)
57+
58+
result = xdc_file.read_text()
59+
assert "macro_GitHubCustomConstraints" not in result
60+
assert "# Header\n" in result
61+
62+
def test_given_no_xdc_files__when_called__then_no_error(self, tmp_path):
63+
vhd_file = tmp_path / "design.vhd"
64+
vhd_file.write_text("entity design is end;")
65+
66+
replace_custom_constraints_in_xdc_folder(str(tmp_path), None)
67+
# Should not raise, vhd file should be untouched
68+
assert vhd_file.read_text() == "entity design is end;"
69+
70+
def test_given_nonexistent_folder__when_called__then_no_error(self):
71+
replace_custom_constraints_in_xdc_folder("/nonexistent/folder", None)
72+
# Should silently return
73+
74+
def test_given_case_insensitive_macro__when_replaced__then_works(self, tmp_path):
75+
xdc_file = tmp_path / "test.xdc"
76+
xdc_file.write_text("#labviewfpgahdltools_macro macro_githubcustomconstraints\n")
77+
custom_file = tmp_path / "custom.xdc"
78+
custom_file.write_text("# custom content")
79+
80+
replace_custom_constraints_in_xdc_folder(str(tmp_path), str(custom_file))
81+
82+
result = xdc_file.read_text()
83+
assert "# custom content" in result
84+
assert "macro_githubcustomconstraints" not in result
85+
86+
def test_given_xdc_without_macro__when_called__then_file_unchanged(self, tmp_path):
87+
xdc_file = tmp_path / "plain.xdc"
88+
original = "# Just a normal constraint file\nset_property FOO BAR\n"
89+
xdc_file.write_text(original)
90+
91+
replace_custom_constraints_in_xdc_folder(str(tmp_path), None)
92+
93+
assert xdc_file.read_text() == original
94+
95+
def test_given_multiple_xdc_files__when_replaced__then_all_processed(self, tmp_path):
96+
for name in ["a.xdc", "b.xdc", "c.xdc"]:
97+
(tmp_path / name).write_text(
98+
"#LabVIEWFPGAHdlTools_Macro macro_GitHubCustomConstraints\n"
99+
)
100+
custom_file = tmp_path / "custom.xdc"
101+
custom_file.write_text("REPLACED")
102+
103+
replace_custom_constraints_in_xdc_folder(str(tmp_path), str(custom_file))
104+
105+
for name in ["a.xdc", "b.xdc", "c.xdc"]:
106+
assert "REPLACED" in (tmp_path / name).read_text()

0 commit comments

Comments
 (0)