Warum funktioniert dieses einfache VHDL-Muster für ein Schieberegister nicht wie erwartet?

Auf den ersten Blick würde man erwarten, dass sich der folgende VHDL-Quellcode wie ein Schieberegister verhält. In diesem q, im Laufe der Zeit wäre

"UUUU0", "UUU00", "UU000", "U0000", "00000", ....

aber stattdessen ist es immer Unach fünf (oder mehr) aufeinanderfolgenden Taktzyklen.

Warum ist das?

Dieser Code ist eigentlich eine viel vereinfachte Version einer viel komplizierteren Simulation. Aber es zeigt die Symptome, die ich sehe.

Es zeigt dieses interessante und unerwartete Ergebnis während der Simulation sowohl unter ModelSim als auch unter ActiveHDL, ich habe keine anderen Simulatoren ausprobiert und würde (neben einer Erklärung der Ursache) gerne wissen, ob andere genauso handeln.

Um diese Frage richtig beantworten zu können, müssen Sie Folgendes verstehen:

  • Ich weiß, dass dies nicht der beste Weg ist, ein Schieberegister zu implementieren
  • Ich weiß, für die RTL-Synthese sollte dies einen Reset haben.
  • Ich weiß, dass ein Array von std_logic ein std_logic_vector ist.
  • Ich kenne den Aggregationsoperator &.

Was ich auch gefunden habe:

  • Wenn die Zuweisung temp(0)<='0';innerhalb des Prozesses verschoben wird, funktioniert es.
  • Wenn die Schleife ausgepackt ist (siehe kommentierter Code), funktioniert es.

Ich möchte wiederholen, dass dies eine sehr vereinfachte Version eines viel komplizierteren Designs (für eine Pipeline-CPU) ist, das so konfiguriert ist, dass es nur die unerwarteten Simulationsergebnisse zeigt. Die eigentlichen Signaltypen sind nur eine Vereinfachung. Aus diesem Grund müssen Sie Ihre Antworten mit dem Code in der Form so betrachten, wie sie sind.

Meine Vermutung ist, dass der Optimierer der VHDL-Simulations-Engine fälschlicherweise (oder vielleicht gemäß der Spezifikation) nicht die Mühe macht, die Ausdrücke innerhalb der Schleife auszuführen, da sich keine Signale außerhalb ändern, obwohl ich dies widerlegen kann, indem ich die ausgepackte Schleife in eine Schleife platziere.

Ich gehe also davon aus, dass die Antwort auf diese Frage eher mit den Standards für die VHDL-Simulation der nicht expliziten VHDL-Syntax und der Art und Weise, wie VHDL-Simulations-Engines ihre Optimierungen durchführen, zu tun hat, als ob das angegebene Codebeispiel der beste Weg ist, etwas zu tun oder nicht.

Und nun zu dem Code, den ich simuliere:

 library ieee;
 use ieee.std_logic_1164.all;   

 entity test_simple is
    port (
        clk : in  std_logic;
        q   : out std_logic
    );                   
 end entity;

 architecture example of test_simple is
    type   t_temp is array(4 downto 0) of std_logic;
    signal temp : t_temp;
 begin

    temp(0) <= '0';

    p : process (clk)
    begin               
        if rising_edge(clk) then
            for i in 1 to 4 loop
                    temp(i) <= temp(i - 1);
            end loop;

            --temp(1) <= temp(0);   
            --temp(2) <= temp(1);
            --temp(3) <= temp(2);
            --temp(4) <= temp(3);
        end if;
    end process p;
    q <= temp(4);
 end architecture;

Und der Prüfstand:

library ieee;
use ieee.std_logic_1164.all;

entity Bench is
end entity;

architecture tb of bench is

component test_simple is
    port (
        clk : in  std_logic;
        q   : out std_logic
    );                   
end component;

signal clk:std_logic:='0';
signal q:std_logic;     
signal rst:std_logic;

constant freq:real:=100.0e3;

begin                       
    clk<=not clk after 0.5 sec / freq;

    TB:process
    begin
        rst<='1';
        wait for 10 us;
        rst<='0';
        wait for 100 us;
        wait;
    end process;

     --Note: rst is not connected
    UUT:test_simple  port map (clk=>clk,q=>q) ;
end architecture;
Versuchen Sie zuerst, temp in der Signaldeklaration zu initialisieren. Ich habe festgestellt, dass vhdl-Simulatoren eigenartig sind, wenn es darum geht, wo Sie Dinge initialisieren
Es scheint, dass der Simulator die gleichzeitige Zuweisung zu ignoriert, temp(0)da der Literalkonstante keine "Ereignisse" zugeordnet sind. Wenn Sie die Zuweisung in die processeinfügen, wird eine Assoziation mit den Uhrereignissen erstellt, die dafür sorgen, dass sie funktioniert. Ich frage mich, ob das Hinzufügen einer afterKlausel zur Aufgabe eine mögliche Problemumgehung wäre.

Antworten (1)

Es hat damit zu tun, was formal zur Zeit der Ausarbeitung leicht ausgewertet werden kann, was als "lokal statischer Ausdruck" bezeichnet wird. Dies ist eine obskur aussehende Regel, aber sie verdient einige Überlegung - schließlich ergibt sie einen Sinn, und Ihr Simulator warnt Sie zu Recht, indem er nicht offensichtliche Ergebnisse generiert.

