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

So erstellen Sie eine Tcl-gesteuerte Testbench für ein VHDL-Code-Sperrmodul

Die meisten VHDL-Simulatoren verwenden die Tool Command Language (Tcl) als Skriptsprache. Wenn Sie einen Befehl in die Konsole des Simulators eingeben, verwenden Sie Tcl. Außerdem können Sie mit Tcl Skripte erstellen, die im Simulator laufen und mit Ihrem VHDL-Code interagieren.

In diesem Artikel erstellen wir eine selbstüberprüfende Testbench, die Tcl anstelle von VHDL verwendet, um zu überprüfen, ob sich ein VHDL-Modul korrekt verhält.

Siehe auch:
Warum Sie Tcl lernen müssen
Interaktive Testbench mit Tcl

Sie können den Code aus diesem Artikel und dem ModelSim-Projekt mit dem unten stehenden Formular herunterladen.

Das DUT:Ein Code-Lock-Modul in VHDL

Bevor wir auf dem Prüfstand loslegen, stelle ich das zu testende Gerät (DUT) vor. Es wird ein Codeschlossmodul sein, das einen Tresor aufschließt, wenn wir die richtige Zahlenfolge auf einem PIN-Pad eingeben.

Ein Zahlenschloss wird oft als Zahlenschloss bezeichnet . Allerdings halte ich diesen Begriff für falsch. Es reicht nicht aus, die richtige Ziffernkombination einzugeben, um es zu entsperren. Sie müssen sie auch in der richtigen Reihenfolge eingeben. Ein Zahlenschloss ist genau genommen ein Permutationsschloss , aber nennen wir es eine Code-Sperre .

Das obige Bild zeigt ein solches Codeschloss in Form eines Hotelsafes. Der Einfachheit halber verwenden wir in unserem Beispiel nur die Zifferntasten und nicht die Schaltflächen „LÖSCHEN“ und „SPERREN“.

Funktionsweise des Codeschloss-Moduls

Unser Modul startet in der gesperrten Position, und wenn wir vier Ziffern hintereinander eingeben, die mit dem geheimen PIN-Code übereinstimmen, wird der Safe entriegelt. Um es wieder zu sperren, können wir eine andere, falsche Nummer eingeben. Daher müssen wir einen Sequenzdetektor in VHDL erstellen.

Die obige Wellenform zeigt, wie das Codeschloss-Modul funktionieren wird. Neben Uhr und Reset gibt es zwei Eingangssignale:input_digit und input_enable . Das Modul soll die Eingangsziffer abtasten, wenn die Freigabe bei einer steigenden Taktflanke „1“ ist.

Es gibt nur eine Ausgabe von diesem Modul:die unlock Signal. Stellen Sie sich vor, dass es den Verriegelungsmechanismus eines Safes oder einer Art Tresor steuert. Die Entsperrung Das Signal darf nur dann „1“ sein, wenn der Benutzer vier aufeinanderfolgende Ziffern eingegeben hat, die mit der richtigen PIN übereinstimmen. In diesem Artikel verwenden wir 1234 als Passcode.

Die Entität

Der folgende Code zeigt die Entität des Codeschlossmoduls. Da der Zweck dieses Moduls darin besteht, ein einfaches Beispiel-DUT für unsere TCL-basierte Testbench zu sein, codiere ich den geheimen Passcode mithilfe von Generika fest. Die vier generischen Konstanten sind binär codierte Dezimalzahlen (BCDs), die als ganze Zahlen mit einem eingeschränkten Bereich realisiert werden.

entity code_lock is
  generic (pin0, pin1, pin2, pin3 : integer range 0 to 9);
  port (
    clk : in std_logic;
    rst : in std_logic;
    input_digit : in integer range 0 to 9;
    input_enable : in std_logic;
    unlock : out std_logic
  );
end code_lock;

Genau wie der Passcode, die Eingabeziffer Signal ist auch ein BCD-Typ. Die anderen Ein- und Ausgänge sind std_logics.

