Problem und Ziel
Eine Wellenform kann plausibel aussehen, obwohl ein Modul in einem Randfall falsch reagiert. Eine selbstprüfende Testbench macht die Erwartung ausführbar: Sie legt Eingänge an, wartet auf einen definierten Beobachtungszeitpunkt und vergleicht das Ergebnis mit einem unabhängigen Sollwert. Diese Note entwickelt dafür ein kleines vollständiges Beispiel. Der Test prüft einen Vier-Bit-Zähler mit synchronem Reset und Enable. Zusätzlich zeigt ein absichtlich ausgelöster Fehler, ob der Testlauf ein negatives Ergebnis zuverlässig weitergibt.
Voraussetzungen, Werkzeuge und Testumgebung
Die Quellen verwenden VHDL-2008, die IEEE-Pakete std_logic_1164 und numeric_std sowie std.env zum Testabschluss. Erforderlich ist ein Simulator, der diese Konstrukte unterstützt. Die Beispielprüfung wurde tatsächlich mit GHDL 6.0.0, mcode, Windows x64 durchgeführt. Das offizielle Releasearchiv wurde per SHA-256 gegen die GitHub-Release-Metadaten geprüft. Ein Lauf in Active-HDL oder Riviera-PRO wurde nicht durchgeführt. GHDL official releases.
Der Bezug zu Aldec ist die vorgesehene Übertragung in einen dortigen VHDL-Ablauf. Dafür müssen Analyseoptionen, Bibliothekszuordnung und die Behandlung von Fehlerschwere und Abschlussstatus in der konkreten Installation geprüft werden. Diese Note behauptet weder eine bestimmte vorhandene Lizenz noch identische Kommandozeilenoptionen zwischen GHDL und Aldec. Die VHDL-Quellen und die Prüfidee sind getrennt von den unten gezeigten GHDL-Befehlen zu betrachten.
Die Spezifikation zuerst formulieren
- Bei jeder steigenden Taktflanke setzt ein aktiver Reset den Ausgang auf null.
- Ohne Reset erhöht ein aktives Enable den Wert modulo 16.
- Ohne Reset und ohne Enable bleibt der Wert unverändert.
- Reset hat Vorrang, wenn Reset und Enable gleichzeitig aktiv sind.
- Vor der ersten wirksamen Resetflanke wird kein definierter Ausgangswert versprochen.
Diese fünf Sätze legen die Prüfabsicht fest. Besonders die letzte Aussage verhindert, dass eine zufällige Initialisierung der Simulation als zugesicherte Hardwarefunktion behandelt wird. Der Zähler besitzt bewusst keine deklarative Initialisierung seines internen Zustands. Ein neues Projekt muss für seine Zieltechnologie gesondert entscheiden, wie Initialisierung und Reset realisiert werden. Der hier verwendete synchrone Reset ist eine didaktische Festlegung, keine allgemeine Empfehlung für sämtliche Designs.
Schritt 1: Das DUT ablegen
Speichern Sie folgenden vollständigen Quelltext als rtl/counter.vhd. Der interne Zustand ist ein unsigned-Vektor mit vier Bit. Die feste Breite sorgt dafür, dass die Addition nach 15 wieder null liefert. Der Ausgang wird kontinuierlich aus dem internen Zustand abgeleitet. Für einen ersten Test bleibt die Schnittstelle klein, damit Reset, Enable und Zählerwert eindeutig beobachtet werden können.
-- EDA Praxis: eigenes didaktisches Beispiel, VHDL-2008.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity counter is
port (
clk : in std_logic;
rst : in std_logic;
enable : in std_logic;
q : out unsigned(3 downto 0)
);
end entity;
architecture rtl of counter is
signal value : unsigned(3 downto 0);
begin
process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
value <= (others => '0');
elsif enable = '1' then
value <= value + 1;
end if;
end if;
end process;
q <= value;
end architecture;
Schritt 2: Stimulus und Prüfung entkoppeln
Die Testbench verwendet eine lokale Prozedur namens step. Sie nimmt Resetwert, Enablewert und erwarteten Ausgang entgegen. Eingänge werden an einer fallenden Taktflanke geändert, geprüft wird eine Nanosekunde nach der nächsten steigenden Flanke. Dadurch liegt zwischen Eingabeänderung und aktiver Flanke ausreichend Abstand für dieses ideale RTL-Modell. Die zusätzliche Abtastverzögerung lässt die Signalzuweisungen einschließlich der Ausgangszuweisung wirksam werden.
Die eine Nanosekunde ist keine Timinganforderung an eine reale Schaltung. Sie ist Teil dieser einfachen RTL-Testbench mit zehn Nanosekunden Taktperiode. Für eine Gate-Level-Simulation mit Verzögerungsinformationen oder für ein anderes Protokoll muss der Beobachtungszeitpunkt neu begründet werden. Ein blind kopiertes Abtastintervall könnte sonst einen echten Fehler verbergen oder einen korrekten Ablauf zu früh bewerten.
Schritt 3: Die vollständige Testbench
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.env.all;
entity tb_counter is
generic (INJECT_FAILURE : boolean := false);
end entity;
architecture test of tb_counter 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);
stimulus : process
variable errors : natural := 0;
procedure step(r, e : std_logic; expected : natural) is
begin
-- An der Gegenflanke anlegen, nach der aktiven Flanke abtasten.
wait until falling_edge(clk);
rst <= r;
enable <= e;
wait until rising_edge(clk);
wait for 1 ns;
if q /= to_unsigned(expected, q'length) then
errors := errors + 1;
report "Zaehlerabweichung, erwartet " & integer'image(expected)
severity error;
end if;
end procedure;
begin
step('1', '0', 0);
for n in 1 to 20 loop
step('0', '1', n mod 16); -- umfasst 15 -> 0
end loop;
step('0', '0', 4);
step('0', '0', 4);
step('1', '1', 0); -- Reset hat Vorrang vor Enable
step('0', '1', 1);
if INJECT_FAILURE then
errors := errors + 1;
report "Absichtlich ausgeloester Negativtest" severity error;
end if;
if errors = 0 then
report "EDA_TB_PASS" severity note;
finish(0);
else
report "EDA_TB_FAIL" severity failure;
finish(1);
end if;
wait;
end process;
watchdog : process
begin
wait for 2 us;
report "Watchdog: Test nicht rechtzeitig beendet" severity failure;
finish(1);
wait;
end process;
end architecture;
Speichern Sie den Text als tb/tb_counter.vhd. Die Schleife zählt zwanzig Schritte und prüft damit den Übergang von 15 auf null. Danach folgen zwei Haltezyklen. Ein Reset bei gleichzeitig aktivem Enable kontrolliert die Priorität. Der letzte Zählschritt prüft, ob nach diesem Reset wieder korrekt weitergezählt wird. Das Referenzmodell besteht aus unabhängig vorgegebenen Zahlen und einer einfachen Modulo-Erwartung; es kopiert keinen internen DUT-Zustand.
Die Testbench sammelt Fehler, falls die Umgebung nach einer Meldung der Schwere error weiterläuft. Eine Erfolgsmeldung wird nur erzeugt, wenn der Fehlerzähler null bleibt. Das allein genügt dennoch nicht als universelle Exit-Code-Garantie: Die Bedeutung von Simulatorabschluss und Prozessstatus muss der jeweilige Runner festlegen. Der separate Watchdog verhindert, dass dieser Test bei gewöhnlich ausbleibenden Takt- oder Stimulusereignissen unbegrenzt weiterläuft.
Schritt 4: Im getrennten Verzeichnis aufbauen
Legen Sie ein neues Verzeichnis build/plain an und wechseln Sie dorthin. Wenn die Quellen wie beschrieben im Projektwurzelverzeichnis liegen und GHDL im Suchpfad erreichbar ist, gelten folgende Befehle. Jeder Schritt muss erfolgreich sein, bevor der nächste startet. Ersetzen Sie nicht einfach den Programmnamen durch einen anderen Simulator: Die Optionen sind GHDL-spezifisch und werden aus dessen offizieller Dokumentation abgeleitet. GHDL simulation runtime options.
ghdl -a --std=08 ../../rtl/counter.vhd ../../tb/tb_counter.vhd
ghdl -e --std=08 tb_counter
ghdl -r --std=08 tb_counter --assert-level=error
Der erste Befehl analysiert die beiden Quellen in ihrer Abhängigkeitsreihenfolge, der zweite elaboriert die Testbench, der dritte führt sie aus. Im tatsächlich ausgeführten Normaltest erschien EDA_TB_PASS, der Prozess endete mit Exit-Code 0. Das ist das beobachtete Ergebnis für die genannte Umgebung. Ein identisches Ergebnis auf einer anderen Simulatorversion sollte durch einen eigenen Lauf bestätigt werden.
Schritt 5: Den Fehlerweg absichtlich auslösen
ghdl -r --std=08 tb_counter -gINJECT_FAILURE=true --assert-level=error
Dieser Aufruf setzt den Generic für den absichtlichen Fehler. Im ausgeführten Gegenversuch endete GHDL mit Exit-Code 1. Damit wurde nicht nur der Erfolgsweg, sondern auch die negative Rückmeldung geprüft. Die Option --assert-level=error sorgt im gezeigten GHDL-Ablauf dafür, dass eine Fehlermeldung dieser Schwere den Lauf abbricht. Ohne passende Einstellung darf ein übergeordnetes Skript nicht blind aus dem bloßen Ende eines Prozesses Erfolg ableiten.
Fehlerdiagnose und Grenzen
Bei einem unerwarteten Wert vergleichen Sie zunächst den beobachteten Zeitpunkt mit der aktiven Taktflanke. Danach prüfen Sie Resetpriorität und Enablefolge. Wenn die Entity nicht gefunden wird, kontrollieren Sie die Analysefolge und die Arbeitsbibliothek. Falls std.env nicht verfügbar ist, stimmt häufig der gewählte Sprachstandard nicht. Wird der Test vom Watchdog beendet, untersuchen Sie Takterzeugung und wartende Prozesse, bevor Sie die Zeitgrenze erhöhen.
Das Beispiel prüft keinen asynchronen Reset, keine Metastabilität, keine physikalische Taktqualität und kein Timing nach Implementierung. Auch die vollständige Menge aller möglichen Eingangsfolgen wird nicht untersucht. Es ist ein bewusst kleiner, nachvollziehbarer Ausgangspunkt. Ergänzungen sollten jeweils aus einer neuen Anforderung entstehen, beispielsweise Verhalten bei wiederholtem Reset oder längeren Enable-Pausen. Eine größere Zahl ähnlicher Zählschritte allein verbessert nicht automatisch die Prüfabdeckung.
Tatsächlicher Teststatus und nächster Schritt
Ausgeführt: Analyse, Elaborierung, positiver Lauf und absichtlicher negativer Lauf unter GHDL 6.0.0. Nicht ausgeführt: Aldec-Simulation, Synthese, Hardwaretest und Timingprüfung. Die Testumgebung und beobachteten Exit-Codes sind im Projektprotokoll dokumentiert.
Die Regression-Note überführt denselben Zähler in zwei separat auswählbare VUnit-Tests. Die OSVVM-Note zeigt eine alternative strukturierte Ergebnisverwaltung. Beide Wege bauen auf derselben Grundidee auf: Die Erwartung muss im Test stehen und ein Fehler muss den gesamten Lauf sichtbar fehlschlagen lassen.
Quellen und Stand
Recherche geprüft am 19.10.2026. IEEE 1076-2019: VHDL, GHDL simulation runtime options, GHDL official releases.
