The Verilog hierarchy is the organization of a design using modules interconnected with each other from a root or top-level module.
So far, we have written all our code in a single file, inside a single module. For a blinking LED that’s fine, but imagine designing a processor or a graphics card by writing everything in one 10,000-line file. That would be insane.
In hardware design, the key to success is Divide and Conquer.
Just as in programming we use functions or classes to encapsulate code, in Verilog we use the Hierarchy. We create small modules that do one thing very well (a counter, a filter, a memory controller) and then connect them to each other in a higher-level module.
Today we will learn the art of Instantiation: how to virtually “solder” these blocks together to create complex systems.
Hierarchy and Top-Level
Imagine you are designing a Printed Circuit Board (PCB).
You have several chips (Timer 555, Microcontroller, Sensors).
You have copper traces connecting them.
In Verilog:
- The Chips are the Modules (
.vfiles you have already designed). - Placing a chip on the board is called Instantiating.
- The Copper Traces are the
wiresignals connecting the instances. - The complete PCB is the Top-Level module.
The Top-Level is the “boss”. It is the root module that has access to the real physical pins of the FPGA (LEDs, buttons, GPIO ports). Inside it, we instantiate sub-modules, which in turn can have other sub-modules inside.
Instantiation Syntax
Suppose we have our debounce module (from the previous article) and we want to use it in our main design.
// Original module definition (The "class" or "blueprint")
module debounce (
input wire clk,
input wire btn_in,
output reg btn_out
);
// ... internal logic ...
endmoduleTo use it, we need to instantiate it (create an object). The syntax is:
ModuleName InstanceName ( ...connections... );
There are two ways to connect the wires (ports), and choosing the right one is a matter of life or death for your project.
Connection by Position
It works like functions in C: order matters.
// ⛔ NOT RECOMMENDED
// We connect: clk -> sys_clock, btn_in -> button_pin, btn_out -> clean_wire
debounce u1 (sys_clock, button_pin, clean_wire);Why is it bad?
If you modify the debounce module tomorrow and change the order of the inputs, or add a new port in the middle, all your connections will fail silently. You could end up connecting the clock to the reset without realizing it.
Connection by Name
Here we explicitly say: “Connect port A of the module to wire B of my design”. We use the syntax .port(signal).
// ✅ RECOMMENDED
debounce u1 (
.clk(sys_clock), // Port 'clk' connected to 'sys_clock'
.btn_in(button_pin), // Port 'btn_in' connected to 'button_pin'
.btn_out(clean_wire) // Port 'btn_out' connected to 'clean_wire'
);- The order doesn’t matter.
- It is self-documenting.
- If you leave a port unconnected, it is easier to spot.
Practical advice: Always use connection by name. It will save you hours of debugging when your projects grow.
Practical Example: Putting Pieces Together
Let’s build a complete system. We want a counter that increments with a button. We need:
- A
debouncemodule (to clean the button). - A
countermodule (the logic). - A
top_systemmodule (which ties everything together and goes to the pins).
Internal Wiring (wire)
To connect the debouncer’s output to the counter’s input, we need an “internal wire”. This wire does not go outside the FPGA; it lives inside the silicon. We declare it as wire.
module top_system (
input wire clk_12mhz, // Physical clock pin
input wire rst,
input wire phys_btn, // Physical button pin
output wire [3:0] leds // Physical LED pins
);
// --- INTERNAL WIRES (Interconnects) ---
wire clean_button; // Wire connecting debounce -> counter
wire enable_pulse; // One cycle per press
reg previous_button;
// --- INSTANCE 1: The debounce filter ---
debounce filter_instance (
.clk(clk_12mhz),
.rst(rst),
.btn_in(phys_btn),
.btn_out(clean_button) // The output feeds into our internal wire
);
// Convert the clean level into a single-cycle pulse
always @(posedge clk_12mhz) begin
if (rst)
previous_button <= 1'b0;
else
previous_button <= clean_button;
end
assign enable_pulse = clean_button & ~previous_button;
// --- INSTANCE 2: The counter ---
// (Assuming we have a counter module ready)
counter_4bits counter_instance (
.clk(clk_12mhz),
.enable(enable_pulse), // Increments once per press
.count(leds) // The output goes directly to the physical pins
);
endmoduleNotice how clean this looks. The top_system doesn’t know how the button is filtered or how the counting happens. It only knows it has two components and that it must connect them together.
Unconnected Ports and Constant Connections
Sometimes we don’t need to use all ports of a module, or we want to fix an input to a constant value.
my_module u2 (
.clk(clk),
.data_input(8'hFF), // Fix input to 255 (Hardcoded)
.reset(1'b0), // Permanently deactivate reset
.debug_output() // Leave this port OPEN (unconnected)
);Leaving inputs unconnected is dangerous (they can float or take random values). Leaving outputs unconnected is safe (the signal is simply lost, and the synthesizer will optimize that logic away).
Common Instantiation Errors
Multiple Drivers
You cannot connect the outputs of two different modules to the same wire.
// ❌ SERIOUS ERROR
module_A u1 (.out(common_wire));
module_B u2 (.out(common_wire));
// Who wins? The synthesizer will typically reject the design.If you need to combine signals, explicitly describe the necessary logic, for example a multiplexer or an OR gate. Two internal outputs facing each other usually produce a synthesis error; the risk of physical damage arises when pitting real external pins against each other.
Confusing the Module with the Instance
The module name (debounce) is the “type”. The instance name (filter_instance) is the unique name for that copy.
You can have many instances of the same type:
debounce btn1 (.btn_in(pin1), ...);
debounce btn2 (.btn_in(pin2), ...);
debounce btn3 (.btn_in(pin3), ...);