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") print(f"XST script file generated at {DIRECTORIES.build}/{project.name}.scr")
run_xst(project) run_xst(project)
print("Run XST")
copy_synthesis_report(project) copy_synthesis_report(project)
print("Copy synthesis report")
run_ngdbuild(project) run_ngdbuild(project)
print("Run ngdbuild")
run_map(project) run_map(project)
print("Run map")
copy_map_report(project) copy_map_report(project)
print("Copy map report")
run_par(project) run_par(project)
print("Run par")
copy_par_report(project) copy_par_report(project)
copy_pinout_report(project) copy_pinout_report(project)

View File

@@ -4,45 +4,20 @@ import shutil
from typing import Optional from typing import Optional
from models.project import ProjectConfig from models.project import ProjectConfig
from models.config import DIRECTORIES from models.config import DIRECTORIES
from tools.xilinx_ise.common import run_tool
def run_bitgen(project: ProjectConfig, working_dir: Optional[str] = None): def run_bitgen(project: ProjectConfig):
""" run_tool(
Führt Xilinx BitGen aus, um den finalen Bitstream zu erzeugen. project=project,
tool_executable_name="bitgen",
Args: tool_option_attr="bitgen",
project (ProjectConfig): Geladene Projektkonfiguration. mandatory_arguments=[
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis. "-w",
""" f"{project.name}.ncd",
if working_dir is None: f"{project.name}.bit"
working_dir = DIRECTORIES.build ]
)
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): def copy_bitstream_file(project: ProjectConfig):
""" """

View File

