Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Industrial programming >> VHDL

Eingeschränkte zufällige Überprüfung

Constrained Random Verification ist eine Testbench-Strategie, die auf der Generierung pseudozufälliger Transaktionen für das zu testende Gerät (DUT) beruht. Ziel ist es, durch zufällige Interaktion mit dem DUT eine funktionale Abdeckung einer Reihe vordefinierter Ereignisse zu erreichen.

Open Source VHDL Verification Methodology (OSVVM) ist eine kostenlose VHDL-Bibliothek, die eine Reihe praktischer Pakete zum Erstellen eingeschränkter zufälliger Testbenches enthält. Uns interessieren besonders RandomPkg und CoveragePck, die wir in diesem Artikel verwenden werden. Ich empfehle den Besuch der OSVVM-GitHub-Seite, um mehr über die Funktionen dieser Bibliothek zu erfahren.

Das zu testende Gerät

Ich werde direkt in ein Beispiel eintauchen, um besser zu erklären, wie sich eine eingeschränkte zufällige Testbench von der klassischen Testbench unterscheidet, die gerichtete Tests verwendet. Wir haben im vorherigen Artikel in diesem Blog einen Ringpuffer-FIFO erstellt, aber wir haben keine selbstprüfende Testbench erstellt, um die Korrektheit des Moduls zu überprüfen.

Wir werden eine geeignete Testbench für das Ringpuffer-FIFO erstellen, die eine eingeschränkte zufällige Verifizierung verwendet.

entity ring_buffer is
  generic (
    RAM_WIDTH : natural;
    RAM_DEPTH : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- Write port
    wr_en : in std_logic;
    wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Read port
    rd_en : in std_logic;
    rd_valid : out std_logic;
    rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Flags
    empty : out std_logic;
    empty_next : out std_logic;
    full : out std_logic;
    full_next : out std_logic;

    -- The number of elements in the FIFO
    fill_count : out integer range RAM_DEPTH - 1 downto 0
  );
end ring_buffer;

Die Entität des Ringpuffermoduls ist im obigen Code dargestellt. Wir werden das DUT als Blackbox behandeln, was bedeutet, dass wir kein Wissen darüber annehmen, wie das DUT implementiert ist. Schließlich geht es in diesem Artikel um die Testbench, nicht um den Ringpuffer-FIFO.

Wir werden das DUT in der Testbench instanziieren, indem wir die Entitäts-Instanziierungsmethode verwenden. Die Instanziierung ist trivial, daher lasse ich den Code vorerst weg, aber er kann später in diesem Artikel heruntergeladen werden.

Die DUT-Generika werden den folgenden Werten zugeordnet:

Testbench-Strategie

Lassen Sie uns die Teststrategie durchgehen, bevor wir überhaupt mit der Implementierung beginnen. Das Bild unten zeigt das Hauptkonzept der Testbench, die wir gerade erstellen werden.

Wir führen zufällige Schreibtransaktionen auf der Eingangsseite des DUT durch. Die Eingangsdaten werden bei jedem Taktzyklus auf einen zufälligen Wert gesetzt, und die Strobes am Schreibfreigabeeingang haben eine zufällige Dauer.

In ähnlicher Weise führen wir Lesevorgänge nach dem Zufallsprinzip durch. Wir werden das Lesefreigabesignal in Bursts geltend machen, die eine zufällige Anzahl von Taktzyklen dauern.

Parallel zum DUT wird es ein Verhaltensmodell geben. Dies ist ein FIFO, der anders implementiert ist als der Ringpuffer, der im DUT verwendet wird, aber immer noch die gleiche Schnittstelle hat. Im Gegensatz zum DUT muss das Verhaltensmodell nicht synthetisierbar sein. Dies gibt uns die Freiheit, erweiterte VHDL-Programmierfunktionen für die Erstellung zu verwenden.

Wir werden die Ausgabe des DUT in einem separaten Prozess mit der Ausgabe des Verhaltensmodells vergleichen. Dieser Prozess ist allein dafür verantwortlich, diesen Vergleich bei jedem Taktzyklus durch die Verwendung von Assert-Anweisungen durchzuführen. Wenn sich die beiden FIFO-Implementierungen zu irgendeinem Zeitpunkt unterschiedlich verhalten, führt ein Behauptungsfehler dazu, dass die Simulation mit einem Fehler beendet wird.

