Skip to content

SystemVerilog · Module 7

Named & Positional Port Connections

Three connection styles — positional, named, and .* — with width/type mismatch handling, inout patterns, 6 debugging labs, and refactoring guidance.

Module 7 · Page 7.3

When you instantiate a module you must connect its ports to signals. SystemVerilog gives you three ways to do this — positional, named .port(signal), and implicit .* — and each has a very different safety profile. This page covers every connection style, what the elaborator actually does, width/sign/type mismatches at the port boundary, inout net rules, six real bugs from tape-out post-mortems, the refactor playbook, and ten interview-ready questions.

Three Ways to Connect Ports

Every time you write an instantiation, you must tell the simulator which signal in the parent connects to which port of the child module. SystemVerilog provides three connection styles:

  • Positional — signals listed in the same order as the module's port list. Fast to type. Dangerous — order is invisible at a glance.
  • Named .port(signal) — each port named explicitly. Order does not matter. Recommended for all production code — self-documenting and refactoring-safe.
  • Implicit .* — connects ports whose name exactly matches a signal in the parent. Fastest to write. Requires strict naming discipline.
// Module being instantiated:module adder_4bit(input a, b, cin, output sum, cout);// ❶ POSITIONAL — signals in exact port-list order (fragile)adder_4bitu_pos(op_a, op_b, carry_in, result, carry_out); ↑ a ↑ b ↑ cin ↑ sum ↑ cout — you must know the order by heart// ❷ NAMED — explicit .port(signal) — safe & self-documenting ✅adder_4bitu_named(.a(op_a),.b(op_b),.cin(carry_in),.sum(result),.cout(carry_out));// ❸ IMPLICIT .* — auto-connects ports whose name matches a signaladder_4bitu_impl(.*); ↑ connects every port to a same-named signal — requires: logic a, b, cin, sum, cout; in parentPOSITIONALNAMEDIMPLICIT .*
Figure 1 — All three connection styles for the same adder_4bit instantiation. Same hardware result, very different readability and safety.

Positional Connections

In positional style, you list the signals inside the parentheses in the exact same order as the ports appear in the module definition. No port names are written. The first signal connects to the first port, the second to the second, and so on.

SystemVerilog — Positional port connection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The module being instantiated:
// module adder_4bit (input logic a, b, cin, output logic sum, cout);
//                              ①  ②   ③                ④     ⑤
 
// Positional instantiation — signal order must match port order
adder_4bit  u_add (
    operand_a,    // ① connects to: a
    operand_b,    // ② connects to: b
    carry_in,     // ③ connects to: cin
    result,       // ④ connects to: sum
    carry_out     // ⑤ connects to: cout
);

The Hidden Danger of Positional Connections

Positional connections look clean, but they carry a serious risk: if anyone reorders or adds a port to the module definition, every instantiation silently rewires itself — often without any compiler error. The design simulates, but produces wrong results.

Named Connections — .port(signal)

In named style, each connection is written as .port_name(signal_name). The dot and port name come from the module definition; the signal name inside the parentheses comes from the parent module. Order is irrelevant — you can list ports in any sequence you like.

SystemVerilog — Named port connection (all forms)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Standard named connections ─────────────────────────────────
adder_4bit  u_add (
    .a   (operand_a),    // port 'a'   ← signal 'operand_a'
    .b   (operand_b),    // port 'b'   ← signal 'operand_b'
    .cin (carry_in),     // port 'cin' ← signal 'carry_in'
    .sum (result),       // port 'sum' ← signal 'result'
    .cout(carry_out)     // port 'cout'← signal 'carry_out'
);
 
// ── Order doesn't matter — this is identical ───────────────────
adder_4bit  u_add2 (
    .cout(carry_out),    // outputs listed first — totally fine
    .sum (result),
    .cin (carry_in),
    .b   (operand_b),
    .a   (operand_a)
);
 
// ── Port name = signal name? You can write .port(port) ─────────
// Or even shorter — named connection where names match:
adder_4bit  u_add3 (
    .a   (a),            // port 'a' connected to signal 'a' in parent
    .b   (b),
    .cin (cin),
    .sum (sum),
    .cout(cout)
);
 
