[Edit of Image1]
Introduction
Hey it's a me again @drifter1!
Today we continue with the Logic Design series on Verilog to cover some additional advanced topics. More specifically, we will talk about Module Parameters and the Generate Block. Using parameters modules become more reusable as they can be instantiated using different parameters each time for different modules in a design, or maybe even completely different designs. Using the generate block, modules or certain statements can be instantiated conditionally, and repeated multiple times if necessary.
So, without further ado, let's dive more into it!
Module Parameters
When defined within a module definition, Parameters are similar to arguments passed to functions during a function call. It's thus common to use them in order to make the bit width of module ports tweakable, control parts of the design etc.
Parameter
Parameters are defined using the parameter keyword followed by the name of the parameter (mostly in capital letters). They are also assigned a default value.
parameter PARAMETER_NAME = default_value;
Parameterized Module
The parameters of a module are declared similar to the port declaration. The parameters can thus enclosed in parentheses and split by comma. It's important to note that the parameter declaration comes before the port declaration so that those parameters can be applied to the ports, and that a # character is appended to the front, as shown below.
module module_name #([parameter list]) ([port map]);
Similar to port declaration, it's of course also possible to define the parameters after the module name, as statements.
module module_name;
parameter PARAMETER_NAME = default_value;
//port declarations
endmodule
Instantiation and Overriding Parameters
When instantiating a parameterized module, the default values are used, and no special syntax is required for that. In order to override those default values, the new parameters are passed within #( )
, the same way as they where declared.
The module instantiation code is:
module_name #(parameter list) ([port map]);
It's also possible to override each value after the instantiation using the defparam keyword, by accessing the specific parameter using <instance_identifier>.PARAMETER_NAME
, as shown below.
module_name <instance_identifier> ([port map]);
defparam <instance_identifier>.PARAMETER_NAME = new_value;
N-bit Adder Example
A simple parameterized adder can be specified as follows:
module adder
#(
parameter N = 4
)
(
output [N - 1 : 0] c,
input [N - 1 : 0] a, b
);
assign c = a + b;
endmodule
Instantiating an 8-bit adder requires tweaking the N parameter using:
adder #(N = 8) d0 ([port map]);
or:
adder d0 ([port map]);
defparam d0.N = 8;
Generate Block
The generate block allows for the instantiation of multiple instances or the conditional instantiation of any module or block of statements. Parameters are used for that purpose.
Generate Syntax
A generate block is declared between the generate and endgenerate keywords. It can't contain port or parameter declarations. Within its body can be module instantiations, continuous assignments, always blocks, primitives...basically anything.
generate
// to generate
endgenerate
The generate blocks can be split into two main types:
- Generate Loops (for)
- Conditional Generation (if-else, case)
Generate For
The first type of generate block is the generate for loop. Such a block is particularly useful in cases where N instances of a module need to be generated. Variables of the type genvar are declared for the purpose of looping. It's common to use i
as the looping variable.
Instantiating a module (or specific statements) N times looks like this:
genvar i;
generate
for (i = 0; i < N; i = i + 1) begin
// module instantiation
end
endgenerate
Generate If
Combining a generate statement with a if statement allows for conditional generation. Depending on the value of some parameter, commonly of Boolean 0-1 type, specific modules or statements can be generated. It's thus even possible to specify that a specific section of a design is not generated at all!
Suppose we have a USE_ADDER parameter of 0-1 type. Specifying the conditional generation of the adder is simply:
generate
if (USE_ADDER)
// instantiate adder
endgenerate
When USE_ADDER is 0, then nothing would be generated!
Let's now suppose that a parameter ADDER_TYPE specifies the use of a particular adder (type 0 and type 1).
Tweaking the previous code, we can easily end up with the required code, which is:
generate
if (ADDER_TYPE)
// instantiate type 1 adder
else
// instantiate type 0 adder
endgenerate
Generate Case
The case statement can be used in a similar manner to the if statement, but works better when there are multiple choices.
Let's suppose that a ADDER_TYPE parameter allows for the specification of 3 types of adders. Generating the required adder for each case, is as simple as:
generate
case (ADDER_TYPE)
0: // instantiate ripple-carry adder
1: // instantiate carry-lookahead adder
2: // instantiate carry-save adder
endcase
endgenerate
That way, a design can be optimized for area, performance, throughput, latency etc. by simply tweaking a single parameter.
RESOURCES:
References
- http://www.asic-world.com/verilog/veritut.html
- https://www.chipverify.com/verilog/verilog-tutorial
- https://www.javatpoint.com/verilog
Images
Block diagrams and other visualizations were made using draw.io
Previous articles of the series
- Introduction → Basic Syntax, Data Types, Operators, Modules
- Combinational Logic → Assign Statement, Always Block, Control Blocks, Gate-Level Modeling and Primitives, User-Defined Primitives
- Combinational Logic Examples → One Circuit - Four Implementations, Encoder, Decoder, Multiplexer
- Sequential Logic → Procedural Blocks (Initial, Always), Blocking and Non-Blocking Assignments, Statement Groups
- Sequential Logic Examples → Flip Flops (DFF, TFF, JKFF, SRFF), N-bit Counter, Single-Port RAM
- Finite-State Machines → Finite-State Machine (FSM), FSM Types, State Encoding, Modeling FSMs in Verilog
- Finite-State Machine Examples → Moore FSM Example (1 and 2 always blocks), Mealy FSM Example (1, 2 and 3 always blocks)
- Testbenches and Simulation → Testbenches (DUT / UUT, Syntax, Test Cases), System Tasks, Simulation Tools
- Combinational Logic Testbench Example → Half Adder Implementation, Testbench and Simulation
- Sequential Logic Testbench Example → Sequence Detector FSM Implementation, Testbench and Simulation
- Functions and Tasks → Function and Task Syntax, Calling, Rules, Examples
Final words | Next up
And this is actually it for today's post!
Next time we will get into some more features of Verilog that we haven't covered yet...
See Ya!
Keep on drifting!