Schließlich werden wir funktionale Abdeckungsdaten sammeln, indem wir die Transaktionen beobachten, die zum DUT gehen und von ihm kommen. Ein funktionaler Deckungspunkt könnte beispielsweise bedeuten, dass gleichzeitig gelesen und geschrieben wird oder dass der FIFO mindestens einmal gefüllt ist. Wir werden diese Ereignisse in unserem Haupttestbench-Sequencer-Prozess überwachen. Die Simulation wird angehalten, wenn alle von uns überwachten funktionalen Abdeckungsereignisse aufgetreten sind.

Importieren der OSVVM-Bibliothek

Die OSVVM-Bibliothek kann mit jedem Simulator verwendet werden, der VHDL-2008 unterstützt. Es ist möglicherweise bereits in den Standardbibliotheken enthalten, die mit Ihrem Simulator geliefert werden. Es ist in der ModelSim PE Student Edition enthalten, die kostenlos von Mentor Graphics heruntergeladen werden kann.

ModelSim wird mit einer älteren Version von OSVVM ausgeliefert, aber das ist in Ordnung, es hat alles, was wir brauchen. Wir können einfach fortfahren und die Zufalls- und Abdeckungspakete wie folgt importieren:

library osvvm;
use osvvm.RandomPkg.all;
use osvvm.CoveragePkg.all;

Die neueste Version der OSVVM-Bibliothek kann immer von der GitHub-Seite heruntergeladen werden. Tun Sie dies, wenn Ihr Simulator es nicht enthält oder wenn Sie die neuesten Funktionen der Bibliothek verwenden möchten.

Die OSSVM-Variablen deklarieren

Die OSVVM-Bibliothek enthält Pakete mit geschützten Typen. Daraus erstellte Variablen wären im Umfang auf den Prozess beschränkt, in dem sie definiert wurden. Daher deklarieren wir sie stattdessen als gemeinsam genutzte Variablen im deklarativen Bereich der Testbench-Architektur, wie im folgenden Code gezeigt.

 -- OSVVM variables
  shared variable rv : RandomPType;
  shared variable bin1, bin2, bin3, bin4, bin5, bin6 : CovPType;

Der rv Variable vom Typ RandomPType dient zum Generieren von Zufallswerten. Wir brauchen nur eines davon, weil wir dasselbe Objekt in jedem Prozess verwenden können, in dem wir Zufallswerte generieren müssen. Die letzte Codezeile deklariert sechs Variablen vom Typ CovPType .

Wir haben sechs Coverage-Variablen deklariert, weil wir sechs Coverage-Ziele haben werden, wir werden diese Objekte als „Bins“ bezeichnen. Die gemeinsam genutzten Variablen müssen initialisiert werden, bevor sie zum Sammeln von Abdeckungsdaten verwendet werden können. Dazu rufen wir AddBins auf Verfahren auf jedem der CovPType Mülleimer.

    -- Set up coverage bins
    bin1.AddBins("Write while empty", ONE_BIN);
    bin2.AddBins("Read while full", ONE_BIN);
    bin3.AddBins("Read and write while almost empty", ONE_BIN);
    bin4.AddBins("Read and write while almost full", ONE_BIN);
    bin5.AddBins("Read without write when almost empty", ONE_BIN);
    bin6.AddBins("Write without read when almost full", ONE_BIN);

Als ersten Parameter für AddBins liefern wir eine Zeichenfolgenbeschreibung des Coverage-Bins Verfahren. Diese Zeichenfolge wird am Ende der Simulation erneut angezeigt, wenn wir die Statistiken für jeden der Coverage-Bins drucken. Wie Sie aus den Textbeschreibungen ersehen können, werden wir die Bins verwenden, um zu prüfen, ob einige sehr spezifische Eckfälle aufgetreten sind oder nicht.

