assign beschreibt eine permanente Wahrheit
Eine wire-Deklaration erzeugt ein Signal. Ein assign beschreibt, was es treibt. Die Beziehung ist kontinuierlich: was immer rechts steht, die linke Seite ist gleich, zu jedem Zeitpunkt der simulierten Zeit:
wire y;
assign y = a & b;
Zwei Dinge, die mit diesen beiden Zeilen impliziert sind:
yexistiert als wire in der Schaltung.yist zu jeder Zeit das bitweise AND vonaundb. Ändere einen der Eingänge undyfolgt.
Es gibt keinen Takt und kein Ereignis, das das Update auslöst. Der Simulator sieht a oder b sich ändern, markiert y als dreckig und wertet den Ausdruck neu aus. In Hardware übersetzt sich das in ein paar AND-Gatter: kombinatorisch, zustandslos, sofort.
Die implizite Form
Du kannst Deklaration und Zuweisung in einer Zeile kombinieren:
wire y = a & b;
Das ist dasselbe wie die beiden Zeilen oben. Praktisch für Inline-Wires, die niemand außerhalb dieses Scopes interessiert. Viele Style-Guides bevorzugen sogar die implizite Form, weil sie die Deklaration direkt neben die Gleichung setzt.
Was assign treiben kann
Das Ziel von assign muss ein Net-Typ sein - in reinem Verilog ist das wire (oder eine der selteneren Verwandten wie tri, wand, wor). Es kann kein reg sein. Wenn du dein Ziel versehentlich als reg deklarierst:
reg y;
assign y = a & b; // ERROR: cannot drive reg with assign
Der Compiler sagt es dir. Ändere entweder das Ziel zu wire oder verschiebe die Logik in einen always @(*)-Block, wo reg das zulässige Ziel ist.
Die rechte Seite kann alles sein, was zu einem Wert ausgewertet werden kann: Literale, Signale, Parameter, Operator-Ausdrücke, Funktionsaufrufe. Sie kann mehrere Eingangsbreiten mischen; es gelten die Standard-Widening-Regeln.
Wann assign vs always
Beide können kombinatorische Logik erzeugen. Die Wahl ist meistens eine Frage, wie der Code sich liest:
assignist am besten, wenn die Beziehung ein einziger Ausdruck ist. Addierer, einfache Multiplexer mit?:, Masken, Paritätsbits, alles, was du in einer Zeile schreiben kannst.always @(*)ist am besten, wenn du prozedurale Anweisungen brauchst.case-Anweisungen mit vielen Zweigen, verschachtelteif/else if, alles, was von benannten Zwischen-regs profitiert. Wir behandeln das im Always-Block.
Hier derselbe 4-zu-1-Mux in beiden Varianten:
Beide Module synthetisieren im Wesentlichen zum selben Multiplexer. Die assign-Variante ist eine Zeile Code; die always-Variante sind sechs. Bei vier Fällen liegen sie nah beieinander; bei sechzehn Fällen ist der case-Block klar besser lesbar.
Übliche Muster
Pure kombinatorische Logik
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
Ein Ausdruck, ein wire. Das Brot-und-Butter-Geschäft von assign.
Ein 2-zu-1-Mux
assign out = sel ? a : b;
Ein einziger bedingter Ausdruck - der Synthesizer macht daraus einen einzelnen 2-zu-1-Mux. Die sauberste Schreibweise von "wähle zwischen a und b".
Bit-Packing
assign status = {error, overflow, ready, busy, 4'b0};
Konkatenation auf der rechten Seite eines assign ist die Art, wie du Flags in ein Status-Byte packst. Das Ergebnis wird kontinuierlich berechnet und getrieben.
Tri-State-Ausgang
assign data_pin = output_enable ? data_out : 1'bz;
Ist output_enable high, treibe den Pin. Ist es low, gib in High-Impedance frei. Das ist das übliche Muster an Chip-Pins, an denen sich mehrere Treiber eine Leitung teilen könnten.
Nebenläufigkeit: Mehrere assigns sind keine Sequenz
Eine Erinnerung, die nie aufhört, relevant zu sein: Mehrere assign-Anweisungen im selben Modul laufen alle parallel. Sie sind keine Sequenz:
assign y = a & b; // existiert zu jedem Zeitpunkt
assign z = a | b; // existiert ebenfalls zu jedem Zeitpunkt, unabhängig
Die Reihenfolge in der Datei ist irrelevant. Beide Gleichungen sind gleichzeitig wahr. Der Synthesizer kann das AND-Gatter links vom OR-Gatter platzieren oder rechts; das ist egal, beide Gatter feuern kontinuierlich.
Wenn du sequenzartiges Verhalten willst, greifst du zu einem always-Block (und wahrscheinlich zu einem Takt). Das ist ein anderes Kapitel.
Mehrere Treiber: Das Bus-Muster
Ein wire kann mehr als ein assign haben, das ihn als Ziel ansteuert, aber das willst du praktisch nie, außer für Tri-State-Busse. Zwei Treiber, die um einen wire kämpfen, erzeugen undefiniertes Verhalten:
assign y = a;
assign y = b; // BAD - zwei Treiber, Simulator wählt einen oder x't das Signal aus
Das legitime Muster: jeder Treiber gibt in z frei, wenn er inaktiv ist, und zu jedem Zeitpunkt ist höchstens einer aktiv.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
Das funktioniert, weil zu jedem Zeitpunkt höchstens einer der beiden Ternäre einen Wert ungleich z produziert. Der tatsächliche Wert des wire ist der, den der nicht-freigebende Treiber liefert.
Bei interner Logik - überall, wo es nicht ein Chip-Pin oder ein geteilter On-Chip-Bus ist - ein Treiber pro wire. Multi-Driver-Bugs sind unangenehm zu debuggen.
Was assign nicht kann
Einige Dinge, für die assign das falsche Werkzeug ist:
- Speicherung.
assignbeschreibt kombinatorische Beziehungen; es kann kein Flip-Flop einführen. Wenn ein Wert über Taktzyklen hinweg erinnert werden soll, ist das einalways @(posedge clk)-Block. - Mehrstufige prozedurale Logik. Du kannst kein
if/elseodercaseinnerhalb einesassignschreiben. Das Nächste, was du bekommst, ist eine?:-Kette, die ab drei Zweigen hässlich wird. - Register aus einem prozeduralen Block treiben.
reg-Ziele brauchen prozedurale Zuweisung, keinassign.
Die Grenzen zu kennen, hilft dir zu entscheiden, wann du auf always umsteigst.
Wie es weitergeht
Du hast jetzt die komplette strukturelle Seite von Verilog gesehen: Module deklarieren, sie instanziieren und kombinatorische Logik mit assign verdrahten. Das nächste Kapitel führt in prozedurale Blöcke ein - die initial- und always-Konstrukte, in denen Zeit und Reihenfolge zu zählen beginnen.
Häufig gestellte Fragen
Was ist eine continuous assignment in Verilog?
assign target = expression; erklärt eine permanente, kontinuierliche Beziehung: target ist immer gleich expression. Wann immer sich ein Signal im Ausdruck ändert, wertet der Simulator die rechte Seite neu aus und aktualisiert target. Es gibt keinen Takt, kein Event - die Beziehung gilt zu jedem Zeitpunkt.
Was kann man mit einem assign in Verilog treiben?
assign kann einen wire treiben, aber niemals einen reg. Das Ziel muss ein Net-Typ sein. Wenn du stattdessen etwas innerhalb eines always-Blocks zuweisen willst, deklariere es als reg. Der Compiler weist assign x = ... ab, wenn x ein reg ist, und weist x = ... innerhalb eines always ab, wenn x ein wire ist.
Wann sollte ich assign vs einen always-Block nehmen?
Nutze assign für einfache kombinatorische Logik - ein Ausdruck rein, ein Signal raus, kein if/else nötig. Nutze always @(*), wenn die Logik prozedurale Anweisungen braucht (ein case, eine if/else if-Kette, eine for-Schleife). Beide erzeugen kombinatorische Hardware; die Wahl ist eine Frage der Lesbarkeit.
Kann man mehrere assigns auf denselben wire in Verilog haben?
Nur, wenn du einen Tri-State-Bus modellierst, bei dem jeder Treiber den wire in z freigibt, wenn er inaktiv ist. Zwei assigns, die den wire gleichzeitig auf definierte Werte treiben wollen, erzeugen Contention - der Simulator wählt vielleicht einen, macht das Signal vielleicht zu x, das hängt vom Tool ab. Für gewöhnliche kombinatorische Logik: ein Treiber pro wire.