Die deklarative Region

Dieses Modul hat nur ein internes Signal:ein Schieberegister, das die letzten vier Ziffern enthält, die der Benutzer eingegeben hat. Aber anstatt den BCD-Bereich von 0 bis 9 zu verwenden, lassen wir die Zahlen von -1 bis 9 laufen. Das sind 11 mögliche Werte.

type pins_type is array (0 to 3) of integer range -1 to 9;
signal pins : pins_type;

Wir müssen einen Reset-Wert verwenden, der keine Ziffer ist, die der Benutzer eingeben kann, und dafür ist die -1 da. Wenn wir den Bereich 0 bis 9 für die Pins verwendet hätten array, hätte das Festlegen des geheimen Passcodes auf 0000 den Tresor ursprünglich geöffnet. Bei diesem Schema muss der Benutzer explizit vier Nullen eingeben.

Die Umsetzung

Am oberen Rand des Architekturbereichs habe ich eine gleichzeitige Anweisung hinzugefügt, die den Tresor entsperrt, wenn die Pins signal stimmt mit den generischen Konstanten überein. Der folgende Code ist kombinatorisch, aber da die Pins Signal getaktet ist, das entsperren Signal ändert sich nur an der steigenden Flanke der Uhr.

unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';

Der folgende Code zeigt den Prozess, der die Benutzereingabe liest. Es macht aus den Pins ein Schieberegister Signal durch Verschieben aller Werte bei input_enable ist bei einer steigenden Taktflanke „1“. Das Ergebnis ist, dass die letzten vier Ziffern, die der Benutzer eingegeben hat, in den Pins gespeichert werden Array.

PINS_PROC : process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      pins <= (others => -1);

    else

      if input_enable  = '1' then
        pins(0) <= input_digit;
        pins(1 to 3) <= pins(0 to 2);
      end if;

    end if;
  end if;
end process;

Die VHDL-Testbench

Zunächst einmal brauchen wir noch eine einfache VHDL-Testbench, obwohl wir Tcl für die Verifikation verwenden. Der folgende Code zeigt die vollständige VHDL-Datei. Ich habe das DUT instanziiert und das Taktsignal erzeugt, aber das ist alles. Abgesehen von der Generierung der Uhr macht diese Testbench nichts.

library ieee;
use ieee.std_logic_1164.all;

entity code_lock_tb is
end code_lock_tb;

architecture sim of code_lock_tb is

  constant clk_hz : integer := 100e6;
  constant clock_period : time := 1 sec / clk_hz;

  signal clk : std_logic := '1';
  signal rst : std_logic := '1';
  signal input_digit : integer range 0 to 9;
  signal input_enable : std_logic := '0';
  signal unlock : std_logic;

begin

  clk <= not clk after clock_period;

  DUT : entity work.code_lock(rtl)
    generic map (1,2,3,4)
    port map (
      clk => clk,
      rst => rst,
      input_digit => input_digit,
      input_enable => input_enable,
      unlock => unlock
    );

end architecture;

Die Tcl-Testbench

Der Tcl-Code in diesem Beispiel funktioniert nur mit dem ModelSim VHDL-Simulator. Wenn Sie es beispielsweise in Vivado verwenden möchten, müssen Sie einige Änderungen daran vornehmen. Das liegt daran, dass einige Befehle verwendet werden, die für diesen Simulator spezifisch sind. Es ist ein Nachteil der Verwendung von Tcl, dass Ihr Code an einen bestimmten Simulatoranbieter gebunden ist.

Als Referenz empfehle ich Tcl Developer Xchange, das die Tcl-Sprache im Allgemeinen abdeckt, und das ModelSim Command Reference Manual, das alle ModelSim-spezifischen Befehle beschreibt.

Wenn Sie ModelSim installiert haben, können Sie das Beispielprojekt mit dem untenstehenden Formular herunterladen.

Einen Namensraum verwenden