AddBins ist eine überladene Prozedur, die zum Erstellen mehrerer Scoreboards innerhalb der bin-Variablen verwendet werden kann. Wir haben jedoch nur eine Anzeigetafel, die jedem Behälter zugeordnet ist. Daher liefern wir die Convenience-Konstante ONE_BIN als Parameter an AddBins Verfahren. Dadurch wird CovPType initialisiert Variablen mit jeweils einem Bin. Die durch die Bins dargestellten Anzeigetafeln gelten als abgedeckt, wenn die von ihnen überwachten Ereignisse mindestens einmal aufgetreten sind.

Generieren zufälliger Eingaben

Beginnen wir mit der Erstellung des Prozesses, der Eingabedaten für das DUT generiert. Der Ringpuffer-FIFO ist so ausgelegt, dass er versuchte Überschreibungen und Überlesevorgänge ignoriert. Daher können wir einfach zufällige Daten in Bursts von zufälliger Dauer schreiben. Wir müssen nicht darüber nachdenken, ob das DUT tatsächlich bereit ist, die Daten aufzunehmen oder nicht.

  PROC_WRITE : process
  begin
    wr_en <= rv.RandSlv(1)(1) and not rst;

    for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop
      wr_data <= rv.RandSlv(RAM_WIDTH);
      wait until rising_edge(clk);
    end loop;
  end process;

Die einzige Überlegung, die dieser Prozess erfordert, ist, dass das DUT nicht zurückgesetzt ist. Wir aktivieren oder deaktivieren das Schreibaktivierungssignal zufällig in der ersten Zeile dieses Prozesses, aber es wird nur aktiviert, wenn rst ist '0' .

Die nachfolgende For-Schleife schreibt für eine zufällige Anzahl von Taktzyklen Zufallsdaten in das DUT, auch wenn das Freigabesignal nicht aktiv ist. Wir können dies tun, weil das DUT den wr_data ignorieren soll Port, außer wr_en Signal ist '1' . Nach der for-Schleife springt das Programm zurück zum Start des Prozesses und löst eine weitere zufällige Schreibtransaktion aus.

Durchführen zufälliger Lesevorgänge

Der Prozess, der Daten von dem DUT liest, ist dem Schreibprozess ähnlich. Wir können zufällig den rd_en aktivieren Signal zu jeder Zeit, da das DUT so ausgelegt ist, dass es Leseversuche ignoriert, wenn es leer ist. Die Daten, die auf dem rd_data erscheinen Port wird nicht wirklich überprüft. Dieser Prozess steuert nur das Lesefreigabesignal.

  PROC_READ : process
  begin
    rd_en <= rv.RandSlv(1)(1) and not rst;

    for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop
      wait until rising_edge(clk);
    end loop;
  end process;

Verhaltensüberprüfung

Wir werden ein Verhaltensmodell des DUT in unserer Testbench erstellen, um sein Verhalten zu überprüfen. Dies ist eine bekannte Testbench-Strategie. Zuerst füttern wir das Verhaltensmodell gleichzeitig mit dem gleichen Input wie das DUT. Dann können wir die Ausgabe der beiden vergleichen, um zu prüfen, ob das DUT das richtige Verhalten aufweist.

Das obige Bild zeigt den prinzipiellen Aufbau einer solchen Testbench. Das Verhaltensmodell arbeitet parallel zum DUT. Wir verwenden es als Blaupause, um die Ausgaben des DUT gegenzuprüfen.

Das Testbench-FIFO

Wir werden eine verknüpfte Liste verwenden, um das Verhaltensmodell zu implementieren. Verkettete Listen können nicht synthetisiert werden, aber sie eignen sich perfekt für Testbenches. Sie erinnern sich vielleicht an So erstellen Sie eine verknüpfte Liste in VHDL Artikel, wenn Sie ein regelmäßiger Leser dieses Blogs sind. Wir werden den Code daraus verwenden, um das Verhaltensmodell für den Ringpuffer-FIFO zu implementieren.

package DataStructures is
   type LinkedList is protected
 
      procedure Push(constant Data : in integer);
      impure function Pop return integer;
      impure function IsEmpty return boolean;
 
   end protected;
end package DataStructures;

Die Paketdeklaration für den Linked-List-FIFO ist im obigen Code dargestellt. Es ist ein geschützter Typ, der die drei Funktionen hat; Push, Pop und IsEmpty. Diese werden zum Hinzufügen und Entfernen von Elementen aus dem FIFO sowie zum Prüfen, ob noch null Elemente darin vorhanden sind, verwendet.

  -- Testbench FIFO that emulates the DUT
  shared variable fifo : LinkedList;

