Skip to content

SystemVerilog · Module 7

Module Definition & Instantiation

Module anatomy, port lists, hierarchical design, multi-instance wiring, named/positional/wildcard connections, and instance arrays.

Module 7 · Page 7.1

Modules are the fundamental building blocks of every digital design. This page teaches the full anatomy of a SystemVerilog module, how to instantiate one, how to assemble many into a hierarchical chip, every port-connection style that exists, instance arrays and generate loops, testbench patterns, five debugging labs from real silicon projects, and twelve interview-ready questions.

What is a Module?

Every piece of hardware you design in SystemVerilog lives inside a module. Think of a module as a black box: it has inputs, outputs, and internal logic. From the outside, you only see the pins — the wires that connect to it. The internals are hidden and self-contained.

A simple AND gate is a module. A 32-bit ALU is a module. A full SoC with hundreds of sub-blocks is also a module — one that contains dozens of other modules inside it. This is the concept of hierarchical design, and it is the most powerful idea in RTL engineering.

abclkrst_nadder_16bitInternal RTL Logic(hidden from outside)INPUTINPUTINPUTINPUTsumcoutOUTPUTOUTPUTInputsOutputs
Figure 1 — A module exposes only its ports to the outside world. The internal logic stays hidden inside.

Anatomy of a Module

Every module in SystemVerilog has the same skeleton. Once you know the anatomy, you can read any RTL file in any company's codebase.

SystemVerilog — half-adder anatomy (all parts labelled)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ① module keyword + module name
module half_adder (
    // ② ANSI port list (direction + type + name)
    input  logic a, b,
    output logic sum, cout
);
 
    // ③ Internal signals (not ports)
    logic carry_internal;
 
    // ④ RTL logic (assign / always / etc.)
    assign sum  = a ^ b;
    assign cout = a & b;
 
endmodule   // ⑤ endmodule closes the module

Let's break down each part:

PartRole
module … endmoduleKeywords that open and close the module boundary. Everything between belongs here.
② Port listDeclares every pin — direction (input/output/inout) and data type.
③ Internal signalsWires and registers used inside the module. Invisible from outside.
④ RTL logicThe actual behaviour: assign statements, always blocks, sub-module instances.
endmoduleCloses the module. Optionally labelled (e.g. endmodule : half_adder).

Your First Module — Every Part Explained

Let's write a complete, realistic module from scratch: a 4-bit adder. Read through the full code first, then check each numbered explanation below.

SystemVerilog — 4-bit Adder Module (ANSI style)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── ① Module declaration: name comes right after 'module' ──────
module adder_4bit (
 
    // ── ② Port list (ANSI style — direction + type + name) ─────
    input  logic [3:0] a,          // 4-bit operand A
    input  logic [3:0] b,          // 4-bit operand B
    input  logic       cin,        // carry-in
 
    output logic [3:0] sum,        // 4-bit result
    output logic       cout        // carry-out
 
);
 
    // ── ③ Internal signal (not visible to the outside world) ────
    logic [4:0] full_result;
 
    // ── ④ RTL logic: combinational addition ─────────────────────
    assign full_result = {1'b0, a} + {1'b0, b} + cin;
 
    // ── ⑤ Drive outputs from internal result ────────────────────
    assign sum  = full_result[3:0];
    assign cout = full_result[4];
 
endmodule   // ← ⑥ Always end with endmodule

① Module Name

The module name follows immediately after the module keyword. By convention, use lowercase with underscores for RTL modules (e.g. adder_4bit, not Adder4Bit). The name must be unique across your project — two modules with the same name cause elaboration errors.

② ANSI Port List

This is the ANSI C-style port declaration — the modern way to write ports in SystemVerilog. Each port declares its direction (input / output / inout), its type (logic, wire, etc.), its width ([3:0]), and its name — all in one line, inside the parentheses right after the module name. Ports are separated by commas; the last port has no comma.

③ Internal Signals

logic [4:0] full_result; is a local wire used only inside adder_4bit. It is invisible when you look at the module from the outside. You declare it with logic (or wire) after the port list closes with );.

④⑤ RTL Logic

