Menu

Verilog Always-Block: Kombinatorische und sequentielle Logik

Wie always-Blöcke funktionieren, der Unterschied zwischen kombinatorischem always @(*) und getaktetem always @(posedge clk), und die Regeln, die entscheiden, welche Hardware jeweils entsteht.

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

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:

  1. always @(*) - läuft neu, wenn sich irgendein im Block gelesenes Signal ändert. Baut kombinatorische Logik.
  2. always @(posedge clk) - läuft nur bei steigender Flanke von clk neu. 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:

  • out ist reg. Alles, was in einem always zugewiesen wird, muss reg sein. 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 out direkt aus sel berechnet - 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 von clk neu. Zwischen den Flanken passiert nichts.
  • Non-blocking-Zuweisung <=. Innerhalb eines getakteten Blocks ist das der richtige Operator. Er sagt: "Plane count so, 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 default nötig. Das if deckt 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:

BlockHardware
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 wire zuweisen: Das Ziel muss reg sein. Compiler-erzwungen.
  • Denselben reg aus zwei verschiedenen always-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.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S