Geschützte Typen sind klassenähnliche Konstrukte in VHDL. Wir erstellen ein Objekt der verknüpften Liste, indem wir eine gemeinsam genutzte Variable im deklarativen Bereich der Testbench deklarieren, wie im obigen Code gezeigt.

Das Verhaltensmodell

Um das Verhalten des Ringpuffer-FIFO vollständig zu emulieren, deklarieren wir zwei neue Signale, die die Ausgangssignale des DUT widerspiegeln. Das erste Signal enthält die Ausgangsdaten des Verhaltensmodells, das zweite das zugehörige gültige Signal.

  -- Testbench FIFO signals
  signal fifo_out : integer;
  signal fifo_out_valid : std_logic := '0';

Der obige Code zeigt die Deklaration der beiden Ausgangssignale des Verhaltensmodells. Wir brauchen keine dedizierten Eingangssignale für das Verhaltensmodell, weil sie die gleichen sind wie die, die mit dem DUT verbunden sind. Wir verwenden Signale, um die DUT-Ausgabe zu emulieren, weil es uns ermöglicht, Abdeckungsdaten einfach zu sammeln, wie wir später in diesem Artikel sehen werden.

PROC_BEHAVIORAL_MODEL : process
begin
  wait until rising_edge(clk) and rst = '0';

  -- Emulate a write
  if wr_en = '1' and full = '0' then
    fifo.Push(to_integer(unsigned(wr_data)));
    report "Push " & integer'image(to_integer(unsigned(wr_data)));
  end if;
    
  -- Emulate a read
  if rd_en = '1' and empty = '0' then
    fifo_out <= fifo.Pop;
    fifo_out_valid <= '1';
  else
    fifo_out_valid <= '0';
  end if;
  
end process;

Der Prozess, der das Verhaltensmodell des Ringpuffer-FIFO implementiert, ist im obigen Code gezeigt. Dieser Vorgang wird bei jeder steigenden Taktflanke ausgelöst, wenn das Reset-Signal nicht aktiv ist.

Das Verhaltensmodell schiebt bei wr_en einen neuen Wert in das Testbench-FIFO Signal wird bestätigt, während full Signal ist '0' . In ähnlicher Weise funktioniert die Leselogik im Verhaltensmodellprozess, indem sie auf rd_en hört und empty Signale. Letzteres wird vom DUT gesteuert, aber wir werden überprüfen, ob es im nächsten Prozess, den wir erstellen, funktioniert.

Prüfung der Ausgänge

Die gesamte Logik, die für die Überprüfung der DUT-Ausgänge verantwortlich ist, wird in einem Prozess namens «PROC_VERIFY» gesammelt. Wir verwenden Assert-Anweisungen, um zu überprüfen, ob die Ausgaben des DUT mit denen des Verhaltensmodells übereinstimmen. Wir überprüfen auch, ob das DUT und das Verhaltensmodell übereinstimmen, wenn das FIFO leer ist.

Der Code für den Verifizierungsprozess wird unten angezeigt.

PROC_VERIFY : process
begin
  wait until rising_edge(clk) and rst = '0';
  
  -- Check that DUT and TB FIFO are reporting empty simultaneously
  assert (empty = '1' and fifo.IsEmpty) or
         (empty = '0' and not fifo.IsEmpty)
    report "empty=" & std_logic'image(empty) 
      & " while fifo.IsEmpty=" & boolean'image(fifo.IsEmpty)
    severity failure;

  -- Check that the valid signals are matching
  assert rd_valid = fifo_out_valid
    report "rd_valid=" & std_logic'image(rd_valid) 
      & " while fifo_out_valid=" & std_logic'image(fifo_out_valid)
    severity failure;

  -- Check that the output from the DUT matches the TB FIFO
  if rd_valid then
    assert fifo_out = to_integer(unsigned(rd_data))
      report "rd_data=" & integer'image(to_integer(unsigned(rd_data)))
        & " while fifo_out=" & integer'image(fifo_out)
      severity failure;
      report "Pop " & integer'image(fifo_out);
  end if;

