Das Modul ist die Einheit von Verilog
Alles in Verilog lebt innerhalb eines module. Ein Modul verpackt ein Stück Schaltung: Es deklariert, welche Signale hereinkommen, welche herausgehen und welche Leitungen/Register/Logik dazwischen liegen. Jeder Chip, den du je gesehen hast, ist ein Baum von Modulen, die andere Module instanziieren, bis hinunter zu den herstellerseitigen Gatter-Primitiven.
Die Form ist immer dieselbe:
module name(port_list);
// Deklarationen: wire, reg, parameter
// Body: assigns, Instanziierungen, always-Blöcke
endmodule
Wir bauen uns Schritt für Schritt zu einem vollständigen Modul vor.
Das kleinste nützliche Modul: Ein UND-Gatter mit zwei Eingängen
Wir brauchen etwas Einfaches, Eigenständiges. Ein UND-Gatter mit zwei Eingängen ist perfekt: zwei Eingänge, ein Ausgang, eine Zeile Logik.
Drücke Run. Du solltest die vier Zeilen einer AND-Wahrheitstabelle sehen. Schauen wir uns jedes Stück an.
Die Modul-Deklaration lesen
module and_gate(
input wire a,
input wire b,
output wire y
);
module and_gatedeklariert ein Modul namensand_gate. Der Name ist die Art, wie andere Module es instanziieren werden.- Die geklammerte Liste ist die Port-Liste - die Signale, die von außen sichtbar sind.
input wire a-aist ein Eingangsport; er ist einwire(von außen getrieben).output wire y-yist ein Ausgangsport, der von etwas innerhalb des Moduls getrieben wird.
Wenn du es knapp halten willst, kannst du input a statt input wire a schreiben - die Richtung allein macht den Defaulttyp wire. Aber explizit zu sein, ist eine gute Angewohnheit. Modul-Ports deckt die ganze Bandbreite an Port-Formen ab.
Der Body
assign y = a & b;
Das ist eine kontinuierliche Zuweisung (continuous assignment). Sie sagt: "Wann immer a oder b sich ändert, berechne y als bitweises AND der beiden neu." Kein Takt, keine Zeitsteuerung - die Beziehung gilt immer. Das ist reine kombinatorische Logik.
endmodule schließt den Block. Das Modul ist fertig.
Die Testbench
Du kannst and_gate nicht für sich laufen lassen. Du brauchst ein zweites Modul, das seine Eingänge treibt und seine Ausgänge beobachtet. Das ist die Testbench, und per Konvention nennen wir sie test, tb oder <design>_tb.
module test;
reg a, b;
wire y;
and_gate dut(.a(a), .b(b), .y(y));
...
endmodule
Drei Dinge fallen auf:
- Keine Port-Liste. Eine Testbench ist die Spitze der Simulation - außerhalb steht nichts.
reg a, bundwire y. Die Eingänge des Design under Test (DUT) sind in der Testbenchreg, weil wir sie aus einem prozeduralen Block treiben. Der Ausgang ist einwire, weil das DUT ihn treibt.and_gate dut(.a(a), .b(b), .y(y)). Das ist die Instanziierung. Wir stempeln eine Kopie vonand_gateaus und nennen siedut(üblicher Name - "design under test"). Die.a(a)-Syntax sagt: "Verbinde den Port mit Namenader Instanz mit dem lokalen Signal mit Namena." Modul-Instanziierung geht tiefer.
Der Stimulus
initial begin
a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
$finish;
end
Der initial-Block läuft einmal beim Start der Simulation. Darin:
a = 0; b = 0;treibt die Eingänge. Das sind blocking-Zuweisungen - die Reihenfolge ist relevant, und jede passiert vor der nächsten.#1rückt die simulierte Zeit um 1 Einheit vor. Wir brauchen das, damityZeit hat, sich nach den Eingangsänderungen einzuschwingen. Ohne das#1würde$displayden alten Wert vonydrucken.$display(...)schreibt auf die Simulationskonsole. Der Format-String funktioniert wie C'sprintf:%bist binär,%dist dezimal,%hist hex,%tist Simulationszeit.$finishbeendet die Simulation. Ohne es würde der Simulator endlos die Zeit weiterticken lassen und auf ein Ereignis warten, das nie kommt.
Probier kaputtzumachen
Mache aus dem Modul ein OR-Gatter (ändere & zu |) und lass es erneut laufen. Die Wahrheitstabelle dreht sich. Jetzt probiere ein XOR:
Gleiches Skelett. Anderer Operator. Das ist das ganze Spiel für kombinatorische Logik - Ports deklarieren, assigns schreiben, Eingänge durchspielen, Ausgänge beobachten.
Ein Modul mit zwei Ausgängen
Module können mehr als einen Ausgang haben. Hier ist ein Halbaddierer - zwei Eingänge, Summe und Carry-Out:
Die beiden assign-Anweisungen stehen nebeneinander, passieren aber parallel - es gibt kein "erst Summe ausrechnen, dann Carry". Beide gelten immer. Das ist die Nebenläufigkeit, über die wir in Hardware vs Software gesprochen haben, jetzt konkret.
Was du jetzt kennst
Du hast das vollständige Skelett einer Verilog-Datei gesehen: ein Design-Modul mit deklarierten Ports und einem Body, plus ein Testbench-Modul, das es instanziiert, seine Eingänge in einem initial-Block treibt und Ergebnisse meldet. Fast jede Verilog-Quelldatei, die du je lesen wirst, passt in dieses Schema. Der Rest der Sprache füllt nur reichere Logik, mehrbittige Signale, getaktetes Verhalten und größere Testbenches darin auf.
Als Nächstes: Kommentare und Code-Stil, damit deine Module beim Wachsen lesbar bleiben.
Häufig gestellte Fragen
Was ist das einfachste Verilog-Modul?
Das kleinste zulässige Verilog-Modul ist einfach module name; endmodule - keine Ports, kein Body. Das kleinste nützliche ist ein Modul mit einem Ausgang: module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. Das ist ein echtes Stück kombinatorischer Logik, das du in jedes größere Design einfügen kannst.
Wie führe ich ein Verilog-Modul aus?
Du kannst ein Modul nicht für sich allein ausführen - es ist eine Schaltungsbeschreibung, kein Programm. Du schreibst ein Testbench-Modul, das dein Design instanziiert und seine Eingänge treibt, und kompilierst dann beides mit iverilog -o sim design.v test.v und lässt vvp sim laufen. Der Browser-Editor auf dieser Seite erledigt beides für dich, wenn du Run drückst.
Was ist eine Testbench in Verilog?
Eine Testbench ist ein zweites Modul - üblicherweise ohne Ports -, dessen Aufgabe darin besteht, dein Design auszuüben. Sie instanziiert das Design, wackelt in einem initial-Block an dessen Eingängen, beobachtet die Ausgänge mit $display oder $monitor und ruft $finish auf, wenn sie fertig ist. Testbenches sind nicht synthetisierbar; sie existieren nur, um Verhalten zu verifizieren.
Warum braucht mein Verilog-Code $finish?
Weil Hardware nie aufhört. Ein Simulator tut so, als verginge Zeit, und ohne ein explizites $finish würde er ewig weiterlaufen und auf neue Ereignisse warten. $finish sagt dem Simulator: 'Wir sind fertig, sauber beenden'. In einer Testbench ist es die letzte Zeile des initial-Blocks - Test ausführen, dann finish.