Verilog Crash Course Lesson 2 / 8
Lesson 2 · Foundations

Modules, Ports & Signal Types

Learning objective Read and write a module's interface confidently: port directions, bit widths (vectors), the wire/reg distinction (and why it's not what it sounds like), and Verilog's four-valued logic system (0, 1, X, Z).

Anatomy of a module

Every piece of hardware you describe lives inside a module ... endmodule block. The port list is the module's interface to the outside world — the pins on the chip, if you like:

module mux2 (
    input  wire       sel,   // 1-bit select
    input  wire [7:0] a,     // 8-bit data input
    input  wire [7:0] b,     // 8-bit data input
    output wire [7:0] y      // 8-bit data output
);

    assign y = sel ? b : a;

endmodule

This is called ANSI-style port declaration (direction, type, and width all given right in the port list) and it's what you should write. You'll still encounter an older, non-ANSI style in textbooks and legacy code, where the port list only names the ports and their direction/type is declared again inside the module body:

module mux2 (sel, a, b, y);
    input  sel;
    input  [7:0] a, b;
    output [7:0] y;
    // ...
endmodule

Functionally identical — just more to type and easier to get out of sync. Recognize it; write ANSI-style.

mux2 sel a[7:0] b[7:0] y[7:0]

input ports (sel, a, b) are arrows pointing into the module; output (y) points out. The [7:0] on a, b, and y means each is an 8-bit bus, not a single wire — that's a vector, covered next.

Vectors: bundles of bits

A single-bit signal is a scalar. A vector declares a bus of related bits with a range, most-significant bit first:

wire [7:0] data;   // an 8-bit bus, bits data[7] (MSB) down to data[0] (LSB)
wire [0:7] weird;   // legal, but now weird[0] is the MSB — avoid this ordering

Always declare [MSB:LSB] with MSB first (e.g. [7:0], [31:0]). You can select a single bit (data[3]) or a contiguous range, called a part-select (data[7:4], the upper nibble). Indices are just numbers — there's no hidden meaning beyond "which physical wire in the bus."

wire vs. reg: not what the names suggest

This trips up almost everyone coming from software, so it's worth being precise. Neither keyword directly means "this becomes a physical register" or "this is just a wire." They are about how a signal is allowed to be assigned, full stop:

wire

A connection with no memory of its own. It must be driven continuously by something — an assign statement, or by being connected to an output port of an instantiated module. Read it as: "this net is a passive wire; something else drives its value."

reg

A variable that holds its value until a procedural statement (inside an always or initial block, Lessons 3–4) explicitly changes it. Read it as: "this variable is assigned procedurally, one statement at a time, and keeps its last value otherwise."

Whether a reg ends up synthesized as a real flip-flop (physical memory) or optimized away into plain combinational logic depends entirely on how you assign it — specifically, whether the assignment is triggered by a clock edge. You'll see both cases in Lessons 3 and 4. The keyword itself never guarantees either outcome.

Common misconception "reg means it's a register (flip-flop); wire means it's just a wire." Both halves are wrong. A reg driven by combinational logic (Lesson 3) synthesizes to zero flip-flops. The name is a historical wart in the language — the rule that actually matters is the assignment-context rule above.
If your class uses SystemVerilog Many courses now use the SystemVerilog keyword logic instead of wire/reg for most signals — it can be assigned either way and sidesteps this whole confusion. It still can't be driven by two different sources at once (that part of the wire rule still applies). If you see logic in your course materials, mentally treat it as "reg, but the tool will yell at you if you use it somewhere only a true multi-driver wire would work" — rare in practice.

Four-valued logic: 0, 1, X, Z

Software booleans have two values. Verilog signals have four, because simulating and describing real hardware needs to represent more than "true or false":

ValueMeaningWhen you'll see it
0Logic lowNormal digital 0.
1Logic highNormal digital 1.
XUnknownAn uninitialized reg at the start of simulation, or a real conflict — two drivers fighting over one wire, output undefined.
ZHigh-impedanceA wire with no driver at all — disconnected, or a tri-state buffer that's currently switched off. Common on shared buses.
0

Logic low. A normal, well-defined signal.

An important practical consequence: an uninitialized reg starts life as X in simulation. If your simulated output is stubbornly X, the usual cause is a signal that's never actually driven under the conditions you're testing — a missing else, or a register with no reset that hasn't been clocked yet. You'll meet this again as a debugging technique in Lesson 8.

Instantiating a module

To place a copy of a module inside another (build hierarchy), use named port connection — match by name, not position, so the connection survives if the port order ever changes:

module top (
    input  wire       choose,
    input  wire [7:0] x, y,
    output wire [7:0] result
);

    mux2 my_mux (
        .sel (choose),
        .a   (x),
        .b   (y),
        .y   (result)
    );

endmodule

my_mux is the instance name — a label for this particular copy of mux2, useful in simulator waveform views and error messages. The dot-parenthesis pairs (.sel(choose)) mean "connect the module's sel port to my local wire choose." You'll see positional instantiation (mux2 my_mux (choose, x, y, result);) in older code — avoid it; it silently breaks if the module's port order ever changes.

Q: A signal is declared reg [3:0] count; and is only ever assigned inside a clocked always block. What does the reg keyword by itself guarantee about the synthesized hardware?
It guarantees count becomes 4 flip-flops. Nothing by itself — it only means the signal is assigned procedurally and holds its value between assignments. It guarantees count is treated as a signed 4-bit value.
Try it Write just the port list (ANSI-style, no body yet) for a module full_adder that takes three 1-bit inputs — a, b, cin — and produces two 1-bit outputs, sum and cout. You'll fill in the logic in the Lesson 3 exercise.

New terms — port, vector, part-select, net, high-impedance, instance — are in the glossary.

Previous← The Mental Model Contents⌂ All lessons