Menu

Verilog blocking vs non-blocking: Wann = und wann <= benutzen

Das am häufigsten missverstandene Thema in Anfänger-Verilog. Was = und <= innerhalb eines always-Blocks wirklich bedeuten und die Regel, die die meisten Race Conditions verhindert.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Die zwei Operatoren

Innerhalb von always- und initial-Blöcken hat Verilog zwei Zuweisungsoperatoren:

  • = ist eine blocking-Zuweisung. Aktualisiere die LHS sofort, bevor du zur nächsten Anweisung gehst.
  • <= ist eine non-blocking-Zuweisung. Werte die RHS jetzt aus, plane die LHS so, dass sie am Ende des aktuellen Zeitschritts aktualisiert wird.

Außerhalb prozeduraler Blöcke (in assign) gibt es nur = - assign a <= b ist ein Syntaxfehler. Innerhalb prozeduraler Blöcke sind beide zulässig, und die Wahl des richtigen Operators ist die einzige wichtigste Entscheidung in Anfänger-Verilog.

Was blocking macht

Blocking ist das, was du von einer Software-Sprache erwartest: Zeile für Zeile, von oben nach unten. Anweisung N läuft fertig, bevor Anweisung N+1 startet:

Das ist rein sequentiell. Jede Anweisung sieht die Auswirkungen der vorhergehenden. Das passt zur Software-Intuition.

Was non-blocking macht

Non-blocking ist hardware-geformt. Alle rechten Seiten werten mit den Werten am Anfang des Zeitschritts aus. Alle linken Seiten aktualisieren sich am Ende des Zeitschritts. Die Reihenfolge der Anweisungen im Quelltext ändert die Abhängigkeiten zwischen Signalen nicht:

Dieser Schnipsel implementiert eine 3-Weg-Rotation a → b → c → a in einem Schritt. Mit blocking bräuchtest du eine temporäre Variable, um keinen der drei zu überschreiben. Mit non-blocking passiert der Tausch atomar, weil jede RHS die _Vor-dem-Schritt-_Werte liest.

Genau so verhalten sich drei Flip-Flops an einer Taktflanke: Sie erfassen alle gleichzeitig ihre Eingänge, unabhängig von den Abhängigkeiten zwischen ihnen.

Die Regel, die dich rettet

Benutze <= in getakteten Blöcken. Benutze = in kombinatorischen Blöcken.

Das ist alles. Lerne es auswendig. Tippe es ohne nachzudenken. Die meisten Race Conditions, Sim/Synthese-Abweichungen und "läuft bei mir, aber nicht in der Synthese"-Bugs kommen daher, diese Regel zu verletzen.

Die Regel hat einen Hardware-Grund: Getaktete Blöcke modellieren Flip-Flops, die alle gleichzeitig ihre Eingänge abtasten. Kombinatorische Blöcke modellieren Logik, die sich so schnell ausbreitet, wie sie kann. Die Operator-Semantik passt zu diesen Verhaltensweisen.

Jedes <= liest den aktuellen Wert des Quellregisters und plant das Zielregister so, dass es diesen Wert am Ende des Zeitschritts annimmt. Der Nettoeffekt ist genau das, was ein Hardware-Schieberegister tut: jedes Flip-Flop erfasst den Wert seines Nachbarn, alle an derselben Taktflanke, kein Race.

Überleg jetzt, was mit blocking-Zuweisung passiert wäre:

// FALSCH - das ist kein Schieberegister!
always @(posedge clk) begin
    out[3] = out[2];   // out[3] wird zu out[2]
    out[2] = out[1];   // out[2] wird zu out[1], das wir gerade oben gesetzt haben
    out[1] = out[0];
    out[0] = in;
end

Jede Anweisung überschreibt die Quelle, bevor die nächste sie liest. In einem Taktzyklus würde sich in ganz bis zu out[3] durchpflanzen, weil jede Zeile den frisch geschriebenen Wert der vorigen sieht. Das Verhalten auf echter Hardware (die non-blocking-Semantik hat) wäre völlig anders als das, was der Simulator gezeigt hat.

Kombinatorische Blöcke: Blocking ist richtig

Für always @(*) ist blocking-Zuweisung korrekt. Es gibt keine Flip-Flops, keine Gleichzeitig-Erfassen-Regel und Zwischenvariablen sind nützlich:

