Verilog Module for Design and Testbench

A Verilog module is a building block that defines a design or testbench component, by defining the building block’s ports and internal behaviour. Higher-level modules can embed lower-level modules to create hierarchical designs. Different Verilog modules communicate with each other through Verilog port. Together, the many Verilog modules communicate and model dataflow of a larger, hierarchical design.

Verilog has a simple organization. All data, functions, and tasks are in modules, except for system tasks and functions, which are global. Any uninstantiated module is at the top level. A model must contain at least one top-level module.

Defining a Verilog Module

A Verilog module is enclosed between the keywords module and endmodule. It has the following components:

  • Keyword module to begin the definition
  • Identifier that is the name of the module
  • Optional list of parameters
  • Optional list of ports (to be addressed more deeply in a future article)
  • Module item
  • Keyword endmodule to end the definition

Let’s address each of these components one by one.

Verilog Parameter

Verilog parameters were introduced in Verilog-2001 (not present in the original Verilog-1995). They allow a single piece of Verilog module code to be more extensible and reusable. Each instantiation of a Verilog module can supply different values to the parameters, creating different variations of the same base Verilog module. For example, a FIFO Verilog module may have a Verilog parameter to adjust its data width (or even data type, in SystemVerilog). They are not strictly necessary for a design, so I will defer discussing the topic further to a future article.

Verilog Port

A Verilog module only optionally needs to have a list of Verilog port (a port list). For example, a top level testbench may not have any Verilog ports at all. Verilog ports allow different modules of a design to communicate with each other. There are other (more backdoor) ways that Verilog modules can communicate. But for a design that intends to be synthesized, Verilog ports is the standard method. There are many ways to code port connections. This will be discussed in more detail in another future article.

Module Item

Module item is essentially the “code that is inside the module” (after the port declaration). It defines what constitutes the module, and can include many different types of declarations and definitions (net and variable declarations, always blocks and initial blocks, etc.)

Putting it Together

Here is a very simple example of Verilog module definition, that puts together all the pieces above.

module my_module
#(
    parameter WIDTH = 1
) (
    input wire              clk,
    input wire              rst_n,
    input wire [WIDTH-1:0]  in_a, in_b,
    output reg [WIDTH-1:0]  out_c
);

always @(posedge clk or negedge rst_n)
    out_c <= in_a & in_b;

endmodule

Instantiating a Verilog Module

A Verilog module can instantiate other Verilog modules, creating a hierarchy of modules to form the full design and testbench. Any uninstantiated module is at the top level.

Instantiation Statement

The Verilog module instantiation statement creates one or more named instances of a defined module. Multiple instances (identical copies of the Verilog module) can be created on the same line of code. This type of coding style is obviously easier with simple modules that have few (or none) ports. Multiple instantiations can even contain a range specification. This allows an array of instances to be created.

Connecting the Ports

For a Verilog module that does have ports, Verilog defines two styles to connect the ports:

  • By position – specify the connection to each port in the same order as the ports were listed in the module declaration
  • By name – specify each port by the name used in the (sub) module declaration, followed by the name used in the instantiating module

When connecting by name, an unconnected port can be indicated by either omitting it from the port list, or by providing no expression in the parentheses ( .name () ). The two types of port connections shall not be mixed (in Verilog) in a single declaration.

For a Verilog module that does not have any port, you still need to write the parentheses when instantiating it.

As to what to connect to the port, from Verilog, it can be a register or net identifier, an expression, or a blank (to indicate no connection that that port). An unconnected port may also be simply omitted from the port list, but only when connecting by name.

Here are some examples to illustrate the concepts above.

wire clk, rst_n;
wire a, b, c1, c2, c3, c4, d;

// Instantiating a module and connecting ports by position
my_module mod_b (clk, rst_n, a, b, c1);

// Instantiating a module and connecting ports by name
my_module mod_a
(
    .clk   (clk),
    .rst_n (rst_n),
    .in_a  (a),
    .in_b  (b),
    .out_c (c2)
);

// Instantiating a module, but leaving a port unconnected (a bug in this case!)
my_module mod_c
(
    .clk   (), // A bug! But this will compile
    .rst_n (rst_n),
    .in_a  (a),
    .in_b  (b),
    .out_c (c3)
);

// Instantiating a module with no ports
my_module_with_no_ports mod_d();

// Connecting an expression to a port
my_module mod_e
(
    .clk   (clk),
    .rst_n (rst_n),
    .in_a  (a & d), // an expression as a port connection
    .in_b  (b),
    .out_c (c4)
);

