Skip to main content

Modules and Ports

A module is Verilog's fundamental unit of hardware: a self-contained block with a name, a set of ports (its connections to the outside world), and a description of what's inside — every piece of hardware, from a single gate to an entire chip, is described as a module or a hierarchy of module instances.

Declaring a module​

module half_adder (
input a,
input b,
output sum,
output cout
);

assign sum = a ^ b;
assign cout = a & b;

endmodule
  • module <name> (<port list>); ... endmodule brackets the whole description — there's no way to leave a module "open"; every module needs a matching endmodule.
  • Each port is declared with a direction (input, output, or inout for bidirectional signals) and, implicitly, a width (a single bit here; multi-bit ports are covered on the next page).
  • The body — here, two assign statements — describes what the module does. assign is covered properly in Dataflow Modeling; for now, read sum = a ^ b exactly the way you'd read the half adder's equation from Digital Design, because that's exactly what it is.

The port list above declares each port's direction and type together, right inside the parentheses — this is called ANSI-style port declaration (named after Verilog-2001, which introduced it). The older non-ANSI style, inherited from the original 1995 standard, only lists bare port names in the parentheses and declares each one's direction separately in the module body:

module half_adder (a, b, sum, cout); // non-ANSI: names only, no direction here
input a, b;
output sum, cout;

assign sum = a ^ b;
assign cout = a & b;
endmodule

Both styles produce identical hardware, and instances of ANSI-style and non-ANSI-style modules can be freely mixed in the same design — but a single module's own declaration can't mix the two styles within itself. ANSI style is preferred in new code because the direction is visible right where the port is declared, instead of split across two places; non-ANSI style still shows up often enough in older or legacy code that recognizing it is worth knowing.

This is the half adder from Combinational Logic Design, and the circuit it builds is exactly the two gates from that page:

a ──┬────────┐
│ ┌─┴─┐
│ │XOR│──── sum
│ └─┬─┘
│ │
b ──┼────┐ │
│ ┌─┴─┐ │
└──│AND│──────── cout
└───┘

Instantiating a module​

A module becomes useful once something else uses it. Instantiation creates a copy of a module's hardware inside another module, wired to specific signals:

module full_adder (
input a,
input b,
input cin,
output sum,
output cout
);

wire ha1_sum, ha1_cout, ha2_cout;

// named port connection — order doesn't matter, port names must match
half_adder ha1 (.a(a), .b(b), .sum(ha1_sum), .cout(ha1_cout));
half_adder ha2 (.a(ha1_sum), .b(cin), .sum(sum), .cout(ha2_cout));

or (cout, ha1_cout, ha2_cout);

endmodule

This is the same "full adder built from two half adders plus an OR gate" decomposition from Combinational Logic Design, coded up directly:

a and b feed HA1; HA1 ha1_sum and cin feed HA2, producing sum; HA1 ha1_cout and HA2 ha2_cout feed an OR gate, producing cout

Each half_adder ha1 (...) line creates one physical copy of the half adder's two gates — ha1 and ha2 are instance names, needed because a module can be instantiated more than once, and each copy needs its own identity for waveform viewers and error messages to refer to. or (cout, ha1_cout, ha2_cout); is a primitive gate instantiation (a built-in Verilog keyword, not a module you wrote) — covered fully in Gate-Level Modeling.

Port connection styles​

The example above uses named port connection (.port_name(signal_name)) — the recommended style, because it's immune to reordering: if half_adder's port list is ever edited, every instantiation using named connection still wires correctly. The alternative is positional connection, matching signals to ports purely by position:

half_adder ha1 (a, b, ha1_sum, ha1_cout); // must match a, b, sum, cout in that exact order
Prefer named connections

Positional connection is shorter to type but silently miswires everything if the module's port order ever changes and the instantiation isn't updated to match — a bug that often shows up as a mysteriously "almost correct" circuit rather than an obvious failure. Named connections cost a few extra characters and avoid that failure mode entirely; use them by default, and reserve positional connection for very small, stable modules (like the primitive gates above) where the port order is fixed by the language itself.

What's next​

half_adder and full_adder above only ever carried single-bit signals. The next page introduces Verilog's data types properly — including vectors, the multi-bit buses needed the moment you want an adder wider than one bit.