Refactors Xilinx ISE tool execution and report handling

Replaces repetitive tool execution logic with a reusable `run_tool` function to streamline code and reduce duplication.

Introduces a shared `copy_report_file` utility for consistent report copying and error handling.

Simplifies individual tool scripts (XST, NGDBuild, MAP, PAR, BitGen) by delegating core logic to common utilities.

Improves maintainability and consistency across the codebase.
This commit is contained in:
2025-04-26 13:59:40 +00:00
parent 91f4f03d97
commit 5c52c0cf59
7 changed files with 116 additions and 274 deletions

View File

@@ -30,21 +30,15 @@ print(f"XST project file generated at {DIRECTORIES.build}/{project.name}.prj")
print(f"XST script file generated at {DIRECTORIES.build}/{project.name}.scr")
run_xst(project)
print("Run XST")
copy_synthesis_report(project)
print("Copy synthesis report")
run_ngdbuild(project)
print("Run ngdbuild")
run_map(project)
print("Run map")
copy_map_report(project)
print("Copy map report")
run_par(project)
print("Run par")
copy_par_report(project)
copy_pinout_report(project)

View File

@@ -4,45 +4,20 @@ import shutil
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import run_tool
def run_bitgen(project: ProjectConfig, working_dir: Optional[str] = None):
"""
Führt Xilinx BitGen aus, um den finalen Bitstream zu erzeugen.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis.
"""
if working_dir is None:
working_dir = DIRECTORIES.build
def run_bitgen(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="bitgen",
tool_option_attr="bitgen",
mandatory_arguments=[
"-w",
f"{project.name}.ncd",
f"{project.name}.bit"
]
)
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64") # oder "nt64" für Windows
bitgen_executable = os.path.join(xilinx_bin_dir, "bitgen")
if not os.path.exists(bitgen_executable):
raise FileNotFoundError(f"BitGen-Executable nicht gefunden unter: {bitgen_executable}")
print(f"[hdlbuild] Starte BitGen über {bitgen_executable}")
print(f"[hdlbuild] Arbeitsverzeichnis: {working_dir}")
cmd = [bitgen_executable]
# Füge zuerst die "common" Optionen ein (falls vorhanden)
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Dann die BitGen-spezifischen Optionen
if project.tool_options and project.tool_options.bitgen:
cmd.extend(project.tool_options.bitgen)
# Dann die Pflicht-Argumente
cmd.extend([
"-w",
f"{project.name}.ncd",
f"{project.name}.bit"
])
subprocess.run(cmd, cwd=working_dir, check=True)
def copy_bitstream_file(project: ProjectConfig):
"""

View File

@@ -1,3 +1,4 @@
import shutil
import subprocess
import os
from typing import Optional, List
@@ -7,20 +8,20 @@ from models.config import DIRECTORIES
def run_tool(
project: ProjectConfig,
tool_executable_name: str,
tool_option_attr: str,
mandatory_arguments: List[str],
tool_option_attr: Optional[str] = None,
working_dir: Optional[str] = None
):
"""
Führt ein beliebiges Xilinx ISE Tool aus (XST, NGDBuild, MAP, PAR, BitGen),
mit Common- und Toolspezifischen Optionen + festen Pflichtargumenten.
mit Common- und ggf. Toolspezifischen Optionen + festen Pflichtargumenten.
Args:
project (ProjectConfig): Das Projekt-Objekt
tool_executable_name (str): z.B. "xst", "map", "par", "bitgen"
tool_option_attr (str): Attribut-Name in tool_options, z.B. "xst", "map"
mandatory_arguments (List[str]): Liste mit Pflicht-Argumenten
working_dir (str, optional): Arbeitsverzeichnis
tool_option_attr (Optional[str]): Attribut-Name in tool_options, z.B. "xst", "map"
working_dir (Optional[str]): Arbeitsverzeichnis
"""
if working_dir is None:
working_dir = DIRECTORIES.build
@@ -40,8 +41,8 @@ def run_tool(
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Füge dann Toolspezifische Optionen ein
if project.tool_options:
# Füge dann Toolspezifische Optionen ein (nur wenn angegeben)
if tool_option_attr and project.tool_options:
tool_opts = getattr(project.tool_options, tool_option_attr, [])
if tool_opts:
cmd.extend(tool_opts)
@@ -52,3 +53,29 @@ def run_tool(
print(f"[hdlbuild] Befehl: {' '.join(cmd)}")
subprocess.run(cmd, cwd=working_dir, check=True)
def copy_report_file(
project: ProjectConfig,
source_filename: str,
destination_filename: str,
description: str = "Report"
):
"""
Kopiert eine beliebige Report-Datei vom Build- in das Report-Verzeichnis.
Args:
project (ProjectConfig): Geladene Projektkonfiguration
source_filename (str): Name der Quelldatei im Build-Ordner
destination_filename (str): Neuer Name der Zieldatei im Report-Ordner
description (str): Optionale Beschreibung für die Ausgabe (z.B. "Synthesis Report")
"""
src_path = os.path.join(DIRECTORIES.build, source_filename)
dst_path = os.path.join(DIRECTORIES.report, destination_filename)
if not os.path.exists(src_path):
raise FileNotFoundError(f"{description} nicht gefunden: {src_path}")
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
print(f"[hdlbuild] {description} kopiert nach {dst_path}")

View File

@@ -4,64 +4,26 @@ import shutil
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_report_file, run_tool
def run_map(project: ProjectConfig, working_dir: Optional[str] = None):
"""
Führt Xilinx MAP aus, basierend auf dem gegebenen Projekt.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis.
"""
if working_dir is None:
working_dir = DIRECTORIES.build
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64") # oder "nt64" für Windows
map_executable = os.path.join(xilinx_bin_dir, "map")
if not os.path.exists(map_executable):
raise FileNotFoundError(f"MAP-Executable nicht gefunden unter: {map_executable}")
print(f"[hdlbuild] Starte MAP über {map_executable}")
print(f"[hdlbuild] Arbeitsverzeichnis: {working_dir}")
cmd = [map_executable]
# Füge zuerst die "common" Optionen ein (falls vorhanden)
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Dann die MAP-spezifischen Optionen
if project.tool_options and project.tool_options.map:
cmd.extend(project.tool_options.map)
# Dann die Pflicht-Argumente
cmd.extend([
"-p", project.target_device,
"-w",
f"{project.name}.ngd",
"-o", f"{project.name}.map.ncd",
f"{project.name}.pcf"
])
subprocess.run(cmd, cwd=working_dir, check=True)
def run_map(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="map",
tool_option_attr="map",
mandatory_arguments=[
"-p", project.target_device,
"-w",
f"{project.name}.ngd",
"-o", f"{project.name}.map.ncd",
f"{project.name}.pcf"
]
)
def copy_map_report(project: ProjectConfig):
"""
Kopiert den Map-Report (.map.mrp) vom Build-Verzeichnis ins Report-Verzeichnis
und benennt ihn sinnvoll um.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
"""
src_path = os.path.join(DIRECTORIES.build, f"{project.name}.map.mrp")
dst_path = os.path.join(DIRECTORIES.report, f"{project.name}.MapReport")
if not os.path.exists(src_path):
raise FileNotFoundError(f"Map-Report nicht gefunden: {src_path}")
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
print(f"[hdlbuild] Map-Report kopiert nach {dst_path}")
copy_report_file(
project=project,
source_filename=f"{project.name}.map.mrp",
destination_filename=f"{project.name}.MapReport",
description="Map Report"
)

View File

@@ -3,44 +3,17 @@ import os
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import run_tool
def run_ngdbuild(project: ProjectConfig, working_dir: Optional[str] = None):
"""
Führt Xilinx NGDBuild aus, basierend auf dem gegebenen Projekt.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis.
"""
if working_dir is None:
working_dir = DIRECTORIES.build
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64") # oder "nt64" für Windows
ngdbuild_executable = os.path.join(xilinx_bin_dir, "ngdbuild")
if not os.path.exists(ngdbuild_executable):
raise FileNotFoundError(f"NGDBuild-Executable nicht gefunden unter: {ngdbuild_executable}")
print(f"[hdlbuild] Starte NGDBuild über {ngdbuild_executable}")
print(f"[hdlbuild] Arbeitsverzeichnis: {working_dir}")
cmd = [ngdbuild_executable]
# Füge zuerst die "common" Optionen ein (falls vorhanden)
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Dann die NGDBuild-spezifischen Optionen
if project.tool_options and project.tool_options.ngdbuild:
cmd.extend(project.tool_options.ngdbuild)
# Dann die Pflicht-Argumente
cmd.extend([
"-p", project.target_device,
"-uc", f"{DIRECTORIES.get_relative_prefix()}{project.constraints}",
f"{project.name}.ngc",
f"{project.name}.ngd"
])
subprocess.run(cmd, cwd=working_dir, check=True)
def run_ngdbuild(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="ngdbuild",
tool_option_attr="ngdbuild",
mandatory_arguments=[
"-p", project.target_device,
"-uc", f"{DIRECTORIES.get_relative_prefix()}{project.constraints}",
f"{project.name}.ngc",
f"{project.name}.ngd"
]
)

View File

@@ -4,82 +4,33 @@ import os
from typing import Optional
from models.project import ProjectConfig
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_report_file, run_tool
def run_par(project: ProjectConfig, working_dir: Optional[str] = None):
"""
Führt Xilinx PAR (Place & Route) aus, basierend auf dem gegebenen Projekt.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis.
"""
if working_dir is None:
working_dir = DIRECTORIES.build
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64") # oder "nt64" für Windows
par_executable = os.path.join(xilinx_bin_dir, "par")
if not os.path.exists(par_executable):
raise FileNotFoundError(f"PAR-Executable nicht gefunden unter: {par_executable}")
print(f"[hdlbuild] Starte PAR über {par_executable}")
print(f"[hdlbuild] Arbeitsverzeichnis: {working_dir}")
cmd = [par_executable]
# Füge zuerst die "common" Optionen ein (falls vorhanden)
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Dann die PAR-spezifischen Optionen
if project.tool_options and project.tool_options.par:
cmd.extend(project.tool_options.par)
# Dann die Pflicht-Argumente
cmd.extend([
"-w",
f"{project.name}.map.ncd",
f"{project.name}.ncd",
f"{project.name}.pcf"
])
subprocess.run(cmd, cwd=working_dir, check=True)
def run_par(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="par",
tool_option_attr="par",
mandatory_arguments=[
"-w",
f"{project.name}.map.ncd",
f"{project.name}.ncd",
f"{project.name}.pcf"
]
)
def copy_par_report(project: ProjectConfig):
"""
Kopiert den Place & Route Report (.par) vom Build-Verzeichnis ins Report-Verzeichnis
und benennt ihn sinnvoll um.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
"""
src_path = os.path.join(DIRECTORIES.build, f"{project.name}.par")
dst_path = os.path.join(DIRECTORIES.report, f"{project.name}.PlaceRouteReport")
if not os.path.exists(src_path):
raise FileNotFoundError(f"PAR-Report nicht gefunden: {src_path}")
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
print(f"[hdlbuild] PAR-Report kopiert nach {dst_path}")
copy_report_file(
project=project,
source_filename=f"{project.name}.par",
destination_filename=f"{project.name}.PlaceRouteReport",
description="Place & Route Report"
)
def copy_pinout_report(project: ProjectConfig):
"""
Kopiert den Pinout Summary Report (_pad.txt) vom Build-Verzeichnis ins Report-Verzeichnis
und benennt ihn sinnvoll um.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
"""
src_path = os.path.join(DIRECTORIES.build, f"{project.name}_pad.txt")
dst_path = os.path.join(DIRECTORIES.report, f"{project.name}.PinoutReport")
if not os.path.exists(src_path):
raise FileNotFoundError(f"Pinout-Report nicht gefunden: {src_path}")
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
print(f"[hdlbuild] Pinout-Report kopiert nach {dst_path}")
copy_report_file(
project=project,
source_filename=f"{project.name}_pad.txt",
destination_filename=f"{project.name}.PinoutReport",
description="Pinout Report"
)

View File

@@ -1,5 +1,6 @@
from typing import Optional
from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_report_file, run_tool
from utils.source_resolver import expand_sources
from models.project import ProjectConfig
import subprocess
@@ -44,58 +45,17 @@ def generate_xst_script_file(project: ProjectConfig, output_path: str):
def run_xst(project: ProjectConfig, working_dir: Optional[str] = None):
"""
Führt Xilinx XST Synthese aus, basierend auf dem gegebenen Projekt.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
working_dir (str, optional): Pfad, wo .prj/.scr liegen und gebaut werden soll.
Wenn None, wird das aktuelle Arbeitsverzeichnis verwendet.
"""
if working_dir is None:
working_dir = DIRECTORIES.build
xilinx_bin_dir = os.path.join(project.xilinx_path, "bin", "lin64") # oder "nt64" für Windows
xst_executable = os.path.join(xilinx_bin_dir, "xst")
if not os.path.exists(xst_executable):
raise FileNotFoundError(f"XST-Executable nicht gefunden unter: {xst_executable}")
print(f"[hdlbuild] Starte XST Synthese über {xst_executable}")
print(f"[hdlbuild] Arbeitsverzeichnis: {working_dir}")
cmd = [xst_executable]
# Füge die "common" Optionen ein, wenn sie existieren
if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common)
# Jetzt die XST-spezifischen Aufrufe
cmd.extend([
"-ifn", f"{project.name}.scr"
])
print(f"[hdlbuild] XST-Befehl: {' '.join(cmd)}")
subprocess.run(cmd, cwd=working_dir, check=True)
def run_xst(project: ProjectConfig):
run_tool(
project=project,
tool_executable_name="xst",
mandatory_arguments=["-ifn", f"{project.name}.scr"]
)
def copy_synthesis_report(project: ProjectConfig):
"""
Kopiert den Synthesebericht (.srp) vom Build-Verzeichnis ins Report-Verzeichnis
und benennt ihn sinnvoll um.
Args:
project (ProjectConfig): Geladene Projektkonfiguration.
"""
src_path = os.path.join(DIRECTORIES.build, f"{project.name}.srp")
dst_path = os.path.join(DIRECTORIES.report, f"{project.name}.SynthesisReport")
if not os.path.exists(src_path):
raise FileNotFoundError(f"Synthesebericht nicht gefunden: {src_path}")
# Stelle sicher, dass das Zielverzeichnis existiert
os.makedirs(DIRECTORIES.report, exist_ok=True)
shutil.copyfile(src_path, dst_path)
print(f"[hdlbuild] Synthesebericht kopiert nach {dst_path}")
copy_report_file(
project=project,
source_filename=f"{project.name}.srp",
destination_filename=f"{project.name}.SynthesisReport",
description="Synthesebericht"
)