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 verknüpfte Liste in VHDL

Die verknüpfte Liste ist eine dynamische Datenstruktur. Eine verknüpfte Liste kann verwendet werden, wenn die Gesamtzahl der Elemente nicht im Voraus bekannt ist. Es wächst und schrumpft im Speicher, relativ zur Anzahl der enthaltenen Elemente.

Verkettete Listen werden am bequemsten unter Verwendung von Klassen in einer objektorientierten Programmiersprache implementiert. VHDL hat einige objektorientierte Eigenschaften, die verwendet werden können, um die Komplexität der Implementierung vom Benutzer zu abstrahieren.

In diesem Artikel verwenden wir Zugriffstypen, Datensätze und geschützte Typen, um eine verknüpfte Liste in VHDL zu implementieren. Wir werden ein neues VHDL-Paket erstellen, in das wir den gesamten Code unserer verknüpften Liste schreiben werden.

Paket

Das erste, was wir tun werden, ist, ein Paket zu deklarieren, das unseren Code enthalten wird. Ein VHDL-Paket ist eine Sammlung von Typen, Objekten oder Unterprogrammen, die in eine andere VHDL-Datei importiert werden können. Die meisten VHDL-Module importieren das Paket std_logic_1164 aus der IEEE-Bibliothek. Wir erstellen unser eigenes Paket, ähnlich wie es.

Der Inhalt unserer neuen DataStructures.vhd-Datei:

package DataStructures is
   -- Public declarations go here
end package DataStructures;
 
package body DataStructures is
   -- Private declarations and implementations go here
end package body DataStructures;

Obwohl das Paket nur die Implementierung der verknüpften Liste enthalten wird, werden wir es zukunftssicher machen, indem wir es DataStructures nennen. Man könnte sich leicht vorstellen, dass jemand später andere Datenstrukturen wie Bäume oder Hash-Maps hinzufügen möchte.

Ein Paket besteht aus zwei Teilen; eine deklarative Region und einen Körper. In den deklarativen Bereich legen Sie alles, was für die Benutzer des Pakets sichtbar sein soll. Der Körper ist privat und von außen nicht zugänglich.

Geschützter Typ

Geschützte Typen sind klassenähnliche Konstrukte in VHDL. Sie können Unterprogramme, Typdeklarationen und Variablen enthalten. Ein geschützter Typ besteht aus einer öffentlichen Deklaration und einem privaten Körper. Wir fügen die Deklaration der Paketdeklaration und den Hauptteil dem Pakethauptteil hinzu.

Unsere DataStructures.vhd-Datei nach dem Hinzufügen des geschützten Typs:

package DataStructures is

   type LinkedList is protected

      -- Prototypes of subprograms
      procedure Push(constant Data : in integer);
      impure function Pop return integer;
      impure function IsEmpty return boolean;

   end protected;

end package DataStructures;

package body DataStructures is

   type LinkedList is protected body
   end protected body;

end package body DataStructures;

Wir haben den geschützten Typ LinkedList genannt, weil er alles enthält, was mit der verknüpften Liste zu tun hat. Wenn wir dem Paket eine andere Art von Datenstruktur hinzufügen würden, würde das einen anderen geschützten Typ bedeuten.

Innerhalb des deklarativen Bereichs des geschützten Typs haben wir drei Unterprogramme deklariert. Es gibt noch keine Implementierung, dazu kommen wir später. Damit die Unterprogramme außerhalb des geschützten Typs sichtbar sind, müssen sie hier deklariert werden.

Die drei Unterprogramme sind die standardmäßigen Linked-List-Methoden:Push, Pop und IsEmpty. Push fügt am Anfang der Liste ein neues Element hinzu. Pop entfernt ein Element vom Ende der Liste. IsEmpty ist eine Hilfsfunktion, die true zurückgibt wenn die Liste leer ist.

Aufnehmen