Das erste, was ich empfehle, ist, einen Tcl-Namespace zu erstellen. Das ist eine gute Idee, da Sie sonst möglicherweise unbeabsichtigt globale Variablen aus Ihrem Tcl-Skript überschreiben. Indem Sie Ihren gesamten Code im Namespace verpacken, vermeiden Sie dieses potenzielle Durcheinander. Wir werden den gesamten Tcl-Code, den wir von nun an schreiben, in codelocktb einfügen Namespace, wie unten gezeigt.

namespace eval ::codelocktb {

  # Put all the Tcl code in here

}

Innerhalb des Namensraums müssen wir damit beginnen, die Simulation zu starten, wie unten gezeigt. Das machen wir mit dem vsim Befehl, gefolgt von der Bibliothek und dem Entitätsnamen der VHDL-Testbench. Dadurch wird die Simulation geladen, aber nicht ausgeführt. Es vergeht keine Simulationszeit, bis wir run verwenden Befehl später im Skript. Ich füge auch gerne eine If-Anweisung ein, die die Wellenform lädt, falls sie existiert.

# Load the simulation
vsim work.code_lock_tb

# Load the waveform
if {[file exists wave.do]} {
  do wave.do
}

Namespace-Variablen deklarieren

Nachdem wir die Simulation geladen haben, können wir mit der Interaktion mit dem VHDL-Code beginnen. Zuerst möchte ich die clock_period lesen Konstante und Passcode generisch in die Tcl-Umgebung.

Im folgenden Code verwende ich das ModelSim-spezifische examine Befehl zum Lesen von VHDL-Signalen und konstanten Werten in Tcl. Dann verwende ich Tcl-String- und Listenbefehle, um den Zeitwert und die Zeiteinheiten zu extrahieren. Der PinCode Variable wird zu einer Liste der vier Ziffern, die wir aus den generischen Konstanten lesen.

# Read the clock period constant from the VHDL TB
variable clockPeriod [examine clock_period]

# Strip the braces: "{10 ns}" => "10 ns"
variable clockPeriod [string trim $clockPeriod "{}"]

# Split the number and the time unit
variable timeUnits [lindex $clockPeriod 1]
variable clockPeriod [lindex $clockPeriod 0]

# Read the correct PIN from the VHDL generics
variable pinCode [examine dut.pin0 dut.pin1 dut.pin2 dut.pin3]

Beachten Sie, dass ich im Tcl-Skript einen anderen Codierungsstil verwende als im VHDL-Code. Anstelle von Unterstrichen verwende ich Camel Casing. Das liegt daran, dass ich dem Tcl-Styleguide folge. Natürlich hindert Sie nichts daran, denselben Stil in den Tcl- und VHDL-Dateien zu verwenden, wenn Sie dies bevorzugen.

Wenn Sie Tcl ohne Namensräume verwendet haben, kennen Sie wahrscheinlich auch das Schlüsselwort set, das die Standardmethode zum Definieren einer Variablen in Tcl ist. Hier verwende ich stattdessen das neuere variable Schlüsselwort. Es ist wie eine globale Variable, die an den aktuellen Namespace statt an den globalen Gültigkeitsbereich gebunden ist.

Schließlich deklarieren wir eine Variable namens errorCount und initialisieren Sie es auf 0, wie unten gezeigt. Während die Simulation die Testfälle durchläuft, erhöhen wir sie jedes Mal, wenn wir einen Fehler entdecken. Am Ende können wir damit feststellen, ob das Modul bestanden oder nicht bestanden wurde.

variable errorCount 0

Drucken von Text in ModelSim

Der Befehl puts ist die Standardmethode, um Text in Tcl auf der Konsole auszugeben. Aber diese Methode funktioniert in ModelSim auf unglückliche Weise. Die Windows-Version tut, was Sie erwarten würden; Es gibt die Zeichenfolge an die Konsole aus. In der Linux-Version hingegen wird der Text in der Shell ausgegeben, von der aus Sie ModelSim gestartet haben, und nicht in der Konsole innerhalb der GUI.

