Parameters
You write an 8-bit adder and it works. Then you need a 16-bit one, then 32, then a colleague wants 12 bits for an audio block. Each time you copy the file, rename the module and edit every [7:0]. Three months later you find a bug in the carry logic, fix it in one copy, and forget the other three. Parameters exist so that this never happens.
The problem
`timescale 1ns / 1ps
`default_nettype none
module adder_8bit ( // the width is in the name, which is already a bad sign
input wire [7:0] a,
input wire [7:0] b,
input wire cin,
output wire [7:0] sum,
output wire cout
);
assign {cout, sum} = a + b + cin;
endmodule
Nothing in the body depends on the width being 8. The copies differ only in a number.
Declaring a parameter
A parameter is a constant with a default, declared in #(...) between the module name and the port list, and usable anywhere a constant is, including port ranges:
`timescale 1ns / 1ps
`default_nettype none
module adder #(
parameter WIDTH = 8
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire cin,
output wire [WIDTH-1:0] sum,
output wire cout
);
assign {cout, sum} = a + b + cin;
endmodule
Overriding it
At instantiation, name the parameter and give it a value:
`timescale 1ns / 1ps
`default_nettype none
module top_level (
input wire [15:0] data_a,
input wire [15:0] data_b,
input wire carry_in,
output wire [15:0] result,
output wire carry_out
);
adder #(
.WIDTH(16)
) u_adder16 (
.a (data_a),
.b (data_b),
.cin (carry_in),
.sum (result),
.cout(carry_out)
);
endmodule
Use the named form, #(.WIDTH(16)), never positional. The same source can serve several widths in one design:
adder u_byte_adder ( // default WIDTH = 8
.a (byte_a),
.b (byte_b),
.cin (1'b0),
.sum (byte_sum),
.cout(byte_cout)
);
adder #(.WIDTH(32)) u_word_adder (
.a (word_a),
.b (word_b),
.cin (1'b0),
.sum (word_sum),
.cout(word_cout)
);
Parameterise every module from the start, even the ones you think will only ever have one width. It costs two lines now and a lot of editing later.
localparam
A localparam is a constant that cannot be overridden from outside. It is for things that are implementation details: state encodings, derived values, magic numbers.
`timescale 1ns / 1ps
`default_nettype none
module simple_fsm #(
parameter NUM_STATES = 4
)(
input wire clk,
input wire rst_n,
input wire start,
output reg done
);
localparam STATE_BITS = $clog2(NUM_STATES); // derived, not overridable
localparam IDLE = 2'd0; // encodings are internal
localparam RUN = 2'd1;
localparam WAIT = 2'd2;
localparam DONE = 2'd3;
reg [STATE_BITS-1:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
always @(*) begin
next_state = state;
done = 1'b0;
case (state)
IDLE: if (start) next_state = RUN;
RUN: next_state = WAIT;
WAIT: next_state = DONE;
DONE: begin
done = 1'b1;
next_state = IDLE;
end
default: next_state = IDLE;
endcase
end
endmodule
If the encodings were parameters, an instantiation could set IDLE to the same value as RUN and break the machine without touching the file.
$clog2
$clog2(N) is the ceiling of $\log_2 N$: the number of bits needed to count N things. It is what turns a depth into an address width.
| N | $\lceil \log_2 N \rceil$ |
|---|---|
| 2 | 1 |
| 4 | 2 |
| 8 | 3 |
| 256 | 8 |
| 1000 | 10 |
| 1024 | 10 |
Here it sizes the pointers of a FIFO:
`timescale 1ns / 1ps
`default_nettype none
module sync_fifo #(
parameter DATA_WIDTH = 8,
parameter DEPTH = 16
)(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire rd_en,
input wire [DATA_WIDTH-1:0] wr_data,
output reg [DATA_WIDTH-1:0] rd_data,
output wire full,
output wire empty
);
localparam ADDR_BITS = $clog2(DEPTH); // 4 for a depth of 16
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
// one extra bit so that full and empty can be told apart
reg [ADDR_BITS:0] wr_ptr;
reg [ADDR_BITS:0] rd_ptr;
assign full = (wr_ptr[ADDR_BITS] != rd_ptr[ADDR_BITS]) &&
(wr_ptr[ADDR_BITS-1:0] == rd_ptr[ADDR_BITS-1:0]);
assign empty = (wr_ptr == rd_ptr);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= {(ADDR_BITS+1){1'b0}};
end else if (wr_en && !full) begin
mem[wr_ptr[ADDR_BITS-1:0]] <= wr_data;
wr_ptr <= wr_ptr + 1'b1;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_ptr <= {(ADDR_BITS+1){1'b0}};
rd_data <= {DATA_WIDTH{1'b0}};
end else if (rd_en && !empty) begin
rd_data <= mem[rd_ptr[ADDR_BITS-1:0]];
rd_ptr <= rd_ptr + 1'b1;
end
end
endmodule
Change the depth from 16 to 1024 and the pointers grow from 4 bits to 10, the memory scales, and the full and empty logic is still right. The full and empty test with the extra pointer bit assumes the depth is a power of two; for other depths the wrap has to be explicit.
One trap: $clog2(1) is 0. If a depth of 1 is legal, guard it: localparam ADDR_BITS = (DEPTH > 1) ? $clog2(DEPTH) : 1;.
Parameter types
module param_types #(
parameter integer WIDTH = 8, // the usual case
parameter ENABLE = 1'b1, // a flag
parameter [3:0] MASK = 4'b1010, // a sized vector
parameter string NAME = "default" // simulation only
)(/* ports */);
endmodule
For synthesis, stick to integers and sized vectors. Real and string parameters are for simulation.
defparam
Old code overrides parameters from outside the instantiation:
defparam u_adder.WIDTH = 16; // legacy; do not write this
It separates the override from the instance, tools dislike it, and SystemVerilog dropped it. Always override at the instantiation.
A parameterised shifter
`timescale 1ns / 1ps
`default_nettype none
module barrel_shifter #(
parameter DATA_WIDTH = 8
)(
input wire [DATA_WIDTH-1:0] data_in,
input wire [$clog2(DATA_WIDTH)-1:0] shift_amt, // 3 bits for 8, 5 bits for 32
input wire left_right, // 1 left, 0 right
output wire [DATA_WIDTH-1:0] data_out
);
assign data_out = left_right ? (data_in << shift_amt)
: (data_in >> shift_amt);
endmodule
The shift-amount port sizes itself from the data width. That pattern, one parameter deriving the width of another port, is everywhere in parameterised arithmetic.
Practice Exercise
Write a synchronous up-counter with a MAX_COUNT parameter that counts 0 to MAX_COUNT-1 and wraps, with the output width derived by $clog2.
Solution
`timescale 1ns / 1ps
`default_nettype none
module up_counter #(
parameter MAX_COUNT = 100
)(
input wire clk,
input wire rst_n,
input wire en,
output reg [$clog2(MAX_COUNT)-1:0] count // 7 bits for 100
);
always @(posedge clk) begin
if (!rst_n) begin
count <= {$clog2(MAX_COUNT){1'b0}};
end else if (en) begin
if (count == MAX_COUNT - 1)
count <= {$clog2(MAX_COUNT){1'b0}};
else
count <= count + 1'b1;
end
end
endmodule
A seconds counter:
up_counter #(.MAX_COUNT(60)) u_seconds (
.clk (clk),
.rst_n(rst_n),
.en (tick_1hz),
.count(seconds) // 6 bits, since $clog2(60) = 6
);
Parameters give a module its shape. The next article is about the constants inside it: how to write a number so that Verilog knows exactly how wide you mean it to be.