assign statements describe combinational logic. The concatenation {1'b0, a} zero-extends a to 5 bits so the addition result can hold the carry without overflow. Then the lower 4 bits go to sum and bit 4 becomes cout.

⑥ endmodule

Every module must be closed with endmodule. You can optionally add the module name as a label for clarity: endmodule : adder_4bit. This is especially useful in long files.

Module Instantiation

Defining a module gives you a blueprint. Instantiation is the act of placing a physical copy of that module inside another module and connecting its ports to signals. Every copy you place is called an instance.

The syntax is:

SystemVerilog — Instantiation Syntax Template
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//  module_name   instance_name  ( .port_name(signal), ... );
//  ───────────   ─────────────    ──────────────────────────
//  what to use   unique label     how to wire the ports
 
adder_4bit  u_adder (
    .a   (operand_a),
    .b   (operand_b),
    .cin (carry_in),
    .sum (result),
    .cout(carry_out)
);

Three parts every instantiation needs:

PartDescription
Module NameThe name of the module you want to use. Exactly as it appears in its definition — case-sensitive.
Instance NameA unique label for this particular copy. Convention: prefix with u_ (unit) or i_. Unique within parent.
Port ConnectionsNamed connections: .port_name(signal_name). Port name from definition; signal name from parent module.

A Complete Parent Module with an Instance Inside

SystemVerilog — Parent Module Instantiating adder_4bit
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module alu_top (
    input  logic [3:0] x, y,
    input  logic       c_in,
    output logic [3:0] alu_result,
    output logic       alu_cout
);
 
    // Internal wires to connect the sub-module
    logic [3:0] add_sum;
    logic       add_cout;
 
    // ── Instantiate adder_4bit ─────────────────────────────────────
    // module_name   instance_name  (named port connections)
    adder_4bit  u_adder (
        .a   (x),          // connect port 'a' to signal 'x'
        .b   (y),          // connect port 'b' to signal 'y'
        .cin (c_in),       // connect port 'cin' to 'c_in'
        .sum (add_sum),    // connect port 'sum' to 'add_sum'
        .cout(add_cout)    // connect port 'cout' to 'add_cout'
    );
 
    // Drive top-level outputs from internal wires
    assign alu_result = add_sum;
    assign alu_cout   = add_cout;
 
endmodule

Building a Design Hierarchy

Real chips are not flat. They are hierarchical — big modules contain smaller modules, which in turn contain even smaller modules. This layered structure is what makes it possible to design billion-transistor chips in manageable pieces.

chip_topTop-level modulecpu_coreu_cpumem_ctrlu_memuart_ifu_uartalu_topu_alureg_fileu_rfdecoderu_decadder_4bitu_adderTop-levelSub-systemBlock-levelLeaf module
Figure 3 — A chip hierarchy. Each box is a module instantiated inside the one above it. Leaf modules (pink) contain only RTL logic, no further instances.

In this hierarchy, chip_top is the top-level module. It instantiates cpu_core, mem_ctrl, and uart_if. Inside cpu_core, there is an instance of alu_top — which itself contains our adder_4bit. Each level only needs to know about the modules directly below it.

Multiple Instances of the Same Module

One of the biggest advantages of modules is reuse. You write a module once — say, a 4-bit adder — and then use it as many times as you need, each time with a different instance name and port connections. The simulator and synthesis tool create a separate physical copy for each instance.

SystemVerilog — Three instances of adder_4bit
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module multi_add (
    input  logic [3:0] p, q, r, s,
    output logic [3:0] result
);
 
    logic [3:0] sum_pq, sum_rs;
    logic       cout_pq, cout_rs, cout_final;
 
    // ── Instance 1: add p + q ──────────────────────────────────
    adder_4bit  u_add_pq (
        .a   (p),      .b   (q),
        .cin (1'b0),   .sum (sum_pq),  .cout(cout_pq)
    );
 
    // ── Instance 2: add r + s ──────────────────────────────────
    adder_4bit  u_add_rs (
        .a   (r),      .b   (s),
        .cin (1'b0),   .sum (sum_rs),  .cout(cout_rs)
    );
 
    // ── Instance 3: add (p+q) + (r+s) ─────────────────────────
    adder_4bit  u_add_final (
        .a   (sum_pq),  .b   (sum_rs),
        .cin (cout_pq), .sum (result),  .cout(cout_final)
    );
 
endmodule
pqrsadder_4bitu_add_pqp+qadder_4bitu_add_rsr+ssum_pqsum_rsadder_4bitu_add_final(p+q)+(r+s)result
Figure 4 — Three instances of adder_4bit wired in a tree. One module definition, three physical copies in hardware.

The Testbench Module

A testbench is also a module — but one with no ports. It sits at the top of the simulation hierarchy, generates stimulus, instantiates the design under test (DUT), and checks the outputs.

SystemVerilog — Testbench for adder_4bit
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Testbench: no port list — it drives itself
module tb_adder_4bit;
 
    // ── Declare signals to drive / observe ──────────────────────
    logic [3:0] a, b;
    logic       cin;
    logic [3:0] sum;
    logic       cout;
 
    // ── Instantiate the DUT (Device Under Test) ──────────────────
    adder_4bit u_dut (
        .a   (a),
        .b   (b),
        .cin (cin),
        .sum (sum),
        .cout(cout)
    );
 
    // ── Apply stimulus ────────────────────────────────────────────
    initial begin
        $dumpfile("adder.vcd");
        $dumpvars(0, tb_adder_4bit);
 
        // Test 1: 3 + 5 + 0 = 8, no carry
        a = 4'd3;  b = 4'd5;  cin = 0;  #10;
        $display("a=%0d b=%0d cin=%0b => sum=%0d cout=%0b", a, b, cin, sum, cout);
 
        // Test 2: 9 + 8 + 0 = 17, carry-out
        a = 4'd9;  b = 4'd8;  cin = 0;  #10;
        $display("a=%0d b=%0d cin=%0b => sum=%0d cout=%0b", a, b, cin, sum, cout);
 
        // Test 3: 15 + 15 + 1 = 31, carry-out
        a = 4'd15; b = 4'd15; cin = 1;  #10;
        $display("a=%0d b=%0d cin=%0b => sum=%0d cout=%0b", a, b, cin, sum, cout);
 
        $finish;
    end
 
endmodule

Old-Style vs ANSI Port Declaration

You will see both styles in the real world. Know how to read the old style — but always write the new ANSI style in your own code.

AspectOld Verilog StyleANSI Style (SystemVerilog)
Port namesListed in parentheses only (no types)Direction + type + name all together in port list
Type declarationsDeclared again separately inside the module bodyDeclared once — in the port list
Lines of codeMore — each port appears twiceLess — each port declared exactly once
Error riskHigher — mismatch between list and bodyLower — single source of truth
Use in new code?No — legacy onlyYes — always prefer ANSI
SystemVerilog — Old Style vs ANSI Style (side by side)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ OLD VERILOG STYLE — you may see this in legacy code ════════
module half_adder_old (a, b, sum, cout);   // names only in list
    input  a, b;                            // direction declared below
    output sum, cout;                       // type defaults to wire
 
    assign sum  = a ^ b;
    assign cout = a & b;
endmodule
 
 
// ════ ANSI STYLE — write this in all new code ════════════════════
module half_adder_ansi (
    input  logic a,                         // direction + type + name
    input  logic b,                         // all in one place
    output logic sum,
    output logic cout
);
    assign sum  = a ^ b;
    assign cout = a & b;
endmodule

Port Connection Styles — Named, Positional & Wildcard

When you instantiate a module, SystemVerilog gives you three ways to wire up its ports. Each has different implications for safety, readability, and maintainability. Understanding all three is critical — not just for writing new code, but for reading the legacy code you will encounter on every real project.

Connection StyleSyntaxOrder Sensitive?SafetyWhen to Use
Named.port_name(signal)No✅ SafestAll production RTL and testbench code
Positional(sig1, sig2, sig3)Yes — must match port order exactly⚠️ FragileLegacy Verilog only — never write new
Wildcard(.*)No — name match⚠️ CarefulTestbench where signal names mirror port names

Named Connections — The Professional Standard

Named connections pair each port with its driving signal using .port_name(signal_name) syntax. Port order is completely irrelevant — you can list them alphabetically, by importance, or in any sequence that improves readability. When the designer adds a new optional port months later, your instantiation site is completely unaffected.

SystemVerilog — Named Port Connections (preferred)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ NAMED CONNECTIONS — order does not matter ══════════════════
adder_4bit  u_add (
    .a    (operand_a),    // port name . signal name
    .cin  (carry_in),     // listed out of "natural" order — still correct
    .b    (operand_b),
    .cout (carry_out),
    .sum  (result_sum)
);
 
// If the designer later adds an optional 'mode' port:
// module adder_4bit(input logic mode, input logic[3:0] a, b, ...)
// Your connection above still compiles. 'mode' just floats (0 or X).
// No silent wrong behaviour. You can address it explicitly when ready.

Positional Connections — Why They Break

In positional connections, there are no port names — just signals listed in the exact order the ports appear in the module definition. This looks compact, but it is extremely brittle. The moment any port is added, removed, or reordered in the module definition, every positional instantiation site silently rewires — with no compiler error.

❌ Positional — silent disaster after port reorder
Positional — fragile
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Original module port order:
// module adder_4bit(a, b, cin, sum, cout)
 
// Positional instantiation (old style):
adder_4bit u_add (opa, opb, ci, res, co);
 
// ────────────────────────────────────────────
// Designer adds carry_kill between cin & sum:
// module adder_4bit(a,b,cin,carry_kill,sum,cout)
 
// NOW: res → carry_kill port ❌
// co  → sum port             ❌
// No compiler error. Wrong results in sim.
✅ Named — safe through all refactors
Named — refactor-safe
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Named instantiation:
adder_4bit u_add (
    .a   (opa),
    .b   (opb),
    .cin (ci),
    .sum (res),
    .cout(co)
);
 
// After port reorder:
// carry_kill is simply unconnected (0/X).
// All existing connections remain correct.
// Easy to add: .carry_kill(1'b0) when ready.

Wildcard .* Connections — Productivity Shortcut with Caveats

The .* syntax tells the simulator: "for every port, look for a signal in the current scope with the same name, and connect them." It is a SystemVerilog-only feature that can dramatically reduce boilerplate in testbenches where you deliberately name your local signals to match the DUT's port names.

SystemVerilog — Wildcard .* Connections (testbench shortcut)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_adder_4bit;
 
    // Signal names deliberately match port names exactly
    logic [3:0] a, b, sum;
    logic       cin, cout;
 
    // .* auto-connects all ports by name match
    adder_4bit  u_dut (.*);       // port a→a, b→b, cin→cin, sum→sum, cout→cout
 
    // ─────────────────────────────────────────────────────────────────
    // Mix: auto-connect most, explicitly override one port
    adder_4bit  u_dut2 (
        .cin (1'b0),   // explicit override — always zero carry-in
        .*             // all other ports matched by name automatically
    );
 
    // ⚠ DANGER: if you rename signal 'a' to 'operand_a' in the TB,
    //   port 'a' silently becomes unconnected → X propagates into DUT.
    //   No compile error. This is why .* is testbench-only.
 
endmodule

Instance Arrays — Replicate Hardware with One Declaration

When your design needs multiple identical blocks with different data slices — think N-lane processors, N-channel DMAs, N-bit carry chains — SystemVerilog lets you declare an array of instances in a single statement. This is far more compact than copy-pasting N instantiations and eliminates the off-by-one mistakes that plague manual replication.

SystemVerilog — Instance Array (syntax and port slicing rules)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ Four separate instances (tedious, error-prone) ══════════════
adder_4bit u_add0 (.a(a[3:0]),  .b(b[3:0]),  .cin(1'b0), .sum(s[3:0]),  .cout(co[0]));
adder_4bit u_add1 (.a(a[7:4]),  .b(b[7:4]),  .cin(1'b0), .sum(s[7:4]),  .cout(co[1]));
adder_4bit u_add2 (.a(a[11:8]), .b(b[11:8]), .cin(1'b0), .sum(s[11:8]), .cout(co[2]));
adder_4bit u_add3 (.a(a[15:12]),.b(b[15:12]),.cin(1'b0), .sum(s[15:12]),.cout(co[3]));
 
// ════ Equivalent instance array (elegant) ════════════════════════
// Creates u_adds[0], u_adds[1], u_adds[2], u_adds[3]
adder_4bit  u_adds [3:0] (
    .a   (a[15:0]),    // 16-bit wide → 4 bits per instance
    .b   (b[15:0]),    // slicing: [0]→[3:0], [1]→[7:4], etc.
    .cin (4'b0),       // scalar: same 1-bit value replicated to all 4
    .sum (s[15:0]),
    .cout(co[3:0])
);

Generate Loop — Full Control for Complex Patterns

When the connection pattern is more complex — particularly when each instance's output feeds the next instance's input (carry chains, pipeline stages, shift registers) — a generate loop gives you element-by-element control that instance arrays cannot express.

SystemVerilog — 16-bit Ripple Carry Adder via Generate Loop
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module adder_16bit (
    input  logic [15:0] a, b,
    input  logic        cin,
    output logic [15:0] sum,
    output logic        cout
);
    // Carry chain: carry[0] = primary cin, carry[4] = final cout
    logic [4:0] carry;
    assign carry[0] = cin;
    assign cout     = carry[4];
 
    genvar i;
    generate
        for (i = 0; i < 4; i++) begin : gen_stage
            adder_4bit  u_adder (
                .a   (a    [i*4+3 : i*4]),
                .b   (b    [i*4+3 : i*4]),
                .cin (carry[i]),
                .sum (sum  [i*4+3 : i*4]),
                .cout(carry[i+1])
            );
        end
    endgenerate
 
endmodule
 
// Resulting hierarchy in simulation/synthesis:
//   adder_16bit
//   ├── gen_stage[0].u_adder  →  bits [3:0]
//   ├── gen_stage[1].u_adder  →  bits [7:4]
//   ├── gen_stage[2].u_adder  →  bits [11:8]
//   └── gen_stage[3].u_adder  →  bits [15:12]
//
// Hierarchical reference in TB assertions:
// tb.u_dut.gen_stage[2].u_adder.cout

Common Mistakes to Avoid

MistakeWhy it bites
Missing endmoduleThe compiler throws a confusing error at the end of the file. Always close every module.
Duplicate instance namesTwo instances named u_add in the same module cause an elaboration error. Every instance needs a unique name.
Leaving ports unconnectedUnconnected output ports float to X. Unconnected input ports drive 0 in synthesis but Z in simulation.
Width mismatchConnecting a 4-bit signal to an 8-bit port causes a width-mismatch warning — or worse, silent truncation.

Debugging Academy — Real Bugs in Module Instantiation

Module instantiation bugs are some of the most insidious in hardware design. They often compile cleanly, simulate without errors, and produce plausible-looking results — until they don't. Each lab below is based on a category of bug seen in real silicon projects. Learn the symptoms, the waveform signature, and the fix before you encounter them under deadline pressure.

1

Width Mismatch — Silent Truncation or Zero-Extension

RTL + VERIFICATION
Symptom

DUT produces correct results for small input values but fails for values above a threshold. Simulation shows width-mismatch warnings that the team has been suppressing.

Root Cause

An 8-bit bus is connected to a 4-bit input port. The simulator truncates the upper bits silently. For inputs 0–15 everything works. For inputs 16–255, the upper nibble is lost.

Buggy Code
8-bit signal driven into 4-bit port
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [7:0] data_bus;   // 8-bit signal
 
// adder_4bit.a is 4-bit wide
adder_4bit u_add (
    .a   (data_bus),    // ❌ 8→4: truncated!
    .b   (b),
    .cin (cin),
    .sum (sum),
    .cout(cout)
);
// data_bus[7:4] silently dropped
// Simulator warns; lint tools flag it
Fix
Explicit slice — intent is documented
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [7:0] data_bus;
 
// Explicitly take the lower nibble
adder_4bit u_add (
    .a   (data_bus[3:0]),   // ✅ explicit
    .b   (b),
    .cin (cin),
    .sum (sum),
    .cout(cout)
);
// Intent is clear. Reviewers see the choice.

Waveform signature: data_bus = 8'hA5 (165 decimal) → port a sees 4'h5 (5 decimal). Upper nibble A is gone. Sum shows 5+b, not 165+b.

Prevention: enable lint checks in your simulator. Synopsys VCS: +lint=all. Cadence Xcelium: -messages. Never suppress width-mismatch warnings without investigating.

2

Unconnected Output — X Propagation Corrupts Downstream Logic

CRITICAL SILICON BUG
Symptom

Simulation shows X values spreading through the design — first on one signal, then contaminating others. The bug appears intermittently and the X seems to "appear from nowhere."

Root Cause

An important output port of a sub-module was left unconnected (no signal, no empty parentheses). The downstream logic that was supposed to receive this output is instead floating.

Buggy Code
cout port omitted entirely
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ BUGGY — carry_out is critical but left unconnected ═════════
logic [3:0] sum;
// carry_out signal never declared — just omitted in the port list
 
adder_4bit  u_add (
    .a   (opa),
    .b   (opb),
    .cin (cin),
    .sum (sum)
    // .cout — MISSING! cout port is not connected at all
);
 
// The next stage that needs carry-out is using an uninitialised signal
adder_4bit  u_add_hi (
    .cin (floating_carry),   // ❌ floating — X or Z value in simulation
    // ...
);
Fix
Either connect it or document the intent
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ FIXED — explicit empty () for intentionally unused, or connect it ═
logic carry_out;          // declare it
 
adder_4bit  u_add (
    .a   (opa),
    .b   (opb),
    .cin (cin),
    .sum (sum),
    .cout(carry_out)         // ✅ connected and declared
);
 
// If you truly don't need an output port:
adder_4bit  u_add_nc (
    .cout(),                 // ✅ intentionally unconnected — documents intent
    // ...
);

Waveform signature: the signal fed by the missing output shows as X (red in most waveform viewers) for the entire simulation. Downstream registers clocking X propagate it further. X-contamination typically fans out in waves with each clock edge.

Debug process: open waveform viewer. Find the first X-valued signal at time 0 (before any stimulus). Trace its driver. If it has no driver, you found the floating port. Simulator $cast or lint checks will report "undriven output connection."

3

Multi-Driver Conflict — Two Instances Driving the Same Net

ELABORATION / SIMULATION
Symptom

Signal constantly shows X in simulation regardless of what either driver produces. Simulator may print a "multi-driven net" warning that was overlooked during integration.

Root Cause

Two module output ports are connected to the same logic signal. In SystemVerilog, a logic net with multiple drivers resolves to X when drivers disagree.

Buggy Code
Two outputs colliding on one wire
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [3:0] shared_result;
 
adder_4bit u_add_a (
    // ...
    .sum(shared_result)    // driver 1
);
 
adder_4bit u_add_b (
    // ...
    .sum(shared_result)    // driver 2 ❌
);
// shared_result = X when add_a ≠ add_b
Fix
Unique nets per output; MUX downstream
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [3:0] result_a, result_b;
 
adder_4bit u_add_a (
    // ...
    .sum(result_a)    // ✅ unique net
);
adder_4bit u_add_b (
    // ...
    .sum(result_b)    // ✅ unique net
);
 
// Mux downstream:
assign out = sel ? result_b : result_a;

Each module output must drive exactly one unique internal net. Use a MUX (assign out = sel ? a : b) or arbitration logic to combine multiple results — never connect two output ports to the same wire.

4

Duplicate Instance Name — Elaboration Fails with Cryptic Error

ELABORATION ERROR
Symptom

Simulator aborts during elaboration with an error like "identifier u_adder already declared" or "hierarchical name conflict". The design never reaches simulation.

Root Cause

Two instances inside the same parent module share the same instance name. Copy-paste of an instantiation block without changing the instance name is the most frequent cause.

Buggy Code
Copy-pasted instance, name not changed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Copy-pasted second instance, forgot to rename
adder_4bit u_adder (
    .a(opa), .b(opb), /* ... */
);
 
adder_4bit u_adder (      // ❌ same name!
    .a(opc), .b(opd), /* ... */
);
// ERROR: elaboration fails
Fix
Unique, descriptive names
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
adder_4bit u_adder_lo (
    .a(opa), .b(opb), /* ... */
);
 
adder_4bit u_adder_hi (   // ✅ unique
    .a(opc), .b(opd), /* ... */
);
// Elaboration succeeds.
// Names are descriptive too: lo/hi

Prevention: use a search-for-duplicate-names lint check. In large integration files with 50+ instances, visually spotting duplicates is unreliable. Most lint tools (Spyglass, Ascent) catch this immediately.

5

Floating Input Port — Z Drives Register, Clock, or Control Logic

SILENT FUNCTIONAL BUG
Symptom

Control signals behave non-deterministically. A feature is sometimes active, sometimes not. The bug disappears when you add debug probes (classic Heisenbug). Sometimes the circuit works at slow clock speeds but fails at speed.

Root Cause

An input port of a module was not connected during instantiation. In simulation, unconnected inputs default to Z (high-impedance). If a register, clock enable, or reset logic feeds on this port, behaviour is undefined. In synthesis, the undriven input gets tied to 0 — which may differ from what the simulation showed.

Buggy Code
enable port omitted at instantiation site
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Imagine a parameterized multiplier with an enable port
module mult_8bit (
    input  logic        clk, rst_n, enable,
    input  logic [7:0]  a, b,
    output logic [15:0] product
);
 
// ════ BUGGY instantiation — 'enable' port not connected ══════════
mult_8bit  u_mult (
    .clk    (sys_clk),
    .rst_n  (sys_rst_n),
    // .enable — OMITTED! Port receives Z in simulation
    .a      (operand_a),
    .b      (operand_b),
    .product(result)
);
// In simulation: enable = Z → non-deterministic multiplier
// In synthesis : enable tied to 0 → multiplier never fires
// Simulation PASSES, silicon FAILS — classic sim-vs-silicon gap
Fix
Every input port wired explicitly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ════ FIXED — all ports explicitly connected ══════════════════════
mult_8bit  u_mult (
    .clk    (sys_clk),
    .rst_n  (sys_rst_n),
    .enable (mult_enable),   // ✅ every input wired
    .a      (operand_a),
    .b      (operand_b),
    .product(result)
);

Key insight: simulation and synthesis treat unconnected inputs differently. Simulator: Z (high-impedance, unpredictable). Synthesis tool: ties to 0 (logic constant). This divergence is one of the most dangerous sim-vs-silicon gap sources. Your simulation passes, your silicon fails.

Tool tip: run Spyglass or CDC checks with the rule NoUnconnectedInPort on every RTL integration. This should be a mandatory signoff gate — not optional lint.

Waveform & Simulation Thinking — How Modules Behave in Time

Understanding what a module's waveform looks like — and why — is the difference between a verification engineer who debugs fast and one who guesses. This section teaches you to read a module's simulation output with the same fluency as reading its code.

Combinational Module — Zero-Delay Evaluation

A purely combinational module like adder_4bit has no registered state. Its outputs respond to input changes within the same simulation time step (delta cycle). The waveform below shows what you should expect when running the testbench from earlier.

Waveform — adder_4bit simulation (tb_adder_4bit)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Time     a[3:0]   b[3:0]   cin   sum[3:0]   cout   Notes
────────────────────────────────────────────────────────────────────────
0 ns     XXXX     XXXX     X     XXXX       X      Before stimulus — all logic uninitialized
0 ns     4'd3     4'd5     0     4'd8       0      Inputs set → outputs update at Δ (same ns)
10 ns    4'd9     4'd8     0     4'd1       1      9+8=17 → sum=1 (LSB 4 bits), cout=1
20 ns    4'd15    4'd15    1     4'd15      1      15+15+1=31 → sum=15 (0b1111), cout=1
────────────────────────────────────────────────────────────────────────
Key: X=Unknown  0=Logic-0  1=Logic-1  Δ=delta cycle (within same ns)

What X Propagation Looks Like in the Waveform

When a module's input is undriven (floating) or a driver produces X, the X value propagates through all dependent logic. This is one of the most important waveform patterns to recognize immediately.

X Propagation — Unconnected Input Port (Debug Lab 5 scenario)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Time     enable   a[7:0]    b[7:0]    product[15:0]    Notes
──────────────────────────────────────────────────────────────────────────────
0 ns     Z        8'hAB     8'hCD     XXXX_XXXX_XXXX   enable=Z (unconnected) → product=X
10 ns    Z        8'h01     8'h01     XXXX_XXXX_XXXX   Despite simple inputs, X persists
20 ns    Z        8'h00     8'h00     XXXX_XXXX_XXXX   Even 0*0 gives X — X is contagious
──────────────────────────────────────────────────────────────────────────────
Z = High-impedance (undriven)  X = Unknown (propagated from Z enable)
In synthesis: enable tied to 0 → product always 0. Sim says X, silicon says 0.

Hierarchy in Waveform Viewers

In any waveform viewer (DVE, Verdi, SimVision, GTKWave), the module hierarchy maps directly to the signal browser tree on the left. Understanding the hierarchy in the waveform viewer is essential for targeted debug.

Verification Patterns — How Modules Are Used in Testbenches

A testbench is itself a module — the top of the simulation hierarchy. Understanding common testbench patterns will prepare you for UVM and professional verification environments.

Pattern 1 — Self-Checking Testbench with Reference Model

A self-checking testbench computes the expected result independently and compares it against the DUT output automatically. This is essential for regression — you cannot manually check results when running thousands of random test vectors.

SystemVerilog — Self-Checking Testbench with Reference Model
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_adder_self_check;
 
    logic [3:0] a, b, sum;
    logic       cin, cout;
    int         pass_count = 0;
    int         fail_count = 0;
 
    // ── DUT instance ──────────────────────────────────────────────
    adder_4bit  u_dut (
        .a(a), .b(b), .cin(cin), .sum(sum), .cout(cout)
    );
 
    // ── Reference model (pure SW, always correct) ─────────────────
    function automatic void check_result (
        input logic [3:0] exp_a, exp_b,
        input logic       exp_cin
    );
        logic [4:0] expected = exp_a + exp_b + exp_cin;
        logic [3:0] exp_sum  = expected[3:0];
        logic       exp_cout = expected[4];
 
        if (sum === exp_sum && cout === exp_cout) begin
            pass_count++;
        end else begin
            fail_count++;
            $display("FAIL: a=%0d b=%0d cin=%0b → sum=%0d(exp %0d) cout=%0b(exp %0b)",
                     exp_a, exp_b, exp_cin, sum, exp_sum, cout, exp_cout);
        end
    endfunction
 
    // ── Stimulus + automatic checking ─────────────────────────────
    initial begin
        // Directed tests
        a = 4'd3;  b = 4'd5;  cin = 0; #1; check_result(a, b, cin);
        a = 4'd9;  b = 4'd8;  cin = 0; #1; check_result(a, b, cin);
        a = 4'd15; b = 4'd15; cin = 1; #1; check_result(a, b, cin);
 
        // Random tests — 200 random vectors
        repeat(200) begin
            a   = $urandom_range(0, 15);
            b   = $urandom_range(0, 15);
            cin = $urandom_range(0, 1);
            #1;
            check_result(a, b, cin);
        end
 
        $display("─── Results: PASS=%0d  FAIL=%0d ───", pass_count, fail_count);
        $finish;
    end
 
endmodule

Pattern 2 — Clock Generator Module

In a real project testbench, the clock generator is often factored into its own module (or task). This demonstrates how a testbench module's own internal logic — including always blocks — co-exists with the DUT instance.

SystemVerilog — Clocked DUT Testbench (Clock + Reset + DUT)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Imaginary registered accumulator DUT
module tb_accum;
 
    parameter int CLK_PERIOD = 10;   // 10 ns → 100 MHz
 
    // ── Testbench signals ──────────────────────────────────────────
    logic        clk   = 0;   // initialise to avoid X at t=0
    logic        rst_n = 0;
    logic [7:0]  data_in;
    logic        valid_in;
    logic [15:0] accum_out;
    logic        valid_out;
 
    // ── Clock generator — always block inside TB module ────────────
    always #(CLK_PERIOD/2) clk = ~clk;
 
    // ── DUT instantiation ──────────────────────────────────────────
    accumulator  u_dut (
        .clk      (clk),
        .rst_n    (rst_n),
        .data_in  (data_in),
        .valid_in (valid_in),
        .accum_out(accum_out),
        .valid_out(valid_out)
    );
 
    // ── Reset sequence ─────────────────────────────────────────────
    initial begin
        rst_n    = 0;
        data_in  = 8'd0;
        valid_in = 0;
        repeat(4) @(posedge clk);     // hold reset for 4 cycles
        @(negedge clk); rst_n = 1;    // deassert at negedge to avoid race
 
        // ── Apply stimulus after reset ────────────────────────────
        repeat(2) @(posedge clk);
        @(negedge clk);
        data_in = 8'd10; valid_in = 1;
 
        repeat(5) @(posedge clk);
        @(negedge clk);
        data_in = 8'd20;
 
        repeat(3) @(posedge clk);
        valid_in = 0;
 
        repeat(10) @(posedge clk);
        $display("Accum result: %0d", accum_out);
        $finish;
    end
 
endmodule

Quick Reference — Module Syntax at a Glance

Bookmark this. Every syntax you need for this page in one place.

SystemVerilog — Module Quick Reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Define a module (ANSI style) ──────────────────────────────
module module_name (
    input  logic [W-1:0] port_in,
    output logic [W-1:0] port_out
);
    // internal signals and logic here
endmodule
 
// ── Instantiate a module (named connections) ──────────────────
module_name  instance_name (
    .port_in  (signal_a),
    .port_out (signal_b)
);
 
// ── Leave an output intentionally unconnected ─────────────────
module_name  u_inst (
    .port_in  (signal_a),
    .port_out ()           // empty () = intentionally unconnected
);
 
// ── Testbench module (no ports) ───────────────────────────────
module tb_module_name;
    // declare signals, instantiate DUT, write stimulus
endmodule
 
// ── endmodule with optional label (good practice) ─────────────
endmodule : module_name

Interview Q&A — Module Definition & Instantiation

These questions span the full spectrum from entry-level to principal engineer. A strong candidate answers the beginner questions fluently without thinking, and can articulate the advanced ones from real experience.

A module definition is the blueprint — it describes the ports, internal signals, and logic but does not create any hardware by itself. A module instance is a physical copy placed inside another module with a unique name and specific port connections. You can have one definition and hundreds of instances. Analogy: a blueprint for a house vs. the actual houses built from it.