Add project configuration and build tools
- Introduced new scripts for project generation and synthesis (dodo.py, generate_prj.py, generate_scr.py, run_xst.py). - Implemented configuration parsing for VHDL sources and project settings (config.py). - Added default configuration values (defaults.py). - Updated .gitignore to include additional file types. - Created test cases for project generation and configuration parsing (test_generate_prj.py, test_generate_scr.py, test_project_cfg.py).
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,5 @@
|
|||||||
working/
|
working/
|
||||||
reports/
|
reports/
|
||||||
|
*__pycache__*
|
||||||
|
*.venv
|
||||||
|
.doit.db
|
||||||
|
78
dodo.py
Normal file
78
dodo.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from tasks.generate_prj import generate_prj
|
||||||
|
from tasks.generate_scr import generate_scr
|
||||||
|
from tools.config import parse_project_cfg
|
||||||
|
from tools.defaults import with_defaults
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERE = Path(__file__).parent.resolve() # Verzeichnis, in dem dodo.py liegt
|
||||||
|
CFG_PATH = HERE.parent / "project.cfg"
|
||||||
|
TOML_PATH = HERE.parent / "vhdl_ls.toml"
|
||||||
|
|
||||||
|
|
||||||
|
def _get_project_name(cfg_path: Path) -> str:
|
||||||
|
return with_defaults(parse_project_cfg(cfg_path))["PROJECT"]
|
||||||
|
|
||||||
|
|
||||||
|
def task_generate_prj():
|
||||||
|
cfg_path = Path(CFG_PATH)
|
||||||
|
toml_path = Path(TOML_PATH)
|
||||||
|
project = _get_project_name(cfg_path)
|
||||||
|
prj_path = Path("working") / f"{project}.prj"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"actions": [(generate_prj, [prj_path, cfg_path, toml_path])],
|
||||||
|
"file_dep": [cfg_path, toml_path],
|
||||||
|
"targets": [prj_path],
|
||||||
|
"verbosity": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def task_generate_scr():
|
||||||
|
cfg_path = Path(CFG_PATH)
|
||||||
|
project = _get_project_name(cfg_path)
|
||||||
|
scr_path = Path("working") / f"{project}.scr"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"actions": [(generate_scr, [scr_path, cfg_path])],
|
||||||
|
"file_dep": [cfg_path],
|
||||||
|
"targets": [scr_path],
|
||||||
|
"verbosity": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def task_xst():
|
||||||
|
"""Run XST synthesizer"""
|
||||||
|
cfg_path = Path(CFG_PATH)
|
||||||
|
cfg = with_defaults(parse_project_cfg(cfg_path))
|
||||||
|
|
||||||
|
build_dir = Path(cfg["BUILD_DIR"])
|
||||||
|
project = cfg["PROJECT"]
|
||||||
|
scr_file = f"{project}.scr"
|
||||||
|
scr_path = build_dir / scr_file
|
||||||
|
prj_path = build_dir / f"{project}.prj"
|
||||||
|
xilinx_path = Path(cfg["XILINX"])
|
||||||
|
xilinx_platform = "lin64" # TODO: dynamisch ermitteln, falls nötig
|
||||||
|
|
||||||
|
xst_exe = xilinx_path / "bin" / xilinx_platform / "xst"
|
||||||
|
common_opts = cfg.get("COMMON_OPTS", "")
|
||||||
|
|
||||||
|
def action():
|
||||||
|
print(f"============ Running XST ============")
|
||||||
|
print(f"> {xst_exe} {common_opts} -ifn {scr_file}")
|
||||||
|
subprocess.run(
|
||||||
|
[str(xst_exe), "-ifn", scr_file],
|
||||||
|
cwd=build_dir,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
"actions": [action],
|
||||||
|
"file_dep": [scr_path, prj_path], # <== beides!
|
||||||
|
"targets": [Path(cfg["REPORT_DIR"]) / f"{project}.SynthesisReport"],
|
||||||
|
"verbosity": 2,
|
||||||
|
"task_dep": ["generate_scr", "generate_prj"],
|
||||||
|
}
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
toml
|
||||||
|
pytest
|
||||||
|
doit
|
14
tasks/generate_prj.py
Normal file
14
tasks/generate_prj.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import os
|
||||||
|
from tools.config import get_vhdl_sources_and_tests
|
||||||
|
from tools.paths import REL_FROM_WORKING_TO_ROOT as REL_ROOT, ROOT, WORKING
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def generate_prj(prj_path: Path, *_):
|
||||||
|
sources, _ = get_vhdl_sources_and_tests()
|
||||||
|
prj_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
with prj_path.open("w", encoding="utf-8") as f:
|
||||||
|
for libname, files in sources.items():
|
||||||
|
for file in files:
|
||||||
|
rel_path = os.path.relpath(ROOT / file, WORKING)
|
||||||
|
f.write(f"vhdl {libname} {rel_path}\n")
|
33
tasks/generate_scr.py
Normal file
33
tasks/generate_scr.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from tools.config import parse_project_cfg
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def generate_scr(scr_path: Path, cfg_path: Path):
|
||||||
|
cfg = parse_project_cfg(cfg_path)
|
||||||
|
|
||||||
|
project = cfg["PROJECT"]
|
||||||
|
top = cfg.get("TOPLEVEL", project)
|
||||||
|
target_part = cfg["TARGET_PART"]
|
||||||
|
xst_opts = cfg.get("XST_OPTS", "")
|
||||||
|
|
||||||
|
# SCR-Datei vollständig in einer Zeile, wie im Makefile
|
||||||
|
scr_parts = [
|
||||||
|
"run",
|
||||||
|
f"-ifn {project}.prj",
|
||||||
|
f"-ofn {project}.ngc",
|
||||||
|
"-ifmt mixed",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Optional: Optionen aus XST_OPTS hinzufügen
|
||||||
|
if xst_opts.strip():
|
||||||
|
scr_parts += xst_opts.strip().split()
|
||||||
|
|
||||||
|
scr_parts += [
|
||||||
|
f"-top {top}",
|
||||||
|
"-ofmt NGC",
|
||||||
|
f"-p {target_part}",
|
||||||
|
]
|
||||||
|
|
||||||
|
scr_line = " ".join(scr_parts).replace("\n", " ").replace("\r", " ")
|
||||||
|
scr_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
scr_path.write_text(scr_line, encoding="utf-8")
|
32
tasks/run_xst.py
Normal file
32
tasks/run_xst.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import subprocess
|
||||||
|
from tools.paths import WORKING, REPORTS, PROJECT_CFG
|
||||||
|
from tools.config import parse_project_cfg
|
||||||
|
from tools.defaults import with_defaults
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_xst():
|
||||||
|
cfg = with_defaults(parse_project_cfg(PROJECT_CFG))
|
||||||
|
|
||||||
|
project = cfg["PROJECT"]
|
||||||
|
scr_file = f"{project}.scr"
|
||||||
|
scr_path = WORKING / scr_file
|
||||||
|
xilinx_path = Path(cfg["XILINX"])
|
||||||
|
xilinx_platform = "lin64" # TODO: Optional dynamisch machen
|
||||||
|
xst_exe = xilinx_path / "bin" / xilinx_platform / "xst"
|
||||||
|
common_opts = cfg.get("COMMON_OPTS", "")
|
||||||
|
|
||||||
|
if not xst_exe.exists():
|
||||||
|
raise FileNotFoundError(f"xst executable not found at {xst_exe}")
|
||||||
|
|
||||||
|
if not scr_path.exists():
|
||||||
|
raise FileNotFoundError(f"SCR file not found: {scr_path}")
|
||||||
|
|
||||||
|
print(f"\n============ Running XST ============\n")
|
||||||
|
print(f"> {xst_exe} {common_opts} -ifn {scr_file}\n")
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
[str(xst_exe), *common_opts.split(), "-ifn", scr_file],
|
||||||
|
cwd=WORKING,
|
||||||
|
check=True
|
||||||
|
)
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
72
tests/test_config.py
Normal file
72
tests/test_config.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from tools.config import parse_vhdl_ls_toml
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_parsing_skips_third_party_libs():
|
||||||
|
content = textwrap.dedent("""
|
||||||
|
[libraries.lib]
|
||||||
|
files = ["src/main.vhd"]
|
||||||
|
|
||||||
|
[libraries.XILINX]
|
||||||
|
files = ["/opt/Xilinx/abc.vhd"]
|
||||||
|
is_third_party = true
|
||||||
|
""")
|
||||||
|
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
f.flush()
|
||||||
|
result = parse_vhdl_ls_toml(Path(f.name))
|
||||||
|
|
||||||
|
assert "work" in result
|
||||||
|
assert "XILINX" not in result
|
||||||
|
assert result["work"] == [Path("src/main.vhd")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_parsing_multiple_libraries():
|
||||||
|
content = textwrap.dedent("""
|
||||||
|
[libraries.lib]
|
||||||
|
files = ["src/foo.vhd", "src/bar.vhd"]
|
||||||
|
|
||||||
|
[libraries.myip]
|
||||||
|
files = ["ipcore/top.vhd"]
|
||||||
|
""")
|
||||||
|
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
f.flush()
|
||||||
|
result = parse_vhdl_ls_toml(Path(f.name))
|
||||||
|
|
||||||
|
assert "work" in result
|
||||||
|
assert "myip" in result
|
||||||
|
assert result["work"] == [Path("src/foo.vhd"), Path("src/bar.vhd")]
|
||||||
|
assert result["myip"] == [Path("ipcore/top.vhd")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_toml_file():
|
||||||
|
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
|
||||||
|
f.write("") # Empty file
|
||||||
|
f.flush()
|
||||||
|
result = parse_vhdl_ls_toml(Path(f.name))
|
||||||
|
|
||||||
|
assert result == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_file_raises_error():
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
parse_vhdl_ls_toml(Path("nonexistent.toml"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_libname_translation_to_work():
|
||||||
|
content = textwrap.dedent("""
|
||||||
|
[libraries.lib]
|
||||||
|
files = ["src/xyz.vhd"]
|
||||||
|
""")
|
||||||
|
with tempfile.NamedTemporaryFile("w+", suffix=".toml", delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
f.flush()
|
||||||
|
result = parse_vhdl_ls_toml(Path(f.name))
|
||||||
|
|
||||||
|
assert "work" in result
|
||||||
|
assert "lib" not in result
|
||||||
|
assert result["work"] == [Path("src/xyz.vhd")]
|
47
tests/test_generate_prj.py
Normal file
47
tests/test_generate_prj.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from tasks.generate_prj import generate_prj
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(path: Path, content: str):
|
||||||
|
path.write_text(content.strip(), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_prj_single_lib(tmp_path):
|
||||||
|
cfg_path = tmp_path / "project.cfg"
|
||||||
|
toml_path = tmp_path / "vhdl_ls.toml"
|
||||||
|
prj_path = tmp_path / "working" / "MyProject.prj"
|
||||||
|
|
||||||
|
write_file(cfg_path, "PROJECT = MyProject\nTARGET_PART = dummy\nXILINX = /some/path")
|
||||||
|
write_file(toml_path, """
|
||||||
|
[libraries.lib]
|
||||||
|
files = ["src/main.vhd"]
|
||||||
|
""")
|
||||||
|
|
||||||
|
generate_prj(prj_path, cfg_path, toml_path)
|
||||||
|
|
||||||
|
assert prj_path.exists()
|
||||||
|
content = prj_path.read_text()
|
||||||
|
assert "vhdl work ../src/main.vhd" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_prj_multiple_libs(tmp_path):
|
||||||
|
cfg_path = tmp_path / "project.cfg"
|
||||||
|
toml_path = tmp_path / "vhdl_ls.toml"
|
||||||
|
prj_path = tmp_path / "working" / "MyProject.prj"
|
||||||
|
|
||||||
|
write_file(cfg_path, "PROJECT = MyProject\nTARGET_PART = dummy\nXILINX = /some/path")
|
||||||
|
write_file(toml_path, """
|
||||||
|
[libraries.lib]
|
||||||
|
files = ["src/A.vhd", "src/B.vhd"]
|
||||||
|
|
||||||
|
[libraries.otherlib]
|
||||||
|
files = ["src/C.vhd"]
|
||||||
|
""")
|
||||||
|
|
||||||
|
generate_prj(prj_path, cfg_path, toml_path)
|
||||||
|
|
||||||
|
content = prj_path.read_text()
|
||||||
|
assert "vhdl work ../src/A.vhd" in content
|
||||||
|
assert "vhdl work ../src/B.vhd" in content
|
||||||
|
assert "vhdl otherlib ../src/C.vhd" in content
|
44
tests/test_generate_scr.py
Normal file
44
tests/test_generate_scr.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from tasks.generate_scr import generate_scr
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(path: Path, content: str):
|
||||||
|
path.write_text(content.strip(), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_scr_basic(tmp_path):
|
||||||
|
cfg_path = tmp_path / "project.cfg"
|
||||||
|
scr_path = tmp_path / "working" / "MyProject.scr"
|
||||||
|
|
||||||
|
write_file(cfg_path, """
|
||||||
|
PROJECT = MyProject
|
||||||
|
TARGET_PART = xc3s50-4-pq208
|
||||||
|
XILINX = /some/path
|
||||||
|
""")
|
||||||
|
|
||||||
|
generate_scr(scr_path, cfg_path)
|
||||||
|
|
||||||
|
content = scr_path.read_text()
|
||||||
|
assert "-ifn MyProject.prj" in content
|
||||||
|
assert "-ofn MyProject.ngc" in content
|
||||||
|
assert "-top MyProject" in content
|
||||||
|
assert "-p xc3s50-4-pq208" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_scr_with_top_and_opts(tmp_path):
|
||||||
|
cfg_path = tmp_path / "project.cfg"
|
||||||
|
scr_path = tmp_path / "working" / "MyProject.scr"
|
||||||
|
|
||||||
|
write_file(cfg_path, """
|
||||||
|
PROJECT = MyProject
|
||||||
|
TARGET_PART = xc3s200-5-ft256
|
||||||
|
TOPLEVEL = TopModule
|
||||||
|
XST_OPTS = -opt_mode Speed -opt_level 2
|
||||||
|
XILINX = /some/path
|
||||||
|
""")
|
||||||
|
|
||||||
|
generate_scr(scr_path, cfg_path)
|
||||||
|
|
||||||
|
content = scr_path.read_text()
|
||||||
|
assert "-top TopModule" in content
|
||||||
|
assert "-opt_mode Speed -opt_level 2" in content
|
51
tests/test_project_cfg.py
Normal file
51
tests/test_project_cfg.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from build.tools.config import parse_project_cfg
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_complex_project_cfg_correctly():
|
||||||
|
cfg = textwrap.dedent("""
|
||||||
|
## Main settings.. ##
|
||||||
|
PROJECT = SpriteChannel
|
||||||
|
TARGET_PART = xc3s1200e-4-fg320
|
||||||
|
XILINX = /opt/Xilinx/14.7/ISE_DS/ISE
|
||||||
|
TOPLEVEL = SpriteChannel
|
||||||
|
CONSTRAINTS = src/SpriteChannel.ucf
|
||||||
|
|
||||||
|
# Sources (should be ignored)
|
||||||
|
VHDSOURCE += src/GenericCounter.vhd
|
||||||
|
VSOURCE += src/something.v
|
||||||
|
VHDTEST += test/SpriteChannel_tb.vhd
|
||||||
|
|
||||||
|
# Options
|
||||||
|
XST_OPTS += -opt_mode Speed
|
||||||
|
XST_OPTS += -opt_level 2
|
||||||
|
MAP_OPTS = -detail -timing -ol high
|
||||||
|
PAR_OPTS = -ol high
|
||||||
|
BITGEN_OPTS = -g StartupClk:JtagClk
|
||||||
|
|
||||||
|
# Programmer
|
||||||
|
PROGRAMMER = digilent
|
||||||
|
""")
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile("w+", suffix=".cfg", delete=False) as f:
|
||||||
|
f.write(cfg)
|
||||||
|
f.flush()
|
||||||
|
result = parse_project_cfg(Path(f.name))
|
||||||
|
|
||||||
|
assert result["PROJECT"] == "SpriteChannel"
|
||||||
|
assert result["TARGET_PART"] == "xc3s1200e-4-fg320"
|
||||||
|
assert result["XILINX"] == "/opt/Xilinx/14.7/ISE_DS/ISE"
|
||||||
|
assert result["TOPLEVEL"] == "SpriteChannel"
|
||||||
|
assert result["CONSTRAINTS"] == "src/SpriteChannel.ucf"
|
||||||
|
assert result["XST_OPTS"] == "-opt_mode Speed -opt_level 2"
|
||||||
|
assert result["MAP_OPTS"] == "-detail -timing -ol high"
|
||||||
|
assert result["PAR_OPTS"] == "-ol high"
|
||||||
|
assert result["BITGEN_OPTS"] == "-g StartupClk:JtagClk"
|
||||||
|
assert result["PROGRAMMER"] == "digilent"
|
||||||
|
|
||||||
|
# Sicherstellen, dass SOURCE-Felder ignoriert wurden
|
||||||
|
assert "VHDSOURCE" not in result
|
||||||
|
assert "VSOURCE" not in result
|
||||||
|
assert "VHDTEST" not in result
|
0
tools/__init__.py
Normal file
0
tools/__init__.py
Normal file
88
tools/config.py
Normal file
88
tools/config.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
from tools.paths import ROOT, PROJECT_CFG, VHDL_LS_TOML
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
import toml
|
||||||
|
import re
|
||||||
|
from typing import Tuple
|
||||||
|
from tools.defaults import with_defaults
|
||||||
|
|
||||||
|
def parse_vhdl_ls_toml(toml_path: Optional[Path] = None) -> Dict[str, List[Path]]:
|
||||||
|
if toml_path is None:
|
||||||
|
toml_path = VHDL_LS_TOML
|
||||||
|
|
||||||
|
if not toml_path.exists():
|
||||||
|
raise FileNotFoundError(f"{toml_path} not found.")
|
||||||
|
|
||||||
|
with open(toml_path, "r", encoding="utf-8") as f:
|
||||||
|
raw = toml.load(f)
|
||||||
|
|
||||||
|
libraries = raw.get("libraries", {})
|
||||||
|
parsed: Dict[str, List[Path]] = {}
|
||||||
|
|
||||||
|
for libname, content in libraries.items():
|
||||||
|
if content.get("is_third_party", False):
|
||||||
|
continue
|
||||||
|
files = content.get("files", [])
|
||||||
|
actual_libname = "work" if libname == "lib" else libname
|
||||||
|
parsed[actual_libname] = [Path(f) for f in files]
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def parse_project_cfg(cfg_path: Optional[Path] = None) -> Dict[str, str]:
|
||||||
|
cfg_path = cfg_path or PROJECT_CFG
|
||||||
|
|
||||||
|
if not cfg_path.exists():
|
||||||
|
raise FileNotFoundError(f"{cfg_path} not found.")
|
||||||
|
|
||||||
|
result: Dict[str, str] = {}
|
||||||
|
ignored_keys = {"VHDSOURCE", "VSOURCE", "VHDTEST", "VTEST"}
|
||||||
|
|
||||||
|
with cfg_path.open("r", encoding="utf-8") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Kommentare und Leerzeilen überspringen
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "+=" in line:
|
||||||
|
key, value = map(str.strip, line.split("+=", 1))
|
||||||
|
if key in ignored_keys:
|
||||||
|
continue
|
||||||
|
if key in result:
|
||||||
|
result[key] += f" {value}"
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
elif "=" in line:
|
||||||
|
key, value = map(str.strip, line.split("=", 1))
|
||||||
|
if key in ignored_keys:
|
||||||
|
continue
|
||||||
|
result[key] = value
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_vhdl_sources_and_tests() -> Tuple[Dict[str, List[Path]], Dict[str, List[Path]]]:
|
||||||
|
cfg = with_defaults(parse_project_cfg())
|
||||||
|
test_filter = cfg.get("TEST_FILTER", r"^tests/|_tb\.vhd$") # Default-Fallback
|
||||||
|
|
||||||
|
try:
|
||||||
|
pattern = re.compile(test_filter)
|
||||||
|
except re.error as e:
|
||||||
|
raise ValueError(f"Invalid TEST_FILTER regex: {e}")
|
||||||
|
|
||||||
|
all_sources = parse_vhdl_ls_toml()
|
||||||
|
|
||||||
|
normal_sources: Dict[str, List[Path]] = {}
|
||||||
|
test_sources: Dict[str, List[Path]] = {}
|
||||||
|
|
||||||
|
for lib, files in all_sources.items():
|
||||||
|
for file in files:
|
||||||
|
# relative Pfade als Strings prüfen
|
||||||
|
rel_path = str(file)
|
||||||
|
if pattern.search(rel_path):
|
||||||
|
test_sources.setdefault(lib, []).append(file)
|
||||||
|
else:
|
||||||
|
normal_sources.setdefault(lib, []).append(file)
|
||||||
|
|
||||||
|
return normal_sources, test_sources
|
43
tools/defaults.py
Normal file
43
tools/defaults.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# tools/defaults.py
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
def get_default_config() -> Dict[str, str]:
|
||||||
|
return {
|
||||||
|
"TOPLEVEL": "", # wird dynamisch ersetzt
|
||||||
|
"CONSTRAINTS": "",
|
||||||
|
"BUILD_DIR": "working",
|
||||||
|
"REPORT_DIR": "reports",
|
||||||
|
"COMMON_OPTS": "-intstyle xflow",
|
||||||
|
"XST_OPTS": "",
|
||||||
|
"NGDBUILD_OPTS": "",
|
||||||
|
"MAP_OPTS": "-detail",
|
||||||
|
"PAR_OPTS": "",
|
||||||
|
"BITGEN_OPTS": "",
|
||||||
|
"TRACE_OPTS": "-v 3 -n 3",
|
||||||
|
"FUSE_OPTS": "-incremental",
|
||||||
|
"ISIM_OPTS": "-gui",
|
||||||
|
"ISIM_CMD": "",
|
||||||
|
"PROGRAMMER": "none",
|
||||||
|
"PROGRAMMER_PRE": "",
|
||||||
|
"IMPACT_OPTS": "-batch impact.cmd",
|
||||||
|
"DJTG_EXE": "djtgcfg",
|
||||||
|
"DJTG_DEVICE": "DJTG_DEVICE-NOT-SET",
|
||||||
|
"DJTG_INDEX": "0",
|
||||||
|
"DJTG_FLASH_INDEX": "1",
|
||||||
|
"XC3SPROG_EXE": "xc3sprog",
|
||||||
|
"XC3SPROG_CABLE": "none",
|
||||||
|
"XC3SPROG_OPTS": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def with_defaults(project_cfg: Dict[str, str]) -> Dict[str, str]:
|
||||||
|
merged = get_default_config()
|
||||||
|
merged.update(project_cfg)
|
||||||
|
|
||||||
|
# Fallbacks sicherstellen (kein leerer oder None-Wert bleibt erhalten)
|
||||||
|
if not merged.get("TOPLEVEL"):
|
||||||
|
merged["TOPLEVEL"] = merged.get("PROJECT", "")
|
||||||
|
if not merged.get("CONSTRAINTS"):
|
||||||
|
merged["CONSTRAINTS"] = f"{merged.get('PROJECT', '')}.ucf"
|
||||||
|
|
||||||
|
# Alle Felder final auf gültigen string casten (zur Sicherheit)
|
||||||
|
return {k: (v if isinstance(v, str) else str(v)) for k, v in merged.items()}
|
21
tools/paths.py
Normal file
21
tools/paths.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Ermittle absoluten Pfad dieser Datei
|
||||||
|
HERE = Path(__file__).resolve()
|
||||||
|
|
||||||
|
# Gehe zurück von tools/ → build/ → Projekt-Root
|
||||||
|
ROOT = HERE.parent.parent.parent
|
||||||
|
|
||||||
|
# Abgeleitete Pfade
|
||||||
|
BUILD = ROOT / "build"
|
||||||
|
WORKING = BUILD / "working"
|
||||||
|
REPORTS = BUILD / "reports"
|
||||||
|
|
||||||
|
# Relativer Pfad von WORKING zurück zur Projektwurzel
|
||||||
|
REL_FROM_WORKING_TO_ROOT = Path(os.path.relpath(ROOT, WORKING))
|
||||||
|
|
||||||
|
# Standard-Konfigurationsdateien
|
||||||
|
PROJECT_CFG = ROOT / "project.cfg"
|
||||||
|
VHDL_LS_TOML = ROOT / "vhdl_ls.toml"
|
Reference in New Issue
Block a user