Das Bild unten zeigt, was passiert, wenn wir puts eingeben Befehl in der ModelSim-Konsole. Es erscheint im Terminalfenster dahinter. Schlimmer noch, wenn Sie ModelSim über eine Desktop-Verknüpfung gestartet haben, werden Sie die Ausgabe nie sehen, weil die Shell ausgeblendet ist.

Es gibt Problemumgehungen, um das Verhalten der Puts zu ändern Befehl. Sie können es zum Beispiel neu definieren (Ja! Das können Sie in Tcl tun) und es auf beiden Plattformen zum Laufen bringen. Aber eine einfachere Möglichkeit, den Text sowohl unter Linux als auch unter Windows auf der Konsole auszugeben, ist die Verwendung des ModelSim-spezifischen echo Befehl.

Wir werden die unten gezeigte benutzerdefinierte Tcl-Prozedur verwenden, um Text zu drucken. Dabei stellen wir der Nachricht auch die aktuelle Simulationszeit voran. In ModelSim können Sie es immer mit $now abrufen globale Variable.

proc printMsg { msg } {
  global now
  variable timeUnits
  echo $now $timeUnits: $msg
}

Simulation für N Taktzyklen

Das DUT ist ein getaktetes Modul, was bedeutet, dass zwischen den ansteigenden Taktflanken nichts passiert. Daher wollen wir in Schritten basierend auf der Dauer eines Taktzyklus simulieren. Die folgende Tcl-Prozedur verwendet die clockPeriod und Zeiteinheiten Variablen, die wir zuvor aus dem VHDL-Code herausgenommen haben, um dies zu erreichen.

proc runClockCycles { count } {
  variable clockPeriod
  variable timeUnits

  set t [expr {$clockPeriod * $count}]
  run $t $timeUnits
}

Die Prozedur nimmt einen Parameter:count . Wir multiplizieren es mit der Länge einer Taktperiode, um die Dauer von N Taktzyklen zu erhalten. Schließlich verwenden wir den ModelSim run Befehl, genau so lange zu simulieren.

Prüfen eines Signalwerts von Tcl

In ModelSim können wir ein VHDL-Signal von Tcl lesen, indem wir examine verwenden Befehl. Der folgende Code zeigt die Tcl-Prozedur, die wir verwenden, um einen Signalwert zu lesen und zu überprüfen, ob er wie erwartet ist. Wenn das Signal nicht mit dem expectedVal übereinstimmt Parameter, geben wir eine unangenehme Nachricht aus und erhöhen den errorCount Variable.

proc checkSignal { signalName expectedVal } {
  variable errorCount

  set val [examine $signalName]
  if {$val != $expectedVal} {
    printMsg "ERROR: $signalName=$val (expected=$expectedVal)"
    incr errorCount
  }
}

Testen einer PIN-Sequenz

Die Ausgabe des Codeschlossmoduls hängt nicht nur von den aktuellen Eingaben, sondern auch von deren vorherigen Werten ab. Daher muss die Überprüfung der Ausgänge mindestens nach dem Senden von vier Ziffern an das DUT erfolgen. Erst dann sollte das Entsperrsignal bei korrekter PIN von „0“ auf „1“ wechseln.

Die folgende Tcl-Prozedur verwendet die force von ModelSim Schlüsselwort zum Ändern von VHDL-Signalen von Tcl. Die -Kaution Wechseln Sie zur Kraft Schlüsselwort bedeutet, dass ModelSim den Wert ändert, aber später einen anderen VHDL-Treiber die Kontrolle übernimmt, obwohl keine anderen Entitäten die DUT-Eingänge in unserer Testbench steuern.

proc tryPin { digits } {
  variable pinCode

  set pinStatus "incorrect"
  if { $digits == $pinCode } {
    set pinStatus "correct"
  }

  printMsg "Entering $pinStatus PIN code: $digits"

  foreach i $digits {
    force input_digit $i -deposit
    force input_enable 1 -deposit
    runClockCycles 1
    force input_enable 0 -deposit
    runClockCycles 1
  }

  if { $pinStatus == "correct" } {
    checkSignal unlock 1
  } else {
    checkSignal unlock 0
  }
}