sum wird zuerst berechnet, dann nutzt result den eben berechneten Wert. Die kombinatorische Logik flacht auf ein einzelnes Stück Hardware ab: result = ~(a + b). Es entstehen keine Flip-Flops, weil kein Takt vorhanden ist.

Würdest du hier <= benutzen, würde der Simulator sum immer noch aktualisieren, bevor result darauf ausgewertet wird (weil beide Updates am Ende des Schritts passieren), aber die Reihenfolge wäre subtil anders, und viele Synthese-Tools beschweren sich. Misch nicht; wähle den Operator, der zum Blocktyp passt.

Der Fehler, der am meisten weh tut

Hier ist er: ein getakteter Block mit blocking-Zuweisung.

// BUG: Race Condition, die nur darauf wartet, zu passieren
always @(posedge clk) begin
    a = b;
    b = c;
    c = a;
end

In der Simulation gibt der Simulator vielleicht zuerst a, dann b, dann c einen neuen Wert und produziert einen Satz Werte. Die Hardware produziert einen anderen Satz, weil echte Flip-Flops gleichzeitig erfassen. Die beiden weichen leise voneinander ab, und du verlierst einen Tag mit der Fehlersuche. Benutze <= in getakteten Blöcken.

Warum es überhaupt zwei Operatoren gibt

Die Designer von Verilog hätten eine Zuweisungssemantik wählen und dabei bleiben können. Haben sie nicht, weil die Sprache zwei verschiedene Hardware-Verhalten modellieren muss:

  • Kombinatorische Logik: Signale propagieren kontinuierlich, Abhängigkeiten zählen, "was berechnet dieses Gatter" macht Sinn.
  • Sequentielle Logik: Eine Taktflanke passiert, jedes Flip-Flop erfasst gleichzeitig, Abhängigkeiten zwischen Flip-Flop-Eingängen und -Ausgängen sind entkoppelt.

Blocking ist für das Erste. Non-blocking für das Zweite. Der Operator wählt die Semantik; den Rest macht der Simulator.

Wie es weitergeht

Du hast jetzt die Regeln, um jeden prozeduralen Block korrekt zu schreiben. Das nächste Kapitel geht von einzelnen Blöcken über zu den Kontrollfluss-Konstrukten, die darin sitzen: if/else, case und for-Schleifen. Die Regeln von blocking vs non-blocking gelten innerhalb all dieser Konstrukte weiter.

Häufig gestellte Fragen

Was ist der Unterschied zwischen blocking und non-blocking-Zuweisung in Verilog?

Blocking (=) aktualisiert das Ziel sofort, bevor die nächste Anweisung läuft. Es modelliert sequentielle Ausführung. Non-blocking (<=) plant das Update für das Ende des aktuellen Zeitschritts - jede non-blocking-RHS wird mit den alten Werten aller Signale ausgewertet, dann werden alle LHS in einem einzigen koordinierten Schritt aktualisiert. Non-blocking entspricht dem Verhalten echter Flip-Flops.

Wann sollte ich = vs <= in Verilog nehmen?

Regel: Nimm <= in getakteten always @(posedge clk)-Blöcken und nimm = in kombinatorischen always @(*)-Blöcken. Diese eine Regel verhindert die gesamte Klasse von Race Conditions, die durch gemischte Zuweisungen entstehen. Innerhalb von Testbench-initial-Blöcken ist = die Standardwahl; <= taucht dort selten auf.

Warum braucht Verilog non-blocking-Zuweisungen?

Weil Hardware-Flip-Flops alle gleichzeitig an einer Taktflanke ihre Eingänge erfassen. Würde man = (blocking) in getaktetem Code verwenden, würde die Reihenfolge der Anweisungen in der Datei ändern, welches Signal den neuen Wert welches anderen Signals sieht - eine Race Condition zwischen Simulation und echter Hardware. <= entspricht dem Hardware-Verhalten: erst alle RHS auswerten, dann alle Updates ausführen.

Was passiert, wenn man = und <= im selben Verilog always-Block mischt?

Eine Race Condition. Der Mix erzeugt Hardware, deren Verhalten von Simulator-Internas (Event-Scheduling-Reihenfolge) abhängt, und schlimmer: die Simulation passt vielleicht nicht zu dem, was die Synthese erzeugt. Die meisten Lint-Tools markieren das als Fehler. Der Fix: Festlegen auf eine Variante, basierend auf der Rolle des Blocks - non-blocking für getaktet, blocking für kombinatorisch.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S