Verilog Module Hierarchy

When instantiating and connecting Verilog modules and ports, a hierarchical design is created. Every identifier (for example every module) has a unique hierarchical path name. This is useful generally in testbench coding, where you sometimes need to reference a particular signal, in a somewhat backdoor way, in a different module within your testbench. This is generally not used in design coding, where you always want to more formally use Verilog ports to make explicit connections and model dataflow (with exception possibly with Verilog defparam—topic for a future article).

The complete hierarchy of names can be viewed as a tree structure, with the root being the top level module. Each module, generate block instance, task, function, named begin-end or fork-join block defines a new hierarchical level (also called a scope), in a particular branch of the tree. A design description contains one or more top-level modules. Each such module forms the root of a name hierarchy. The following figure shows an example Verilog module hierarchy.

Verilog module hierarchical name example
Verilog module hierarchical name example

Verilog generate block, that do not have a name label, creates a hierarchy that is only visible within the block, and within the sub-tree formed by this block—and nowhere else. Therefore it’s good practice to always name Verilog generate blocks so all identifiers can be referenced throughout your environment. See my article Verilog Generate Configurable RTL Designs.

Now that we have defined a hierarchy, we can reference any named Verilog object or hierarchical name reference, by concatenating the names of the modules, module instance names, generate blocks, tasks, functions, or named blocks that contain it. Each of the names in the hierarchy is separated by a period.

You can reference the complete path name, starting from the top-level (root) module, or you can reference “downwards”, starting from the level where the path is being used. You can also reference a particular instance of an array (or a generate loop) by a constant expression within square brackets. The expression shall evaluate to one of the legal index values of the array.

The following code matches the example design hierarchy above. We’ll use it to illustrate some examples of referencing.

// A trivial AND module
module my_and
(
  input wire in1,
  input wire in2,
  output wire out
);
  assign out = in1 & in2;

endmodule

// A design that instantiates the AND module
module dut
(
  input wire clk,
  input wire rst_n,
  input wire [3:0] a, b,
  output reg [3:0] c
);
  my_and i_my_and_3 (a[3], b[3], c[3]);

  generate
    genvar gi;
    for (gi=0; gi<3; gi=gi+1) begin : gen_and
      my_and i_my_and (a[gi], b[gi], c[gi]);
    end // gen_and
  endgenerate
endmodule

// Testbench that instantiates the design under test (DUT)
module testbench;
  wire clk, rst_n;
  wire [3:0] a, b, c;

  dut i_dut
  (
    .clk   (clk),
    .rst_n (rst_n),
    .a     (a),
    .b     (b),
    .c     (c)
  );

  // Some examples of hierarchical references
  initial begin
    $monitor(a); // reference to testbench hierarchy
    $monitor(i_dut.a); // reference to dut hierarchy
    $monitor(i_dut.i_my_and_3.out); // reference to dut sub-module hierarchy
    $monitor(i_dut.gen_and[0].i_my_and.out); // reference to generated sub-module
  end
endmodule

Some final notes on Verilog module hierarchy:

  • Each node in the hierarchical name tree creates a new scope for identifiers. Within each scope, the same identifier (e.g. net name, variable name) can be declared only once. Conversely, it is permitted to use the same identifier in different scopes, or different Verilog modules.
  • Objects declared in automatic tasks and functions are one exception that cannot be referenced by hierarchical names.
  • It is also possible, from Verilog LRM perspective, to reference “upwards” in the hierarchy. For example, it is possible for a lower level module to reference a variable in the module that instantiates the lower level module. However, the usage is tricky, and application is dubious (certainly not recommended for design code), so I will not go into it here.

Verilog Module Summary

Verilog module is one of the fundamental hierarchical constructs in Verilog. It encapsulates code and functionality, allowing a larger design to be built from lower level components, enhancing modularity and reuse. This article described the basic syntax of a Verilog module, how to define a module, how to connect multiple modules together, and how the interconnection creates a design hierarchy.

The next article in this fundamentals series will dive deeper into Verilog ports, exploring how the syntax evolved from the original Verilog to the latest SystemVerilog language manuals. Stay tuned!

References

2 thoughts on “Verilog Module for Design and Testbench”

  1. Hi,
    This is going to throw an error as the module name is not mentioned after the module keyword.
    module #(
    parameter WIDTH = 1
    ) my_module (
    It should be
    module my_module #(
    parameter WIDTH = 1
    ) (

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.