end process;

Der Prozess wird durch die steigende Flanke der Uhr ausgelöst, wie wir an der ersten Codezeile sehen können. Das DUT ist ein getakteter Prozess, und es wird erwartet, dass die nachgeschaltete Logik, die mit dem DUT verbunden ist, ebenfalls synchron zum Taktsignal ist. Daher ist es sinnvoll, die Ausgänge auf der steigenden Taktflanke zu prüfen.

Der zweite Codeblock prüft, ob der empty Das vom DUT kommende Signal wird nur aktiviert, wenn der Testbench-FIFO leer ist. Das DUT und das Verhaltensmodell müssen beide übereinstimmen, dass der FIFO leer ist oder nicht, damit dieser Test bestanden wird.

Dann folgt ein Vergleich der Lesedaten-Gültigkeitssignale. Das DUT und das Verhaltensmodell sollten gleichzeitig Daten ausgeben, sonst stimmt etwas nicht.

Schließlich prüfen wir, ob die Ausgangsdaten des DUT mit dem nächsten Element übereinstimmen, das wir aus dem Testbench-FIFO entnehmen. Das passiert natürlich nur, wenn rd_valid Signal wird bestätigt, was bedeutet, dass das rd_data Signal kann abgetastet werden.

Sammeln von Abdeckungsdaten

Um den Hauptfluss der Testbench zu steuern, erstellen wir einen Sequencer-Prozess. Dieser Prozess initialisiert die Abdeckungsbehälter, führt die Tests aus und stoppt die Testbench, wenn alle Abdeckungsziele erreicht wurden. Der folgende Code zeigt den vollständigen «PROC_SEQUENCER»-Prozess.

PROC_SEQUENCER : process
begin

  -- Set up coverage bins
  bin1.AddBins("Write while empty", ONE_BIN);
  bin2.AddBins("Read while full", ONE_BIN);
  bin3.AddBins("Read and write while almost empty", ONE_BIN);
  bin4.AddBins("Read and write while almost full", ONE_BIN);
  bin5.AddBins("Read without write when almost empty", ONE_BIN);
  bin6.AddBins("Write without read when almost full", ONE_BIN);

  wait until rising_edge(clk);
  wait until rising_edge(clk);
  rst <= '0';
  wait until rising_edge(clk);

  loop
    wait until rising_edge(clk);

    -- Collect coverage data
    bin1.ICover(to_integer(wr_en = '1' and empty = '1'));
    bin2.ICover(to_integer(rd_en = '1' and full = '1'));
    bin3.ICover(to_integer(rd_en = '1' and wr_en = '1' and
                           empty = '0' and empty_next = '1'));
    bin4.ICover(to_integer(rd_en = '1' and wr_en = '1' and
                           full = '0' and full_next = '1'));
    bin5.ICover(to_integer(rd_en = '1' and wr_en = '0' and
                           empty = '0' and empty_next = '1'));
    bin6.ICover(to_integer(rd_en = '0' and wr_en = '1' and
                           full = '0' and full_next = '1'));

    -- Stop the test when all coverage goals have been met
    exit when
      bin1.IsCovered and
      bin2.IsCovered and
      bin3.IsCovered and
      bin4.IsCovered and
      bin5.IsCovered and
      bin6.IsCovered;
  end loop;
  
  report("Coverage goals met");

  -- Make sure that the DUT is empty before terminating the test
  wr_en <= force '0';
  rd_en <= force '1';
  loop
    wait until rising_edge(clk);
    exit when empty = '1';
  end loop;

  -- Print coverage data
  bin1.WriteBin;
  bin2.WriteBin;
  bin3.WriteBin;
  bin4.WriteBin;
  bin5.WriteBin;
  bin6.WriteBin;
  
  finish;
end process;

Zuerst initialisieren wir die Coverage-Bin-Objekte, indem wir AddBins aufrufen Verfahren auf ihnen, wie wir bereits früher in diesem Artikel besprochen haben. Nachdem das Zurücksetzen freigegeben wurde, fahren wir fort, Abdeckungsdaten zu sammeln. Bei jeder steigenden Flanke der Uhr wird der Code innerhalb des Schleifenkonstrukts ausgeführt.