Ein Datensatz ist ein zusammengesetzter Typ, der mehrere Mitgliedstypen enthalten kann. Es funktioniert ähnlich wie eine Struktur in der Programmiersprache C. Wenn ein Signal oder eine Variable aus dem Datensatztyp erstellt wird, können die Datenelemente mit dem „.“ referenziert werden. Notation. Zum Beispiel MyItem.Data .

Unsere Datensatzerklärung innerhalb des Körpers des geschützten Typs:

   type LinkedList is protected body

      type Item is record
         Data : integer;
         NextItem : Ptr;
      end record;

   end protected body;

Wir haben den Datensatztyp im Körper des geschützten Typs deklariert. Der deklarative Bereich eines geschützten Typs kann keine anderen Typ- oder Signaldeklarationen enthalten, also müssen wir sie im Hauptteil des geschützten Typs deklarieren.

Das ist für uns kein Problem, da Item ein intern verwendeter Typ ist. Es muss nicht von außen sichtbar sein. Der Benutzer der verknüpften Liste sollte nur über die drei öffentlich deklarierten Unterprogramme darauf zugreifen.

Unser Datensatz enthält zwei Datenelemente; Daten und NextItem. Während Daten vom Typ integer sind , NextItem ist vorerst vom mysteriösen Ptr-Typ.

Zugriffstyp

Zugriffstypen sind VHDL-Zeiger. Indem wir sie verwenden, können wir Objekte während der Laufzeit dynamisch erstellen. Wir werden einen neuen Zugriffstyp namens Ptr deklarieren, der auf ein dynamisch erstelltes Objekt des Typs Item zeigt.

Der Paketkörper mit dem neuen hinzugefügten Zugriffstyp:

package body DataStructures is

   type LinkedList is protected body

      -- Declaration of incomplete Item type
      type Item;

      -- Declaration of pointer type to the Item type
      type Ptr is access Item;

      -- Full declaration of the Item type
      type Item is record
         Data : integer;
         NextItem : Ptr; -- Pointer to the next Item
      end record;

      -- Root of the linked list
      variable Root : Ptr;

end package body DataStructures;

Ein verknüpfter Listenknoten muss einen Verweis auf den nächsten Knoten in der Liste enthalten. Wir haben dies im Item-Datensatz mit dem NextItem-Member implementiert. Es ist vom Typ Ptr, der wiederum ein Zeiger zurück auf den Item-Typ ist. Um dieses Henne/Ei-Problem zu vermeiden, deklarieren wir Item zunächst als unvollständigen Typ. Dann deklarieren wir den Ptr-Typ als Zeiger auf den unvollständigen Typ. Schließlich spezifizieren wir die vollständige Deklaration des Item-Typs.

Als letztes haben wir eine neue Variable namens Root vom Typ Ptr deklariert. Dies wird der Stamm der verknüpften Liste sein. Wenn die Liste leer ist, ist der Wert von Root null . Andernfalls zeigt es auf das erste Element in der Liste.

Linked-List-Methoden

Jetzt ist es an der Zeit, die Unterprogramme zu implementieren, die wir im deklarativen Bereich des geschützten Typs deklariert haben. Das waren die Push-Prozedur und die zwei unreinen Funktionen:Pop und IsEmpty.

Wir werden die Implementierung der Unterprogramme innerhalb des Hauptteils des geschützten Typs platzieren.

Drücken

Push ist das einzige der Unterprogramme, das eine Prozedur ist. Funktionen in VHDL erfordern einen Rückgabewert, der nicht weggelassen werden kann. Um die Verwendung eines Dummy-Rückgabewerts zu vermeiden, ist eine Prozedur vorzuziehen.

Das Push-Verfahren:

procedure Push(Data : in integer) is
   variable NewItem : Ptr;
   variable Node : Ptr;
