Skip to content

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

Design module versus testbench moduleTestbench modulesim-only harness · neversynthesizedDesign module (DUT)ships to silicon ·synthesizableSilicononly the design module getshere12
Two kinds of module with opposite jobs. The design module (the DUT) becomes silicon — synthesizable subset only, one cohesive job, clean interface. The testbench module is a simulation-only harness that exercises the DUT and never ships. This page is about writing good design modules; the testbench is Chapter 9.5.

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, parametersinitial 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):

register.v — a well-formed design module shell
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ...
endmodule

The conventions that mark it as a quality block:

  • (a) Parameterize configuration. Width, depth, and counts are parameters, not magic numbers scattered through the body. One register definition serves every width (parameter, 6.1).
  • (b–c) Clock and reset first, by convention. Sequential design modules lead with clk then rst_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 meaningclk, rst_n, en, d, q communicate role; x1, tmp, sig2 communicate 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 flow
What makes a design module goodSynthesizableonly hardware constructsSingleresponsibilityone cohesive jobClean interfaceminimal, well-named portsReadable &reusableparameters, clear names
The compiler checks none of these. A design module that merely compiles can fail every one. The four qualities — synthesizable, single-purpose, cleanly-interfaced, readable/reusable — are what separate a block worth shipping from one that merely runs in a smoke test.

6. 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, full tell a reader the protocol; a, b, s1, f force 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 $display inside 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

Testbench wrapping the design under testTestbench (sim-only)drives + observes + checksstimulusinto DUT inputsDUT (design module)synthesizable hardwareobserve + checkfrom DUT outputs12
The design module is the DUT — the hardware under test. The testbench surrounds it: driving stimulus into the DUT's input ports, observing its outputs, and checking correctness. The DUT contains only synthesizable design logic and never reaches outside its boundary to print; the testbench does all observation from outside. Building this testbench is Chapter 9.5.

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)

sync_fifo.v — design module shell, conventions applied
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.
endmodule

One 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

before — a module that 'works' but is a bad design block
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.
endmodule

The 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

alu.v — a design module, ready to be a DUT
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)
endmodule

Because 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

  1. 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).
  2. The god-module — one block doing several unrelated jobs. Destroys reuse, verification, and comprehension (§5, DebugLab 2).
  3. Unclear or bloated interface — meaningless port names, exposed internals, no grouping. Causes integration errors and unreadable wiring (§6, DebugLab 3).
  4. Magic numbers instead of parameters — a hardcoded width/depth repeated through the body. Un-reusable and bug-prone; parameterize it (§4).
  5. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.
endmodule

14. 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, no initial-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.

  • 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.