Der erste Codeblock innerhalb der Schleife dient zum Sammeln von Abdeckungsdaten. Wir können die ICover anrufen Prozedur auf dem Behälter, um einen Treffer an dem Abdeckungspunkt aufzuzeichnen, den er repräsentiert. Wenn wir den ganzzahligen Parameter 0 angeben , hat der Aufruf keine Wirkung. Wenn wir den Integer-Parameter 1 verwenden , wird es als Treffer gewertet.

Es gibt nur einen „Bin“ innerhalb jedes Coverage-Bin-Objekts, weil wir sie mit dem ONE_BIN initialisiert haben Konstante. Dieser einzelne Behälter kann durch Aufrufen von ICover(1) erreicht werden . Wir können einen Treffer oder Fehlschlag am Abdeckungspunkt registrieren, indem wir unsere booleschen Ausdrücke in die Ganzzahlen 1 umwandeln oder 0 mit dem to_integer Funktion

Nachdem die Versorgungsdaten erfasst wurden, prüfen wir mit einem Anruf unter IsCovered, ob alle Versorgungsziele erreicht wurden Funktion auf allen Behältern. Dann verlassen wir die Schleife, wenn alle Abdeckungsziele erreicht wurden.

Wir stellen sicher, dass das DUT leer ist, bevor wir den Test beenden. Um dies zu erreichen, übernehmen wir die Kontrolle von den Writer- und Reader-Prozessen, indem wir wr_en erzwingen Signal an '0' und die rd_en Signal an '1' .

Schließlich drucken wir Statistiken darüber aus, wie oft jedes Abdeckungsziel erreicht wurde, indem wir WriteBin aufrufen Funktion auf jedem der Coverage-Bins. Der finish Schlüsselwort am Ende des Prozesses bewirkt, dass der Simulator die Simulation beendet.

Laufen der Testbench

Sie können das gesamte ModelSim-Projekt mit allen VHDL-Dateien herunterladen, indem Sie das folgende Formular verwenden.

Nachdem wir das Projekt durch Ausführen des im Zip enthaltenen Do-Files geladen haben, können wir die Testbench starten, indem wir einfach «runtb» in die ModelSim-Konsole eingeben. Die Laufzeit der Testbench wird zufällig sein, da die Abdeckungsziele zufällig erreicht werden. Die Testergebnisse sind jedoch reproduzierbar, da tatsächlich eine pseudozufällige Sequenz verwendet wird.

Wir haben in unserem Code keinen Seed initialisiert, was bedeutet, dass der Standard-Seed-Wert für den Pseudozufallsgenerator verwendet wird. Es ist möglich, einen anderen Startwert festzulegen, indem Sie InitSeed aufrufen Verfahren auf der RandomPType Objekt, erzeugt dies eine andere zufällige Sequenz.

Die Konsolenausgabe

Die Ausgabe, die an die ModelSim-Konsole ausgegeben wird, nachdem wir den Befehl «runtb» gegeben haben, ist unten gezeigt. Während die Simulation läuft, wird es eine Menge zufälliges Hin- und Herschieben zum Testbench-FIFO geben.

VSIM 2> runtb
# ** Warning: Design size of 15929 statements or 2 leaf instances exceeds
#             ModelSim PE Student Edition recommended capacity.
# Expect performance to be quite adversely affected.
# ** Note: Push 34910
#    Time: 790 ns  Iteration: 0  Instance: /ring_buffer_tb
...
# ** Note: Pop 37937
#    Time: 83100 ns  Iteration: 0  Instance: /ring_buffer_tb
# ** Note: Pop 13898
#    Time: 83110 ns  Iteration: 0  Instance: /ring_buffer_tb
# %% WriteBin: 
# %% Write while empty  Bin:(1)   Count = 2  AtLeast = 1
# 
# %% WriteBin: 
# %% Read while full  Bin:(1)   Count = 3  AtLeast = 1
# 
# %% WriteBin: 
# %% Read and write while almost empty  Bin:(1)   Count = 106  AtLeast = 1
# 
# %% WriteBin: 
# %% Read and write while almost full  Bin:(1)   Count = 1  AtLeast = 1
# 
# %% WriteBin: 
# %% Read without write when almost empty  Bin:(1)   Count = 1  AtLeast = 1
# 
# %% WriteBin: 
# %% Write without read when almost full  Bin:(1)   Count = 3  AtLeast = 1
#
# Break in Process PROC_SEQUENCER at C:/crv/ring_buffer_tb.vhd line 127

