Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 19

Module Instantiation and Port Mapping

Recommended resources

Hello! In our last lesson, you learned how to define a single, self-contained hardware block using a Verilog module. You now know how to specify its inputs, outputs, and internal signals like wire and reg.

Today, we'll learn how to connect these individual blocks to build larger, more complex systems. This concept, known as hierarchical design, is fundamental to managing the complexity of modern chips. The Verilog mechanism for this is called module instantiation. You'll learn how to "place" a copy of one module inside another and connect its ports, a process analogous to placing an integrated circuit on a circuit board and wiring it up.

This lesson directly addresses the learning outcome: Instantiate one module within another using port mapping.

1. The Concept of Hierarchical Design

Before diving into the code, it's important to understand why we build designs hierarchically. Large, complex circuits are almost never designed as one monolithic block. Instead, they are broken down into smaller, manageable, and often reusable sub-components.

For example, a 32-bit adder can be built from 32 individual 1-bit full adders. You design the full adder once and then reuse it 32 times. This principle of "divide and conquer" makes design, verification, and debugging much more efficient.

This first video explains this concept using a 4-to-1 multiplexer built from smaller 2-to-1 multiplexers—an example that should be familiar from your electronics studies.

9 - Hierarchical Design

This video introduces the concept of hierarchical design and why it's a core principle in digital logic.

Watch the first part of the video explaining the hierarchical approach. Pay attention to how a larger component (4-to-1 MUX) is constructed from multiple smaller, identical components (2-to-1 MUXes).


2. Module Instantiation: The Syntax

Instantiation is the process of creating a unique instance (a copy) of a module within another, higher-level module (the "parent"). Each instance is like a physical component placed in your design.

The basic syntax for instantiating a module is:

module_name #(.PARAMETER(...)) instance_name (.port_name(signal_name), ...);

Let's break this down:

  • module_name: The name of the module you want to use (e.g., mux2to1). This is like choosing a specific chip type from your parts library.
  • #(.PARAMETER(...)): Used for passing parameters. We'll explore this in more detail in a later lesson. For now, we can omit it.
  • instance_name: A unique name you give to this specific copy (e.g., mux_stage1_a). Every instance within a module must have a different name.
  • (.port_name(signal_name), ...): This is the port mapping. It specifies how the ports of the instance (port_name) are connected to the signals (signal_name) within the parent module.

The image below shows a great example of building a full adder from two half-adders. Notice how the half_adder module is instantiated twice, each with a unique instance name (ha1 and ha2), and how their ports are connected to wires in the full_adder module.

Verilog Module Hierarchy and Instantiation Example
A full adder (`full_adder`) constructed by instantiating two half-adder (`half_adder`) modules. This visual demonstrates how internal `wire`s (`w1`, `w2`, `w3`) are used to connect the instances.

3. Port Mapping: Positional vs. Named

There are two ways to map ports, but as you'll see, one is far superior for professional design. This article from ChipVerify gives an excellent overview.

Verilog Module Instantiations

This article clearly explains the two methods for port connection and gives strong recommendations for best practices.

Read the introduction and the sections "Port Connection by ordered list" and "Port Connection by name". Focus on the syntax for each method and the reasons why named connection is preferred.

Method 1: Port Connection by Ordered List (Positional)

In this method, you list the signals in the parent module in the same order as the ports were declared in the child module.

// Child Module Definition
module mux2to1 (
  input  wire a,
  input  wire b,
  input  wire sel,
  output wire y
);
  // ... logic ...
endmodule

// Parent Module using Positional Instantiation
module top;
  wire i0, i1, s, out;
  // ...
  // DON'T DO THIS in real designs!
  mux2to1 mux_inst (i0, i1, s, out); // Maps i0->a, i1->b, s->sel, out->y
endmodule

Why it's bad: This method is extremely error-prone. If anyone modifies the port order in the mux2to1 module, your connections in the top module will be silently scrambled, leading to bugs that are very hard to find. It's also difficult to read and understand for modules with many ports.

Method 2: Port Connection by Name (Recommended)

This is the industry-standard method. You explicitly connect the child module's ports to the parent module's signals by name. The order does not matter.

