Menu

Verilog case-Anweisung: Mehrweg-Decoding richtig gemacht

Wie case für sauberes Mehrweg-Decoding funktioniert, der default, den du nie weglassen solltest, und die Unterschiede zwischen case, casex und casez.

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

Die Mehrweg-Verzweigung

case ist Verilogs flaches Dispatch-Konstrukt. Du gibst ihm einen Ausdruck; er wählt den passenden Zweig:

case (expression)
    pattern_1: statement_1;
    pattern_2: statement_2;
    pattern_3: begin
        statement_3a;
        statement_3b;
    end
    default: default_statement;
endcase

Es ist strukturell ähnlich zum switch aus C, aber:

  • Es gibt kein break - jeder Zweig wird implizit vom nächsten beendet.
  • Muster können Vektoren sein, nicht nur Ganzzahlen.
  • Der Synthesizer verwandelt das Ganze in einen flachen Mux (oder einen One-Hot-Decoder, wenn die Muster es ermöglichen).

Ein durchgearbeitetes Beispiel: 4-zu-1-Mux

Der case-Body hat vier explizite Muster plus ein default. Der Synthesizer sieht einen 2-Bit-Eingang, der auf einen von vier Werten gemappt wird, und erzeugt einen 4-zu-1-Multiplexer. Sauber, flach, schnell.

Warum du default brauchst

Bei kombinatorischem case ist das Weglassen von default dieselbe Falle wie ein if ohne else: Jeder ungematchte Eingangswert lässt out unzugewiesen, und der Synthesizer leitet ein Latch ab.

Bei einem 2-Bit-sel decken die obigen Muster alle vier möglichen Werte ab - theoretisch ist default also redundant. In der Praxis:

  1. Der Synthesizer beweist nicht immer, dass die Cases vollständig sind.
  2. Der Selektor kann in der Simulation x oder z sein, was zu keinem expliziten Case passt.
  3. Ein später hinzugefügter Case kann das Default-Verhalten unspezifiziert lassen.

Schreibe immer default. Für Automaten und Mux-Logik, bei der du weißt, dass der Default unerreichbar ist, weise 'x zu:

default: out = 8'bx;

… das sagt dem Synthesizer "das ist ein Don't-Care, optimiere frei" und lässt in der Simulation ein leuchtend rotes x auftauchen, falls der unerreichbare Case doch erreicht wird. Das ist das Beste aus beiden Welten.

Ein Automat in case

Die klassische Anwendung von case ist die Zustandsübergangs-Logik eines endlichen Automaten:

Der case (state)-Block ist die Zustandsübergangs-Logik. Jeder Zweig entscheidet, was der nächste Zustand ist und wie lange darin verweilt wird. default ist hier unerreichbar (wir decken RED/GREEN/YELLOW im 2-Bit-Raum vollständig ab), aber er ist als Sicherheitsnetz da - falls state irgendwie 2'd3 wird, resettet der Automat sauber auf RED, statt zu latchen.

Endliche Automaten geht in diesem Muster in die Tiefe.

Mehrere Muster pro Zweig

Du kannst mehrere Muster auflisten, die sich eine einzige Anweisung teilen, getrennt durch Kommas:

case (opcode)
    4'h0, 4'h1, 4'h2: result = a + b;
    4'h3, 4'h4:       result = a - b;
    4'h8:             result = a & b;
    default:          result = 8'd0;
endcase

Das sind zwei Opcodes, die beide "subtrahieren" bedeuten, und drei, die "addieren" bedeuten. Der Synthesizer ODER't die Muster für den Komparator zusammen.

casez und casex: Don't-Care-Matching

Manchmal willst du ein Muster matchen, bei dem manche Bits unspezifiziert sind - "jeder Opcode, der mit 010 anfängt":

casez (opcode)
    8'b010?_????: instruction = ALU_OP;
    8'b110?_????: instruction = LOAD_OP;
    8'b1110_????: instruction = JUMP_OP;
    default:      instruction = UNKNOWN;
endcasez