begin
   -- Dynamically allocate a new item
   NewItem := new Item;
   NewItem.Data := Data;

   -- If the list is empty, this becomes the root node
   if Root = null then
      Root := NewItem;

   else
      -- Start searching from the root node
      Node := Root;

      -- Find the last node
      while Node.NextItem /= null loop
         Node := Node.NextItem;
      end loop;

      -- Append the new item at the end of the list
      Node.NextItem := NewItem;
   end if;
end;

Als erstes wird dynamisch ein neues Objekt vom Typ Item zugewiesen. Dies geschieht mit dem new Stichwort. Jedes Mal, wenn die new Schlüsselwort verwendet wird, wird dynamischer Speicher vom Simulator verbraucht.

Der Rest des Codes ist nur ein standardmäßiger Linked-List-Algorithmus. Das neue Element wird an das Ende der Liste angehängt oder wird zum Root-Element, wenn die Liste leer ist. Informieren Sie sich über verknüpfte Listen auf Wikipedia, wenn Sie mehr Hintergrundinformationen benötigen.

Pop

Pop entfernt das älteste Element aus der Liste und gibt es an den Aufrufer zurück. Wenn wir einfach den Verweis auf das geknallte Element entfernen und zurückgeben, kommt es im Simulator zu Speicherverlust. Nachdem der Funktionsaufruf beendet ist, geht der Verweis auf das dynamisch erstellte Objekt für immer verloren.

Vor VHDL-2017 (auch als VHDL-2018 oder VHDL-2019 bezeichnet) gibt es in VHDL keine Garbage Collection. Und VHDL-2017 wird von den meisten Simulatoren nicht unterstützt. Daher müssen wir deallocate aufrufen bevor Sie von der Funktion zurückkehren.

Die deallocate -Operator gibt den Speicher frei, der von einem dynamisch zugewiesenen Objekt verwendet wird. Für jeden Aufruf von new , sollte ein Aufruf von deallocate erfolgen bevor der Verweis auf ein Objekt gelöscht wird.

Die Pop-Funktion:

impure function Pop return integer is
   variable Node : Ptr;
   variable RetVal : integer;
begin
   Node := Root;
   Root := Root.NextItem;

   -- Copy and free the dynamic object before returning
   RetVal := Node.Data;
   deallocate(Node);

   return RetVal;
end;

Nachdem wir deallocate angerufen haben , können wir nicht auf die Daten verweisen, auf die die Node-Variable zeigt. Es wurde vom Simulator befreit. Die Lösung besteht darin, die Daten vor dem Freigeben in eine lokale Variable zu kopieren und dann den Wert der Variablen zurückzugeben.

Ist leer

Die Überprüfung, ob die Liste leer ist oder nicht, kann einfach dadurch erreicht werden, dass überprüft wird, ob der Wurzelknoten auf etwas anderes als null zeigt:

impure function IsEmpty return boolean is
begin
   return Root = null;
end;

Testbench

Um unseren Code auszuführen, müssen wir eine Testbench erstellen. Eigentlich kann die verknüpfte Liste nur in Testbenches verwendet werden. Zugriffsarten sind nicht synthetisierbar, aber für Überprüfungszwecke sehr nützlich.

Ein Bibliothekspaket verwenden

In der Testbench importieren wir zunächst den work Bibliothek. Dies ist die Standardbibliothek in VHDL, und hier befindet sich unser DataStructures-Paket. Danach können wir den geschützten LinkedList-Typ wie folgt importieren:

library work;
use work.DataStructures.LinkedList;

Gemeinsame Variable

Eine Shared Variable ist eine Variable, auf die mehrere Prozesse zugreifen können. Im Gegensatz zu regulären Variablen, die nur innerhalb eines einzelnen Prozesses deklariert werden können, können gemeinsam genutzte Variablen im deklarativen Bereich der Architektur deklariert werden. Damit haben sie den gleichen Geltungsbereich wie Signale.

Wir müssen eine gemeinsam genutzte Variable verwenden, da es nicht möglich ist, ein Signal mit geschütztem Typ zu deklarieren. Wenn wir es versuchen würden, würde sich ModelSim beschweren:(vcom-1464) Illegale Deklaration des Signals „List“ des Typs work.DataStructures.LinkedList (Typ ist ein geschützter Typ).

