Verilog · Chapter 9.1 · Design & Testbench Creation
Module Structure & Elements in Verilog — The Hardware Building Block
This is your first design-construction lesson, and it centers on the module, the fundamental building block every digital system is assembled from. A module is best understood not as a file or a function but as a reusable hardware block. It is a sealed box with a defined boundary, its ports, an internal implementation, its logic, and a name so it can be reused. This page teaches what a module represents, why modules and hierarchy exist, how a module boundary encapsulates its internals, and the elements a module can contain. The approach is hardware-first and structure-first rather than syntax-first. It stops short of connecting modules together, since instantiation, port mapping, and design methodology each get their own lesson. Finish this and you will read a module the way an engineer does, as a block of hardware with an interface.
Foundation20 min readVerilogModulesHierarchyEncapsulationRTL Design
Chapter 9 · Section 9.1 · Design & Testbench Creation
1. The Engineering Problem
A modern system-on-chip packs a CPU, a UART, a DMA engine, a memory controller, an SPI controller, and a bank of timers onto a single die — millions of gates, described across thousands of RTL files, built by a large team. Nobody can hold millions of gates in their head, and nobody can edit a single file thousands of pages long. So the first question of real design is not "what is the syntax?" but:
How do engineers organize this much complexity so a human can design it, a team can divide it, and a tool can verify it?
Every engineering discipline answers the same way: build big things from small, reusable, well-bounded parts. A car is engine + transmission + chassis + electronics, each built from smaller parts. A program is functions and classes. A chip is modules.
A module is the Verilog unit of that decomposition — a self-contained block of hardware with a clear boundary, reusable wherever it is needed. The CPU is a module. So is the UART. So is the FIFO inside the UART, and the register inside the FIFO. The entire chip is a hierarchy of modules nested inside modules. This page is about that building block: what it is, why it exists, and what lives inside it.
2. Mental Model — A Module Is a Reusable Hardware Block
Visual A — hierarchy: every box is a module
3. The Hardware View — The Module Boundary
The most important feature of a module is its boundary. A module draws a line between inside (its logic) and outside (the rest of the design), and everything crosses that line through a defined set of ports.
outside world
│ (inputs cross in)
▼
+---------------------+
| Module |
| |
| internal logic |
| (hidden inside) |
| |
+---------------------+
│ (outputs cross out)
▼
outside worldThe boundary gives you three things that make large design possible:
- Interface. The ports are the contract. Anything outside the module interacts with it only through its inputs and outputs — never by reaching inside. The interface is the promise the module makes to the rest of the chip.
- Encapsulation. The internal logic is hidden. You can redesign a module's guts completely — swap a slow multiplier for a fast one — and nothing outside changes, as long as the boundary (the ports and their behaviour) stays the same.
- Independence. Because each module is sealed behind its boundary, engineers can build and verify blocks independently and trust them to compose. The boundary is what turns "a million gates" into "fifty blocks I can reason about one at a time."
Think of a module like an integrated-circuit chip on a board: you wire to its pins and trust its datasheet; you never open the package. The ports are the pins; the module's behaviour is the datasheet; the silicon inside is sealed.
Visual B — the module boundary
4. What a Module Is Not
Three misconceptions arrive with learners from other backgrounds. Each is worth rejecting explicitly, because each leads to a different design mistake.
- A module is not a file. A file is a place to store text; a module is a hardware block. One file can hold several modules, and a large module can be split across files. Equating the two leads to thinking "one file = one thing," which misses that the real unit is the hardware block, not the storage.
- A module is not a function. A function (in software) runs when called and returns; a module exists and runs continuously, in parallel with everything else. A module is not "invoked" — it is built and then it is always on. Treating modules like functions produces the single most common beginner error: expecting sequential, call-and-return behaviour from parallel hardware.
- A module is not a software object. It has no methods you call, no runtime construction in the software sense, no garbage collection. The superficial resemblance (a bounded thing with an interface) hides a fundamental difference: a module is physical concurrency, not an object whose methods execute on a CPU.
The through-line: a module is hardware that exists and runs continuously, defined once and reusable. Hold that, and the misconceptions dissolve.
5. Module Anatomy — The Structure
Now the structure. Here is the smallest complete module — a two-input AND gate — used only to expose the anatomy, not to teach the logic operator inside it.
module and_gate ( // 1. `module` keyword + 2. module name
input a, // 3. port list — the boundary/interface
input b,
output y
);
// 4. declarations (none needed here) + 5. implementation
assign y = a & b; // the internal logic
endmodule // 6. `endmodule` — closes the blockSix structural elements, in order:
modulekeyword — opens the block. Every module definition begins here.- Module name (
and_gate) — the identifier the block is known by, so it can be reused. Naming it well is a real design decision (§10). - Port list — the boundary. Each port is an
input,output, orinout, declared with a direction. This is the interface every other part of the design connects through. (The mechanics of connecting to these ports is 9.3 — here we only declare the boundary.) - Declarations — internal signals, parameters, and other local items the body needs (none required for this tiny example; introduced in §6).
- Implementation — the logic that defines what the module does. Here, one
assign. In real modules this is the bulk of the block. endmodule— closes the definition. Mandatory; a module without it is incomplete (§11).
The shape is invariant: module name (ports); declarations; implementation; endmodule. Every module you ever write — a gate or a CPU — has exactly this skeleton. Learn the skeleton and the rest is filling it in.
Visual C — module anatomy
The structure of every module
data flow6. The Elements a Module Can Contain
A module is a container. Inside its boundary it can hold any of the following — introduced here conceptually, each drilled in its own later chapter.
| Element | What it is (one line) | Taught in |
|---|---|---|
| Ports | The boundary signals — input / output / inout — that form the interface. | 9.3 (mapping) |
Nets (wire) | Internal connections that carry a value driven from elsewhere. | Ch 5 (live) |
Registers (reg) | Internal signals assigned in procedural blocks; may become storage. | Ch 5 (live) |
| Parameters | Build-time constants that configure the block per instance. | Ch 6 (live) |
| Instances | Other modules placed inside this one — how hierarchy is built. | 9.2 |
Continuous assignments (assign) | Dataflow logic that continuously drives a net. | Ch 13 |
Procedural blocks (always / initial) | Behavioural logic — combinational or sequential. | Ch 14 |
The single idea to take from this table: a module is the container that holds all of these. You do not need to know how each works yet — only that the module is the box they live in, and that the box has a boundary separating them from the outside world. The bulk of the rest of the Verilog track is learning to fill this container well.
7. Module vs Instance — A Preview
One distinction must be introduced now, because it underlies everything, even though its mechanics belong to the next page.
A module is a definition — a blueprint. Writing module fifo (...) ... endmodule does not create any hardware; it describes a kind of hardware block, the way an architect's blueprint describes a kind of house without building one.
An instance is actual hardware — a specific FIFO placed into the design, built from the blueprint. One module definition can produce many instances, each independent silicon, just as one blueprint builds many identical houses on a street.
Module = blueprint (a definition). Instance = actual hardware (built from the blueprint).
This is why "design once, use many times" works: you write the module definition a single time, and the design uses as many instances of it as it needs. How you place an instance into a design — the instantiation syntax — is Chapter 9.2, the very next page. Here, only hold the conceptual split: the thing you write is the blueprint; the thing on silicon is the instance.
Visual D — module vs instance (preview)
8. Worked Examples
Three modules, growing in size, each chosen to show structure — not to teach the logic inside.
8.1 Example 1 — an AND gate (structure in the smallest form)
module and_gate (
input a,
input b,
output y
);
assign y = a & b;
endmoduleThe minimum viable module: a name, a three-port boundary (two inputs, one output), and a one-line body. Read it as hardware — a block with two input pins and one output pin, AND-ing continuously. Every structural element from §5 is present even at this scale.
8.2 Example 2 — a 4-bit adder shell (larger boundary, vector ports)
module adder4 (
input [3:0] a, // 4-bit input bus
input [3:0] b, // 4-bit input bus
input cin, // carry in
output [3:0] sum, // 4-bit result bus
output cout // carry out
);
// implementation goes here — the boundary is what matters at this stage
assign {cout, sum} = a + b + cin;
endmoduleThe same skeleton, a richer interface: multi-bit (vector) ports and five signals total. Notice how much the boundary alone communicates — a reader knows this block adds two 4-bit numbers with carry-in and carry-out before reading a line of the body. That is the value of a well-shaped interface: the boundary documents the block. (The body's + and concatenation are Chapter 10/13 material — here, focus on how the ports organize the block.)
8.3 Example 3 — a counter shell (a module can contain sequential logic)
module counter4 (
input clk, // clock
input rst_n, // active-low reset
output [3:0] count // current count
);
// A module is a CONTAINER — it can hold sequential logic too.
// (The always-block mechanics are Chapter 14; shown here only to prove
// that sequential hardware lives inside a module like everything else.)
reg [3:0] count_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count_q <= 4'd0;
else count_q <= count_q + 4'd1;
end
assign count = count_q;
endmoduleThe point of this example is not the sequential logic — that is Chapter 14 — but that a module is a container general enough to hold it. Combinational blocks (Example 1, 2), an internal reg declaration, a procedural always block, and a continuous assign all coexist inside one boundary. Whatever the logic, the module is the box around it, and the box always has the same skeleton.
9. Industry Perspective
Module count is a direct measure of design scale, and it is why hierarchy is not optional but mandatory.
| Design scale | Approximate module count | Why hierarchy is required |
|---|---|---|
| Small FPGA project | ~10–50 modules | Even here, one flat module would be unreadable and un-reusable. |
| Medium ASIC block | ~100–500 modules | No single engineer owns it all; clean boundaries let a team divide it. |
| Large SoC | thousands of modules | Impossible to design, verify, or comprehend without deep hierarchy. |
Three industrial truths follow:
- Hierarchy is mandatory, not stylistic. Beyond a few hundred gates, a flat design cannot be read, reused, or verified. Decomposition into modules is what makes scale physically achievable for a human team.
- Modules are the unit of ownership. Teams are organized around module boundaries — one engineer or squad owns a block and its interface. The boundary is also an organizational line, not just a technical one.
- Reuse is the economic engine. A verified module (a FIFO, a UART, a standard interface) is reused across projects and licensed as IP. The whole semiconductor-IP industry exists because a module is a reusable hardware block.
10. Common Mistakes
Five mistakes that come from misunderstanding the module as a block, not from forgetting syntax.
- Missing
endmodule. Every module must close withendmodule. Omit it and the parser keeps reading the next module (or end-of-file) as part of this one — a confusing cascade of errors far from the real cause (§11, DebugLab 1). - Treating modules like functions. Expecting a module to "run when called" and "return a value." A module is parallel hardware that exists continuously; it is built, not invoked. This misconception produces designs that simulate nothing like the author expected.
- One giant monolithic module. Cramming an entire design into a single module. It cannot be read, reused, divided among a team, or verified in isolation — the opposite of why modules exist (§11, DebugLab 3).
- Mixing unrelated functionality. Putting, say, a UART and a memory controller in one module because they happen to share a clock. A module should do one cohesive thing; unrelated logic in one box destroys reuse and clarity.
- Poor naming. A module named
block1ortemptells a reader nothing. The name is part of the interface —uart_tx,fifo_sync,adder4communicate the block's role at a glance. Bad names compound across thousands of modules.
11. Debugging Lab
Three module-structure debug post-mortems
Pitfall 1 — missing endmodule cascades into the next module
module and_gate (
input a,
input b,
output y
);
assign y = a & b;
// <-- endmodule is missing here
module or_gate (
input a,
input b,
output z
);
assign z = a | b;
endmodule
// Compiler error points at 'module or_gate' or even further down —
// nowhere near the actual problem in and_gate.The compiler reports a syntax error on the 'module or_gate' line (or a confusing 'unexpected module / nested module not allowed' error), even though or_gate looks perfectly correct. The reported line has nothing wrong with it. The real defect is elsewhere.
A module definition ends ONLY at its 'endmodule'. Because and_gate's endmodule is missing, the parser never closes and_gate — so when it reaches 'module or_gate', it sees a module declaration INSIDE the still- open and_gate. The error is reported at or_gate, but the cause is the missing endmodule one module earlier.
This is why structural errors often point past the real problem: the parser only discovers something is wrong when the next construct doesn't fit inside the block it wrongly thinks is still open.
The fix is to close every module with its own endmodule. When a module error points at the START of the NEXT module, suspect a missing endmodule in the PREVIOUS one.
module and_gate (
input a,
input b,
output y
);
assign y = a & b;
endmodule // <-- close the block
module or_gate (
input a,
input b,
output z
);
assign z = a | b;
endmodule
// Each module now opens and closes cleanly. Both compile.Pitfall 2 — malformed module declaration / boundary
// Ports referenced in the body but never declared on the boundary.
module mux2 (
input sel
input d0, // <-- missing comma after 'sel' above
input d1
// 'y' is used below but never declared as a port
);
assign y = sel ? d1 : d0;
endmodule
// Errors: a parse error around the port list, plus 'y' is an undeclared
// identifier — the module's boundary is malformed.The module fails to compile with a port-list parse error and an 'undeclared identifier y' message. The logic the author intended (a 2:1 mux) is correct in spirit, but the boundary — the interface — is broken: ports are run together without separators and the output isn't declared.
A module's boundary is a precise contract: every port is named, given a direction (input/output/inout), and separated by commas. Two defects break it here. First, there is no comma after 'sel', so the parser cannot tell where one port ends and the next begins. Second, 'y' is driven in the body but never appears in the port list, so it is not part of the interface — the outside world has no way to read the mux's output, and the compiler sees an undeclared name.
The boundary is not optional detail; it IS the module's interface. A malformed boundary means the block cannot be connected or even parsed.
The fix is to declare every port completely and separate them correctly, including the output the block is supposed to produce.
module mux2 (
input sel,
input d0,
input d1,
output y // <-- output is part of the boundary
);
assign y = sel ? d1 : d0;
endmodule
// Every port is named, directioned, and comma-separated. The interface
// is well-formed: sel/d0/d1 in, y out.Pitfall 3 — one giant module doing everything
// A single module that is the entire design: UART + timer + GPIO + an
// arbiter, all in one boundary, sharing one tangle of internal signals.
module soc_everything (
input clk, rst_n,
input [31:0] cpu_addr, cpu_wdata,
output [31:0] cpu_rdata,
input uart_rx,
output uart_tx,
output [7:0] gpio,
// ... 80 more ports ...
);
// 4,000 lines: UART logic, timer logic, GPIO logic, bus arbiter,
// all interleaved, all sharing internal nets with no boundaries.
// ... unreadable, untestable, un-reusable ...
endmoduleThe design 'works' in a basic test but is impossible to maintain. No one can find the UART logic without reading all 4,000 lines. A change to the timer accidentally breaks the GPIO because they share internal signals. The UART cannot be reused in another project because it is welded to everything else. Verification can only test the whole blob, never one function.
This is the anti-pattern modules exist to prevent. A module should be ONE cohesive hardware block with a clean boundary. Cramming unrelated functions (UART, timer, GPIO, arbiter) into a single module destroys every benefit of modular design: encapsulation (everything shares internals), reuse (nothing can be lifted out), independent verification (only the whole can be tested), and team ownership (no one owns a piece).
The error is structural, not syntactic — it compiles fine. That is what makes it dangerous: the tool won't warn you, but the design becomes unmaintainable as it grows.
The fix is to decompose into a hierarchy: each function its own module with its own boundary, composed under a thin top module. (HOW to place those sub-modules into the top is instantiation, Chapter 9.2 — here the lesson is simply that the giant module must be split.)
// Each function becomes its own module with a clean boundary:
module uart ( /* uart ports */ ); /* ... */ endmodule
module timer ( /* timer ports */ ); /* ... */ endmodule
module gpio ( /* gpio ports */ ); /* ... */ endmodule
module arbiter ( /* arb ports */ ); /* ... */ endmodule
// A thin top module composes them into a hierarchy. (The composition
// mechanics — instantiation and port mapping — are Chapters 9.2 / 9.3.)
module soc_top ( input clk, rst_n, /* ... */ );
// sub-modules placed and connected here
endmodule
// Now each block is readable, reusable, independently verifiable, and
// individually ownable. This is what 'every box is a module' buys you.12. Interview Q&A
13. Exercises
Four exercises, progressive — from reading structure to critiquing it.
Exercise 1 — Identify the module elements
For the module below, label each structural element: the module keyword, the module name, each port (with its direction), the implementation, and endmodule.
module xor_gate (
input a,
input b,
output y
);
assign y = a ^ b;
endmoduleExercise 2 — Create a simple module shell
Write the shell (boundary only, body left as a comment) of a module named inverter with one input a and one output y. Produce only the structure — module / ports / // logic here / endmodule. Do not write the logic.
Exercise 3 — Design a module interface for a comparator
Design the boundary (port list only) of a module named comparator4 that compares two 4-bit numbers a and b and reports three results: a > b, a == b, and a < b. Decide the port names and directions; do not implement the logic. State in one sentence what your interface communicates to a reader before they read the body.
Exercise 4 — Review a poorly structured module
The module below compiles but is badly structured. List the structural problems (from §10) and describe how you would reorganize it — without writing the fix in full.
module thing (
input clk, rst_n, uart_rx, spi_miso,
input [31:0] mem_addr,
output uart_tx, spi_mosi, spi_clk,
output [31:0] mem_rdata,
output [7:0] led
);
// 1,500 lines mixing UART, SPI, a memory interface, and LED blink
// logic, all sharing the same internal nets, no internal boundaries.
endmodule14. Summary
A module is the fundamental building block of digital design — a reusable hardware block with a boundary, an interface, and contained logic. It is not a file, not a function, and not a software object: it is hardware that exists and runs continuously, defined once and reusable many times.
The core ideas:
- Module = reusable hardware block. Hardware (exists, runs in parallel), reuse (one definition, many instances), encapsulation (internals hidden behind the boundary).
- The boundary is the interface. Ports are the contract; the outside world connects only through them; the internals can change freely as long as the boundary holds.
- Hierarchy is a tree of modules. Modules contain modules, from the top-level chip down to individual gates — the only way million-gate designs become tractable.
- The anatomy is invariant.
module+ name → ports → declarations → implementation →endmodule. The same skeleton scales from a gate to a CPU. - A module is a container for ports, nets, registers, parameters, sub-module instances, continuous assignments, and procedural blocks — each drilled in its own later chapter.
- Module vs instance (preview): the module is the blueprint (a definition); an instance is actual hardware built from it. One blueprint, many instances.
The discipline this page instils:
- One module = one cohesive hardware block. Decompose; never build a monolith.
- Name modules for their role — the name is part of the interface.
- Close every module with
endmodule— and when an error points at the next module, suspect a missing one here.
You now read a module the way an engineer does: as a block of hardware with an interface, not a block of text. The next step is to use these blocks — Chapter 9.2 Module Instantiation teaches how to place a module definition into a design as a working instance, and 9.3 Module Port Mapping teaches how to wire instances together into the hierarchy this page has been describing.
Related Tutorials
- Design & Testbench Creation — Chapter 9 overview; why modules and testbenches are the two artifacts all RTL is built from.
- RTL Designing — Chapter 3; the register-transfer mental model the logic inside a module implements.
- reg — Chapter 5.2.1; one of the internal elements a module contains.
- parameter — Chapter 6.1; the build-time constants that configure a module per instance.