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.

Illustration der Programmiersprachen bei Coddy

Lerne mit Coddy zu programmieren

Los geht's