MODULE in Verilog
The fundamental building block of every Verilog design — understand what a module is, how ports work, and how to instantiate and compose modules into larger systems.
📦 What is a Module?
Every Verilog program begins with the keyword module. A module is the fundamental building block of any Verilog design — it is a named unit of logic that encapsulates its internal implementation and exposes only its interface (its ports) to the outside world.
Think of it like an IC chip: you can see the pins (ports), but the internal circuitry is hidden. You use the chip by connecting to its pins — you don’t need to know how it works internally.
🔌 Port Types
The ports attached to a module can be of three types:
wire by default. An output port that needs to hold a value across clock edges should be declared as output reg.
⬛ Module as a Black Box
A module is always treated as a black box by any module that instantiates it. Only the port list matters from the outside — the internal implementation is completely hidden.
📝 Module Syntax
Every module follows a consistent structure — header, port list, internal declarations, and body — closed by endmodule.
// ┌─ keyword that starts every module ─────────────────────────┐ module module_name // user-defined name (identifier) #( /* parameters */ ) // optional — for configurable modules ( // Port list input clk, // 1-bit input input reset, input [7:0] data_in, // 8-bit input bus output [7:0] data_out,// 8-bit output bus output valid, inout [7:0] bus // bidirectional ); // Internal signal declarations wire [7:0] internal_net; reg [7:0] state; // Module body — logic description at any level always @(posedge clk) begin // sequential logic end assign data_out = internal_net; endmodule // ← always required, no semicolon
endmodule does not take a semicolon, unlike most Verilog statements.
🔧 Port Declaration Styles
Verilog supports two styles for declaring ports. Both are valid — Verilog-2001 style is preferred in modern code:
Verilog-1995 Style (older)
// Port names listed in header, // types declared separately inside module adder (a, b, cin, sum, cout); input [3:0] a, b; input cin; output [3:0] sum; output cout; assign {cout,sum} = a+b+cin; endmodule
Verilog-2001 Style (modern ✅)
// Port type declared inline in header // — more concise, less error-prone module adder ( input [3:0] a, input [3:0] b, input cin, output [3:0] sum, output cout ); assign {cout,sum} = a+b+cin; endmodule
🔁 Module Instantiation
Instantiation means using a module inside another module. Each use creates an independent hardware copy. Two connection styles exist:
⚠️ Positional Connection
// Ports connected by position — // order must match module definition adder u1 (a_sig, b_sig, c_in, sum_out, c_out);
✅ Named Port Connection
// Ports connected by name — // order doesn't matter adder u1 ( .a (a_sig), .b (b_sig), .cin (c_in), .sum (sum_out), .cout(c_out) );
Multiple Instances of the Same Module
// Same module, three different instances — each is independent hardware adder u1 (.a(a0), .b(b0), .cin(1'b0), .sum(s0), .cout(c0)); adder u2 (.a(a1), .b(b1), .cin(c0), .sum(s1), .cout(c1)); adder u3 (.a(a2), .b(b2), .cin(c1), .sum(s2), .cout(c2)); // Above: three 4-bit adders chained into a 12-bit ripple-carry adder
Unconnected Ports
// Leave a port unconnected using empty connection adder u4 (.a(x), .b(y), .cin(1'b0), .sum(result), .cout(/*unconnected*/));
z (high-impedance), which often behaves as x inside gates. Always be intentional about unconnected ports.
🌳 Module Hierarchy
Complex designs are built by composing modules — a top-level module instantiates mid-level modules, which in turn instantiate leaf modules. This forms a tree called the design hierarchy.
soc_top.u_cpu.u_alu.carry_out
⚙️ Parameterized Modules
Parameters make modules configurable and reusable. You define default values in the module and override them at instantiation time — without changing the module source.
// Module with parameters — configurable bit width module adder_n #( parameter N = 8 // default: 8-bit ) ( input [N-1:0] a, b, input cin, output [N-1:0] sum, output cout ); assign {cout, sum} = a + b + cin; endmodule // Instantiate with different widths adder_n u8 (...); // uses default N=8 adder_n #(.N(16)) u16 (...); // 16-bit adder adder_n #(.N(32)) u32 (...); // 32-bit adder adder_n #(.N(64)) u64 (...); // 64-bit adder
Multiple Parameters
module fifo #( parameter DATA_WIDTH = 8, parameter DEPTH = 16, parameter ADDR_WIDTH = $clog2(DEPTH) // derived parameter ) ( input clk, rst, input [DATA_WIDTH-1:0] wdata, output [DATA_WIDTH-1:0] rdata, input wr_en, rd_en, output full, empty ); // ... FIFO logic using DATA_WIDTH and DEPTH ... endmodule // Override at instantiation fifo #(.DATA_WIDTH(32), .DEPTH(64)) u_fifo (...);
defparam instance.PARAM = value; — but this is discouraged in modern code. Use the #() syntax instead.
📋 Module Rules
| Rule | Detail | Allowed? |
|---|---|---|
| Defined once | Each module is defined exactly once in the design. Redefining with the same name causes a compile error. | One definition only |
| Nested definition | A module cannot be defined inside another module. All module definitions are at the top level. | Not allowed |
| Multiple instances | A module can be instantiated any number of times inside other modules, each with a unique instance name. | Allowed ✓ |
| Recursive instantiation | A module cannot instantiate itself (directly or indirectly). Verilog does not support recursion. | Not allowed |
| No semicolon on endmodule | endmodule does not take a semicolon at the end. Common beginner mistake. |
No semicolon |
| Port direction defaults | All ports are wire type by default. Declare output reg explicitly if the output is driven from an always block. |
wire default |
| Instance name required | Every instantiation must have a unique instance name (e.g., u1, u_adder). Unnamed instances are not allowed. |
Name required |
💡 Complete Examples
Fig 5 — D Flip-Flop Module
module dff ( input clk, input rst_n, // active-low reset input d, output reg q // reg because driven from always block ); always @(posedge clk or negedge rst_n) begin if (!rst_n) q <= 1'b0; else q <= d; end endmodule // Instantiate 8 flip-flops to make an 8-bit register module reg8 ( input clk, rst_n, input [7:0] d, output [7:0] q ); genvar i; generate for (i=0; i<8; i++) begin : bit dff u (.clk(clk), .rst_n(rst_n), .d(d[i]), .q(q[i])); end endgenerate endmodule
Fig 6 — 2-to-1 Multiplexer with Inout Bus
// 2-to-1 mux + bidirectional data bus module mux_bus #( parameter W = 8 ) ( input sel, // select line input [W-1:0] a, b, // data inputs output [W-1:0] y, // mux output input oe, // output enable for bus inout [W-1:0] data_bus // bidirectional bus ); // Mux logic assign y = sel ? a : b; // Tri-state driver: drive bus when oe=1, else float (z) assign data_bus = oe ? y : {W{1'bz}}; endmodule
Fig 7 — Top-Level Module Composing Sub-modules
module top ( input clk, rst_n, input [7:0] data_a, data_b, output [7:0] result, output carry ); // Internal wires connecting sub-modules wire [7:0] sum_raw; wire cout_raw; // Sub-module 1: 8-bit adder adder_n #(.N(8)) u_add ( .a (data_a), .b (data_b), .cin (1'b0), .sum (sum_raw), .cout(cout_raw) ); // Sub-module 2: 8-bit register to pipeline the result reg8 u_reg ( .clk (clk), .rst_n(rst_n), .d (sum_raw), .q (result) ); // Register the carry too dff u_carry ( .clk (clk), .rst_n(rst_n), .d (cout_raw), .q (carry) ); endmodule
top module above has no logic of its own — it only wires sub-modules together. This is called structural modeling and is the standard approach for top-level integration in real VLSI projects. Each sub-module can be independently verified before integration.
