diff --git a/src/hdlbuild/cli.py b/src/hdlbuild/cli.py index a7c6130..74d2676 100644 --- a/src/hdlbuild/cli.py +++ b/src/hdlbuild/cli.py @@ -2,6 +2,8 @@ import argparse import sys from hdlbuild.dependencies.resolver import DependencyResolver +from hdlbuild.models.config import DIRECTORIES +from hdlbuild.tools.xilinx_ise.isim import build_testbench, generate_simulation_project_file, run_testbench from hdlbuild.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth from hdlbuild.utils.console_utils import ConsoleUtils from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories, ensure_directories_exist @@ -38,6 +40,12 @@ def dep(args): console_utils.print("Starting dependencies process...") DependencyResolver(project).resolve_all() +def test(args): + """Starts the test process.""" + console_utils.print("Starting test process...") + build_testbench(project, args.target) + run_testbench(args.target) + def main(): parser = argparse.ArgumentParser( @@ -67,12 +75,21 @@ def main(): parser_build.set_defaults(func=build) # Synth command - parser_build = subparsers.add_parser("synth", help="Start the synth process") - parser_build.set_defaults(func=synth) + parser_synth = subparsers.add_parser("synth", help="Start the synth process") + parser_synth.set_defaults(func=synth) # Dependencies command - parser_build = subparsers.add_parser("dep", help="Start the dependencies process") - parser_build.set_defaults(func=dep) + parser_dep = subparsers.add_parser("dep", help="Start the dependencies process") + parser_dep.set_defaults(func=dep) + + # Tests command + parser_test = subparsers.add_parser("test", help="Start the Tests process") + parser_test.set_defaults(func=test) + parser_test.add_argument( + "target", + nargs="?", + help="Select the target to test" + ) args = parser.parse_args() args.func(args) diff --git a/src/hdlbuild/tools/xilinx_ise/bitgen.py b/src/hdlbuild/tools/xilinx_ise/bitgen.py index 3eab141..f9b5cba 100644 --- a/src/hdlbuild/tools/xilinx_ise/bitgen.py +++ b/src/hdlbuild/tools/xilinx_ise/bitgen.py @@ -23,7 +23,8 @@ def copy_bitstream_file(project: ProjectConfig): copy_file( project=project, source_filename=f"{project.name}.bit", - destination_filename=f"{project.name}.Bitstream", + destination_filename=f"{project.name}.bit", + destination_dir=DIRECTORIES.copy_target, description="Bitstream File", step_number=10, total_steps=12 ) \ No newline at end of file diff --git a/src/hdlbuild/tools/xilinx_ise/common.py b/src/hdlbuild/tools/xilinx_ise/common.py index 2f83958..fc22dfc 100644 --- a/src/hdlbuild/tools/xilinx_ise/common.py +++ b/src/hdlbuild/tools/xilinx_ise/common.py @@ -46,6 +46,7 @@ def copy_file( source_filename: str, destination_filename: str, description: str = "Report", + destination_dir: str = DIRECTORIES.report, step_number: Optional[int] = None, total_steps: Optional[int] = None ): @@ -59,12 +60,12 @@ def copy_file( 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) + dst_path = os.path.join(destination_dir, destination_filename) if not os.path.exists(src_path): raise FileNotFoundError(f"{description} nicht gefunden: {src_path}") - os.makedirs(DIRECTORIES.report, exist_ok=True) + os.makedirs(destination_dir, exist_ok=True) shutil.copyfile(src_path, dst_path) diff --git a/src/hdlbuild/tools/xilinx_ise/isim.py b/src/hdlbuild/tools/xilinx_ise/isim.py new file mode 100644 index 0000000..d3a8350 --- /dev/null +++ b/src/hdlbuild/tools/xilinx_ise/isim.py @@ -0,0 +1,140 @@ +import os +from typing import List + +from hdlbuild.models.project import ProjectConfig +from hdlbuild.models.config import DIRECTORIES +from hdlbuild.dependencies.resolver import DependencyResolver +from hdlbuild.utils.console_utils import ConsoleTask +from hdlbuild.utils.source_resolver import expand_all_sources, expand_testbenches + +def generate_simulation_project_file(project: ProjectConfig, output_path: str, testbench_name: str): + """ + Generiert die ISim Simulationsprojektdatei (.prj). + + Args: + project (ProjectConfig): Das Hauptprojekt. + output_path (str): Zielpfad für die .prj Datei. + testbench_name (str): Name der Testbench-Datei (z.B. "VGATimingGenerator_test_tb"). + """ + resolver = DependencyResolver(project, offline_mode=True) + resolver.resolve_all() + + vhdl_sources, verilog_sources = expand_all_sources(project, resolver.resolved) + + with open(output_path, "w") as f: + # Normale VHDL-Sources + for lib, file in vhdl_sources: + f.write(f"vhdl {lib} \"{DIRECTORIES.get_relative_prefix()}{file}\"\n") + + # Normale Verilog-Sources + for lib, file in verilog_sources: + f.write(f"verilog {lib} \"{DIRECTORIES.get_relative_prefix()}{file}\"\n") + + # Testbench-Datei suchen und einfügen + testbench_file = find_testbench_file(project, testbench_name) + normalized_tb = os.path.normpath(testbench_file) + f.write(f"vhdl work \"{DIRECTORIES.get_relative_prefix()}{normalized_tb}\"\n") + + # glbl.v immer zuletzt + f.write(f"verilog work /opt/Xilinx/14.7/ISE_DS/ISE/verilog/src/glbl.v\n") + + +def find_testbench_file(project: ProjectConfig, testbench_name: str) -> str: + """ + Findet eine Testbench-Datei im Projekt anhand ihres Namens (ohne Endung, Case-Insensitive). + + Args: + project (ProjectConfig): Projektdefinition. + testbench_name (str): Gesuchter Dateiname (z.B. "VGATimingGenerator_test_tb"). + + Returns: + str: Vollständiger Pfad zur Testbench-Datei. + + Raises: + FileNotFoundError: Wenn die Datei nicht gefunden wurde. + """ + candidates = expand_testbenches(project) + + # Vergleichswerte vorbereiten (Name ohne Endung, in Kleinbuchstaben) + search_name = os.path.splitext(testbench_name)[0].lower() + + for _, filepath in candidates: + filename = os.path.basename(filepath) + filename_no_ext = os.path.splitext(filename)[0].lower() + + if filename_no_ext == search_name: + return filepath + + raise FileNotFoundError(f"Testbench '{testbench_name}' wurde nicht gefunden.") + +def build_testbench(project: ProjectConfig, testbench_name: str): + """ + Baut eine einzelne Testbench mit FUSE. + + Args: + project (ProjectConfig): Hauptprojekt-Konfiguration + testbench_name (str): Name der Testbench-Datei, z.B. "VGATimingGenerator_test_tb.vhd" + """ + # Pfade + isim_exe_name = f"isim_{testbench_name.replace('.vhd', '').replace('.v', '')}" + isim_exe_path = os.path.join(DIRECTORIES.build, isim_exe_name) + + # 1. Simulation-Projektdatei generieren + generate_simulation_project_file( + project=project, + output_path=os.path.join(DIRECTORIES.build, f"{project.name}_sim.prj"), + testbench_name=testbench_name + ) + + # 2. Befehl bauen + xilinx_path = project.xilinx_path + xilinx_bin_dir = os.path.join(xilinx_path, "bin", "lin64") # oder nt64 bei Windows + fuse_executable = os.path.join(xilinx_bin_dir, "fuse") + + cmd = [ + fuse_executable, + "-intstyle", "xflow", + "-prj", f"{project.name}_sim.prj", + "-o", isim_exe_name, + f"work.{testbench_name.replace('.vhd', '').replace('.v', '')}", + "work.glbl" + ] + + # 3. Ausführen mit Konsole + task = ConsoleTask(prefix="hdlbuild", title=f"FUSE {testbench_name}") + result = task.run_command(cmd, cwd=DIRECTORIES.build) + + if result != 0: + raise RuntimeError(f"FUSE fehlgeschlagen für Testbench {testbench_name}") + +def run_testbench(testbench_name: str): + """ + Führt eine gebaute Testbench-Executable aus (ISim Simulation). + + Args: + testbench_name (str): Name der Testbench-Datei (z.B. "VGATimingGenerator_test_tb.vhd") + """ + # Pfade + isim_exe_name = f"isim_{testbench_name.replace('.vhd', '').replace('.v', '')}" + isim_exe_path = os.path.join(DIRECTORIES.build, isim_exe_name) + + isim_cmd_file = os.path.join(DIRECTORIES.build, f"{isim_exe_name}.cmd") + + # 1. TCL-Skript für ISim erzeugen (einfache Simulation) + with open(isim_cmd_file, "w") as f: + f.write("") + + # 2. Kommando bauen + cmd = [ + f"./{isim_exe_name}", + "-gui", + "-tclbatch", + f"{isim_exe_name}.cmd" + ] + + # 3. Ausführen + task = ConsoleTask(prefix="hdlbuild", title=f"RUN {testbench_name}") + result = task.run_command(cmd, cwd=DIRECTORIES.build) + + if result != 0: + raise RuntimeError(f"Testbench {testbench_name} ist während der Simulation fehlgeschlagen!") \ No newline at end of file diff --git a/src/hdlbuild/utils/source_resolver.py b/src/hdlbuild/utils/source_resolver.py index e711ec5..ea6c7dc 100644 --- a/src/hdlbuild/utils/source_resolver.py +++ b/src/hdlbuild/utils/source_resolver.py @@ -66,3 +66,35 @@ def expand_all_sources(root_project: ProjectConfig, resolved_dependencies: List[ all_verilog_sources.extend(verilog_dep) return all_vhdl_sources, all_verilog_sources + + +def expand_testbenches(project: ProjectConfig) -> List[Tuple[str, str]]: + """ + Expandiert nur die Testbenches (vhdl und verilog) aus dem Hauptprojekt. + + Args: + project (ProjectConfig): Das Hauptprojekt. + + Returns: + List of (library, filepath) Tupel + """ + expanded = [] + + if project.testbenches: + # VHDL Testbenches + for source in project.testbenches.vhdl: + full_pattern = os.path.join(".", source.path) + matched_files = glob.glob(full_pattern, recursive=True) + for file in matched_files: + normalized = os.path.normpath(file) + expanded.append((source.library, normalized)) + + # Verilog Testbenches (optional) + for source in project.testbenches.verilog: + full_pattern = os.path.join(".", source.path) + matched_files = glob.glob(full_pattern, recursive=True) + for file in matched_files: + normalized = os.path.normpath(file) + expanded.append((source.library, normalized)) + + return expanded \ No newline at end of file