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:
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