Das Arbeitspferd des Verhaltens-Verilog
assign beschreibt kombinatorische Logik aus einer einzelnen Gleichung. Sobald du if/else, case oder Speicher brauchst, greifst du zu always. Ein always-Block ist ein Stück prozeduraler Code, der jedes Mal neu läuft, wenn sich bestimmte Signale ändern. Die Signale, die das Neu-Laufen auslösen, sind die Sensitivitätsliste.
Zwei Formen von always siehst du am häufigsten:
always @(*)- läuft neu, wenn sich irgendein im Block gelesenes Signal ändert. Baut kombinatorische Logik.always @(posedge clk)- läuft nur bei steigender Flanke vonclkneu. Baut getaktete sequentielle Logik (Flip-Flops).
Andere Formen existieren (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)), aber die beiden oben decken fast jeden synthetisierbaren Block ab, den du schreibst.
Kombinatorisches always @(*)
Drei Dinge fallen auf:
outistreg. Alles, was in einemalwayszugewiesen wird, mussregsein. Das Schlüsselwort heißt hier nicht "das ist ein Flip-Flop"; es heißt nur "ich werde aus einem prozeduralen Block geschrieben".always @(*). Der*sagt: "Wach auf, wann immer sich etwas ändert, das ich lese." Der Simulator ermittelt die Sensitivitätsliste automatisch. Du kannst die Liste auch von Hand schreiben -always @(sel)-, aber@(*)ist sicherer, weil ein vergessenes Signal eine klassische Bug-Quelle ist.- Kein Takt. Dieser Block beschreibt kombinatorische Logik. Der Synthesizer erzeugt ein Stück Logik, das
outdirekt ausselberechnet - keine Flip-Flops, kein Takt-Pin nötig.
Der default-Fall ist im Geist nicht optional, auch wenn er es in der Syntax ist. Lass ihn weg, und jeder nicht spezifizierte Eingangswert lässt out seinen alten Wert behalten - was zu einem unbeabsichtigten Latch synthetisiert. Schreib immer den default.
Sequentielles always @(posedge clk)
Die wichtigsten Unterschiede zur kombinatorischen Version:
always @(posedge clk). Der Block läuft nur bei steigender Flanke vonclkneu. Zwischen den Flanken passiert nichts.- Non-blocking-Zuweisung
<=. Innerhalb eines getakteten Blocks ist das der richtige Operator. Er sagt: "Planecountso, dass es am Ende des Zeitschritts seinen neuen Wert annimmt" - genau so verhält sich ein Flip-Flop. Das Warum und die Alternative liegen in Blocking vs Non-blocking. - Kein
defaultnötig. Dasifdeckt beide Zweige ab (Reset und Nicht-Reset). Kein Latch-Risiko.
Der Synthesizer sieht diese Form - getaktete Sensitivität, non-blocking-Zuweisung - und erzeugt ein 4-Bit-Register (vier Flip-Flops) plus die kombinatorische Logik, die count + 1 berechnet, und den Mux, der zwischen Reset und Inkrement auswählt.
Der Synthese-Unterschied
Derselbe Modul-Quelltext kann je nach Form des always-Blocks zwei völlig verschiedene Stücke Hardware beschreiben:
| Block | Hardware |
|---|---|
always @(*) y = expr; | Pure kombinatorische Logik. Kein Gedächtnis. |
always @(posedge clk) y <= expr; | Flip-Flop. Erfasst expr einmal pro Taktzyklus. |
always @(*) if (en) y = expr; | Latch - üblicherweise ein Bug. Der "else"-Fall behält den alten Wert. |
always @(posedge clk) if (en) y <= expr; | Flip-Flop mit Enable. Erfasst nur, wenn en high ist. |
Der dritte Fall ist die Latch-Falle. Ein Latch ist eine transparente Speicherzelle, die ihren Ausgang hält, solange der Eingang nicht aktiv ist - in bestimmten Designs nützlich, fast immer ein Bug, wenn versehentlich. Die meisten Synthese-Tools warnen laut, wenn sie ein Latch ableiten, das du nicht wolltest. Behandle die Warnung als Fehler.
Varianten der Sensitivitätsliste
Du wirst ein paar weniger häufige Sensitivitätslisten sehen:
always @(a or b or c)- explizite Liste. Verilog-2001 hat den,-Separator hinzugefügt:always @(a, b, c). Beides funktioniert.always @(posedge clk or negedge reset_n)- asynchroner Reset. Der Block läuft bei einer steigenden Taktflanke oder einer fallenden Resetflanke. Wird verwendet, wenn Reset sofort wirken muss und nicht auf den nächsten Takt warten kann.always @(negedge clk)- Taktung an der fallenden Flanke. Selten; manche Designs nutzen es für "fallend-flankengesteuerte" Flip-Flops, die statt an der steigenden an der fallenden Flanke erfassen.
Bevorzuge in neuen Designs always @(*) für kombinatorisch und always @(posedge clk) für sequentiell. Greife nur dann zum asynchronen Reset, wenn das Design ihn wirklich braucht.
Zwei Blöcke sind zwei Stücke Hardware
Mehrere always-Blöcke im selben Modul sind unabhängig - jeder wird zu seinem eigenen Stück Hardware:
Der getaktete Block erzeugt ein Flip-Flop-Register. Der kombinatorische Block erzeugt ein XOR-Gatter. Sie leben nebeneinander; keiner weiß vom anderen. Die beiden Ausgänge ändern sich nach völlig unterschiedlichen Zeitplänen.
Was always-Blöcke nicht können
Ein paar Dinge, die verlockend aussehen, aber nicht erlaubt sind:
- Einem
wirezuweisen: Das Ziel mussregsein. Compiler-erzwungen. - Denselben
regaus zwei verschiedenenalways-Blöcken zuweisen: erzeugt undefiniertes Verhalten in der Simulation und synthetisiert nicht. Ein Treiber pro Signal. - Im selben kombinatorischen Block dasselbe Signal lesen und schreiben in einer Weise, die eine Rückkopplungsschleife erzeugt:
always @(*) x = x + 1;ist eine Zero-Delay-Schleife, die der Simulator nicht auflösen kann.
Die ersten beiden fängt der Compiler ab. Das dritte zeigt sich manchmal erst in der Simulation als Hänger.
Wie es weitergeht
Das nächste Doc - Initial-Block - behandelt das Geschwister von always: einen Block, der genau einmal beim Simulationsstart läuft. Das ist das Arbeitspferd von Testbenches. Danach folgen die Regeln für blocking vs non-blocking-Zuweisung, die entscheiden, ob dein getakteter Block das tut, was du gemeint hast.
Häufig gestellte Fragen
Was ist ein always-Block in Verilog?
always führt einen prozeduralen Block ein, der jedes Mal neu läuft, wenn sich die Signale seiner Sensitivitätsliste ändern. Es gibt zwei Varianten: always @(*) baut kombinatorische Logik (läuft neu, wenn sich ein Eingang ändert), und always @(posedge clk) baut sequentielle Logik (läuft neu bei jeder steigenden Flanke von clk). Im Body eines always-Blocks darf if, case, for und prozedurale Zuweisung stehen.
Was ist der Unterschied zwischen always @(*) und always @(posedge clk)?
always @(*) ist sensitiv auf jedes im Block gelesene Signal; es erzeugt kombinatorische Logik ohne Gedächtnis. always @(posedge clk) ist nur auf die steigende Flanke von clk sensitiv; es erzeugt Flip-Flops, die einmal pro Taktzyklus Zustand erfassen. Ersteres hat keinen Takt und kein Register; Letzteres beides.
Was ist eine Sensitivitätsliste in Verilog?
Die Liste der Signale hinter @, die festlegt, wann ein always-Block neu läuft. @(*) ist die Kurzform für 'jedes im Block gelesene Signal'. @(posedge clk) läuft nur bei steigender Flanke von clk. @(posedge clk or negedge reset_n) läuft bei einem von beiden Ereignissen - wird für asynchrone Resets benutzt. Eine falsche Sensitivitätsliste ist eine der häufigsten Ursachen für Sim/Synthese-Abweichungen.
Kann man einem wire innerhalb eines always-Blocks zuweisen?
Nein. always-Blöcke dürfen nur reg (oder logic in SystemVerilog) zuweisen. Der Compiler erzwingt das. Wenn du einen wire als Ausgang prozeduraler Logik haben willst, deklariere ein Zwischen-reg, treibe es im always und assigne den wire außerhalb vom reg - oder ändere den wire einfach zu reg.