Die Schnittstelle eines Moduls
Die Port-Liste eines Moduls ist seine Schnittstelle - alles, was die Außenwelt sehen und anfassen kann. Innerhalb des Moduls berechnet der Body Ausgänge aus Eingängen. Die Ports sind die Leitungen, die die Grenze überqueren.
Die moderne ANSI-Deklaration packt Richtung, Typ und Breite direkt in die Port-Liste:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// body
endmodule
Das ist die ganze Form. Jeder Port hat:
- Eine Richtung:
input,outputoderinout. - Einen Typ:
wireoderreg(oderlogicin SystemVerilog). - Eine Breite: ein Bereich wie
[7:0], oder einzelbittig, wenn weggelassen. - Einen Namen: den Bezeichner, den du im Body verwendest.
Die drei Richtungen
input - von außen getrieben
Eingänge sind immer wire. Der Treiber liegt außerhalb des Moduls - entweder ein Signal eines übergeordneten Moduls oder ein reg der Testbench. Innerhalb dieses Moduls kannst du Eingänge in Ausdrücken lesen, ihnen aber nie zuweisen:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
data = ... innerhalb des Moduls zu schreiben, wäre ein Fehler.
output - von innen getrieben
Outputs werden von etwas innerhalb dieses Moduls getrieben. Sie können entweder wire sein (getrieben von assign oder vom Output eines Submoduls) oder reg (getrieben aus always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire - durch assign getrieben
output reg q // reg - durch always getrieben
);
assign y = a & b; // OK, weil y ein wire ist
always @(posedge clk)
q <= a; // OK, weil q ein reg ist
endmodule
Versuchst du, y innerhalb des always-Blocks zuzuweisen oder q mit einem assign zu treiben, gibt's einen Compile-Fehler. Die Wahl des Schlüsselworts muss zum Treiber passen.
inout - bidirektional
inout-Ports sind Leitungen, die abwechselnd vom Modul oder von dem getrieben werden können, was extern angeschlossen ist. Sie tauchen an Chip-Grenzen auf - I²C-SDA-Leitungen, bidirektionale GPIO-Pins, geteilte Datenbusse. Innerhalb des Moduls steuerst du die Richtung mit einem Tri-State-Muster:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin ist die geteilte Leitung. Wenn output_enable high ist, treibt das Modul pin auf data_out. Wenn low, gibt es den Pin in High-Impedance (z) frei, damit ein externer Treiber ihn benutzen kann. data_in beobachtet immer das, was gerade am Pin liegt.
inout ist in reiner Internlogik selten. Wenn du einen Speichercontroller, einen CPU-Kern oder eine Bildverarbeitungs-Pipeline baust, brauchst du es vielleicht nie. Es ist ein Feature für den I/O-Ring an Chip-Grenzen.
Einzelbittige vs. mehrbittige Ports
Der Bereich ist optional - lass ihn weg für einen Einzelbit-Port:
input wire valid, // 1 Bit
input wire [7:0] data, // 8 Bits
input wire [31:0] addr, // 32 Bits
Du kannst Parameter für Breiten verwenden, so funktionieren parametrierte Module:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
ANSI- vs Verilog-1995-Stil
Gelegentlich siehst du älteren Code, der Port-Liste und Deklarationen trennt:
// Alter Verilog-1995-Stil - mach das NICHT in neuem Code
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
Die Port-Liste enthält nur Namen; jeder Port wird dann separat im Body deklariert. Das ist umständlich, anfällig für "in Port-Liste benannt, aber nie deklariert"-Fehler und doppelte Tipparbeit. Der ANSI-2001-Stil (der in diesen Docs durchgehend gezeigt wird) ersetzt beides:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Nimm den ANSI-Stil. Tools unterstützen ihn seit 2001.
Ein vollständiges Beispiel
Dieses eine Modul hat:
- Einen Parameter (
WIDTH). - Eingänge für Takt, Reset, Load-Enable, Daten und Shift-Enable.
- Einen
reg-Ausgang (data_out), getrieben in einemalways-Block. - Einen
wire-Ausgang (msb_out), getrieben durch eine kontinuierliche Zuweisung. - Eine vollständige ANSI-Deklaration, die Richtung, Typ und Breite jedes Ports direkt auflistet.
So ist fast jedes synthetisierbare Modul aufgebaut, das du schreiben wirst.
Wie es weitergeht
Das nächste Doc - Modul-Instanziierung - zeigt, wie du dieses Modul nimmst und in einem größeren Design einsetzt. Der Shifter, den du gerade geschrieben hast, ist allein nicht nützlich; er verdient sich seinen Lohn, wenn ihn etwas instanziiert und in ein System einbindet.
Häufig gestellte Fragen
Welche drei Port-Richtungen gibt es in Verilog?
input, output und inout. input wird von außen getrieben. output wird von innerhalb des Moduls getrieben. inout ist bidirektional - nützlich für Tri-State-Busse, bei denen das Modul denselben Pin sowohl treibt als auch liest. Die große Mehrheit interner Ports ist input oder output; inout taucht nur an Chip-Pins auf.
Was ist der Unterschied zwischen output wire und output reg?
output wire heißt, der Ausgang wird durch eine kontinuierliche Zuweisung oder durch den Ausgang eines Submoduls getrieben - alles außerhalb eines always-Blocks. output reg heißt, er wird aus einem prozeduralen Block (initial oder always) getrieben. Das Schlüsselwort entscheidet, ob du das Signal aus einem always heraus ansprechen kannst - nicht, ob die synthetisierte Hardware ein Flip-Flop enthält.
Was ist der ANSI-Stil für Verilog-Ports?
Der ANSI-Stil deklariert Richtung und Typ jedes Ports direkt in der Port-Liste: module foo(input wire [7:0] data, output reg [7:0] result);. Der ältere Verilog-1995-Stil führte in der Port-Liste nur Namen auf und re-deklarierte sie im Modul-Body. Nutze in neuem Code immer den ANSI-Stil - er ist kürzer, weniger fehleranfällig und überall modern Standard.
Kann ein Verilog-Modul mehrere Inputs und Outputs haben?
Ja - ein Modul kann beliebig viele Ports in jeder Richtungskombination haben. Trenne sie in der Port-Liste mit Kommas. Die Reihenfolge in der Port-Liste bestimmt die positionsbasierte Verbindungsreihenfolge bei der Instanziierung, aber du verwendest fast immer benannte Verbindungen (.port(signal)), um nicht auf die Reihenfolge angewiesen zu sein.