As we saw in a previous article, bigger and complex designs are built by integrating multiple modules in a hierarchical manner. Modules can be instantiated within other modules and ports of these instances can be connected with other signals inside the parent module.

These port connections can be done via an ordered list or by name.

What You'll Learn

  • How to instantiate one module inside another
  • How to connect ports by ordered list and by name
  • How unconnected and floating ports behave
  • Common instantiation mistakes, and how to avoid them

Syntax

The basic syntax for Verilog Module Instantiations:


// Ordered list: signals connect to ports by position
module_name instance_name (signal_a, signal_b, signal_c);

// By name: order does not matter
module_name instance_name (.port_a(signal_a), .port_b(signal_b), .port_c(signal_c));

// With parameter overrides
module_name #(.PARAM_A(value_a)) instance_name (.port_a(signal_a), .port_b(signal_b));

module_name is the module being instantiated, instance_name is a unique name for this copy of it, and the parentheses connect the ports of the instance to signals in the parent module. Both connection styles are explained below.

Port Connection by ordered list

One method of making the connection between the port expressions listed in a module instantiation with the signals inside the parent module is by the ordered list.

Where is This Used?

Verilog Module Instantiations is commonly used in digital design for building processors, memory controllers, and communication interfaces. Modern chip designs extensively use this construct in RTL code for ASIC and FPGA implementations.

mydesign is a module instantiated with the name d0 in another module called tb_top. Ports are connected in a certain order which is determined by the position of that port in the port list of the module declaration. For example, b in the testbench is connected to y of the design simply because both are at the second position in the list of ports.


	module mydesign ( input x, y, z, // x is at position 1, y at 2, z at 3 and
	 output o); // o is at position 4
	 
	endmodule

	module tb_top;
		wire [1:0] a;
		wire b, c;
		
		mydesign d0 (a[0], b, a[1], c); // a[0] is at position 1 so it is automatically connected to x
		 // b is at position 2 so it is automatically connected to y
		 // a[1] is at position 3 so it is connected to z
		 // c is at position 4, and hence connection is with o
	endmodule

Order of ports in the design module should be known for a correct connection.

This is very inconvenient because the order might change if a new port is added to the list or when the number of ports in the design is very large.

Port Connection by name

A better way to connect ports is by explicitly linking ports on both the sides using their port name.

The dot . indicates that the port name following the dot belongs to the design. The signal name to which the design port has to be connected is given next within parentheses ( ).


module design_top;
	wire [1:0] a;
	wire b, c;
	
	mydesign d0 ( .x (a[0]), // signal "x" in mydesign should be connected to "a[0]" in this module (design_top)
	 .y (b), // signal "y" in mydesign should be connected to "b" in this module (design_top)
	 .z (a[1]), 
	 .o (c));
endmodule

It is recommended to code each port connection in a separate line so that any compilation error message will correctly point to the line number where the error occured. This is much easier to debug and resolve compared to not knowing which port created the error had they been all in the same line.

Because these connections are made by name, the order in which they appear is irrelevant. Multiple module instance port connections are not allowed.


	module design_top;
		mydesign d0 ( .x (a[0]),
		 .z (a[1]), // z at second position is okay because of explicit connection
		 .y (a[1]),
		 .x (b), // illegal - x is already connected to a[0]
		 .o (c));
	endmodule

Unconnected/Floating Ports

Ports that are not connected to any wire in the instantiating module will have a value of high-impedance.

Industry Application

This technique is employed in high-performance computing systems, where efficient hardware implementation is critical. Synthesis tools optimize this construct into area-efficient gate-level logic.


module design_top;
	mydesign d0 ( // x is an input and not connected, hence a[0] will be Z
	 .y (a[1]),
	 .z (a[1]),
	 .o ()); // o has valid value in mydesign but since
	 // it is not connected to "c" in design_top, c will be Z
endmodule

Example

Let us take the shift register example we had seen before, and leave some ports unconnected.


module shift_reg ( input d,
 input clk,
 input rstn,
 output q);
 
 wire [2:0] q_net;
 
 dff u0 (.d(d), .clk(clk), .rstn(rstn), .q(q_net[0]));
 dff u1 (.d(q_net[0]), .clk(clk), .rstn(rstn), .q()); 						// Output q is left floating
 dff u2 (.d(q_net[1]), .clk(clk), .rstn(rstn), .q()); 						// Output q is left floating
 dff u3 (.d(q_net[2]), .clk(clk), .rstn(rstn), .q(q));
 