Der tryPin Prozedur verwendet unsere printMsg Verfahren, um darüber zu informieren, was es tut, welchen PIN-Code es eingibt und ob es das richtige Passwort ist. Es verwendet auch die runClockCycles Prozedur für genau eine Taktperiode laufen, während die DUT-Eingaben manipuliert werden, um einen Benutzer zu simulieren, der eine PIN eingibt.

Schließlich verwendet es das checkSignal Verfahren, um zu überprüfen, ob sich das DUT wie erwartet verhält. Wie ich bereits erklärt habe, ist das checkSignal Die Prozedur gibt eine Fehlermeldung aus und erhöht den errorCount Variable, wenn die entsperren Signal entspricht nicht dem erwarteten Wert.

Testfälle und Abschlussstatus

Im obigen Tcl-Code haben wir die Simulation gestartet und eine Reihe von Variablen und Prozeduren definiert, aber zu keinem Zeitpunkt simuliert. Die Simulation ist immer noch bei 0 ns. Es ist keine Simulationszeit vergangen.

Gegen Ende unseres benutzerdefinierten Namensraums beginnen wir mit dem Aufruf der Tcl-Prozeduren. Wie im folgenden Code gezeigt, beginnen wir mit der Ausführung für zehn Taktzyklen. Danach geben wir den Reset frei und prüfen, ob das unlock Ausgang hat den erwarteten Wert '0'.

runClockCycles 10

# Release reset
force rst '0' -deposit
runClockCycles 1

# Check reset value
printMsg "Checking reset value"
checkSignal unlock 0

# Try a few corner cases
tryPin {0 0 0 0}
tryPin {9 9 9 9}
tryPin $pinCode
tryPin [lreverse $pinCode]

if { $errorCount == 0 } {
  printMsg "Test: OK"
} else {
  printMsg "Test: Failure ($errorCount errors)"
}

Wir könnten alle 10000 verschiedenen PIN-Codes ausprobieren, aber das würde viel Zeit in Anspruch nehmen. Tcl-gesteuerte Simulation ist viel langsamer als eine reine VHDL-Testbench. Der Simulator muss viel starten und stoppen, und das kostet viel Zeit. Daher habe ich mich entschieden, nur Eckfälle zu prüfen.

Wir nennen tryPin viermal mit den PIN-Codes:0000, 9999, der richtigen PIN und den Ziffern der richtigen PIN in umgekehrter Reihenfolge. Ich stelle mir vor, dass dies beim Erstellen eines Codeschlosses ein leichter Fehler ist, nur auf die Kombination und nicht auf die Reihenfolge der Zahlen zu achten.

Schließlich prüfen wir ganz am Ende des Tcl-Codes, aber immer noch innerhalb des Namensraums, den errorCount Variable und geben Sie „Test:OK“ oder „Test Failure“ aus.

Laufen der Testbench

Und jetzt kommt der spaßige Teil:das Ausführen der Testbench. Ich ziehe es vor, den Tcl-Quellbefehl zu verwenden, wie unten gezeigt, aber Sie können auch das ModelSim-spezifische do verwenden Befehl. Tatsächlich sind ModelSim DO-Dateien wirklich nur Tcl-Dateien mit einem anderen Suffix.

source code_lock/code_lock_tb.tcl

In der endgültigen Version meines Codes gibt es keine Fehler. Die folgende Auflistung zeigt die Ausgabe einer erfolgreichen Simulation. Das Tcl-Skript informiert uns darüber, was es tut, und wir können sehen, dass alle Nachrichtenzeilen einen Zeitstempel haben. Das ist unsere printMsg Verfahren bei der Arbeit. Schließlich stoppt die Testbench und druckt „Test:OK“.

