Menu

Verilog Parameter: Module konfigurierbar machen

Wie du parameter und localparam für Compile-Zeit-Konstanten verwendest, Breiten und Tiefen parametrierst und Werte beim Instanziieren eines Moduls überschreibst.

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

Konstanten mit einem Twist

Ein parameter ist eine Konstante - der Compiler wählt ihren Wert und backt ihn vor Beginn der Simulation in das Design ein. Der Twist: Wer auch immer das Modul instanziiert, kann den Wert ändern. Dieselbe Modul-Quelldatei kann Schaltungen unterschiedlicher Größen erzeugen, je nachdem, wie Aufrufer sie parametrieren.

So baut man wiederverwendbare IP. Ein FIFO-Modul mit WIDTH- und DEPTH-Parametern kann jedem Team in einer Firma dienen. Ein Counter mit einem WIDTH-Parameter ist dieselbe Quelle, ob er bis 16 oder bis 4 Milliarden zählt.

Grundlegende Parameter-Deklaration

Drei neue Syntaxteile:

  1. Parameter-Block: #( parameter WIDTH = 8 ) zwischen Modulnamen und Port-Liste. Der Default-Wert 8 ist das, was das Modul nimmt, wenn niemand überschreibt.
  2. Nutzungsstelle: output reg [WIDTH-1:0] count. Der Parameter ist nur eine Konstante - wir stecken ihn in den Bit-Bereich. Dieselbe Modul-Quelle produziert je nach WIDTH einen 8-Bit- oder 16-Bit-Ausgang.
  3. Override-Syntax: counter #(.WIDTH(16)) dut16(...) bei der Instanziierung. Der #(...)-Block steht vor dem Instanznamen, nach dem Modulnamen. Nicht erwähnte Parameter behalten ihre Defaults.

localparam: Interne Konstanten

Es gibt Konstanten, die Aufrufer nicht überschreiben sollen. Zustandskodierungen sind der Klassiker:

localparam macht IDLE, RUNNING, DONE innerhalb des Moduls verfügbar, aber nicht von außen überschreibbar. Das ist die richtige Wahl - von außen zu ändern, welcher Zustandswert IDLE ist, wäre erschreckend.

Nutze parameter für Dinge, die Aufrufer konfigurieren sollen (Breiten, Tiefen, Verhaltensoptionen), und localparam für alles andere. Ein übliches Muster: localparams aus parameters ableiten:

parameter WIDTH = 32;
localparam WIDTH_M1 = WIDTH - 1;   // einmal berechnet; nicht überschreibbar

Parametrierte Breiten in der Praxis

Die häufigste Nutzung von Parametern ist, Busbreiten flexibel zu machen. Hier ein Addierer, der bei jeder Breite funktioniert:

Eine Quelldatei, zwei Instanzen, zwei verschiedene Breiten, kein Copy-Paste. Das ist der ganze Lohn von Parametern.

Mehrere Parameter, benannter Override

Bei größeren Modulen hast du oft mehrere Parameter. Überschreibe sie namentlich in beliebiger Reihenfolge:

module fifo #(
    parameter WIDTH = 8,
    parameter DEPTH = 16,
    parameter AFULL = DEPTH - 2
)(
    input  wire             clk,
    input  wire             reset,
    // ... Ports ...
);
    // ...
endmodule

// An der Aufrufstelle:
fifo #(.WIDTH(32), .DEPTH(1024)) cmd_queue (.clk(clk), .reset(reset), ...);

Du musst nicht jeden Parameter überschreiben; nicht erwähnte behalten ihre Defaults. Der AFULL-Default im Beispiel wird aus DEPTH berechnet, was heißt: Überschreibst du DEPTH, folgt AFULL automatisch - genau die Art Abhängigkeit, die auch localparam abdecken würde, wenn du nicht wolltest, dass Aufrufer AFULL unabhängig überschreiben können.

Häufige Fehler

Das # im Override vergessen. counter (.WIDTH(16)) dut(...) sieht aus wie ein Override, aber Verilog liest (.WIDTH(16)) als Port-Verbindung. Du brauchst counter #(.WIDTH(16)) dut(...).

Parameter dort nutzen, wo sie nicht konstant genug sind. Parameter lösen sich zur Elaborationszeit auf, bevor irgendein Signal existiert. Du kannst nicht auf ein Laufzeitsignal parametrieren - hängt der Wert von dem ab, was ein Eingang zur Simulationszeit tut, ist es kein Parameter, sondern Logik.

parameter und localparam in Port-Listen verwechseln. Nur parameter gehört in den #(...)-Block oben. localparam lebt im Body. Der Compiler sagt dir, wenn du sie vertauschst.

Wie es weitergeht

Du kannst jetzt Module bauen, deren Größe zur Compile-Zeit festgelegt wird. Die nächsten beiden Docs behandeln die Regeln zum Schreiben literaler Konstanten (8'h1F, 4'b1010, 32'd100) und die x- und z-Werte, die auftauchen, wenn Signale ungetrieben sind oder umkämpft werden. Danach gehen wir zu Operatoren über - alles, was du mit den Vektoren und Parametern machen kannst, die du jetzt gesehen hast.

Häufig gestellte Fragen

Was ist ein parameter in Verilog?

Ein parameter ist eine Compile-Zeit-Konstante, die innerhalb eines Moduls deklariert wird. Er kann überall verwendet werden, wo eine Konstante zulässig ist - Busbreiten, Speichertiefen, Zustandskodierungen, Default-Werte. Entscheidend: Jede Instanz des Moduls kann den Parameter überschreiben, sodass dieselbe Quelldatei z.B. einen 8-Bit-Counter an einer Stelle und einen 32-Bit-Counter an einer anderen erzeugt.

Was ist der Unterschied zwischen parameter und localparam in Verilog?

parameter-Werte können von außen überschrieben werden, wenn das Modul instanziiert wird. localparam-Werte können nicht überschrieben werden - sie sind interne Konstanten. Nimm localparam für Zustandskodierungen und abgeleitete Konstanten, an denen der Modul-Autor Aufrufer nicht herumschrauben lassen will.

Wie überschreibt man einen Parameter in Verilog?

Wenn du das Modul instanziierst, füge einen Override-Block vor dem Instanznamen ein: counter #(.WIDTH(16)) my_inst (.clk(clk), .count(count));. Die .WIDTH(16)-Syntax setzt diesen spezifischen Parameter; nicht aufgelistete Parameter behalten ihre Defaults. Mehrere Overrides trennst du mit Kommas innerhalb des #(...).

Was ist ein parametrisiertes Modul?

Ein Modul, das eine oder mehrere parameter-Deklarationen offenlegt, die Aufrufer überschreiben können. Ein parametriertes FIFO könnte WIDTH- und DEPTH-Parameter haben, sodass dieselbe Quelle ein 32-Bit-x-16-tiefes FIFO an einer Stelle und ein 8-Bit-x-1024-tiefes FIFO an einer anderen produziert. So werden Bibliotheken wiederverwendbarer IP-Blöcke geschrieben.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S