endmodule

Note that outputs from instances u1 and u2 are left unconnected in the RTL schematic obtained after synthesis. Since the input d to instances u2 and u3 are now connected to nets that nothing drives, so those inputs float at high impedance (z) in simulation.

unconnected-port-after-synthesis

In simulations, such unconnected ports will be denoted as high impedance ('hZ) typically shown in waveforms as an orange line vertically aligned in the middle.

verilog-unconnected-port

All port declarations are implicitly declared as wire and hence the port direction is sufficient in that case. However output ports that need to store values should be declared as reg data type and can be used in a procedural block like always and initial only.

Ports of type input or inout cannot be declared as reg because they are being driven from outside continuously and should not store values, rather reflect the changes in the external signals as soon as possible. It is perfectly legal to connect two ports with varying vector sizes, but the one with lower vector size will prevail and the remaining bits of the other port with a higher width will be ignored.



	// Case #1 : Inputs are by default implicitly declared as type "wire"
	module des0_1	(input wire clk ...); 		// wire need not be specified here
	module des0_2 	(input clk, ...); 			// By default clk is of type wire
		
	// Case #2 : Inputs cannot be of type reg
	module des1 (input reg clk, ...); 		// Illegal: inputs cannot be of type reg
	
	// Case #3: Take two modules here with varying port widths
	module des2 (output [3:0] data, ...);	// A module declaration with 4-bit vector as output
	module des3 (input [7:0] data, ...); 	// A module declaration with 8-bit vector as input
	
	module top ( ... );
		wire [7:0] net;
		des2 u0 ( .data(net) ... ); 		// Upper 4-bits of net are undriven
		des3 u1 ( .data(net) ... ); 		
	endmodule
	
	// Case #4 : Outputs cannot be connected to reg in parent module
	module top_0 ( ... );
		reg [3:0] data_reg;
		
		des2 u0 ( .data(data_reg) ...); 	// Illegal: data output port is connected to a reg type signal "data_reg"
	endmodule
		

Common Beginner Mistakes

Mistake #1: Positional Mapping Instead of Named Mapping

While Verilog allows you to connect signals based on their order (a, b, c), it is extremely error-prone. If the module definition changes or you miscount a comma, you’ll connect the wrong wires without any compiler errors.

Best Practice: Always use named mapping (.port_name(signal_name)) for clarity and safety.

Mistake #2: Implicit Net Declarations

If you instantiate a module and connect a port to a name you haven't declared, Verilog will often "helpfully" create a 1-bit wire for you by default. If you intended to connect a 32-bit bus, the upper 31 bits will be lost, leading to silent failures.

Best Practice: Use `default_nettype none at the top of your file to force the compiler to flag undeclared signals.

Mistake #3: Instantiating Inside an Always Block

Beginners often treat module instantiation like a function call in C++ and try to put it inside an always or initial block. Modules represent physical hardware components; they must be instantiated at the top level of your module, not inside procedural blocks.

Mistake #4: Incorrect Port Directions

Attempting to drive an output port of a child module with a constant or trying to use an input port to drive a signal in the parent module will cause synthesis errors. Remember: outputs are driven by the child, inputs are driven to the child.

Best Practice: Match your widths! Always double-check that the bit-width of the signal in your parent module matches the bit-width of the port in the child module to avoid unexpected truncation or zero-padding.

Practice Exercise

Challenge: Apply What You've Learned

Build a module that implements an AND gate. Use this module to build the logic z = a & b | c where a, b, c are inputs and z is the output of the top level module.

Summary

In this tutorial, you learned:

  • A module is instantiated with its module name, an instance name and a list of port connections
  • Ports can be connected by position or by name, and named connections are safer because the order does not matter
  • An unconnected input floats at z, and an unconnected output is simply not used
  • Parameters can be overridden at the instance with #( )
  • Connect signals of matching width, because a mismatch silently truncates or pads the value
  • An undeclared name in a port connection creates a 1-bit implicit net, so use `default_nettype none
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5