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:
- RAM_WIDTH:16
- RAM_DEPTH:256
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
- Trittfrequenz beschleunigt die SoC-Verifizierung auf Milliarden von Gates
- Siemens ergänzt Veloce für nahtlose hardwaregestützte Verifizierung
- Synopsys ermöglicht Multi-Die-Designs mit HBM3-IP und Verifizierung
- Zutrittskontrolle mit QR, RFID und Temperaturüberprüfung
- Die unbequeme, unvorhersehbare und zufällige Seite der Wartung
- So generieren Sie Zufallszahlen in Java
- Java 8 - Streams
- Schrägbett-Drehmaschine verfügt über eine Steuerung mit Verifizierungsgrafiken
- Differentiale isometrische Verarbeitung und Simulationsverifizierung von Hochgeschwindigkeits-PCB-Design
- Das CAGI-Leistungsüberprüfungsprogramm für Rotationskompressoren