// ── Connecting a constant or expression directly ────────────────
adder_4bit  u_add4 (
    .a   (operand_a),
    .b   (operand_b),
    .cin (1'b0),         // ← constant tied to a port
    .sum (result),
    .cout()              // ← intentionally unconnected (empty parens)
);

Why Named Connections Are Safe

Named connections are unaffected by new ports being added. The compiler warns about the unconnected new port — you fix it immediately instead of spending days debugging wrong results.

Implicit .* Connections

SystemVerilog's .* (dot-star) connection is the most compact style. When you write (.*), the compiler automatically connects every port to a signal in the parent module that has exactly the same name. No port names are written at all.

SystemVerilog — Implicit .* connection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module top;
 
    // Signals named IDENTICALLY to the module's ports
    logic [3:0] a, b, sum;
    logic       cin, cout;
 
    // .* connects every port to a same-named signal automatically
    // adder_4bit has ports: a, b, cin, sum, cout
    adder_4bit  u_add (.*);     // one token — zero manual wiring!
 
    // ── Mixing .* with explicit overrides ──────────────────────
    // You can override specific ports while .* handles the rest
    logic [3:0] alt_b;
 
    adder_4bit  u_add2 (
        .*,                    // connect a, cin, sum, cout by name
        .b(alt_b)              // override: connect b to alt_b instead
    );
 
endmodule

When .* Is a Good Choice

SystemVerilog — .* in a practical testbench
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_counter;
 
    // Declare signals with the EXACT same names as the DUT's ports
    logic        clk  = 0;
    logic        rst_n;
    logic        en;
    logic [7:0]  count;       // output port 'count'
    logic        is_zero;     // output port 'is_zero'
 
    // DUT: counter_8bit has ports: clk, rst_n, en, count, is_zero
    counter_8bit  u_dut (.*);   // all five connected in one token
 
    always #5 clk = ~clk;
 
    initial begin
        rst_n = 0; en = 0;
        @(posedge clk); rst_n = 1; en = 1;
        repeat(20) @(posedge clk);
        $finish;
    end
 
endmodule

How the Elaborator Resolves .*

Understanding .* deeply requires understanding what the elaborator does — not the parser. Parsing only tokenises; elaboration is the stage where the simulator builds the hierarchical netlist, resolves names, and wires ports. Here is what really happens, in order, every time it sees .*:

  1. Collect ports. The elaborator looks at the instantiated module's port list — every input, output, and inout.
  2. Search the enclosing scope. For each port name, it searches the immediately enclosing scope (the parent module, generate block, or begin–end block where the instance lives) for an identifier with an exact match — same name, same case. It does not walk up the hierarchy.
  3. Type compatibility check. The found signal must be width- and type-compatible. A 4-bit port connecting to an 8-bit signal will silently truncate (covered below) — no error.
  4. Skip overridden ports. Any port explicitly listed with .port(sig) in the same instance is skipped — explicit overrides win over .*.
  5. Warn on unmatched ports. A port with no matching parent signal is left unconnected — most tools emit a warning, not an error. Read your warnings.

Leaving Ports Unconnected — The Right Way

Sometimes you genuinely do not need to connect a port. For example, you might not care about the carry-out of an adder, or an enable input should always be tied high. SystemVerilog gives you clean ways to handle both cases.

SystemVerilog — Handling unconnected ports
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Empty parentheses = intentionally unconnected output ───────
adder_4bit  u_add (
    .a   (operand_a),
    .b   (operand_b),
    .cin (1'b0),          // tie carry-in LOW with a constant
    .sum (result),
    .cout()               // empty () = "I know it's unconnected, intentional"
);
 
// ── Tying inputs to constants ───────────────────────────────────
uart_tx  u_uart (
    .clk    (sys_clk),
    .rst_n  (rst_n),
    .en     (1'b1),        // always enabled — tied HIGH
    .data   (tx_data),
    .baud   (2'b00),       // fixed baud rate — tied to constant
    .tx_out (serial_out),
    .busy   ()             // don't care about busy flag — unconnected
);
 
// ── What NOT to do ─────────────────────────────────────────────
// Simply omitting a port in named style also leaves it unconnected,
// but gives no documentation of intent — harder to review:
adder_4bit  u_add_bad (
    .a   (operand_a),
    .b   (operand_b),
    .cin (1'b0),
    .sum (result)
    // cout omitted — compiler warns, reader wonders if it was a mistake
);
SituationHow to Handle ItWhat Happens
Output port you don't need.cout() — empty parensPort floats — value computed but discarded. Simulator may warn if the port is used in the child.
Input port always HIGH.en(1'b1)Constant tied to port. Synthesis optimises the logic accordingly.
Input port always LOW.cin(1'b0) or .cin('0)Zero tied to port. Clean and explicit.
Input port — wide zero bus.data('0)'0 auto-sizes to match port width.
Input port left disconnectedAvoid — input floats to XUndefined behaviour. The module reads X on that input every cycle.

Comparison — Choosing the Right Style

CriterionPositionalNamed .port(sig)Implicit .*
Syntax lengthShortestLongestOne token
Order matters?Yes — strictlyNoNo (by name)
Port visible in code?No — hiddenYes — explicitNo — implicit
Safe if ports change?No — silent rewireYes — compiler warnsMostly — warns on new port with no match
Refactor-safe?NoYesOnly if signal names match
Self-documenting?NoYes — very clearNo — reader must look up ports
Can mix with others?No — must be all positionalCan mix with .*Can mix with .port(sig)
Best forQuick throwaway testbenches onlyAll production RTL and verificationTestbenches where port=signal names align

Complete Working Example — All Three Styles

Here is a full file showing all three connection styles on the same module, so you can compare them side by side in a real simulator.

SystemVerilog — all three styles on the same module
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── The DUT: simple half-adder ──────────────────────────────────
module half_adder (
    input  logic a, b,
    output logic sum, cout
);
    assign sum  = a ^ b;
    assign cout = a & b;
endmodule
 
 
// ── Testbench comparing all three styles ────────────────────────
module tb_connection_styles;
 
    // Signals for named & positional instances
    logic p, q, s_pos, c_pos;      // for positional
    logic x, y, s_nam, c_nam;      // for named
 
    // Signals named to MATCH half_adder ports (for .* instance)
    logic a, b, sum, cout;         // for implicit .*
 
    // ── ❶ Positional ──────────────────────────────────────────────
    half_adder  u_pos   (p, q, s_pos, c_pos);
 
    // ── ❷ Named .port(signal) ─────────────────────────────────────
    half_adder  u_named (.a(x), .b(y), .sum(s_nam), .cout(c_nam));
 
    // ── ❸ Implicit .* ─────────────────────────────────────────────
    half_adder  u_impl  (.*);   // a,b,sum,cout connected by name
 
    // ── Apply the same stimulus to all three ──────────────────────
    initial begin
        $monitor("p=%b q=%b | pos: s=%b c=%b | named: s=%b c=%b | impl: s=%b c=%b",
                 p, q, s_pos, c_pos, s_nam, c_nam, sum, cout);
 
        {p,x,a} = 3'b000; {q,y,b} = 3'b000; #10;
        {p,x,a} = 3'b111; {q,y,b} = 3'b000; #10;
        {p,x,a} = 3'b000; {q,y,b} = 3'b111; #10;
        {p,x,a} = 3'b111; {q,y,b} = 3'b111; #10;
        $finish;
    end
 
endmodule

Common Mistakes — Wrong vs Correct

❌ Wrong — Mixing positional and named
Cannot mix styles in the same instance
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
half_adder u_mix (
    a,                // positional
    .b(y)             // ← NAMED — illegal!
);
// Error: cannot mix positional
// and named port connections.
✅ Correct — Use one style consistently
All named — consistent and legal
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
half_adder u_fix (
    .a(x),
    .b(y),
    .sum (s),
    .cout(c)
);
❌ Wrong — .* with mismatched signal names
Signal names don't match ports
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic operand_a, operand_b;
logic result, carry;
 
// half_adder ports: a, b, sum, cout
half_adder u_bad (.*);
// Error: no match for a, b, sum, cout
// — operand_a ≠ a
✅ Correct — Signal names match exactly
Signals named to match ports exactly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic a, b;       // matches port 'a', 'b'
logic sum, cout;  // matches port 'sum', 'cout'
 
half_adder u_good (.*);
// All four ports connected — clean ✅
❌ Wrong — Unconnected input (no constant)
cin floats to X
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
adder_4bit u_bad (
    .a   (data_a),
    .b   (data_b),
    // .cin not connected!
    // cin floats to X
    .sum (result),
    .cout(co)
);
✅ Correct — Tie unused input to constant
Explicit constant + intentional unconnected output
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
adder_4bit u_good (
    .a   (data_a),
    .b   (data_b),
    .cin (1'b0),     // tied to 0 — explicit
    .sum (result),
    .cout()          // output: intentionally unconnected
);

Quick Reference — Connection Syntax Cheat Sheet

SystemVerilog — port connection quick reference
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── ❶ Positional — signal order must match port order ──────────
mod_name  inst_name (sig1, sig2, sig3);
 
// ── ❷ Named — .port(signal) — RECOMMENDED for all RTL ──────────
mod_name  inst_name (
    .port1(signal_a),
    .port2(signal_b),
    .port3(1'b0),       // tie input to constant
    .port4()            // intentionally unconnected output
);
 
// ── ❸ Implicit .* — connect every port by name match ───────────
mod_name  inst_name (.*);
 
// ── ❸b Mix .* with explicit overrides ──────────────────────────
mod_name  inst_name (.*, .port2(different_signal));
 
// ── Cannot mix: positional + named ─────────────────────────────
// mod_name inst_name (sig1, .port2(sig2));  ← ILLEGAL
 
// ── Tie to constants ────────────────────────────────────────────
// .en   (1'b1)      tie HIGH
// .sel  (1'b0)      tie LOW
// .data ('0)        tie whole bus to zero
// .data ('1)        tie whole bus to ones
// .out  ()          output — intentionally unconnected

Width, Sign & Type Mismatches at the Port Boundary

One of the most overlooked port-connection bugs has nothing to do with named vs positional style — it has to do with width. SystemVerilog's LRM is permissive at module boundaries: a wider signal connected to a narrower port is silently truncated, and a narrower signal connected to a wider port is silently zero-extended (or sign-extended, depending on the type). No error. No warning, on many tool versions. Just wrong silicon.

SystemVerilog — silent width mismatch at port boundary
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── DUT: an 8-bit register file write port ─────────────────────
module reg_file (
    input  logic       clk,
    input  logic       we,
    input  logic [2:0] waddr,
    input  logic [7:0] wdata      // ← 8 bits
);
    logic [7:0] mem [8];
    always_ff @(posedge clk) if (we) mem[waddr] <= wdata;
endmodule
 
 
// ── Parent: somebody widens the data bus to 16 bits ────────────
module cpu_core;
    logic        clk, we;
    logic [2:0]  waddr;
    logic [15:0] alu_result;  // ← now 16 bits!
 
    // LRM-legal but DANGEROUS — upper 8 bits silently truncated
    reg_file u_rf (
        .clk(clk),
        .we (we),
        .waddr(waddr),
        .wdata(alu_result)   // ❌ alu_result[15:8] thrown away silently
    );
endmodule
 
// ── The fix: be explicit. The truncation is now a CHOICE ──────
reg_file u_rf_safe (
    .clk(clk), .we(we), .waddr(waddr),
    .wdata(alu_result[7:0])  // ✅ truncation is documented & intentional
);

What the Simulator Does — Truncation & Extension Table

Port widthSignal widthSignal typeWhat happensCompiler diagnostic
8 bits (input)16 bitsanyUpper 8 bits silently truncated. Module sees signal[7:0] only.Often no warning. Strict lint catches it.
16 bits (input)8 bitslogic (unsigned)Zero-extended to 16 bits. Upper byte = 0.Usually no warning.
16 bits (input)8 bitslogic signedSign-extended — upper 8 bits replicate bit[7]. Wreaks havoc if you assumed unsigned.No warning unless signed/unsigned mismatch checks are on.
1 bit (input)4 bitsanyOnly bit[0] connected. Bits [3:1] discarded.Most tools warn here — it's a common typo.
4 bits (output)4 bits but declared wire on input portMulti-driver if anything else also drives the wire. Hard X bug.Elaboration-time error if both drivers are continuous.

Sign-Extension Bug — A Real Trap

❌ The trap — signed silently extended
Unsigned 0xFF becomes signed -1
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Port is signed 16-bit:
module mac(input  logic signed [15:0] op, ...);
 
// Parent signal is unsigned 8-bit:
logic [7:0] data;   // 0xFF intended as 255
 
mac u_m (.op(data));
// At port boundary:
//   data=8'hFF → sign-extended →
//   op = 16'hFFFF  (= -1, NOT 255!) ❌
✅ Explicit cast — intent visible
Cast makes the widening explicit
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Cast clearly says "treat as unsigned":
mac u_m (
    .op(16'(unsigned'(data)))
);
// op = 16'h00FF = 255 ✅
 
// Or even more explicit:
mac u_m2 (
    .op({8'b0, data})
);
// Zero-extension is now in the source ✅

Bidirectional (inout) Connection Patterns

inout ports are the most error-prone connection in SystemVerilog. They must be driven by a net (typically wire or tri) on both sides of the boundary — never a logic variable in the parent. Getting this wrong gives elaboration errors at best, and silent X-propagation at worst.

SystemVerilog — proper inout pad connection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── A tri-state I/O pad model ──────────────────────────────────
module tristate_pad (
    inout  wire  pad,         // ← MUST be a net, not logic
    input  logic drive_en,
    input  logic drive_val,
    output logic rx_val
);
    assign pad    = drive_en ? drive_val : 1'bz;  // tri-state
    assign rx_val = pad;
endmodule
 
 
// ── ❌ WRONG: parent uses 'logic' for the inout net ────────────
module top_bad;
    logic bus_net;          // ← variable! cannot drive an inout
    logic en, dval, rval;
 
    tristate_pad u_pad (
        .pad(bus_net),      // ❌ elaboration error in most tools
        .drive_en(en), .drive_val(dval), .rx_val(rval)
    );
endmodule
 
 
// ── ✅ CORRECT: declare the shared bus as a wire/tri ───────────
module top_good;
    wire        bus_net;     // ✅ net — can be driven by multiple sources
    logic       en, dval, rval;
 
    // Optional weak pull-up so bus is not X when nobody drives
    pullup      (bus_net);    // strength 'pull' on bus_net
 
    tristate_pad u_pad (
        .pad(bus_net),
        .drive_en(en), .drive_val(dval), .rx_val(rval)
    );
endmodule

Verification Engineer Patterns — DUT ↔ TB Connections

In a real UVM/SV verification environment, port connections are made in three layers, and each layer has a preferred connection style. Knowing the pattern saves hours of hierarchical-path debugging later.

The Three Layers

  • Layer 1 — Pin-Level (DUT instance). The DUT is instantiated once at the top of the testbench. Always named. Almost always uses a SystemVerilog interface to bundle ports cleanly.
  • Layer 2 — Interface Binding. The interface is bound to the agent through a virtual interface handle. Connection happens via uvm_config_db::set / get — not at instantiation.
  • Layer 3 — Driver / Monitor. Drivers/monitors talk to DUT signals through the virtual interface — no port connections at all. This is the power of interface-based verification.
SystemVerilog — production-style TB connection
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── ❶ The interface bundles the DUT-facing signals ────────────
interface axi_if (input logic aclk, aresetn);
    logic [31:0] awaddr;
    logic        awvalid, awready;
    logic [31:0] wdata;
    logic        wvalid, wready;
    // ... plus modports for master/slave/monitor
endinterface
 
 
// ── ❷ Top-level TB instantiates DUT with NAMED connections ────
module tb_top;
    logic aclk = 0, aresetn;
    always #5 aclk = ~aclk;
 
    // One interface instance per AXI port
    axi_if m_if (aclk, aresetn);
 
    // DUT instantiation — NAMED, with interface signals on the right
    dma_engine u_dut (
        .aclk     (aclk),
        .aresetn  (aresetn),
        .m_awaddr (m_if.awaddr),
        .m_awvalid(m_if.awvalid),
        .m_awready(m_if.awready),
        .m_wdata  (m_if.wdata),
        .m_wvalid (m_if.wvalid),
        .m_wready (m_if.wready)
    );
 
    // ❸ Publish the virtual interface so UVM driver/monitor can grab it
    initial begin
        uvm_config_db#(virtual axi_if)::set(null, "uvm_test_top.env.agent*",
                                              "vif", m_if);
        run_test("axi_smoke_test");
    end
endmodule

.* in a Clean Verification Wrapper

.* shines when you write thin pass-through wrappers — a very common verification pattern where a DUT is wrapped in a debug shell that just forwards every signal. With .*, adding a new DUT port requires touching exactly two places: the DUT and the wrapper's signal list.

SystemVerilog — pass-through debug wrapper
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module dut_with_probes (
    // SAME port list as the DUT — wrapper is transparent
    input  logic clk, rst_n, en,
    input  logic [31:0] data_in,
    output logic [31:0] data_out, status
);
    // ← .* connects every signal whose name matches — fast & safe
    dut u_dut (.*);
 
    // Add SVA / coverage / debug probes here — independent of port list
    assert property (@(posedge clk) disable iff (!rst_n)
        en |-> ##[1:5] status[0])
        else $error("DUT did not ack within 5 cycles");
 
endmodule

Debugging Academy — 6 Real Port Connection Bugs

These are the bugs you will actually meet in a real RTL or DV project — pulled from tape-out post-mortems, FPGA bring-up logs, and verification regression triages. For each, study the symptom and the root cause until you can spot the pattern in your own code review.

1

Positional Reorder Silent Break

SILENT REWIRE
Buggy Code
Positional after port reorder
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// DUT modified — clk_en added at pos 3
module fifo(
   input clk, rst_n,
   input clk_en,    // NEW
   input [7:0] threshold);
 
// Parent left unchanged
fifo u_f(clk, rst, th_val);
// th_val drives clk_en! ❌
// threshold floats unconnected
Symptom

After a clean RTL release, an unrelated block (FIFO depth programmability) starts misbehaving in regression. Waveform shows the FIFO's threshold input driven by the wrong value.

Root Cause

Positional connections silently shifted by one position when the new port was inserted. The simulator accepts the new wiring as legal — no compile error, no warning.

Fix
Convert to named — order-independent
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fifo u_f(
    .clk      (clk),
    .rst_n    (rst_n),
    .clk_en   (1'b1),     // tied HIGH
    .threshold(th_val)    // correct port
);
// Now order-independent ✅

Ban positional connections in CI via a lint check (Spyglass ConnectByName).

2

Silent Width Truncation

WIDTH TRUNCATION
Buggy Code
Wider signal silently truncated
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [31:0] alu_out;
 
// reg_file.wdata is only 16 bits
reg_file u_rf (
    .wdata(alu_out)  // upper 16 lost!
);
// No error. No warning by default.
Symptom

ALU computes 0x1_2345 but the register file shows 0x2345. Random-looking. Only fails on values greater than 16 bits.

Root Cause

SystemVerilog allows a wider signal to be connected to a narrower port; the upper bits are silently discarded at the port boundary with no diagnostic.

Fix
Explicit truncation or widened port
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Choice #1 — explicit truncate
reg_file u_rf (.wdata(alu_out[15:0]));
 
// Choice #2 — widen the port
// (preferred — keeps data integrity)
module reg_file(
   input [31:0] wdata);

Enable Verilator -Wall -Werror or VCS +lint=PCWM-L to make width mismatches blocking.

3

Inout Driven by `logic` Variable

INOUT NET
Buggy Code
Variable on inout port
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic sda;   // variable
logic scl;
 
i2c_master u_m (
    .sda(sda),    // ❌ X on bus
    .scl(scl)
);
i2c_slave  u_s (.sda(sda),
                .scl(scl));
Symptom

Elaboration error in VCS: "Net type required for inout connection 'sda', got variable". Some tools accept this silently and the bus stays at X for the entire simulation.

Root Cause

inout requires a net (typically wire) because nets support multiple drivers and resolve by strength. A logic variable has exactly one driver and no resolution function, so it cannot represent a shared bus.

Fix
wire + pullups (I²C spec)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire sda, scl;     // nets ✅
pullup(sda); pullup(scl);
 
i2c_master u_m (.sda(sda),
                .scl(scl));
i2c_slave  u_s (.sda(sda),
                .scl(scl));

Always declare bidirectional shared buses as wire (or tri) with appropriate pullup/pulldown strength.

4

`.*` Picking the Wrong Scope

SCOPE SHADOWING
Buggy Code
.* hijacked by generate-local 'a'
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic [7:0] a;   // parent
logic [7:0] b [4];
 
generate for(genvar i=0; i<4; i++) begin : g
   logic [7:0] a; // shadows parent!
   assign a = b[i];
   cmp u(.*);  // uses g.a not top.a!
end endgenerate
Symptom

A generate-array of comparators behaves correctly for instance [0] but garbage for [1..N-1]. Waveforms show all instances reading the same a input from instance [0].

Root Cause

Inside a generate block, .* searches the genvar-iterated scope as well as the parent. The generate-local a shadows the parent a, and the elaborator binds the inner-scope name silently — no warning on most simulators.

Fix
Explicit named — no shadowing risk
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
generate for(genvar i=0; i<4; i++)
                              begin : g
   cmp u (
      .a(b[i]),     // explicit ✅
      .b(b[i+1])
   );
end endgenerate
// No name shadowing risk ✅

Defensive rule: never use .* inside generate blocks unless you have audited every name in both scopes.

5

Unconnected Input Floating to X

FLOATING INPUT
Buggy Code
Input port omitted
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ctrl u_c (
    .clk  (clk),
    .rst_n(rst_n),
    // .mode_sel omitted!
    .out  (dout)
);
// Warning ignored at compile.
// mode_sel = X → every
// case-statement → X branch.
Symptom

The entire downstream pipeline is X from cycle 0. Reset doesn't help. The block's internal state goes X within one clock.

Root Cause

An unconnected input port floats to X. Every case statement reading that input falls through to the default (X) branch, poisoning all dependent logic.

Fix
Tie inputs explicitly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ctrl u_c (
    .clk     (clk),
    .rst_n   (rst_n),
    .mode_sel(2'b00),  // chosen mode
    .out     (dout)
);
// Promote "port unconnected" to ERROR
// in CI — never let it ship.

Promote "port unconnected" lint warnings (Spyglass W287, Synopsys VC UndrivenInTerm) to errors in CI.

6

Mixing `wire` and `logic` at the Connection

IMPLICIT WIRE
Buggy Code
Implicit wire created — two drivers
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// status not declared in parent
block_a u_a(.out(status));
block_b u_b(.out(status));
 
// Verilog auto-declares 'status'
// as a 1-bit wire (`default_nettype
// wire is on). Both blocks now drive
// the same net → X on conflict
// or merged in synthesis. 🐛
Symptom

Elaboration succeeds. Simulation runs. RTL-level results match the testbench. But synthesis silently drops a continuous-assign because two parallel drivers tied to the same implicit net are merged.

Root Cause

With `default_nettype wire (the legacy default), any undeclared identifier used at a port connection becomes an implicit 1-bit wire. Two instances driving the same name produce a multi-driver net — undefined in RTL, silently merged in synthesis.

Fix
SystemVerilog — default_nettype none + explicit declarations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`default_nettype none
// Force every signal to be declared.
 
logic status_a, status_b;
block_a u_a(.out(status_a));
block_b u_b(.out(status_b));
// One net per driver ✅
// Catches typos at compile.

Put `default_nettype none as the first line of every .sv file via a template or pre-commit hook.

Senior Engineer Insights & Best Practices

  • 🚀 RTL Insight. Treat your port list as a versioned API. Adding, reordering, or renaming a port is a breaking change to every parent. Bump a version comment at the top of the file and announce it on the team channel.
  • ⚠️ Common Mistake. Engineers use positional in "just a small instantiation" and tell themselves they will fix it later. They never do. By the time the design has 50 instantiations, refactor cost is prohibitive — and silent bugs are inevitable.
  • 🔍 Verification Tip. In UVM testbenches, prefer interfaces + virtual interfaces over long named port lists. It collapses hundreds of connections to one .vif(vif) and refactors cleanly.
  • 🏗 Synthesis Concern. Unconnected outputs synthesise to optimised-away logic — fine. Unconnected inputs can drive synthesis to insert pull-low cells or X-propagation gates, ballooning area and creating CDC false-positives.
  • ⏱ Timing Analysis. Wide buses tied to constants (.data('0)) are optimised at synthesis and produce zero-timing-paths. This is the cleanest way to disable a feature block without ifdef-storming the RTL.
  • 📜 LRM Reality. IEEE 1800-2017 §23.3.2 — port connections are evaluated at elaboration time, not runtime. That is why you cannot use a runtime variable to pick which signal drives a port — only generate + parameter expressions, evaluated before time 0.

Team-Wide Rules That Save Tape-Outs

RuleWhy it mattersHow to enforce
No positional connections in RTLEliminates silent reorder bugs forever.Lint rule (Spyglass ConnectByName); reject at CI.
`default_nettype none at every file topForces every signal to be declared — catches typos.Add as the first line of every .sv via template / git pre-commit hook.
Width-mismatch warnings → errorsStops silent truncation/extension at the source.Verilator -Wall -Werror; VCS +lint=PCWM-L.
Unconnected input port → errorStops floating X bugs from reaching simulation.Spyglass W287; Synopsys VC UndrivenInTerm.
Ban .* inside generate blocksAvoids the scope-shadowing trap (Bug #4).Team coding-style document; PR review checklist.
Interfaces for any bus with more than 8 signalsReduces refactor cost, eliminates connection mistakes.Code review: long port lists must be justified.

Refactoring Legacy Positional → Named (Industrial Approach)

You inherit a codebase with thousands of positional instantiations. You cannot rewrite them all by hand. Here is the pragmatic, low-risk approach used by real teams in IP houses and SoC groups.

  1. Lock the design. Tag the current commit. Run full regression to get a known-good baseline (lint + sim + synth + equivalence-check report).
  2. Identify hot modules. Use grep / ripgrep to find the most-instantiated modules (highest blast radius if their port list ever changes). Refactor those first.
  3. Script the conversion. Tools like verible-verilog-format, Pyverilog, or in-house Perl scripts can re-emit positional instantiations as named by parsing the module definition and the instantiation site.
  4. Run formal equivalence (LEC). After the refactor, run Synopsys Formality or Cadence Conformal against the baseline RTL. If LEC passes, the refactor is guaranteed-equivalent — no functional risk.
  5. Land in small chunks. One module per PR. Easy to revert, easy to review, easy to bisect if a downstream tool chokes.
  6. Lock it in. Add the lint rule banning positional connections. Past that day, no new positional instantiation can be merged.
Shell — find & rank positional instantiations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# Find positional instantiations: open-paren followed by
# an identifier (NOT a dot) — heuristic but catches 95% of cases.
rg -t verilog -nP '\b\w+\s+\w+\s*\(\s*[a-zA-Z_]' rtl/ \
  | grep -v '\.\w' \
  | awk -F: '{print $1}' \
  | sort | uniq -c | sort -rn \
  | head -20
 
# Output (most-frequent files first — refactor these first):
#    34  rtl/cpu/decode_stage.sv
#    27  rtl/mem/cache_ctrl.sv
#    19  rtl/io/uart_top.sv
#    ...

Interview Q&A — Port Connections

These are the questions asked at the senior-RTL and senior-DV interview loops at Intel, NVIDIA, Qualcomm, AMD, Apple Silicon, and most SoC product companies. Each has a one-liner answer and the longer answer your interviewer wants to hear.

Short: because adding or reordering a port silently re-wires every instantiation with no compiler error.

Long: positional connections rely on the lexical order of the module's port list. Any maintenance change to that order changes hardware behaviour everywhere the module is used. Bugs surface late — usually in regression on an unrelated test, or worse, in silicon — and they are hard to triage because the simulator accepts the new code as legal.