Verilog · Chapter 9.4 · Design & Testbench Creation
Design Module Fundamentals in Verilog — Writing a Good Hardware Block
Earlier sections gave you the mechanics of how to structure a module, instantiate it, and wire its ports. This page is about judgement, meaning the difference between a module that merely compiles and one that is well designed. The central idea is a distinction every engineer must internalize. A design module is hardware that ships to silicon, so it must be synthesizable, do one thing well, and present a clean interface. A testbench module is a simulation-only harness that never becomes a chip. The block a testbench exercises is the design under test, and writing good ones is a discipline of its own built on single responsibility, a minimal and well-named interface, and separation of design from verification. This lesson teaches those qualities rather than the RTL constructs that fill a module.
Foundation20 min readVerilogRTL DesignDUTSynthesizableDesign QualityModules
Chapter 9 · Section 9.4 · Design & Testbench Creation
1. The Engineering Problem
Two engineers submit a fifo module. Both pass the same smoke test. One is accepted; the other is sent back. The difference is not correctness — both "work" — it is design quality.
The rejected module mixes a $display debug print into its logic, lumps a FIFO and an unrelated status-LED blinker into one block, names its ports x1, x2, tmp, hardcodes a depth of 16 in five places, and uses an initial block to set its starting state. It simulates fine. But it won't synthesize cleanly (the $display and initial-state don't map to hardware), it can't be reused (the LED logic is welded in), it can't be verified in isolation (two concerns tangled together), and the next engineer can't read it (the names mean nothing).
The accepted module does one thing — a FIFO — behind a clear, well-named interface, with depth as a parameter, containing only synthesizable logic. It is a design block: a piece of hardware that ships.
"It compiles" and "it simulates" are not the bar. A design module is hardware that must synthesize, be reused, be verified, and be read by others. Those four demands, not the compiler, define a good design block.
This page is about meeting that bar — the fundamentals that separate a module that merely works from one worth putting on a chip.
2. Mental Model — Design Module vs Testbench Module
Visual A — design module vs testbench module
3. The Hardware View — A Design Module Becomes Gates
A design module is not a program; it is a description of a circuit that will physically exist. Everything inside it must map to hardware — gates, flip-flops, wires. This single fact drives every rule in this page: if a construct has no hardware meaning, it does not belong in a design module.
The synthesizable subset is the part of Verilog that maps to hardware. A design module lives entirely inside it:
| Belongs in a design module (synthesizable) | Does NOT belong (simulation-only) |
|---|---|
assign continuous assignments | $display / $monitor / $strobe |
always blocks describing comb/seq logic | $finish / $stop |
Module instances (u_sub ( ... )) | # delays (#10) |
wire / reg declarations, parameters | initial blocks for state |
Operators, if/case, for (bounded, static) | $random, file I/O, force/release |
The right-hand column is testbench material — legitimate, but in a testbench, never a DUT. Putting it in a design module ranges from a synthesis warning (a stripped $display) to silent hardware corruption (a synthesis tool deleting an entire always block that contained a $display alongside real logic — the §7.4 hazard). The discipline is absolute: a design module contains only synthesizable constructs. (The constructs themselves — operators, always, if/case — are taught in Chapters 10–14; here the point is simply which side of the line they sit on.)
4. The Anatomy of a Good Design Module
Beyond "synthesizable," a quality design module follows conventions that make it readable and reusable. Here is the shape — a register block shown as a shell, with the conventions called out (the internal logic is Chapter 14 material):
module register #(
parameter integer WIDTH = 8 // (a) configurable, not hardcoded
)(
input wire clk, // (b) clock first
input wire rst_n, // (c) reset second (active-low convention)
input wire en, // (d) control inputs grouped
input wire [WIDTH-1:0] d, // (e) data inputs, parameterized width
output reg [WIDTH-1:0] q // (f) outputs last, descriptive names
);
// synthesizable logic only — sequential body is Chapter 14
// ... q updates from d on a clock edge when en is asserted ...
endmoduleThe conventions that mark it as a quality block:
- (a) Parameterize configuration. Width, depth, and counts are
parameters, not magic numbers scattered through the body. Oneregisterdefinition serves every width (parameter, 6.1). - (b–c) Clock and reset first, by convention. Sequential design modules lead with
clkthenrst_n. A reader knows the timing backbone at a glance. (Reset/clock behaviour is Chapter 14; here it is an interface convention.) - (d) Group ports by role — clock/reset, then control, then data in, then data out. The port list reads as documentation.
- (e) Tie data widths to parameters so the interface scales with configuration.
- (f) Name ports for meaning —
clk,rst_n,en,d,qcommunicate role;x1,tmp,sig2communicate nothing.
The shape is invariant across good design modules: parameters → clock/reset → control → data-in → data-out → synthesizable body. It is not enforced by the language; it is enforced by every team that has to maintain RTL.
5. Single Responsibility — One Module, One Job
The most important quality rule: a design module should do exactly one cohesive thing. A FIFO is a FIFO; a UART is a UART; an ALU is an ALU. The moment a module does two unrelated things, every benefit of modularity collapses.
Why single responsibility is non-negotiable:
- Reuse. A pure FIFO drops into the next project; a "FIFO + LED blinker" cannot — the next project doesn't want the LED logic.
- Verification. One concern can be verified in isolation against a clear spec; two tangled concerns can only be tested together, weakly.
- Maintainability. A change to one function can't accidentally break an unrelated one if they live in separate modules.
- Comprehension. "What does this module do?" should have a one-sentence answer. If it needs an "and," the module is doing too much.
The litmus test: if you cannot describe the module's job in a single sentence without using "and," split it. A uart_with_timer_and_gpio is three modules wearing one boundary.
Visual B — the four qualities of a good design module
What makes a design module good
data flow6. The Clean Interface — The Boundary Is the Contract
9.1 said the port list is the interface; 9.3 showed wiring it wrong is a silent bug. 9.4 adds the design judgement: a good interface is minimal, clear, and stable.
- Minimal. Expose only what callers need. Every extra port is extra surface to connect, misconnect, and maintain. Internal signals stay internal.
- Well-named and grouped.
clk,rst_n,wr_en,wr_data,fulltell a reader the protocol;a,b,s1,fforce them to reverse-engineer it. Group related ports (a write side, a read side) so the interface reads as structure. - Stable. The interface is a contract with every instantiation. Changing it ripples to every caller, so design it to last: parameterize what varies, and resist adding one-off ports for special cases.
- Self-documenting. A well-shaped interface tells a reader what the block does before they read the body — recall the 4-bit adder in 9.1 whose ports alone announced its function.
A clean interface is the difference between a block other engineers reach for and one they avoid. The interface is the product; the implementation is replaceable behind it.
7. Separation of Concerns — Keep Verification Out of the Design
The deepest structural rule, and the one that sets up the next page: keep the design and its verification separate. Debug prints, assertions, stimulus, and reference checks belong in the testbench (9.5), not in the design module.
- No simulation code in the DUT. A
$displayinside the design module pollutes a synthesizable block with sim-only constructs — at best a stripped warning, at worst a deleted logic block (§3). Observation belongs to the testbench, which watches the DUT from outside through its ports. - The DUT exposes; the testbench inspects. A design module makes its behaviour observable through its interface; it does not narrate its own internals with print statements. If a value needs watching, the testbench reads the port.
- Two artifacts, built together, kept apart. Recall the Chapter 9 overview: design and verification evolve together but live in separate modules. The design block and its testbench grow in the same loop, but the synthesizable hardware and the sim-only harness never share a boundary.
Within the design itself, a related discipline applies — separating control (the FSM that decides) from datapath (the logic that computes) into distinct blocks. That is a structural pattern you will meet in the design chapters; here, the headline separation is the one that matters most now: design module vs testbench module.
Visual C — the DUT wrapped by its testbench
8. Worked Examples
Three modules — a clean design block, a poor one improved, and a design block as a DUT.
8.1 Example 1 — a clean design module (shell)
module sync_fifo #(
parameter integer WIDTH = 8,
parameter integer DEPTH = 16
)(
input wire clk,
input wire rst_n,
// write side
input wire wr_en,
input wire [WIDTH-1:0] wr_data,
output wire full,
// read side
input wire rd_en,
output wire [WIDTH-1:0] rd_data,
output wire empty
);
// synthesizable FIFO logic only (Chapter 14 material) — no $display,
// no #delays, no initial-state, nothing but hardware.
endmoduleOne job (a synchronous FIFO), parameterized (WIDTH/DEPTH), a clean interface grouped into write and read sides, clock/reset first, every port named for its role. A reader knows the protocol from the port list alone. This is a design block worth shipping.
8.2 Example 2 — a poor module, improved
module thing (
input c, r,
input [7:0] x1, x2,
output [7:0] o1,
output led // unrelated LED blinker mixed in
);
// FIFO-ish logic AND an LED blinker, magic-number depth 16 hardcoded,
// and a $display narrating internal state ... compiles, simulates.
endmoduleThe problems, by this page's rules: two concerns in one module (§5), a $display in a design block (§3/§7), magic numbers instead of parameters (§4), and meaningless names (§4/§6). The improvement is not a rewrite of the logic but a restructure: split the LED blinker into its own module, remove the $display (the testbench will observe), parameterize the depth, and rename the ports for meaning — yielding two clean, single-purpose, synthesizable design blocks instead of one tangled one. (The split is done by instantiation, 9.2 — here the lesson is recognising that it must be split.)
8.3 Example 3 — the design module as a DUT
module alu #(
parameter integer WIDTH = 16
)(
input wire [WIDTH-1:0] a,
input wire [WIDTH-1:0] b,
input wire [2:0] op,
output wire [WIDTH-1:0] y,
output wire zero
);
// purely synthesizable combinational logic (Chapters 10/13/14)
endmoduleBecause this alu is a clean, single-purpose, synthesizable design module with a clear interface, it is ready to be a DUT: a testbench can wrap it, drive a/b/op, observe y/zero, and check correctness — entirely through the interface, with no sim-only code inside the block. A well-designed module is, by construction, a verifiable one. Building that testbench is the next page, 9.5.
9. Industry Perspective
- The design module is the deliverable. What an RTL engineer ships, and what an IP vendor licenses, is a set of clean, synthesizable, well-interfaced design modules. The testbench proves them; the design module is the product.
- Coding standards encode these rules. Every serious team has an RTL style guide: clock/reset conventions, naming rules, "one module one job," parameterize-don't-hardcode, no sim constructs in design. They exist because these qualities, unlike correctness, are invisible to the compiler.
- Lint enforces synthesizability and structure. Lint decks flag sim-only constructs in design modules, unused ports, magic numbers, and interface smells — automating the judgement this page describes so it scales across thousands of modules.
- Reuse is the economic payoff. A clean, single-purpose, parameterized design module becomes reusable IP across projects and chips. A tangled monolith is a one-off liability. Design quality is what makes a block an asset.
10. Common Mistakes
- Sim code in the design module —
$display,#delays,initial-for-state inside a block meant to be hardware. Won't synthesize cleanly; belongs in the testbench (§3/§7, DebugLab 1). - The god-module — one block doing several unrelated jobs. Destroys reuse, verification, and comprehension (§5, DebugLab 2).
- Unclear or bloated interface — meaningless port names, exposed internals, no grouping. Causes integration errors and unreadable wiring (§6, DebugLab 3).
- Magic numbers instead of parameters — a hardcoded width/depth repeated through the body. Un-reusable and bug-prone; parameterize it (§4).
- No clock/reset convention — clock and reset buried mid-list or named inconsistently. Obscures the timing backbone (§4).
11. Debugging Lab
Three design-module-quality debug post-mortems
12. Interview Q&A
13. Exercises
Exercise 1 — Classify the constructs
For each construct, state whether it belongs in a design module, a testbench module, or both: assign, $display, always @(posedge clk), #10, initial (for stimulus), a module instance, $finish, a parameter.
Exercise 2 — Critique an interface
module m (input a, b, c, d, output e, f); is a 16-bit-wide arithmetic block. List what is wrong with this interface as a design-module boundary (reference §4/§6) and propose a better port list with meaningful, grouped, parameterized ports.
Exercise 3 — Apply single responsibility
A module system contains a SPI controller, a CRC calculator, and a debug-LED driver in one boundary. Describe how you would restructure it into good design modules, and state the one-sentence job of each resulting module. (Do not write the logic; describe the decomposition.)
Exercise 4 — Find the design-quality defects
List every design-module-quality problem in the block below (reference §3–§7), and give a one-line fix for each.
module proc (input c, r, input [7:0] x, output [7:0] o, output led);
initial o = 0;
// SPI logic + an LED blinker, depth hardcoded as 16 in several places,
// with a $display narrating internal state.
endmodule14. Summary
Design Module Fundamentals is the step from mechanics (structure, instantiation, port mapping) to judgement — what makes a module a quality piece of hardware, not just a working one.
The core ideas:
- Two kinds of module. A design module ships to silicon (synthesizable, the DUT); a testbench module is a sim-only harness that exercises it and never ships. Conflating them is the root of most beginner trouble.
- The DUT — Design Under Test — is the design module a testbench wraps and drives. A well-designed module is, by construction, a good DUT.
- Four qualities the compiler never checks: synthesizable (hardware constructs only), single responsibility (one cohesive job), a clean interface (minimal, well-named, grouped, stable), and readable/reusable (parameters, clear names).
- Synthesizable discipline. A design module contains only constructs that map to hardware — no
$display, no#delays, noinitial-for-state. Those are testbench material. - Separation of concerns. Keep verification out of the design: the DUT exposes behaviour through its interface; the testbench observes from outside.
The discipline this page instils:
- One module, one job — if you need 'and' to describe it, split it.
- Design the interface as the contract — minimal, meaningful, grouped, stable; it documents the block.
- Keep the design module pure hardware — observation, stimulus, and initial conditions live in the testbench; starting state comes from reset.
- Parameterize configuration, name for meaning, lead with clock/reset.
You can now build a good design module — the DUT. The natural next step is to prove it works: Chapter 9.5 Testbench Creation Techniques builds the simulation-only harness around the DUT — generating a clock, applying stimulus, observing outputs, and checking correctness — using the design-vs-testbench split this page established and the Chapter 8 system-task toolkit. 9.6 Practical Examples then ties a complete DUT-plus-testbench pair together end to end.
Related Tutorials
- Module Port Mapping — Chapter 9.3; wiring the instances of the design modules this page teaches you to write well.
- Design & Testbench Creation — Chapter 9 overview; the design-and-verification-as-one-activity framing this page builds on.
- System Tasks & Functions — Chapter 8; the sim-only toolkit that belongs in the testbench, never the design module.
- parameter — Chapter 6.1; the configuration mechanism that replaces magic numbers in a good design module.