Verilog · Chapter 9.3 · Design & Testbench Creation
Module Port Mapping in Verilog — Named vs Positional Connections
When you instantiate a module, its pins attach to signals in the surrounding design, and this page makes that connection precise. It matters more than it looks, because a wrong connection produces a design that compiles cleanly yet is silently incorrect. Port mapping is how an instance's ports wire to parent signals, and there are two ways to do it. Connecting by name attaches each port to a signal explicitly, so the order does not matter, while connecting by position relies on order and is the source of one of the most punishing silent bugs in RTL. This lesson teaches both styles, the width and direction rules that govern every connection, what happens when a port is left unconnected, and why production teams insist on always connecting by name.
Foundation20 min readVerilogPort MappingInstantiationNamed ConnectionRTL Design
Chapter 9 · Section 9.3 · Design & Testbench Creation
1. The Engineering Problem
You instantiate a verified divider block — inputs dividend and divisor, output quotient — and connect it by position, the way it looked natural to write:
// divider ports, in declaration order: (dividend, divisor, quotient)
divider u_div ( b, a, result ); // intended a/b ... but wired b/aThe design compiles. No error, no warning. Simulation runs. And every division is backwards — the block computes b / a instead of a / b — because the positional connection wired a to the divisor port and b to the dividend port. The divider block is flawless; the connection is wrong. There is nothing in the source to catch your eye, because order-based connection has no names to check against.
This is the defining hazard of port mapping:
A wrong port connection is not a syntax error — it is a silent functional bug. The tool happily wires whatever you tell it to; whether you told it the right thing is not something it can check when you connect by position.
Port mapping looks like trivial plumbing, but it is where verified blocks get wired into broken designs. This page teaches the two ways to connect ports, the rules that govern every connection, and the one practice — connecting by name — that turns the silent-swap class of bug into an impossibility.
2. Mental Model — Mapping Honors the Interface Contract
Visual A — two ways to connect the same instance
3. The Hardware View — A Connection Is a Wire Join
Physically, a port connection merges two nodes into one. The instance's port pin and the parent signal it connects to become the same electrical wire. Which side drives that wire is set by the port's direction:
inputport — the parent drives, the instance receives. The connected parent signal feeds a value into the instance.outputport — the instance drives, the parent receives. The instance's internal logic feeds a value out to the parent signal.inoutport — bidirectional; either side may drive (a bus). Always a net.
parent: in1 ───────────┐
│ (same wire after connection)
instance port: a ──────┘ a receives in1's value (a is an input)
instance port: y ──────┐
│
parent: result ────────┘ result receives y's value (y is an output)This is why direction governs what a port may connect to. An output is driven by the instance, so in the parent it must connect to something that can receive a drive — a net (wire), not a procedural variable (reg). An input is driven by the parent, so the parent side can be a net or a variable (whatever computes the value). Get the direction-vs-target rule wrong and the tool rejects it — or worse, the design behaves unexpectedly. (The net-vs-variable distinction is Chapter 5; here, the rule to hold is outputs connect to nets in the parent.)
4. Named Connection — The Robust Form
Named connection states each pairing explicitly with .port_name(parent_signal).
// definition: module full_adder (input a, b, cin, output sum, cout);
full_adder u_fa (
.a (x),
.b (y),
.cin (carry_in),
.sum (s),
.cout (carry_out)
);Why named connection is the production default:
- Order-independent. You can list the ports in any order; the names carry the meaning. Reordering the definition's ports later does not break a named instantiation.
- Self-checking. A wrong or misspelled port name is a compile error — the tool knows the module's port names and rejects
.dividentd(...). The silent-swap bug from §1 is impossible: you cannot connect to the wrong port without naming it. - Readable and reviewable. Each line says exactly which pin goes where. A reviewer can verify the wiring without counting positions or opening the definition.
- Robust to interface change. Add a new port to the module and named instances simply leave it unconnected (a catchable condition) rather than silently shifting every connection by one.
For anything beyond a trivial gate, named connection is the only form a serious codebase allows.
5. Positional Connection — Compact and Fragile
Positional connection lists only the signals, matched to ports in declaration order.
// definition: module full_adder (input a, b, cin, output sum, cout);
// 1 2 3 4 5
full_adder u_fa ( x, y, carry_in, s, carry_out );
// 1 2 3 4 5The first signal connects to the first declared port, the second to the second, and so on. It is compact, and that is its only advantage. Its dangers:
- Order is the meaning, and nothing checks it. Swap two same-width signals and you get a silent functional bug (§1) — no error, because every position is still "valid," just wrong.
- Fragile to interface change. Insert a port into the middle of the definition, and every positional instantiation downstream silently shifts by one — a catastrophic, hard-to-find break across the whole codebase.
- Unreadable at scale.
dma u_dma ( clk, rst_n, a, b, c, d, e, f, ... );tells a reviewer nothing about which signal is which without cross-referencing the definition.
Positional connection is tolerable only for tiny, stable, obvious interfaces (a single primitive gate) — and even there, most teams ban it. The rule below is not stylistic preference; it is bug prevention.
Visual B — the silent-swap hazard of positional connection
6. Named vs Positional — The Decision
Named (.port(sig)) | Positional (ordered) | |
|---|---|---|
| Meaning carried by | The names | The order |
| Wrong connection | Compile error (caught) | Silent functional bug |
| Order sensitivity | Order-independent | Order is everything |
| Interface change | New ports just left open | Shifts every connection |
| Readability | Self-documenting | Opaque at scale |
| Compactness | More verbose | Terse |
| Use in production | ✅ Required | ❌ Banned (or gate-only) |
The rule: connect by name. The verbosity of named connection buys self-checking, readability, and immunity to the silent-swap and interface-shift bug classes — a trade every production team makes without hesitation. Reserve positional connection, if at all, for a single-line primitive whose port order is universally known. (SystemVerilog adds .port implicit-name shorthand and .* auto-connect to reduce named-connection verbosity, but those are IEEE 1800 conveniences — the portable Verilog default is explicit named connection.)
7. Connection Rules — Width, Direction, and Unconnected Ports
Beyond which signal connects to which port, three rules govern whether the connection is sound.
7.1 Width matching
The port and the signal connected to it should have the same bit width. A mismatch does not error — it silently adjusts:
// module reg8 (input [7:0] d, ...); // port is 8 bits
wire [3:0] data4;
reg8 u_r ( .d(data4), ... ); // connecting 4 bits to an 8-bit port
// -> upper 4 bits of d are unconnected (read as 0); usually a bug, only a warningConnecting a narrow signal to a wide port zero-fills the upper bits; connecting a wide signal to a narrow port truncates the top bits. Both are common, silent defects (DebugLab 2). Match widths deliberately; treat width-mismatch warnings as errors.
7.2 Direction matching
Direction decides what a port may connect to in the parent:
outputport → connect to a net (wire) in the parent. The instance drives it; a parent that wants to use the value in procedural code reads that wire. Connecting an output to a parentregis illegal (DebugLab 3).inputport → connect to a net or a variable. The parent drives the value; it may come from awire, areg, or a literal.inoutport → connect to a net only. Bidirectional buses are nets.
7.3 Unconnected ports
A port may be left unconnected, with different consequences by direction:
- Unconnected
input— floats (z), which propagates asxthrough logic. Almost always a bug; the instance is missing a value it needs. - Unconnected
output— left open. Harmless if the parent genuinely does not use that output; otherwise a missing connection.
Named connection makes "this port is intentionally unconnected" explicit and reviewable; positional connection forces you to account for every position, often with awkward placeholder commas.
Visual C — the connection rules at a glance
What governs a sound connection
data flow8. Worked Examples
Three connections — the right way, the dangerous way, and the partial-connection case.
8.1 Example 1 — named connection (the standard)
module top (
input [3:0] a, b,
input cin,
output [3:0] sum,
output cout
);
adder4 u_add (
.a (a),
.b (b),
.cin (cin),
.sum (sum),
.cout (cout)
);
endmoduleEvery port named, every pairing explicit. A reviewer verifies the wiring line by line; reordering adder4's ports later cannot break this; a misspelled port name is caught at compile. This is the form to write by default, every time.
8.2 Example 2 — the positional pitfall
// module divider (input [7:0] dividend, input [7:0] divisor, output [7:0] quotient);
module top (
input [7:0] a, b,
output [7:0] result
);
// INTENT: result = a / b. But the first two ports are swapped.
divider u_div ( b, a, result ); // wires b→dividend, a→divisor → computes b/a
// Compiles clean. Simulates. Silently computes the wrong thing.
endmoduleBoth a and b are 8 bits, so the swap is type-valid and the tool says nothing. The fix is not "be more careful with order" — it is to connect by name, which makes the swap a non-issue:
divider u_div ( .dividend(a), .divisor(b), .quotient(result) ); // unambiguous8.3 Example 3 — partial / intentionally unconnected ports
// module uart (input clk, rst_n, rx, output tx, output [3:0] status, output parity_err);
module top (
input clk, rst_n, rx,
output tx
);
// This design uses tx but not the status or parity_err outputs.
uart u_uart (
.clk (clk),
.rst_n (rst_n),
.rx (rx),
.tx (tx),
.status (), // intentionally unconnected output — open is fine
.parity_err() // intentionally unconnected output
);
endmoduleNamed connection makes the intent explicit and reviewable: every input is driven, and the two unused outputs are deliberately left open with empty (). An unconnected input here (say .rx() left empty) would float and be a real bug — the symmetry is the teaching point: open outputs can be fine, open inputs rarely are.
9. Industry Perspective
- Named connection is policy, not preference. Essentially every production RTL team mandates named port connections and lints positional ones out — the silent-swap and interface-shift bug classes are too costly to leave to care. A code review that finds positional connection on a non-trivial block sends it back.
- Width-mismatch warnings are treated as errors. Mature flows fail the build on connection-width warnings, because a silent truncate/zero-fill is a functional bug that simulation may not expose until a corner case.
- IP integration is port mapping at scale. Integrating a purchased IP block means connecting dozens or hundreds of its ports to the SoC fabric — done entirely by name, often generated from a machine-readable interface description, precisely because manual positional wiring at that scale would be hopeless.
- Lint rules guard the connection. Standard lint decks flag unconnected inputs, width mismatches, and positional connections automatically — the connection is considered too error-prone to leave unchecked.
10. Common Mistakes
- Positional connection on a non-trivial block — invites the silent-swap bug (§1, DebugLab 1). Use named.
- Swapped same-width ports — the specific, undetectable failure of positional connection. Named connection prevents it.
- Width mismatch — connecting a narrow signal to a wide port (or vice versa); silent zero-fill/truncation (DebugLab 2).
- Connecting an output to a parent
reg— direction violation; outputs connect to nets (DebugLab 3). - Unconnected input left floating — the instance is missing a value; propagates
x. Distinct from an intentionally-open output.
11. Debugging Lab
Three port-mapping debug post-mortems
12. Interview Q&A
13. Exercises
Exercise 1 — Convert positional to named
Given module mux2 (input sel, input d0, input d1, output y);, rewrite this positional instantiation as a named one:
mux2 u_mux ( s, in0, in1, out );Exercise 2 — Spot the silent bug
module sub (input [7:0] minuend, input [7:0] subtrahend, output [7:0] diff); is instantiated as sub u_sub ( x, y, d ); with the intent d = x - y. Is the connection correct? If the intent were d = y - x, what is the safest way to express it? Explain why the positional form is risky here.
Exercise 3 — Apply the connection rules
For module sensor (input clk, output [11:0] reading); instantiated in a parent that needs to register reading into a flop: (a) what type must the parent-side signal connected to reading be, and why; (b) how does the parent then get reading into a reg? Answer in two or three sentences (no full code needed).
Exercise 4 — Review the connections
List every port-mapping problem in the instantiation below (referencing §7/§10), and give a one-line fix for each.
// module alu (input [15:0] a, input [15:0] b, input [2:0] op, output [15:0] y, output zero);
reg [15:0] y_result;
wire [7:0] operand_a;
alu u_alu ( operand_a, b, op, y_result, zero_flag );14. Summary
Port mapping is how an instance's ports connect to signals in the parent — the wiring layer of instantiation, and the place where verified blocks get wired into correct (or silently broken) designs.
The core ideas:
- A connection is a wire join between an instance port and a parent signal; the port's direction decides which side drives it (input: parent drives; output: instance drives; inout: bidirectional).
- Two connection styles — by name (
.port(signal), order-free, self-checking) and by position (ordered, where the order is the meaning and nothing checks it). - Positional connection is dangerous — a wrong order is a valid connection to the wrong port, a silent functional bug; it is also fragile to interface change and unreadable at scale.
- Connection rules: match widths (mismatch silently zero-fills or truncates), respect direction (outputs connect to nets, not parent
regs), and treat unconnected inputs as bugs (they float) while open outputs may be fine.
The discipline this page instils:
- Connect by name, always — the names are the contract; let the tool check them.
- Match widths deliberately and treat width-mismatch warnings as errors.
- Drive outputs onto nets; leave a port open only on purpose, and never an input.
You can now define modules (9.1), turn them into instances (9.2), and wire those instances together correctly (9.3) — the complete mechanical toolkit for assembling a hierarchy. What remains is judgement: Chapter 9.4 Design Module Fundamentals moves from "how to connect blocks" to "how to write a good design block" — clean interfaces, separation of concerns, and the structural habits that make a module a quality piece of hardware, not just a working one.
Related Tutorials
- Module Instantiation — Chapter 9.2; placing the instances whose ports this page connects.
- Module Structure & Elements — Chapter 9.1; the port list that forms the interface being mapped.
- reg — Chapter 5.2.1; the net-vs-variable distinction behind the output-connects-to-a-net rule.
- wire and tri Nets — Chapter 5.1.1; the nets that instance outputs drive.