Modules, Signals, and Numbers
Verilog Module Structure
Reference for the parts that form every Verilog module: the module keyword, ANSI-style ports (input/output/inout), the body, and endmodule, plus how to wire one up by name.
4 min read · Updated
Every Verilog design is a module — a named block with a defined boundary. Four structural parts appear in every one: the module keyword and name, the port list that declares input, output, and inout signals, the body that describes behavior, and the closing endmodule.
The port list is the module's contract with the outside world: it names what comes in and what goes out, and nothing else crosses the boundary. The body then describes how the outputs depend on the inputs, using continuous assignments or procedural blocks.
Reading an unfamiliar module is easiest in this order: find the name, read the ports to learn its interface, then read the body to learn its behavior. The lessons keep this skeleton constant so you can focus on the body.
The three port directions
| Direction | Meaning | Typical net type |
|---|---|---|
input |
Driven from outside the module; read-only inside it | wire |
output |
Driven from inside the module; read-only outside it | wire, or reg if assigned procedurally |
inout |
Bidirectional — driven from either side at different times | wire, typically with tri-state logic |
inout is the least common of the three. It shows up for genuinely bidirectional pins — an I2C data line or a shared external bus — where exactly one side may drive the net at a time and everyone else releases it to high-impedance (z). Using inout for a signal that is really unidirectional just to "keep options open" makes the interface harder to reason about for no benefit.
ANSI-style vs. legacy port declarations
Modern Verilog-2001-style ("ANSI-style") ports declare the direction and width together, in the port list itself:
module adder2 (
input wire [3:0] a,
input wire [3:0] b,
output wire [4:0] sum
);
assign sum = a + b;
endmoduleOlder Verilog-1995-style ("legacy") declarations only name the ports in the port list, then declare each one's direction and width separately in the body:
module adder2_legacy (a, b, sum);
input [3:0] a;
input [3:0] b;
output [4:0] sum;
assign sum = a + b;
endmoduleBoth compile to the same hardware. ANSI-style is shorter and keeps a port's name, direction, and width in one place, so this tutorial and course use it throughout; you will still meet legacy-style ports reading older codebases and some vendor IP.
Predict before running
The supplied checker instantiates adder2 and connects it by named port — .a(x) reads as "connect this module's port a to my local signal x." Named connections check port names at compile time, so a typo becomes a compile error instead of a silently miswired signal; positional connections (adder2 u_adder(x, y, total);) rely entirely on argument order matching the port list.
Before running it, predict sum for x = 15, y = 15 — does a 5-bit output hold the full result, or does it truncate the way a narrower output would?
module adder2 (
input wire [3:0] a,
input wire [3:0] b,
output wire [4:0] sum
);
assign sum = a + b;
endmodule
module tb;
reg [3:0] x, y;
wire [4:0] total;
adder2 u_adder (
.a (x),
.b (y),
.sum (total)
);
initial begin
x = 4'd9;
y = 4'd6;
#1;
$display("x=%0d y=%0d sum=%0d", x, y, total);
x = 4'd15;
y = 4'd15;
#1;
$display("x=%0d y=%0d sum=%0d", x, y, total);
$display("PASS");
$finish;
end
endmoduleExpected output — reveal after you predict
x=9 y=6 sum=15
x=15 y=15 sum=30
PASSA 4-bit plus 4-bit addition needs up to 5 bits (15 + 15 = 30, and 5'd30 fits exactly). Because sum is declared [4:0], both results fit with no truncation — see Numbers in Hardware for what happens when the output is too narrow.
Common mistakes
- Positional port connections on a module with more than two or three ports. One reordered argument silently miswires the design; named connections make the same mistake a compile error.
- Declaring a port
inout"just in case." Every genuinely unidirectional signal should beinputoroutput—inoutadds tri-state complexity that only pays for itself on a truly shared, bidirectional net. - Mixing ANSI and legacy declarations for the same port. Pick one style per module; a port declared in the ANSI list should not also be re-declared with
input/outputin the body.
Sources and verification
Example provenance: SkillLift Labs authored the example for this tutorial. tests/scripts/tutorial-verified-examples.test.ts extracts the complete marked example from this Markdown file, compiles it in Verilog-2005 mode with Icarus Verilog, runs it, and requires the simulator output to match the Expected output block exactly. The same test also carries an independent, code-owned semantic oracle, and exercises the article-to-runner source-splitting path. Automation verifies those stated properties; human technical review for indexing remains a separate gate.