VSIM> source code_lock/code_lock_tb.tcl
# vsim 
...
# 110 ns: Checking reset value
# 110 ns: Entering incorrect PIN code: 0 0 0 0
# 190 ns: Entering incorrect PIN code: 9 9 9 9
# 270 ns: Entering correct PIN code: 1 2 3 4
# 350 ns: Entering incorrect PIN code: 4 3 2 1
# 430 ns: Test: OK

Ich möchte Ihnen jedoch zeigen, wie es aussieht, wenn das DUT einen Test nicht besteht. Dazu habe ich einen Fehler im Code-Lock-Modul erstellt. Ich habe die Überprüfung von Pin1 ersetzt mit pin2 damit das DUT den Pin1 ignoriert Wert. Es ist ein einfacher Tippfehler, wie im folgenden Code gezeigt.

unlock <= '1' when pins = (pin3, pin2, pin2, pin0) else '0';

Wenn wir nun die Testbench laufen lassen, können Sie der Auflistung unten entnehmen, dass der Fehler erkannt wird. Und schließlich druckt die Testbench „Test:Failure“ zusammen mit der Anzahl der Fehler.

VSIM> source code_lock/code_lock_tb.tcl
# vsim 
...
# 110 ns: Checking reset value
# 110 ns: Entering incorrect PIN code: 0 0 0 0
# 190 ns: Entering incorrect PIN code: 9 9 9 9
# 270 ns: Entering correct PIN code: 1 2 3 4
# 350 ns: ERROR: unlock=0 (expected=1)
# 350 ns: Entering incorrect PIN code: 4 3 2 1
# 430 ns: Test: Failure (1 errors)

Abschließende Gedanken

Ich habe in meiner Karriere viele Tcl-basierte Testbenches erstellt, aber meine Meinung dazu ist etwas geteilt.

Einerseits können Sie einige coole Dinge tun, die mit VHDL allein nicht möglich sind. Zum Beispiel die interaktive Testbench. Schön ist auch, dass man die Testbench ändern kann, ohne neu kompilieren zu müssen. Und schließlich kann die Verifizierung in einer ganz anderen Sprache vorteilhaft sein. Sie müssten den gleichen Fehler in zwei verschiedenen Technologien machen, damit er unentdeckt bleibt, und das ist unwahrscheinlich.

Auf der anderen Seite gibt es auch einige Nachteile. Die Tcl-basierten Testbenches sind um Größenordnungen langsamer als ihre VHDL-Pendants. Ein weiteres wichtiges Problem ist die Anbieterbindung. Es ist unmöglich, eine vollständig portable Tcl-Testbench zu erstellen, während eine VHDL-Testbench auf jedem leistungsfähigen Simulator ausgeführt werden kann.

Und der letzte Grund, warum sich Tcl-Testbenches möglicherweise nicht lohnen, ist die Sprache selbst. Es hat keine großartigen Funktionen zum Verhindern von Programmierfehlern, und das Debuggen eines Tcl-Problems ist schwierig. Es ist keine intuitive oder nachsichtige Sprache wie Python oder Java.

Es dient jedoch als Verbindungssprache zwischen VHDL und der Softwarewelt. Und da die meisten FPGA-Tools, nicht nur Simulatoren, Tcl unterstützen, empfehle ich dringend, es zu lernen.

Diese Gedanken sind nur meine Meinung. Sag mir deine Meinung im Kommentarbereich!


VHDL

  1. So erstellen Sie eine Liste von Zeichenfolgen in VHDL
  2. So stoppen Sie die Simulation in einer VHDL-Testbench
  3. So erstellen Sie einen PWM-Controller in VHDL
  4. So erstellen Sie einen Ringpuffer-FIFO in VHDL
  5. Interaktive Testbench mit Tcl
  6. So erstellen Sie eine selbstüberprüfende Testbench
  7. So erstellen Sie eine verknüpfte Liste in VHDL
  8. So verwenden Sie eine unreine Funktion in VHDL
  9. So verwenden Sie eine Funktion in VHDL
  10. So installieren Sie kostenlos einen VHDL-Simulator und -Editor