Vertraute Syntax, anderes mentales Modell
if/else sieht genauso aus wie in C:
if (condition) begin
// ... Anweisungen ...
end else begin
// ... Anweisungen ...
end
Aber die Regeln sind anders, weil Verilog keine Software ist. Zwei Dinge zu beachten:
if/elselebt nur innerhalb eines prozeduralen Blocks. Du kannst kein freistehendesifauf Modul-Ebene schreiben.- Wozu das
if/elsesynthetisiert, hängt vom Blocktyp ab. In einem kombinatorischen Block wird es zu einem Multiplexer oder Priority Encoder. In einem getakteten Block wird es zu einem Flip-Flop mit bedingter Update-Logik.
In einem kombinatorischen Block
Der kombinatorische always @(*)-Block läuft jedes Mal neu, wenn sich a, b oder c ändert. Die if/else-Kette wählt einen Zweig, weist max zu, und der Block ist fertig. Da max immer zugewiesen wird (jeder Pfad hat eine Zuweisung), erzeugt der Synthesizer reine kombinatorische Logik - kein Latch.
Beachte, dass max als reg deklariert ist, obwohl in der Hardware kein Flip-Flop existiert. Gleiche Regel wie immer: Alles, was in einem always zugewiesen wird, muss reg sein.
Die Latch-Falle
Das ist der häufigste Bug in kombinatorischem Anfänger-Code:
// FALSCH - leitet ein Latch ab
always @(*) begin
if (enable)
out = data;
// kein else! Was tut `out`, wenn `enable` low ist?
end
Der Synthesizer liest "wenn enable low ist, wird out nicht zugewiesen" und entscheidet, dass out seinen vorherigen Wert behalten muss. Einen Wert zu erinnern braucht eine Speicherzelle, also fügt das Tool ein Latch ein. Latches in synchronen Designs verursachen Timing-Probleme, sind schwer zu resetten und sind fast nie das, was du gemeint hast.
Zwei Wege zur Lösung:
Beide erzeugen dieselbe kombinatorische Hardware - einen 2-zu-1-Mux. Das Muster "Default oben" skaliert besser, wenn du viele konditionale Zuweisungen auf dasselbe Signal hast.
In einem getakteten Block
Sieh, was anders ist als im kombinatorischen Fall:
- Der Block ist
always @(posedge clk)- Flip-Flop-Territorium. - Zuweisung nutzt
<=(non-blocking). - Es gibt kein
elsefür den "weder reset noch enable"-Fall. Das ist okay. In einem getakteten Block, wenn kein Zweig feuert, behält das Flip-Flop einfach seinen vorigen Wert - genau das, was ein Flip-Flop physisch tut. Kein Latch wird abgeleitet, weil das Signal bereits ein Register ist.
Das ist die eine Stelle, an der das Weglassen eines else sicher ist. Außerhalb getakteter Blöcke immer jeden Pfad behandeln.
Verkettete else if: Ein Priority Encoder
Eine Kette von else if-Anweisungen hat implizite Priorität - frühere Bedingungen schlagen spätere:
requests[0] hat höchste Priorität - ist es gesetzt, ist grant 0, egal was die höherwertigen Bits tun. Der Synthesizer verwandelt die Kette in einen kaskadierten Mux: erst Bit 0 prüfen, dann Bit 1, dann Bit 2, dann Bit 3. Jede Ebene fügt etwas Delay hinzu.
Sind die Bedingungen sich gegenseitig ausschließend - etwa beim Decodieren eines One-Hot-Eingangs -, produziert eine case-Anweisung (nächstes Doc) flachere, schnellere Hardware als eine else if-Kette. Nimm die case-Form, wenn keine echte Prioritäts-Anforderung besteht.
if ohne else in getaktetem Code
Ein getakteter Block braucht kein else, weil "vorigen Wert halten" der Default ist. Damit baut man Enables:
always @(posedge clk) begin
if (load) target <= incoming;
// kein else: wenn load low ist, behält target seinen Wert
end
Das ist ein Load-Enable-Register. Die meisten Pipeline-Register, Zähler und Konfigurationsregister nutzen dieses Muster.
begin/end und einzelne Anweisungen
Wie in C kannst du begin/end für eine einzelne Anweisung weglassen:
if (a) out = 1;
else out = 0;
Für mehr als eine Anweisung nimm den Block:
if (a) begin
out = 1;
flag = 1;
end else begin
out = 0;
flag = 0;
end
Die beiden Muster lassen sich frei mischen. Style-Guides empfehlen allgemein, immer begin/end zu verwenden, damit das Hinzufügen einer zweiten Anweisung schmerzlos ist.
Wie es weitergeht
Das nächste Doc - Case-Anweisung - behandelt case, das richtige Werkzeug für Mehrweg-Decoding (Automaten, Opcode-Dispatch, ROM-Tabellen). Danach folgen for-Schleifen, die sich subtil von ihren Software-Verwandten unterscheiden, weil sie zur Elaborationszeit aufgerollt werden.
Häufig gestellte Fragen
Wie funktioniert eine if-Anweisung in Verilog?
if (cond) statement; führt statement aus, wenn cond ungleich null ist. Mehrere Anweisungen kannst du in begin ... end einpacken. Mit else statement; bekommst du den Alternativ-Zweig oder mit else if (other_cond) ... eine Kette. if/else existiert nur innerhalb prozeduraler Blöcke - initial oder always - nicht auf der obersten Ebene eines Moduls.
Was ist ein inferred latch in Verilog?
Ein Latch, das das Synthese-Tool ungefragt erzeugt hat, weil dein kombinatorischer always-Block ein Signal nicht in jedem Pfad zugewiesen hat. Das Tool sieht 'wenn a, dann out = 1' ohne else, entscheidet, dass der unbelegte Fall den vorigen Wert behalten muss, und erzeugt ein Latch. Latches sind fast immer falsch; der Fix: jedem Signal oben im Block einen Default geben oder ein explizites else.
Wie vermeidet man inferred latches in Verilog?
Stelle in einem kombinatorischen always @(*)-Block sicher, dass jedes Output-reg in jedem Code-Pfad zugewiesen wird. Das sauberste Muster: Defaults oben im Block setzen und dann konditional überschreiben. Der Compiler warnt üblicherweise, wenn er ein Latch ableitet - behandle die Warnung als Fehler.
Was synthetisiert aus einer if-else-Kette in Verilog?
Ein Priority Encoder. Das erste if hat höchste Priorität, das nächste else if wird nur geprüft, wenn das erste falsch ist, usw. In Hardware wird daraus eine Kette von Multiplexern mit eingebackener Prioritätsreihenfolge. Sind die Bedingungen sich gegenseitig ausschließend, synthetisiert eine case-Anweisung mit derselben Logik oft zu flacherer Hardware und liest sich klarer.