Verilog Modules, Ports, Wires, and Regs
A Verilog design is built from modules connected by signals. A module boundary tells the rest of the project what the block consumes and produces. Clean module boundaries make simulation, reuse, synthesis warnings, and design reviews much easier.
Learning Objectives
By the end of this lesson, you should be able to:
- write a readable Verilog module header;
- choose port directions and widths;
- use
wire,reg, andlogiccorrectly; - instantiate one module inside another using named ports;
- catch common width, implicit-net, and multiple-driver mistakes.
Module Boundaries
A module is a hardware block. Ports are the pins of that block.
module module_name (
input wire clk,
input wire rst,
input wire [3:0] data_in,
output wire [3:0] data_out
);
// internal signals and logic go here
endmodule
The range [3:0] means four bits: bit 3 down to bit 0. Bit 3 is normally treated as the most significant bit when the value is used as an unsigned number.
Port Directions and Widths
| Declaration | Meaning |
|---|---|
input wire clk |
one-bit input net |
input wire [7:0] din |
8-bit input bus |
output wire y |
output driven by continuous assignment or submodule |
output reg q |
output assigned in an always block in Verilog |
inout wire sda |
bidirectional net, usually only at I/O boundary |
Avoid inout inside normal RTL. Internal FPGA tri-states usually become muxes, and accidental multi-driver logic is hard to debug.
Wires
Use wire for a net driven by a continuous assignment or by another module output.
wire both_on;
assign both_on = sw0 & sw1;
A wire should normally have one driver. Two continuous assignments to the same wire are usually a design error:
assign led = sw0;
assign led = sw1; // bad: second driver for led
Some buses use deliberate tri-state behavior at physical pins, but that should be explicit and reviewed carefully.
Reg and Logic
Old Verilog uses reg for signals assigned in procedural blocks:
reg [7:0] count;
SystemVerilog commonly uses logic:
logic [7:0] count;
The type does not decide the hardware by itself. The assignment style does:
always @* begin
mux_y = sel ? a : b; // combinational logic
end
always @(posedge clk) begin
count <= count + 1'b1; // flip-flops
end
In combinational procedural blocks, assign every output on every path. Missing assignments infer latches.
Instantiating a Module
Create a reusable majority gate:
module majority3 (
input wire a,
input wire b,
input wire c,
output wire y
);
assign y = (a & b) | (a & c) | (b & c);
endmodule
Use it inside a top module:
module top (
input wire sw0,
input wire sw1,
input wire sw2,
output wire led
);
majority3 u_majority (
.a(sw0),
.b(sw1),
.c(sw2),
.y(led)
);
endmodule
Named port connections are easier to review than positional connections. This matters when modules grow or ports are reordered.
Width Rules
Verilog will often extend or truncate signals. Do not rely on silent behavior for important logic.
wire [7:0] a;
wire [3:0] b;
wire [8:0] sum;
assign sum = {1'b0, a} + {5'b00000, b};
The braces make the extension explicit. The 9-bit sum preserves carry out from adding two 8-bit values.
Common width calculations:
values represented by N unsigned bits = 0 to 2^N - 1
values represented by N signed two's complement bits = -2^(N-1) to 2^(N-1) - 1
bits needed for unsigned count 0 to MAX = ceil(log2(MAX + 1))
For a counter that must count 0 through 999, you need ceil(log2(1000)) = 10 bits.
Worked Example: Nibble Swap
Task: swap the upper and lower nibbles of an 8-bit value.
module nibble_swap (
input wire [7:0] din,
output wire [7:0] dout
);
assign dout[7:4] = din[3:0];
assign dout[3:0] = din[7:4];
endmodule
Equivalent compact style:
assign dout = {din[3:0], din[7:4]};
Instantiate it:
module top (
input wire [7:0] sw,
output wire [7:0] led
);
nibble_swap u_swap (
.din(sw),
.dout(led)
);
endmodule
If sw = 8'b1011_0010, then led = 8'b0010_1011.
Practical Checks
Before synthesis, check:
- every module instance uses the intended port names;
- bus widths match at module boundaries;
- no implicit one-bit nets were created by typos;
- each internal signal has one driver unless multi-driver behavior is intentional;
- combinational
alwaysblocks assign outputs on every path; - top-level ports match the board constraint file.
Add this directive near the top of Verilog files during learning:
`default_nettype none
It makes undeclared signal names an error instead of silently creating accidental wires.
Common Mistakes
- Forgetting bus widths in module ports.
- Connecting ports positionally and swapping signals by accident.
- Assigning to a
wireinside analwaysblock in old Verilog. - Assuming
regalways means a physical register. - Ignoring width truncation or sign-extension warnings.
- Letting a misspelled signal become an implicit net.
Summary
Modules define reusable hardware boundaries. Ports define what crosses the boundary; wires connect continuous logic and submodules; regs or logic variables hold procedural assignments. Make widths explicit, prefer named port connections, and treat synthesis warnings as design feedback.
Next: Combinational Logic in Verilog.
Further Reading
- Verilator warnings for width, implicit net, and port mismatches
- Yosys Verilog frontend documentation
- AMD and Intel HDL coding style guides
- LowRISC Verilog/SystemVerilog style guide