Zwei-Zustände vs. Vier-Zustände-Logik
In Software ist ein Bit 0 oder 1. In Verilog kann ein Bit einen von vier Werten haben:
0- die Leitung ist auf Low getrieben.1- die Leitung ist auf High getrieben.x- der Wert der Leitung ist unknown. Der Simulator kann es nicht sagen.z- die Leitung ist in High-Impedance. Nichts treibt sie.
Dieses Vier-Zustands-Modell existiert, weil Hardware dasselbe Problem hat. Eine echte Leitung kann low gebunden, high gebunden, undefiniert (von zwei sich bekämpfenden Quellen getrieben) oder floatend (kein Treiber) sein. Der Simulator muss alle vier modellieren, um nützlich zu sein.
Wie x auftaucht
Lass das laufen und schau dir die Ausgabe an:
a ist deklariert, aber nie geschrieben, also sitzt es auf x. x + 5 zu addieren ergibt x - jede Arithmetik mit einem Unknown produziert ein Unknown. Die Ausgabe liest aaaa mit lauter x'en.
Häufige Quellen von x in deinen Designs:
- Ein
reg, der deklariert, aber nie resettet wird (das meiste synthetisierbare Verilog nutzt einen expliziten Reset zum Löschen). - Eine
case-Anweisung ohnedefault, getroffen von einem Eingangswert, zu dem kein Case passt. - Ein
wire, der nach einem Refactor seinen einzigen Treiber verloren hat. - Eine
if/else-Kette, in der ein Zweig ein Signal nicht zuweist, das der andere zuweist (latchtx, wenn nicht abgedeckt). - Außerhalb eines Vektors oder Arrays lesen.
X-Propagation: Ein bisschen x ruiniert alles
Das Gemeine an x: es propagiert. Ein x-Bit in einem Operanden macht das ganze Ergebnis x:
Beachte, dass 0 & x 0 ist (AND mit 0 ist immer 0) und 1 | x 1 ist (OR mit 1 ist immer 1). Der Simulator ist bitweise pessimistisch, respektiert aber Identitäten. Arithmetik und Vergleich sind weniger gnädig.
Genau deshalb kann ein einziges uninitialisiertes Register einen ganzen Output-Bus auf xxxx setzen. Verfolge rückwärts von jedem x und du findest die Quelle.
Wie z auftaucht
z ist der Wert einer Leitung, die niemand treibt:
Zwei Muster in diesem Schnipsel:
floatingist nur deklariert und nie getrieben. Es defaultet aufz.data_outist ein bewusster Tri-State. Wennenablelow ist, gibt der Ausgang explizit inzfrei. So lässt ein Bustreiber "los", damit ein anderer Treiber übernehmen kann.
Auf interner Logik ist z fast immer falsch. An einem bidirektionalen Pin oder einem geteilten Bus ist z genau richtig.
Vergleich mit == vs ===
Der gewöhnliche Gleichheitsoperator == liefert x, wenn einer der Operanden ein x- oder z-Bit hat:
=== (und sein Partner !==) macht einen strengen bitweisen Vergleich, der x und z einbezieht. Nutze ihn, wann immer du in einer Testbench auf oder gegen x/z testen musst. === ist nicht synthetisierbar, aber innerhalb eines initial-Blocks in einer Testbench ist das egal.
Die $isunknown(expr)-Systemfunktion ist der sauberste Weg, um zu fragen "hat dieser Ausdruck x- oder z-Bits?" - liefert 1 bei ja, 0 bei nein.
x als absichtliches Don't-Care
Ein kontroverses, aber legitimes Muster: 'x in einem default-Fall eines Automaten sagt dem Synthesizer "dieser Zustand ist unerreichbar, optimiere frei":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // unerreichbar
endcase
Der Synthesizer kann das x nutzen, um Zustände zu mergen und Gatterzahl zu reduzieren. In der Simulation: War deine Überlegung falsch und der Default wird erreicht, siehst du x von next_state propagieren und der Bug wird sofort sichtbar.
Nutze das nur, wenn du wirklich durchgedacht hast, ob der Default wirklich unerreichbar ist. Wenn nicht, setze den Default stattdessen auf einen sicheren Zustand.
Übliche Debug-Anleitung
Du starrst auf eine Waveform voller x. Die Anleitung:
- Finde das früheste
x. Geh die Waveform zeitlich rückwärts. Das früheste Signal, dasxwird, ist am nächsten an der Quelle. - Finde seinen Treiber. Öffne den Quelltext. Was weist diesem Signal zu? Ein
assign? Einalways-Block? - Prüfe die Eingaben des Treibers. Hat die RHS des Treibers irgendein
x, tut Propagation das, was sie soll - der Bug ist weiter oben. - Wenn der Treiber saubere Eingänge hat, aber
xproduziert, ist der Treiber unvollständig. Eincaseohne Default, einifohneelse, ein Register ohne Reset.
Die meisten x-Sturm-Bugs reduzieren sich auf eines von: fehlender Reset, fehlender default oder ein Submodul, das nicht angeschlossen ist.
Wie es weitergeht
Du hast jetzt die vollständige Datentyp-Story: wire/reg, Vektoren, Parameter, Zahlen-Literale und das Vier-Zustands-Logik-Modell. Das nächste Kapitel beginnt, all das zum Bauen von Ausdrücken zu nutzen - Operatoren jeder Sorte, einschließlich der Bit-Level-Operatoren, die in Software keinen Sinn ergeben würden.
Häufig gestellte Fragen
Was bedeutet x in Verilog?
x ist der Unknown-Wert. Ein Signal, das x ist, könnte 0 oder 1 sein - der Simulator kann es nicht sagen. Es taucht auf, wenn ein Signal nicht getrieben wird, wenn zwei Treiber konkurrieren, wenn ein Register vor dem Reset gelesen wird oder überall dort, wo sonst undefiniertes Verhalten still propagieren würde. Behandle x als Bug-Signal; es bedeutet fast nie das, was du willst.
Was bedeutet z in Verilog?
z ist der High-Impedance-Wert - die Leitung wird überhaupt nicht getrieben. Das ist der legitime Zustand von Tri-State-Ausgängen (Datenbusse, bidirektionale Pins), aber auf internen Signalen ist z üblicherweise ein Fehler und bedeutet 'hier ist nichts angeschlossen'. Synthesizer lehnen die meisten z-Muster außerhalb expliziter Tri-State-Output-Enables ab.
Warum ist mein Verilog-Output xxxx?
Fast immer, weil ein Signal von nichts getrieben wird oder weil eine Operation x von einem anderen Signal propagiert hat. Geh rückwärts: Welches Signal ist x, was speist es, ist der Treiber aktiv? Häufige Verdächtige sind fehlende Default-Cases in case-Anweisungen, nicht resettete Register und wires, die nach einem Refactor ihren Treiber verloren haben.
Wie prüfe ich in Verilog auf x oder z?
Mit dem ===-Operator, der bitgenau einschließlich x und z vergleicht. a === 1'bx ist wahr, wenn a tatsächlich x ist. Das normale == liefert x, wenn einer der Operanden ein x-Bit hat, das heißt a == 1'bx ist nie die Antwort, die du willst. Es gibt auch $isunknown(a), ein ordentliches Boolean.
Kann man x als Default in Verilog zuweisen?
Ja, und es ist eine bewusste Technik in case-Anweisungen: default: out = 'x; sagt dem Synthesizer 'ich verspreche, dieser Fall passiert nie, optimiere frei'. Die Kosten sind: Tritt er in der Simulation doch ein, propagiert x und der Bug wird sichtbar. Nutze es, wenn du bereits bewiesen hast, dass der Default unerreichbar ist - nicht, um das Schreiben des Cases zu umgehen.