@@ -1,3 +1,4 @@
import shutil
import subprocess import subprocess
import os import os
from typing import Optional, List from typing import Optional, List
@@ -7,20 +8,20 @@ from models.config import DIRECTORIES
def run_tool( def run_tool(
project: ProjectConfig, project: ProjectConfig,
tool_executable_name: str, tool_executable_name: str,
tool_option_attr: str,
mandatory_arguments: List[str], mandatory_arguments: List[str],
tool_option_attr: Optional[str] = None,
working_dir: Optional[str] = None working_dir: Optional[str] = None
): ):
""" """
Führt ein beliebiges Xilinx ISE Tool aus (XST, NGDBuild, MAP, PAR, BitGen), 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: Args:
project (ProjectConfig): Das Projekt-Objekt project (ProjectConfig): Das Projekt-Objekt
tool_executable_name (str): z.B. "xst", "map", "par", "bitgen" 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 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: if working_dir is None:
working_dir = DIRECTORIES.build working_dir = DIRECTORIES.build
@@ -40,8 +41,8 @@ def run_tool(
if project.tool_options and project.tool_options.common: if project.tool_options and project.tool_options.common:
cmd.extend(project.tool_options.common) cmd.extend(project.tool_options.common)
# Füge dann Toolspezifische Optionen ein # Füge dann Toolspezifische Optionen ein (nur wenn angegeben)
if project.tool_options: if tool_option_attr and project.tool_options:
tool_opts = getattr(project.tool_options, tool_option_attr, []) tool_opts = getattr(project.tool_options, tool_option_attr, [])
if tool_opts: if tool_opts:
cmd.extend(tool_opts) cmd.extend(tool_opts)
@@ -52,3 +53,29 @@ def run_tool(
print(f"[hdlbuild] Befehl: {' '.join(cmd)}") print(f"[hdlbuild] Befehl: {' '.join(cmd)}")
subprocess.run(cmd, cwd=working_dir, check=True) 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 typing import Optional
from models.project import ProjectConfig from models.project import ProjectConfig
from models.config import DIRECTORIES 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): def run_map(project: ProjectConfig):
""" run_tool(
Führt Xilinx MAP aus, basierend auf dem gegebenen Projekt. project=project,
tool_executable_name="map",
Args: tool_option_attr="map",
project (ProjectConfig): Geladene Projektkonfiguration. mandatory_arguments=[
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis. "-p", project.target_device,
""" "-w",
if working_dir is None: f"{project.name}.ngd",
working_dir = DIRECTORIES.build "-o", f"{project.name}.map.ncd",
f"{project.name}.pcf"
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 copy_map_report(project: ProjectConfig): def copy_map_report(project: ProjectConfig):
""" copy_report_file(
Kopiert den Map-Report (.map.mrp) vom Build-Verzeichnis ins Report-Verzeichnis project=project,
und benennt ihn sinnvoll um. source_filename=f"{project.name}.map.mrp",
destination_filename=f"{project.name}.MapReport",
Args: description="Map Report"
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}")

View File

@@ -3,44 +3,17 @@ import os
from typing import Optional from typing import Optional
from models.project import ProjectConfig from models.project import ProjectConfig
from models.config import DIRECTORIES from models.config import DIRECTORIES
from tools.xilinx_ise.common import run_tool
def run_ngdbuild(project: ProjectConfig, working_dir: Optional[str] = None): def run_ngdbuild(project: ProjectConfig):
""" run_tool(
Führt Xilinx NGDBuild aus, basierend auf dem gegebenen Projekt. project=project,
tool_executable_name="ngdbuild",
Args: tool_option_attr="ngdbuild",
project (ProjectConfig): Geladene Projektkonfiguration. mandatory_arguments=[
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis. "-p", project.target_device,
""" "-uc", f"{DIRECTORIES.get_relative_prefix()}{project.constraints}",
if working_dir is None: f"{project.name}.ngc",
working_dir = DIRECTORIES.build f"{project.name}.ngd"
]
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)

View File

@@ -4,82 +4,33 @@ import os
from typing import Optional from typing import Optional
from models.project import ProjectConfig from models.project import ProjectConfig
from models.config import DIRECTORIES 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): def run_par(project: ProjectConfig):
""" run_tool(
Führt Xilinx PAR (Place & Route) aus, basierend auf dem gegebenen Projekt. project=project,
tool_executable_name="par",
Args: tool_option_attr="par",
project (ProjectConfig): Geladene Projektkonfiguration. mandatory_arguments=[
working_dir (str, optional): Arbeitsverzeichnis; Standard: build-Verzeichnis. "-w",
""" f"{project.name}.map.ncd",
if working_dir is None: f"{project.name}.ncd",
working_dir = DIRECTORIES.build f"{project.name}.pcf"
]
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 copy_par_report(project: ProjectConfig): def copy_par_report(project: ProjectConfig):
""" copy_report_file(
Kopiert den Place & Route Report (.par) vom Build-Verzeichnis ins Report-Verzeichnis project=project,
und benennt ihn sinnvoll um. source_filename=f"{project.name}.par",
destination_filename=f"{project.name}.PlaceRouteReport",
Args: description="Place & Route Report"
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}")
def copy_pinout_report(project: ProjectConfig): def copy_pinout_report(project: ProjectConfig):
""" copy_report_file(
Kopiert den Pinout Summary Report (_pad.txt) vom Build-Verzeichnis ins Report-Verzeichnis project=project,
und benennt ihn sinnvoll um. source_filename=f"{project.name}_pad.txt",
destination_filename=f"{project.name}.PinoutReport",
Args: description="Pinout Report"
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}")

View File

@@ -1,5 +1,6 @@
from typing import Optional from typing import Optional
from models.config import DIRECTORIES from models.config import DIRECTORIES
from tools.xilinx_ise.common import copy_report_file, run_tool
from utils.source_resolver import expand_sources from utils.source_resolver import expand_sources
from models.project import ProjectConfig from models.project import ProjectConfig
import subprocess 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): def run_xst(project: ProjectConfig):
""" run_tool(
Führt Xilinx XST Synthese aus, basierend auf dem gegebenen Projekt. project=project,
tool_executable_name="xst",
Args: mandatory_arguments=["-ifn", f"{project.name}.scr"]
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 copy_synthesis_report(project: ProjectConfig): def copy_synthesis_report(project: ProjectConfig):
""" copy_report_file(
Kopiert den Synthesebericht (.srp) vom Build-Verzeichnis ins Report-Verzeichnis project=project,
und benennt ihn sinnvoll um. source_filename=f"{project.name}.srp",
destination_filename=f"{project.name}.SynthesisReport",
Args: description="Synthesebericht"
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}")