// Parent Module using Named Instantiation
module top;
  wire i0, i1, s, out;
  // ...
  // ALWAYS DO THIS!
  mux2to1 mux_inst (
    .a   (i0),   // Connects port 'a' to wire 'i0'
    .b   (i1),   // Connects port 'b' to wire 'i1'
    .sel (s),    // Connects port 'sel' to wire 's'
    .y   (out)   // Connects port 'y' to wire 'out'
  );
endmodule

Why it's good:

  • Readable: It's obvious which signal connects to which port.
  • Robust: If the port order in the child module changes, the connections remain correct.
  • Easier to Debug: One connection per line makes it easy for tools to report the exact location of an error.

The image below provides a perfect side-by-side comparison of the syntax for both methods.

Verilog Module Instantiation: Positional vs. Named Assignment
A clear visual summary of positional (top) and named (bottom) port mapping syntax. Named assignment is the preferred, professional standard.

4. Practical Example: Building a 4-to-1 MUX

Now let's apply these concepts to build the 4-to-1 MUX from the first video. We will need one child module (mux2to1) and a parent module (mux4to1) that instantiates it three times.

First, here is the mux2to1 module we will be instantiating. We'll use a simple assign statement for its logic.

// Child Module: mux2to1.v
module mux2to1 (
  input  wire a,
  input  wire b,
  input  wire sel,
  output wire y
);
  assign y = sel ? b : a;
endmodule

Now, watch how the video author instantiates this module to build the larger mux4to1. Pay close attention to how internal wires are declared to connect the outputs of the first stage of multiplexers to the inputs of the final stage.

9 - Hierarchical Design

This part of the video demonstrates the full process of instantiating a sub-module, including declaring internal wires and using named port mapping.

First, watch the demonstration of instantiation using ordered ports and the explanation of its weakness. Next, watch carefully as the code is converted to use the more robust named port connection. This is the method you should always use.

Here is the final mux4to1 code using the recommended named-port style, reflecting what was shown in the video:

// Parent Module: mux4to1.v
module mux4to1 (
  input  wire [3:0] i,   // 4-bit data input
  input  wire [1:0] s,   // 2-bit select
  output wire       y    // 1-bit output
);

  // Internal wires to connect the first stage to the second
  wire w0, w1;

  // Instantiate the first MUX for the first stage
  mux2to1 m0 (
    .a   (i[0]),
    .b   (i[1]),
    .sel (s[0]),
    .y   (w0)
  );

  // Instantiate the second MUX for the first stage
  mux2to1 m1 (
    .a   (i[2]),
    .b   (i[3]),
    .sel (s[0]),
    .y   (w1)
  );

  // Instantiate the final MUX for the second stage
  mux2to1 m2 (
    .a   (w0),
    .b   (w1),
    .sel (s[1]),
    .y   (y)
  );

endmodule

5. Common Beginner Mistakes

Module instantiation is powerful, but it has strict rules because it describes physical hardware. Misunderstanding this can lead to common errors.

Common Beginner Mistakes

This section of the ChipVerify article is invaluable. Reading it will help you avoid some of the most common pitfalls when you start writing your own code.

Read the section titled "Common Beginner Mistakes". Pay special attention to: Mistake #2: Implicit Net Declarations: This can cause silent, hard-to-find bugs. Using `default_nettype none at the top of your files is a great habit to build. Mistake #3: Instantiating Inside an Always Block: This is a critical concept. Instantiation creates a static piece of hardware; it is not a function call that executes conditionally.


Conclusion

You have now learned one of the most important skills in RTL design: building a hierarchy by instantiating modules. This allows you to create complex, reusable, and maintainable hardware designs.

Here are the key takeaways from this lesson:

  • Hierarchical design is the practice of building complex circuits from smaller, reusable modules.
  • Module instantiation is the Verilog syntax for creating a copy of a module within a parent module.
  • Every instance needs a module type (what it is) and a unique instance name (what you call this copy).
  • There are two methods for port mapping: positional and named.
  • Named port mapping (.port(signal)) is the industry standard because it is readable, robust, and easier to debug. You should always use it.
  • Internal wires are often needed to connect instances together within a parent module.

In our next lesson, we will put this skill to immediate use. You'll learn how to write a testbench, which is simply a Verilog module that instantiates your design (often called the DUT, or Design Under Test) in order to simulate it and verify its correctness.