casez behandelt ? (und z) im Muster als Don't-Cares. Jedes ? matcht 0 oder 1. Nützlich zum Decodieren von Instruktionsformaten, bei denen einige Bit-Positionen für bestimmte Opcode-Klassen ungenutzt sind.

casex erweitert das und behandelt auch x als Don't-Care. casex ist gefährlich, weil uninitialisierte Signale (die in der Simulation x sind) zu jedem Case passen und überraschendes Verhalten erzeugen. Die meisten modernen Style-Guides empfehlen casez und verbieten casex.

In SystemVerilog gibt es zudem case inside, das die sauberste Variante ist - es akzeptiert Bereiche und Listen -, aber klassisches Verilog hört bei casez auf.

case vs if/else if-Kette

Beide Konstrukte können Mehrweg-Entscheidungen ausdrücken, aber sie synthetisieren zu unterschiedlicher Hardware:

  • case ist ein flaches Dispatch. Der Synthesizer kann einen One-Hot-Decoder, einen balancierten Mux-Baum oder andere flache Strukturen erzeugen. Konstante Auswertungszeit.
  • if/else if ist eine Prioritätskette. Der Synthesizer erzeugt eine Kaskade, bei der jede Ebene Verzögerung hinzufügt. Logarithmisch langsamer.

Funktional überlappen sie. Stilistisch: Nutze case, wann immer die Bedingungen den Wert eines einzelnen Ausdrucks betreffen. Nutze if/else if, wenn es eine echte Priorität gibt oder Bedingungen verschiedene Signale betreffen.

// Besser als case:
if      (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else                  out = d;

// Besser als if/else if:
if      (urgent_event)  next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready)    next_state = PROCESS;
else                    next_state = state;

Die ersten drei Bedingungen sind alle "was ist sel?" - ein case liest sich natürlicher und synthetisiert flacher. Die zweiten drei sind unabhängige Ereignisse mit einer offensichtlichen Priorität - if/else if passt besser.

Wie es weitergeht

Das letzte Doc dieses Kapitels - For-Schleifen - behandelt Verilogs for und das Überraschende, das passiert, wenn du es in synthetisierbarem Code benutzt. Danach geht es ernsthaft in sequentielle Logik und Automaten.

Häufig gestellte Fragen

Was ist die case-Anweisung in Verilog?

case (expr) ... endcase ist Verilogs Mehrweg-Verzweigung. Sie wertet den Ausdruck einmal aus und springt in den passenden Zweig. Es ist die idiomatische Wahl für Automaten, Opcode-Decoder, Mux-Selektoren und alles andere, was zwischen mehreren sich gegenseitig ausschließenden Optionen wählt.

Was ist der Unterschied zwischen case, casex und casez in Verilog?

case matcht bitgenau, einschließlich x und z. casez behandelt z (und ?) in case-Items als Don't-Cares. casex behandelt sowohl x als auch z als Don't-Cares. Don't-Care-Matching ist nützlich für Opcode-Muster, bei denen einige Bit-Positionen irrelevant sind, aber casex ist in der Simulation gefährlich, weil uninitialisierte Signale (x) versehentlich auf jeden Fall matchen können.

Warum brauche ich default in einer Verilog case-Anweisung?

Ohne default sieht das Synthese-Tool die Möglichkeit, dass kein Case gematcht hat, entscheidet, dass das Output-Signal seinen vorherigen Wert behalten muss, und leitet ein ungewolltes Latch ab. Der default-Zweig behandelt jeden ungematchten Wert - üblicherweise indem er den Ausgang auf einen sicheren Wert setzt oder den Case mit einer x-Zuweisung als unerreichbar markiert. Setze ihn immer.

Wann sollte ich case vs if-else in Verilog verwenden?

Nimm case, wenn die Bedingungen sich gegenseitig ausschließen und auf dem Wert eines einzigen Ausdrucks aufbauen - Automaten, Opcode-Decoder, Mux-Selektoren. Nimm if/else, wenn es eine echte Prioritätsreihenfolge gibt oder die Bedingungen verschiedene Signale betreffen. case synthetisiert zu flacherer, schnellerer Hardware als eine lange else if-Kette.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S