The module: Verilog's only building block
Everything in Verilog is built from one construct: the module. A module is a named block of hardware description, bracketed by the keywords module and endmodule, with a port list declaring how it connects to the outside world. There is no separate notion of a "function" or a "class" the way a software language has โ a module is simultaneously the unit of hierarchy, the unit of reuse, and the unit a synthesis tool turns into an actual piece of hardware.
A module cannot be nested inside another module's module/endmodule pair โ that is illegal in Verilog, full stop. Instead, a design is built by instantiating one module inside another: placing a named copy of it, the way a schematic places a symbol on a sheet and wires it up. This is worth being precise about, because the vocabulary matters for the rest of this course. The module is the template โ written once. An instance is one particular copy of that template, used inside some other module, with its own name and its own connections. A module can be instantiated many times in the same design; each instance behaves identically but is a distinct object as far as simulation and synthesis are concerned.
A first module: the half adder
Let us write the smallest module that does something real โ the half adder from the Digital Electronics path's fourth lesson, which adds two single bits and produces a sum and a carry.
module half_adder (
input wire a,
input wire b,
output wire sum,
output wire carry
);
assign sum = a ^ b;
assign carry = a & b;
endmodule
This uses the modern ANSI-style port list, where each port's direction (input/output) and data kind (wire) are declared right inside the parentheses, next to its name. This is the style every example in this course uses, and the style every synthesis tool and every piece of Verilog written after the year 2001 uses. Older code instead declares a bare list of port names in the parentheses and then redeclares each one's direction in a separate line below โ both forms mean exactly the same thing to a tool, but the ANSI-style form says everything about a port in one place, which is why it has become the default. You may still see the older two-step form in legacy code, so it is worth being able to read it even though this course will not write it.
The body of the module โ everything between the port list and endmodule โ is two assign statements. An assign drives a wire continuously: whenever a or b changes, sum and carry are recomputed immediately, with no clock and no notion of "before" or "after." This is dataflow modeling, and it is the natural way to describe combinational logic; lesson 5 covers it in depth, alongside the alternative, event-driven style for writing the same kind of logic.
Instantiating a module: named versus positional connection
A module on its own does nothing โ it has to be instantiated somewhere to become part of a design. Verilog allows two ways to connect an instance's ports to signals in the surrounding module.
// Named connection -- order does not matter, port names must match exactly.
half_adder u_ha1 (.a(x), .b(y), .sum(s), .carry(c));
// Positional connection -- order must match the module's port list exactly:
// (a, b, sum, carry), in that order.
half_adder u_ha2 (x, y, s, c);
Named connection is the style used throughout this course, and the style used almost universally in real RTL, for a concrete reason: positional connection is a silent trap. If half_adder's port order is ever changed โ say, a later revision swaps sum and carry in the port list to match a new convention โ every positional instance in the design now connects the wrong signals, with no error from any tool, since the number and type of ports still match. A named connection is immune to this, because it binds by name rather than position; the instance keeps working correctly even if the module's internal port order is later rearranged. The instance name itself (u_ha1 above) is not optional in modern Verilog and must be unique within the enclosing module โ it is how a simulator's waveform viewer and a synthesis tool's area report both refer to that particular copy.
Running it: a first testbench
A module by itself cannot be simulated โ nothing drives its inputs. A testbench is a second module, written purely for simulation, that instantiates the design and manually drives its inputs over time. Lesson 10 covers testbenches properly; for now, here is the smallest one that will actually run.
`timescale 1ns/1ps
module tb_half_adder;
reg a, b;
wire sum, carry;
half_adder dut (.a(a), .b(b), .sum(sum), .carry(carry));
initial begin
$display(" a b | sum carry");
a = 0; b = 0; #1 $display(" %b %b | %b %b", a, b, sum, carry);
a = 0; b = 1; #1 $display(" %b %b | %b %b", a, b, sum, carry);
a = 1; b = 0; #1 $display(" %b %b | %b %b", a, b, sum, carry);
a = 1; b = 1; #1 $display(" %b %b | %b %b", a, b, sum, carry);
$finish;
end
endmodule
Two new things appear here that lesson 3 and lesson 10 will explain properly: reg for a signal the testbench itself assigns a value to (a and b are driven by the initial block, so they must be declared reg, not wire), and #1, a delay that tells the simulator to let one time unit pass before continuing โ with no physical meaning at all, since there is no real delay in the circuit being tested, only in the stimulus applied to it. Compiling and running this pair of files in Icarus Verilog produces:
a b | sum carry
0 0 | 0 0
0 1 | 1 0
1 0 | 1 0
1 1 | 0 1
This matches the half adder's truth table exactly, bit for bit, which is the entire point of a testbench: it is not a demonstration, it is a check.
Common mistakes
Forgetting the instance name is the first mistake every beginner makes once, usually by writing half_adder (.a(x), ...) with no name before the parenthesis โ most tools reject this outright, since an unnamed instance has no way to be referred to later. The second is swapping the port direction and the port's effective driver in the testbench: a and b must be reg because the testbench assigns them inside an initial block, while sum and carry must be wire because they are driven by the module instance's own output ports, not by the testbench โ declaring an output port's connection as reg is a compile error. The third is realizing too late that an instance and its module share the same identifier space only by convention, not by rule: nothing stops a designer from naming an instance identically to its own module (this course avoids it, but it compiles), which makes reading a hierarchy dump unnecessarily confusing for no benefit.
Interview questions
What is the difference between a module and an instance? A module is a template โ a named description of hardware, written once. An instance is one specific, named copy of that template placed inside another module; the same module can be instantiated any number of times, each instance acting as an independent object in simulation and synthesis.
Why is named port connection generally preferred over positional? Named connection binds signals to a module's ports by name, so it keeps working correctly even if the module's internal port order changes later. Positional connection binds purely by position, so a reordered port list silently reconnects every positional instance to the wrong signals with no error from any tool.
Can a module definition appear inside another module definition? No โ nesting a module/endmodule block inside another is illegal in Verilog. Hierarchy is built only by instantiation, never by nesting definitions.
Key takeaways
- A module is Verilog's only building block: declared once with
module/endmoduleand an ANSI-style port list, it cannot be nested inside another module definition. - An instance is a specific, named copy of a module placed inside another module; a module can be instantiated many times, and each instance is a distinct object.
- Named port connection (
.port(signal)) survives a later reordering of the module's port list; positional connection does not, and silently reconnects the wrong signals when it breaks. - A testbench is an ordinary module, written purely for simulation, that instantiates the design and drives its inputs โ it has no synthesis meaning and will reappear, in much more depth, in lesson 10.