Wir deklarieren die gemeinsam genutzte Variable vom Typ LinkedList im deklarativen Bereich der Testbench:

architecture sim of LinkedListTb is

   shared variable List : LinkedList;

begin

Der endgültige Code für die Testbench

Um unsere drei Hilfsfunktionen zu testen, erstellen wir einen neuen Prozess. Dabei fügen wir eine For-Schleife hinzu, die fünf ganze Zahlen in die verknüpfte Liste schiebt. Schließlich leeren wir die verknüpfte Liste in einer While-Schleife, die ausgeführt wird, bis unsere IsEmpty-Funktion true zurückgibt :

library work;
use work.DataStructures.LinkedList;

entity LinkedListTb is
end entity;

architecture sim of LinkedListTb is

   shared variable List : LinkedList;

begin

   process is
   begin

      for i in 1 to 5 loop
         report "Pushing " & integer'image(i);
         List.Push(i);
      end loop;

      while not List.IsEmpty loop
         report "Popping " & integer'image(List.Pop);
      end loop;

      wait;
   end process;

end architecture;

Der endgültige Code für das DataStructures-Paket

Nachdem Sie den gesamten Code geschrieben haben, über den wir zuvor in diesem Artikel gesprochen haben, sollte das DataStructures-Paket wie folgt aussehen:

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;

package body DataStructures is

   type LinkedList is protected body

      type Item;
      type Ptr is access Item;
      type Item is record
         Data : integer;
         NextItem : Ptr;
      end record;

      variable Root : Ptr;

      procedure Push(Data : in integer) is
         variable NewItem : Ptr;
         variable Node : Ptr;
      begin
         NewItem := new Item;
         NewItem.Data := Data;

         if Root = null then
            Root := NewItem;

         else
            Node := Root;

            while Node.NextItem /= null loop
               Node := Node.NextItem;
            end loop;

            Node.NextItem := NewItem;
         end if;
      end;

      impure function Pop return integer is
         variable Node : Ptr;
         variable RetVal : integer;
      begin
         Node := Root;
         Root := Root.NextItem;

         RetVal := Node.Data;
         deallocate(Node);

         return RetVal;
      end;

      impure function IsEmpty return boolean is
      begin
         return Root = null;
      end;

   end protected body;

end package body DataStructures;

Das Ergebnis

Die Ausgabe an die Simulatorkonsole, wenn wir die Run-Taste in ModelSim drücken:

VSIM 10> run
# ** Note: Pushing 1
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Pushing 2
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Pushing 3
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Pushing 4
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Pushing 5
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Popping 1
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Popping 2
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Popping 3
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Popping 4
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb
# ** Note: Popping 5
#    Time: 0 ns  Iteration: 0  Instance: /linkedlisttb

Wie wir aus dem Ausdruck sehen können, werden fünf Ganzzahlen in die verknüpfte Liste geschoben. Dann entfernt die While-Schleife in der Testbench sie aus der Liste in der Reihenfolge, in der sie eingefügt wurden.


VHDL

  1. So erstellen Sie eine Liste von Zeichenfolgen in VHDL
  2. So erstellen Sie eine Tcl-gesteuerte Testbench für ein VHDL-Code-Sperrmodul
  3. So stoppen Sie die Simulation in einer VHDL-Testbench
  4. So erstellen Sie einen PWM-Controller in VHDL
  5. So generieren Sie Zufallszahlen in VHDL
  6. So erstellen Sie einen Ringpuffer-FIFO in VHDL
  7. So erstellen Sie eine selbstüberprüfende Testbench
  8. So verwenden Sie eine Prozedur in einem Prozess in VHDL
  9. So verwenden Sie eine unreine Funktion in VHDL
  10. So verwenden Sie eine Funktion in VHDL