Kann jetzt temp(1)zur Kompilierzeit (sogar früher als zur Ausarbeitungszeit) ausgewertet werden und einen Treiber auf Bit 1 von "temp" generieren.

Allerdings temp(i)bedeutet das etwas mehr Arbeit für die Werkzeuge. Angesichts der trivialen Natur der Schleifengrenzen hier ( 1 bis 4 ) ist es für uns Menschen offensichtlich, dass temp(0) nicht gefahren werden kann und was Sie tun, ist sicher. Aber stellen Sie sich vor, die Grenzen wären Funktionen lower(foo) to upper(bar)in einem Paket, das woanders deklariert wurde ... jetzt können Sie höchstens mit Sicherheit sagen, dass tempes gesteuert wird - also ist der "lokal statische" Ausdruck temp.

Und das bedeutet, dass der Prozess durch diese Regeln eingeschränkt wird, um alle zu steuern temp, an welchem ​​Punkt Sie mehrere Treiber haben temp(0)- den Prozess, der (kein Anfangswert, dh 'u') und den externen antreibt temp(0) <= '0';. Also lösen sich die beiden Treiber natürlich auf 'U' auf.

Die Alternative wäre eine "kleine Hacky-Regel" (Meinung), dass, wenn die Schleifengrenzen Konstanten wären, eine Sache tun, aber wenn sie als etwas anderes deklariert wurden, etwas anderes tun und so weiter ... desto mehr solche seltsamen kleinen Regeln es gibt, je komplexer die Sprache wird ... meiner Meinung nach keine bessere Lösung.

Gute Antwort (+1), aber ich bin mit Ihrer Charakterisierung von "hacky little rule" nicht einverstanden. Der ganze Sinn der Simulation besteht darin, das Verhalten echter Hardware darzustellen. Ich verstehe die Einschränkungen, die durch die unabhängige Kompilierung einzelner Module entstehen, aber ich denke, dass die Regel sein sollte, dass alles, was zur Kompilierzeit ausgewertet werden kann , auch ausgewertet werden sollte . Dies wäre eine viel allgemeinere Regel und würde dem System helfen, das Prinzip der "kleinsten Überraschung" einzuhalten. Den Tools zu erlauben , diese Bewertungen nicht durchzuführen, fühlt sich für mich "hackiger" an.
Fairer Kommentar - Ada zum Beispiel hat viel mehr Komplexität in Bezug auf Regeln wie diese (und drückt sie formal aus) und schafft es, uns Benutzern eine viel einfachere Ansicht zu präsentieren (ohne den WTF-Faktor von C!). VHDL wurde ursprünglich von Ada vereinfacht (meiner Meinung nach etwas zu weit). Aber vielleicht könnte es Adas "Type Freezing" -Regeln übernehmen, die diese Art der Optimierung erlauben würden, wenn sie eindeutig sicher ist (wie hier), und sie ansonsten verbieten ...
Danke Brian, was du sagst macht sicherlich Sinn. Die Idee einer einfachen Regel anstelle vieler obskurer Regeln scheint ebenfalls sinnvoll zu sein. Würden Sie sagen, dass dieses Verhalten für alle Simulatoren wahr (und tatsächlich spezifiziert) ist, oder sind es nur die beiden, die ich ausprobiert habe?
Wenn ich einen finde, der etwas anderes macht, würde ich einen Fehler dagegen einreichen! Eine Sache, die die größten Kritiker von VHDL zu ihren Gunsten sagen werden, ist, dass es konsistente Simulationsergebnisse in Fällen garantiert, in denen andere Sprachen (nicht nur Verilog) dies nicht tun. (obwohl ja, manchmal stören mich seine Mängel auch!)
Schnelles Fix-Experiment: Wenn meine Antwort richtig ist, können Sie "temp(0) <= 'Z';" fahren innerhalb des Prozesses, also den Phantomtreiber "trennen", und der externe Treiber wird funktionieren ...
Hervorragende Einsicht, Brian. Der Akt des (sinnlosen?) Fahrens temp(0)<='Z'innerhalb des Prozesses „repariert“ das Verhalten (wenn „reparieren“ das richtige Wort ist), vielleicht sollte es Maske oder Fudge sein …
Vielen Dank. Seine Hauptaufgabe besteht darin, das Verständnis zu unterstützen. Es ist keine schreckliche Lösung (Sie erhalten dort keinen Tristate-Treiber in der Synthese!), Aber wie Sie darauf hingewiesen haben, gibt es bessere Möglichkeiten, das Problem überhaupt auszudrücken ... zum einen die gesamte Schleife durch eine aggregierte Zuweisung zu ersetzen .
Meine Lösung wäre, "temp" - das konzeptionell sowohl Eingabe- als auch gespeicherte Daten umfasst - in zwei Teile aufzuteilen (benannt nach ihren Rollen). Ziemlich oft, wenn VHDL (oder Ada) mich so erwischt, zeigt eine Reflexion, dass mein Designdenken durcheinander war, und als Ergebnis entsteht ein saubereres, einfacheres Design.