Menu

Verilog Parameters: Making Modules Configurable

How to use parameter and localparam to define compile-time constants, parameterize widths and depths, and override values when you instantiate a module.

This page includes runnable editors - edit, run, and see output instantly.

Constants With a Twist

A parameter is a constant - the compiler picks its value and bakes it into the design before simulation starts. The twist is that whoever instantiates the module can change the value. The same module source file can produce circuits of different sizes depending on how callers parameterize it.

That's how you build reusable IP. A FIFO module with WIDTH and DEPTH parameters can serve every team in a company. A counter with a WIDTH parameter is the same source whether it counts to 16 or 4 billion.

Basic Parameter Declaration

Three pieces of new syntax:

  1. Parameter block: #( parameter WIDTH = 8 ) between the module name and the port list. The default value 8 is what the module uses when nobody overrides.
  2. Use site: output reg [WIDTH-1:0] count. The parameter is just a constant - we plug it into the bit range. Same module source produces an 8-bit or 16-bit output depending on WIDTH.
  3. Override syntax: counter #(.WIDTH(16)) dut16(...) at instantiation time. The #(...) block goes before the instance name, after the module name. Any parameters you don't mention keep their defaults.

localparam: Internal Constants

There are constants you don't want callers to override. State encodings are the classic case:

localparam makes IDLE, RUNNING, DONE available inside the module but not overrideable from instantiation. That's the right choice - changing what state value means IDLE from outside the module would be terrifying.

Use parameter for things callers should configure (widths, depths, behavior options) and localparam for everything else. A common pattern is to derive localparams from parameters:

parameter WIDTH = 32;
localparam WIDTH_M1 = WIDTH - 1;   // computed once; not overrideable

Parameterized Widths in Practice

The most common use of parameters is to make bus widths flexible. Here's an adder that works at any width:

One source file, two instances, two different widths, no copy-paste. That's the entire payoff of parameters.

Multiple Parameters, Named Override

For larger modules you'll often have several parameters. Override them by name in any order:

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

// At the call site:
fifo #(.WIDTH(32), .DEPTH(1024)) cmd_queue (.clk(clk), .reset(reset), ...);

You don't have to override every parameter; un-mentioned ones keep their defaults. The AFULL default in the example is computed from DEPTH, which means if you override DEPTH, AFULL follows automatically - exactly the kind of dependency localparam would also handle if you didn't want callers to be able to override AFULL independently.

Common Mistakes

Forgetting the # in the override. counter (.WIDTH(16)) dut(...) looks like an override but Verilog reads (.WIDTH(16)) as a port connection. You need counter #(.WIDTH(16)) dut(...).

Using parameters where they aren't constant enough. Parameters resolve at elaboration time, before any signals exist. You can't parameterize on a runtime signal - if the value depends on what an input is doing at simulation time, it's not a parameter, it's logic.

Confusing parameter and localparam in port lists. Only parameter goes in the #(...) block at the top. localparam lives inside the body. The compiler will tell you if you swap them.

What Comes Next

You can now make modules whose size is set at compile time. The next two docs cover the rules for writing literal constants (8'h1F, 4'b1010, 32'd100) and the x and z values that show up when signals are undriven or contended. After that we move to operators - everything you can do with the vectors and parameters you've now seen.

Frequently Asked Questions

What is a parameter in Verilog?

A parameter is a compile-time constant declared inside a module. It can be used anywhere a constant is allowed - bus widths, memory depths, state encodings, default values. Crucially, each instance of the module can override the parameter, so the same source file can produce, say, an 8-bit counter in one place and a 32-bit counter in another.

What is the difference between parameter and localparam in Verilog?

parameter values can be overridden from outside the module when it's instantiated. localparam values cannot - they're internal constants only. Use localparam for state encodings and derived constants that the module author doesn't want callers to mess with.

How do you override a parameter in Verilog?

When you instantiate the module, add a parameter override block before the instance name: counter #(.WIDTH(16)) my_inst (.clk(clk), .count(count));. The .WIDTH(16) syntax sets that specific parameter; any parameters you don't list keep their defaults. Multiple overrides are separated by commas inside the #(...).

What is a parameterized module?

A module that exposes one or more parameter declarations callers can override. A parameterized FIFO might have WIDTH and DEPTH parameters so the same source produces a 32-bit-by-16-deep FIFO in one place and an 8-bit-by-1024-deep FIFO in another. This is how libraries of reusable IP blocks are written.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED