Loading header...

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, and logic correctly;
  • 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.

flowchart LR CLK["clk"] --> MOD["module_name"] RST["rst"] --> MOD DIN["data_in[3:0]"] --> MOD MOD --> DOUT["data_out[3:0]"]

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 always blocks 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 wire inside an always block in old Verilog.
  • Assuming reg always 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

Mind Map

mindmap root((Verilog Modules)) Core concept Module is block Ports are boundary Signals connect logic Instances build hierarchy Applications Top level wiring Reusable IP Testbench DUT Bus wrappers Board interfaces Formulas Unsigned N bits gives 0 to two power N minus 1 Signed N bits gives negative half range to positive half minus 1 Counter bits use ceiling log2 of max count plus 1 Design rules Name ports Declare widths One driver per net Use default_nettype none Avoid internal inout Practical checks Port names match Width warnings clean Constraint pins match top No implicit nets Latches not inferred Common mistakes Positional swap Width truncation Wire in always Reg meaning confused Typo creates net