Compare commits
5 Commits
961d182bf7
...
cef035329a
Author | SHA1 | Date | |
---|---|---|---|
cef035329a | |||
93e67c0380 | |||
8caafe862e | |||
cfa62a5624 | |||
263c3fac5e |
7
.github/workflows/build-deb.yml
vendored
7
.github/workflows/build-deb.yml
vendored
@@ -8,14 +8,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 🛒 Checkout Repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: ⚙️ Prepare Environment Variables
|
- name: ⚙️ Prepare Environment Variables
|
||||||
run: |
|
run: |
|
||||||
echo "AGENT_TOOLSDIRECTORY=/home/runner/toolcache" >> $GITHUB_ENV
|
echo "AGENT_TOOLSDIRECTORY=/home/runner/toolcache" >> $GITHUB_ENV
|
||||||
mkdir -p /home/runner/toolcache
|
mkdir -p /home/runner/toolcache
|
||||||
|
|
||||||
|
- name: 🛒 Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: 🐍 Setup Python
|
- name: 🐍 Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
@@ -25,6 +25,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
curl -sSL https://install.python-poetry.org | python3 -
|
curl -sSL https://install.python-poetry.org | python3 -
|
||||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
poetry self add poetry-plugin-export
|
||||||
|
|
||||||
- name: 🔧 Install Project Dependencies
|
- name: 🔧 Install Project Dependencies
|
||||||
run: |
|
run: |
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -184,4 +184,5 @@ vhdl/
|
|||||||
poetry.lock
|
poetry.lock
|
||||||
project.yml
|
project.yml
|
||||||
.project/
|
.project/
|
||||||
.devcontainer/
|
.devcontainer/
|
||||||
|
vhdltests/
|
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hdlbuild"
|
name = "hdlbuild"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Flexible FPGA Build System"
|
description = "Flexible FPGA Build System"
|
||||||
authors = ["0xMax42 <Mail@0xMax42.io>"]
|
authors = ["0xMax42 <Mail@0xMax42.io>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -18,10 +18,6 @@ pydantic = "^2.11.3"
|
|||||||
rich = "^14.0.0"
|
rich = "^14.0.0"
|
||||||
gitpython = "^3.1.44"
|
gitpython = "^3.1.44"
|
||||||
|
|
||||||
[tool.poetry.requires-plugins]
|
|
||||||
poetry-plugin-export = ">=1.8"
|
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
@@ -2,6 +2,8 @@ import argparse
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from hdlbuild.dependencies.resolver import DependencyResolver
|
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.tools.xilinx_ise.main import xilinx_ise_all, xilinx_ise_synth
|
||||||
from hdlbuild.utils.console_utils import ConsoleUtils
|
from hdlbuild.utils.console_utils import ConsoleUtils
|
||||||
from hdlbuild.utils.directory_manager import clear_build_directories, clear_directories, ensure_directories_exist
|
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...")
|
console_utils.print("Starting dependencies process...")
|
||||||
DependencyResolver(project).resolve_all()
|
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():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -67,12 +75,21 @@ def main():
|
|||||||
parser_build.set_defaults(func=build)
|
parser_build.set_defaults(func=build)
|
||||||
|
|
||||||
# Synth command
|
# Synth command
|
||||||
parser_build = subparsers.add_parser("synth", help="Start the synth process")
|
parser_synth = subparsers.add_parser("synth", help="Start the synth process")
|
||||||
parser_build.set_defaults(func=synth)
|
parser_synth.set_defaults(func=synth)
|
||||||
|
|
||||||
# Dependencies command
|
# Dependencies command
|
||||||
parser_build = subparsers.add_parser("dep", help="Start the dependencies process")
|
parser_dep = subparsers.add_parser("dep", help="Start the dependencies process")
|
||||||
parser_build.set_defaults(func=dep)
|
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 = parser.parse_args()
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
@@ -23,7 +23,8 @@ def copy_bitstream_file(project: ProjectConfig):
|
|||||||
copy_file(
|
copy_file(
|
||||||
project=project,
|
project=project,
|
||||||
source_filename=f"{project.name}.bit",
|
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",
|
description="Bitstream File",
|
||||||
step_number=10, total_steps=12
|
step_number=10, total_steps=12
|
||||||
)
|
)
|
@@ -46,6 +46,7 @@ def copy_file(
|
|||||||
source_filename: str,
|
source_filename: str,
|
||||||
destination_filename: str,
|
destination_filename: str,
|
||||||
description: str = "Report",
|
description: str = "Report",
|
||||||
|
destination_dir: str = DIRECTORIES.report,
|
||||||
step_number: Optional[int] = None,
|
step_number: Optional[int] = None,
|
||||||
total_steps: 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")
|
description (str): Optionale Beschreibung für die Ausgabe (z.B. "Synthesis Report")
|
||||||
"""
|
"""
|
||||||
src_path = os.path.join(DIRECTORIES.build, source_filename)
|
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):
|
if not os.path.exists(src_path):
|
||||||
raise FileNotFoundError(f"{description} nicht gefunden: {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)
|
shutil.copyfile(src_path, dst_path)
|
||||||
|
|
||||||
|
140
src/hdlbuild/tools/xilinx_ise/isim.py
Normal file
140
src/hdlbuild/tools/xilinx_ise/isim.py
Normal file
@@ -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!")
|
@@ -66,3 +66,35 @@ def expand_all_sources(root_project: ProjectConfig, resolved_dependencies: List[
|
|||||||
all_verilog_sources.extend(verilog_dep)
|
all_verilog_sources.extend(verilog_dep)
|
||||||
|
|
||||||
return all_vhdl_sources, all_verilog_sources
|
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
|
Reference in New Issue
Block a user