Problem und Ziel
Eine Regression muss mehr leisten als mehrere Simulatoraufrufe hintereinander zu starten. Sie muss die richtigen Quellen verwenden, Tests eindeutig benennen, Fehlschläge zuverlässig weitergeben und Ergebnisse eines Laufs von alten Dateien trennen. Diese Note baut dafür einen kleinen ausführbaren Ablauf mit VUnit auf. Derselbe Vier-Bit-Zähler wird in zwei Tests geprüft. Ein zusätzlicher Python-Wrapper akzeptiert Erfolg nur, wenn tatsächlich erfolgreiche Testfälle im neu erzeugten Ergebnisbericht stehen.
Voraussetzungen und getestete Versionen
Das eigene Beispiel wurde mit Python 3.12.14, VUnit 4.7.1 und GHDL 6.0.0 mcode unter Windows x64 ausgeführt. Die Python-Version ist im Testprotokoll maßgeblich und wird dort aus dem tatsächlichen Interpreter gelesen. VUnit 4.7.1 ist bewusst festgelegt; eine spätere Vorabversion wurde für diesen Test nicht eingesetzt. Das Paketverzeichnis wurde lokal für den Versuch eingerichtet. VUnit 4.7.1 package release.
Für den Übertrag auf Aldec nennt die offizielle VUnit-Installationsdokumentation Riviera-PRO und Active-HDL im VHDL-Kontext. Ihre konkrete Simulatorversion und Lizenz müssen separat passen. Ein erfolgreicher GHDL-Lauf belegt weder die Aldec-Installation noch beliebige SystemVerilog-Funktionen. Halten Sie die Kombination aus Sprache, Testframework und Simulator deshalb ausdrücklich fest. VUnit installation and supported simulators.
Architektur und Verzeichnisstruktur
counter/
rtl/counter.vhd
tb/tb_counter_vunit.vhd
run.py # VUnit-Projekt und Testauswahl
ci.py # Zeitgrenze und Berichtprüfung
requirements.txt # festgelegte Python-Pakete
build/<lauf-id>/ # neuer Ordner je Regression
Die Dateien stehen als eigene Beispiele im Projekt bereit. Das DUT ist vollständig in der Testbench-Note beschrieben. Jeder Wrapper-Lauf erzeugt einen neuen Ordner mit Zeitstempel und zufälligem Zusatz. Dadurch kann ein alter erfolgreicher Bericht einen neuen fehlgeschlagenen Lauf nicht verdecken. Die Ordner werden nicht automatisch gelöscht; Aufbewahrung und gezielte Bereinigung sind eine gesonderte Betriebsentscheidung.
Schritt 1: Python-Umgebung vorbereiten
python -m venv .venv
# Windows PowerShell:
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
# Linux entsprechend:
# .venv/bin/python -m pip install -r requirements.txt
Die Datei requirements.txt enthält vunit_hdl==4.7.1 und colorama==0.4.6. Verwenden Sie für alle folgenden Befehle denselben Interpreter aus der virtuellen Umgebung. Speichern Sie zusätzlich dessen Versionsausgabe und den tatsächlich eingesetzten Simulatorstand. Für langfristig abgesicherte Builds kann ein internes Paketarchiv mit Hashprüfung ergänzt werden; ein Versionspin allein garantiert nicht die dauerhafte Verfügbarkeit aller externen Pakete.
Schritt 2: VUnit-Projekt definieren
"""VUnit 4.7.1, eigenes Beispiel; Simulator ueber VUNIT_SIMULATOR waehlen."""
from pathlib import Path
import os
from vunit import VUnit
root = Path(__file__).resolve().parent
vu = VUnit.from_argv(compile_builtins=False)
vu.add_vhdl_builtins()
lib = vu.add_library("counter_lib")
lib.add_source_file(root / "rtl" / "counter.vhd")
lib.add_source_file(root / "tb" / "tb_counter_vunit.vhd")
lib.test_bench("tb_counter_vunit").set_generic(
"inject_failure", os.environ.get("EDA_INJECT_FAILURE") == "1"
)
vu.main()
Der Runner leitet den Projektpfad aus seiner eigenen Datei ab. Er hängt damit nicht davon ab, aus welchem zufälligen Verzeichnis jemand den Befehl startet. Nur die ausdrücklich angegebenen Quelldateien werden in counter_lib aufgenommen. Das verhindert, dass eine experimentelle weitere Testbench unbemerkt durch ein sehr breites Dateimuster in die Regression gerät. Größere Projekte können die Quellliste später aus einer geprüften Projektbeschreibung erzeugen.
Die Umgebungsvariable EDA_INJECT_FAILURE dient ausschließlich der Gegenprobe. Sie setzt einen Generic, der absichtlich eine Prüfung fehlschlagen lässt. Im normalen Betrieb bleibt sie ungesetzt. Ein übergeordnetes System sollte solche Testparameter in seiner Laufkonfiguration protokollieren, ohne zugleich beliebige andere Umgebungsvariablen oder Geheimnisse zu veröffentlichen. Die Prüflogik muss anhand der dokumentierten Eingaben wiederholbar bleiben.
Schritt 3: Zwei benannte Tests implementieren
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library vunit_lib;
context vunit_lib.vunit_context;
entity tb_counter_vunit is
generic (
runner_cfg : string;
inject_failure : boolean := false
);
end entity;
architecture test of tb_counter_vunit is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal enable : std_logic := '0';
signal q : unsigned(3 downto 0);
begin
clk <= not clk after 5 ns;
dut : entity work.counter(rtl) port map (clk, rst, enable, q);
test_runner_watchdog(runner, 2 us);
main : process
procedure step(r, e : std_logic; expected : natural) is
begin
wait until falling_edge(clk);
rst <= r;
enable <= e;
wait until rising_edge(clk);
wait for 1 ns;
check_equal(q, to_unsigned(expected, q'length), "Zaehlerwert");
end procedure;
begin
test_runner_setup(runner, runner_cfg);
while test_suite loop
if run("reset_and_priority") then
step('1', '0', 0);
step('0', '1', 1);
step('1', '1', 0);
elsif run("count_wrap_and_hold") then
step('1', '0', 0);
for n in 1 to 20 loop
step('0', '1', n mod 16);
end loop;
step('0', '0', 4);
step('0', '0', 4);
end if;
check(not inject_failure, "Absichtlich ausgeloester Negativtest");
end loop;
test_runner_cleanup(runner);
wait;
end process;
end architecture;
Der Test reset_and_priority untersucht Reset und dessen Vorrang gegenüber Enable. count_wrap_and_hold untersucht Zählen, Überlauf und Halten. Jeder Test stellt seinen Ausgangszustand mit einem Reset selbst her. Damit darf die Ausführungsreihenfolge geändert werden, ohne dass ein Test unbemerkt vom Ende seines Vorgängers abhängt. Bei größeren Umgebungen gehören auch Referenzmodell, Warteschlangen und Protokollzustände zu dieser bewussten Initialisierung.
VUnit stellt Teststeuerung und Vergleichsfunktionen bereit. Der eigene Inhalt des Tests bleibt die Auswahl der Situationen und Erwartungswerte. Die offizielle Anleitung beschreibt die Verbindung von Python-Projekt und VHDL-Runner; diese Note verwendet sie für ein eigenes kleines Design. Der Watchdog begrenzt die Simulationszeit. Ein zusätzlicher äußerer Timeout bleibt nötig, falls ein Werkzeug hängt oder die Simulation ohne Zeitfortschritt läuft. VUnit user guide.
Schritt 4: Simulator wählen und Tests anzeigen
# Windows PowerShell, wenn GHDL installiert und im PATH ist:
$env:VUNIT_SIMULATOR = 'ghdl'
.\.venv\Scripts\python.exe run.py --list
.\.venv\Scripts\python.exe run.py 'counter_lib.tb_counter_vunit.reset_and_priority'
# Für einen separat eingerichteten Aldec-Arbeitsplatz:
# $env:VUNIT_SIMULATOR = 'rivierapro'
# oder: $env:VUNIT_SIMULATOR = 'activehdl'
# Erst nach Prüfung von Installation, Lizenz und VUnit-Kompatibilität verwenden.
Die Testliste ist ein schneller Schutz gegen versehentlich leere Regressionen. Prüfen Sie, dass beide erwarteten Namen erscheinen. Anschließend kann ein einzelner Test zur Fehlersuche gestartet werden. Die Syntax zur Auswahl und zum xUnit-Export ist in der offiziellen Kommandozeilenbeschreibung dokumentiert. Ein Listenaufruf ist allerdings kein Simulationsnachweis; erst der tatsächliche Lauf prüft Compiler, Elaborierung und Funktion. VUnit command-line interface.
Schritt 5: Einen strikten Gesamtlauf verwenden
"""Strikter Runner: kein Erfolg ohne ausgefuehrte, erfolgreiche Testfaelle."""
from pathlib import Path
from datetime import datetime, timezone
import os
import signal
import subprocess
import sys
import uuid
import xml.etree.ElementTree as ET
ROOT = Path(__file__).resolve().parent
def report_status(path):
try:
tree = ET.parse(path)
except (OSError, ET.ParseError):
return 3 # Bericht fehlt oder ist ungueltig.
cases = list(tree.iter("testcase"))
if not cases:
return 3
if any(c.find(tag) is not None for c in cases for tag in ("failure", "error", "skipped")):
return 1
return 0
def run():
run_id = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + "-" + uuid.uuid4().hex[:8]
out = ROOT / "build" / run_id
out.mkdir(parents=True)
report = out / "results.xml"
command = [sys.executable, str(ROOT / "run.py"),
"--output-path", str(out / "vunit"), "--xunit-xml", str(report)]
options = {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} if os.name == "nt" else {"start_new_session": True}
print("Ergebnisse:", out, flush=True)
with (out / "runner.log").open("w", encoding="utf-8") as log:
try:
process = subprocess.Popen(command, cwd=ROOT, stdout=log, stderr=subprocess.STDOUT, **options)
except OSError:
return 2
try:
returncode = process.wait(timeout=120)
except subprocess.TimeoutExpired:
# Nur den von diesem Lauf gestarteten Prozessbaum beenden.
if os.name == "nt":
subprocess.run(["taskkill", "/PID", str(process.pid), "/T", "/F"], check=False,
stdout=log, stderr=subprocess.STDOUT)
else:
os.killpg(process.pid, signal.SIGKILL)
process.wait()
return 124
if returncode != 0:
return 1
return report_status(report)
if __name__ == "__main__":
raise SystemExit(run())
Starten Sie den Wrapper mit dem Python-Interpreter der virtuellen Umgebung und ci.py. Er schreibt das komplette Runnerprotokoll in den neuen Ausgabepfad. Der Prozessstatus wird zunächst geprüft. Anschließend wird der XML-Bericht gelesen: Ein fehlender, ungültiger oder leerer Bericht ist ein Infrastrukturproblem. Fehlgeschlagene, fehlerhafte und in dieser strikten Beispielpolitik auch übersprungene Tests führen zu einem negativen Gesamtergebnis.
Die Rückgabewerte haben eine bewusst einfache Bedeutung: 0 für erfolgreiche ausgeführte Tests, 1 für einen negativen Test- beziehungsweise Runnerlauf, 2 für einen nicht startbaren Prozess, 3 für einen fehlenden oder unbrauchbaren Bericht und 124 für den äußeren Timeout. Diese Aufteilung ist eine eigene Projektkonvention. Sie behauptet keine identischen Fehlercodes aller beteiligten Simulatoren. Im Log bleiben deren ursprüngliche Diagnosen erhalten.
Die Zeitgrenze beträgt im Beispiel 120 Sekunden. Beim Timeout wird nur der vom Wrapper gestartete Prozessbaum beziehungsweise die eigene Prozessgruppe beendet. Diese Logik wurde auf Windows implementiert; der Linux-Zweig ist vorhanden, aber in diesem Auftrag nicht praktisch geprüft. Für einen produktiven CI-Dienst sind dessen eigene Job-Isolation und Zeitgrenzen zusätzlich zu konfigurieren. Die kleine Grenze passt zum Demonstrationsdesign und muss für reale Projekte begründet angepasst werden.
Schritt 6: Den negativen Weg ausführen
# Windows PowerShell, innerhalb der vorbereiteten Umgebung:
$env:EDA_INJECT_FAILURE = '1'
.\.venv\Scripts\python.exe ci.py
$edaResult = $LASTEXITCODE
Remove-Item Env:EDA_INJECT_FAILURE
if ($edaResult -eq 0) { throw 'Negativtest wurde unerwartet als Erfolg gewertet' }
Im tatsächlichen Versuch bestand der normale Gesamtlauf mit zwei erfolgreichen Tests und Wrapper-Exit-Code 0. Der injizierte Fehler führte zu fehlgeschlagenen Tests und Wrapper-Exit-Code 1. Zusätzlich wurde die Berichtauswertung mit fehlendem, leerem, ungültigem, erfolgreichem, fehlgeschlagenem und übersprungenem Testbericht geprüft. Diese Gegenproben sind besonders wertvoll, weil ein fehlerhaftes Erfolgssignal die gesamte spätere Qualitätsentscheidung entwerten würde.
Diagnose, Grenzen und Übertragung
Bei einem Compilerfehler öffnen Sie zuerst runner.log und suchen die erste relevante Diagnose. Bei einem leeren Bericht prüfen Sie die Testauswahl und ob der Runner vor Simulationsbeginn abgebrochen ist. Ein Lizenzproblem auf einem kommerziellen Arbeitsplatz wird als Fehler behandelt, nicht als übersprungene erfolgreiche Regression. Bei Parallelbetrieb braucht jeder Lauf getrennte Ausgaben und eine zur verfügbaren Lizenzzahl passende Prozessanzahl.
Nicht getestet wurden der Timeout mit tatsächlich hängendem Simulator, der Linux-Prozessgruppen-Zweig und die Aldec-Adapter. Ebenso wurden keine Laufzeitvergleiche oder Coverage-Messungen vorgenommen. Das kleine Design bildet keinen umfassenden Abnahmetest für ein großes FPGA-Projekt. Es liefert einen geprüften Ausgangspunkt, der anschließend mit echten Quellen, Bibliotheken und Anforderungen erweitert werden kann.
Für den nächsten Schritt übertragen Sie einen vorhandenen Modultest und führen Normalfall, absichtlichen Fehler und einen frischen Aufbau aus. Die Projektstruktur-Grundlagen helfen beim Einbinden weiterer Bibliotheken. Eine Beratung zur Automatisierung sollte anschließend die reale Simulatorinstallation, Lizenzsituation und die erwarteten Testmengen gemeinsam betrachten.
Quellen und Stand
Recherche geprüft am 19.10.2026. VUnit 4.7.1 package release, VUnit installation and supported simulators, VUnit user guide, VUnit command-line interface, GHDL simulation runtime options.