Statistiken für alle Abdeckungsbehälter werden ausgedruckt, wenn alle Abdeckungsziele erreicht wurden. Einige der Behälter wurden nur einmal getroffen, während einer 106 Mal getroffen wurde. Aber am Ende wurde jede Tonne mindestens einmal getroffen. Somit wissen wir, dass alle Ereignisse, für die wir Abdeckungs-Bins definiert haben, getestet und verifiziert wurden.

Die Wellenform

Lassen Sie uns die Wellenform untersuchen, um eine Vorstellung davon zu bekommen, was die Testbench getan hat. Das Bild unten zeigt die Wellenform mit dem fill_count als Analogwert dargestelltes Signal. Der FIFO ist voll, wenn die Kurve für dieses Signal oben ist, und leer, wenn er unten ist.

Wie wir aus der Wellenform sehen können, wird der Ringpuffer zufällig gefüllt und geleert. Allerdings haben wir diese Steigungen und Gefälle des Füllstands nicht explizit programmiert. Nichtsdestotrotz sehen wir ein organisch aussehendes Interaktionsmuster mit dem DUT.

Weitere Informationen zur eingeschränkten zufälligen Überprüfung

Die eingeschränkte zufällige Verifizierung ist eine gute Testbench-Strategie, wenn der Testvektor zu viele Permutationen aufweist, als dass ein erschöpfender Test praktikabel wäre. Die zufälligen Interaktionen zeigen ein natürlicheres Verhalten als es ein gezielter Corner-Case-Test getan hätte, ohne die Genauigkeit zu beeinträchtigen.

Wir können sicher sein, dass alle Corner Cases erfüllt sind, solange wir die Erfassung der Abdeckungsdaten korrekt eingerichtet haben. Der zusätzliche Vorteil besteht darin, dass randomisierte Tests mit größerer Wahrscheinlichkeit Schwachstellen im DUT aufdecken, auf die Sie nicht speziell testen. Solange Sie alle Eckfälle kennen, können Sie gezielte Tests dafür erstellen. Sonderfälle werden jedoch leicht übersehen, und genau dann könnten Sie von der eingeschränkten zufälligen Überprüfungsmethode profitieren.

Dieser Artikel hat nur an der Oberfläche dessen gekratzt, was Sie mit eingeschränkter zufälliger Überprüfung tun können. Ich empfehle, die Dokumentation auf der OSVVM-GitHub-Seite zu lesen, um tiefer in das Thema einzutauchen.

Ich empfehle auch den Advanced VHDL Testbenches and Verification-Kurs von SynthWorks, dem ich nicht angehöre. Ich habe jedoch an der 5-tägigen Version dieses physischen Kurses teilgenommen. Der Kurs wird von Jim Lewis, dem Vorsitzenden der VHDL Analysis and Standardization Group (VASG), geleitet. Insgesamt eine großartige Investition für jedes Unternehmen, das seine VHDL-Testbenches auf die nächste Stufe bringen möchte.


VHDL

  1. Trittfrequenz beschleunigt die SoC-Verifizierung auf Milliarden von Gates
  2. Siemens ergänzt Veloce für nahtlose hardwaregestützte Verifizierung
  3. Synopsys ermöglicht Multi-Die-Designs mit HBM3-IP und Verifizierung
  4. Zutrittskontrolle mit QR, RFID und Temperaturüberprüfung
  5. Die unbequeme, unvorhersehbare und zufällige Seite der Wartung
  6. So generieren Sie Zufallszahlen in Java
  7. Java 8 - Streams
  8. Schrägbett-Drehmaschine verfügt über eine Steuerung mit Verifizierungsgrafiken
  9. Differentiale isometrische Verarbeitung und Simulationsverifizierung von Hochgeschwindigkeits-PCB-Design
  10. Das CAGI-Leistungsüberprüfungsprogramm für Rotationskompressoren