Menu

Hardware vs. Software: Wie Verilog anders denkt als C oder Python

Warum sich Verilog nach Software-Sprachen anfühlt wie ein Schock: Nebenläufigkeit per Default, Zeit als First-Class-Konzept und Anweisungen, die nicht der Reihe nach laufen.

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

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:

  1. 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.
  2. Prozedurale Blöcke - initial und always - 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 mehrere always-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.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S