Software-Zeit vs. Hardware-Zeit
In einem Programm rückt die Zeit eine Anweisung nach der anderen vor. Die CPU beendet Zeile 1, dann führt sie Zeile 2 aus. Wenn du zwei Dinge gleichzeitig erledigen willst, greifst du zu Threads, async oder einer zweiten Maschine.
In Hardware passiert alles gleichzeitig. Eine Schaltung kennt kein Abwechseln. Der Addierer addiert ständig, der Multiplexer wählt ständig aus, das Flip-Flop beobachtet ständig den Takt. Es gibt keinen Programmzähler. Es gibt keine "aktuelle Zeile".
Verilog muss diese Welt in Text beschreiben. Wie es das tut, ist die Quelle jeder Verwirrung in diesem Kapitel.
Zwei Ebenen der Nebenläufigkeit
Wenn du ein Verilog-Modul liest, schaust du gleichzeitig auf zwei verschiedene Dinge:
- Die statische Beschreibung der Schaltung. Leitungen, Register, Gatter-Instanzen, Sub-Modul-Instanzen, kontinuierliche Zuweisungen. Diese existieren alle gleichzeitig. Ihre Reihenfolge in der Datei spielt keine Rolle.
- Prozedurale Blöcke -
initialundalways- die wie kleine Programme aussehen, durch die der Simulator schrittweise geht. Innerhalb eines dieser Blöcke laufen die Anweisungen tatsächlich in einer bestimmten Reihenfolge. Aber mehrerealways-Blöcke können gleichzeitig aktiv sein, jeder in seinem eigenen kleinen Faden simulierter Zeit.
module example(input wire a, input wire b, output wire y, output wire z);
assign y = a & b; // existiert zu jedem Zeitpunkt
assign z = a | b; // existiert ebenfalls zu jedem Zeitpunkt, parallel
always @(posedge a) begin
// ein separater "always-on"-Reaktor, der bei jeder
// steigenden Flanke von `a` aufwacht
end
endmodule
Die beiden assign-Zeilen sind keine Sequenz. Sie beschreiben zwei Stücke kombinatorischer Logik, die das Synthese-Tool nebeneinander platzieren kann. Der always-Block ist ein drittes Ding, das parallel passiert.
Was "Signal" bedeutet
In Software hält eine Variable einen Wert, bis du sie änderst. In Verilog hält ein Signal einen Wert kontinuierlich - und zu jedem Zeitpunkt hängt er von dem ab, was es treibt.
Ein wire wird von außen getrieben (ein assign, der Ausgangs-Port eines Sub-Moduls, eine inout-Verbindung). Ein reg wird von innen aus einem prozeduralen Block getrieben. Beide haben jederzeit einen Wert. Es gibt kein "uninitialisiert" in dem Sinn, den C kennt - Signale sind entweder auf einen definierten Wert getrieben, auf den speziellen Unknown-Wert x oder auf High-Impedance z. Die letzten beiden behandeln wir in X- und Z-Werte.
Takte ändern alles
Sobald ein Takt ins Spiel kommt, fängt Zeit an, wichtig zu werden. Ein Flip-Flop ist ein winziges Stück Hardware, das seinen Eingangswert an der steigenden (oder fallenden) Flanke eines Takts erfasst und ihn bis zur nächsten Flanke hält. Was dir erlaubt, Zähler, Automaten, Pipelines zu bauen - alles, was Speicher hat - ist der Takt.
Beachte q <= d statt q = d. Das ist eine non-blocking-Zuweisung - das Arbeitspferd getakteter Logik. Sie sagt: "An der nächsten Taktflanke wird geplant, dass q den aktuellen Wert von d annimmt." Wir vertiefen die Regeln in Blocking vs Non-blocking; für jetzt reicht zu erkennen, dass die Zuweisung nicht so tut, als wäre sie eine Software-Anweisung.
RTL: Das mentale Modell des Register Transfer
Der Großteil synthetisierbaren Verilogs wird in einem Stil geschrieben, der Register Transfer Level oder RTL heißt. Die Idee ist einfach:
- Entscheide, welchen Zustand deine Schaltung braucht (die Register).
- Beschreibe für jedes Register zwei Dinge: was es zurücksetzt, und welche kombinatorische Logik seinen nächsten Wert berechnet.
- Verdrahte die Ausgänge kombinatorischer Logik mit den Eingängen der Register und du hast eine funktionierende Schaltung.
always @(posedge clk) begin
if (reset) state <= IDLE;
else state <= next_state;
end
always @(*) begin
case (state)
IDLE: next_state = start ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = IDLE;
endcase
end
Das ist ein Zwei-Zustände-Automat. Der erste always-Block ist getaktet - er ist ein Flip-Flop. Der zweite ist rein kombinatorisch - er ist nur eine Gleichung. Fast jeder Automat, Zähler und jede Pipeline, die du schreiben wirst, folgt dieser Form.
Die Gewohnheiten, die dich aufhalten
Wenn du aus der Software kommst, hier eine kurze Liste an Gewohnheiten, die du ablegen solltest:
- "Variablen werden aktualisiert, wenn ich ihnen etwas zuweise." An einer Taktflanke nicht - eine non-blocking-Zuweisung plant das Update für das Ende des Zeitschritts.
- "Anweisungen werden von oben nach unten ausgeführt." Außerhalb prozeduraler Blöcke: nein. Innerhalb eines getakteten Blocks: irgendwie - aber blocking vs non-blocking ändert, was "in Reihenfolge" eigentlich bedeutet.
- "Ich alloziere das, wenn ich es brauche." Hardware alloziert nicht. Jedes Register und jedes Gatter muss zur Synthese-Zeit existieren. Die Größe jedes Vektors ist fest.
- "Diese Schleife ist schnell - es ist ja nur eine Operation." Eine
for-Schleife in synthetisierbarem Verilog wird in parallele Hardware aufgerollt. Eine Schleife mit 64 Durchläufen wird zu 64 Kopien des Rumpfes, nicht zu einer CPU-Instruktion, die 64-mal läuft.
Du lernst nicht, ein Programm zu schreiben. Du lernst, eine Schaltung zu beschreiben. Der Instinkt, von oben nach unten zu lesen, ist genau der falsche, und es dauert eine Weile, ihn umzutrainieren.
Wie es weitergeht
Die nächsten Docs führen durch das Einrichten einer lokalen Toolchain (optional - der Browser-Editor reicht), dann durch das Schreiben deines ersten Moduls von Grund auf. Wir kommen jedes Mal auf den Hardware-vs-Software-Kontrast zurück, wenn etwas seltsam aussieht, denn die meisten "seltsamen" Stellen haben die gleiche Ursache: Das ist keine Software.
Häufig gestellte Fragen
Was ist der Unterschied zwischen Hardware und Software aus Verilog-Sicht?
Software ist eine Folge von Anweisungen, die eine CPU nacheinander abarbeitet. Hardware - das, was Verilog beschreibt - ist ein Netz aus Gattern und Leitungen, das gleichzeitig Signale führt. Eine Verilog-Datei beschreibt dieses Netz. Der Simulator ahmt das parallele Verhalten nach; ein Synthese-Tool verwandelt es in echtes Silizium.
Läuft Verilog-Code von oben nach unten wie C?
Nein - und das so zu behandeln ist der häufigste Anfängerfehler. Anweisungen auf Modul-Ebene (assigns, Modul-Instanzen, always-Blöcke) 'existieren' alle gleichzeitig. Nur innerhalb prozeduraler Blöcke - initial und always - passiert etwas, das einer sequentiellen Ausführung ähnelt, und selbst dort zerstören non-blocking-Zuweisungen die Illusion.
Was bedeutet 'nebenläufig' in Verilog?
Es bedeutet, dass mehrere Anweisungen Teile einer Schaltung beschreiben, die alle gleichzeitig arbeiten. Zwei assign-Anweisungen im selben Modul sind nicht 'Zeile 1 dann Zeile 2' - es sind zwei Stücke Hardware, die parallel laufen und beide kontinuierlich auf ihre Eingänge reagieren.
Was ist RTL-Design?
RTL steht für Register Transfer Level. Es ist ein Stil, Verilog zu schreiben, bei dem du die Schaltung als Register (Flip-Flops) und die kombinatorische Logik beschreibst, die ihre nächsten Werte berechnet. Der Großteil synthetisierbaren Verilogs ist RTL. Die Ebene darüber ist Verhaltensbeschreibung; die Ebene darunter ist Gate-Level.