top of page

System Verilog Program Structure — module, program, initial

1. Introduction

Once you move beyond basic syntax in System Verilog, the first real confusion beginners face is:

  • Why does my simulation sometimes behave differently?
     

  • Why does SystemVerilog have both module and program?
     

  • What exactly does initial control?
     

This topic appears very frequently in interviews, but more importantly, it directly affects whether your simulation code is safe or unsafe.

A key point to understand early is this:

Code can compile, simulate, and still be logically unsafe.

This article explains why that happens and how SystemVerilog prevents it.

2. Before Anything Else: Design vs Testbench (No Assumptions)

Before discussing keywords, we must clearly separate two ideas.

What is Design (DUT)?

Design (DUT – Design Under Test):

  • Represents real hardware
     

  • Meant to be synthesized
     

  • Describes how a circuit behaves
     

In System Verilog, design is written using module.

Example designs:

  • Flip-flop
     

  • Counter
     

ALU

What is a Testbench?

A testbench:

  • Is not hardware
     

  • Exists only in simulation
     

  • Drives inputs to the design
     

  • Observes outputs from the design
     

You can think of a testbench as a test program for hardware.

Even in simple testbenches, two roles exist:

  • Driver → applies inputs
     

  • Checker → checks outputs
     

System Verilog introduces the program block specifically for testbench code.

3. What Is the Difference Between module and program? (Snippet Target)

In System Verilog, a module models hardware and executes in the active simulation region, while a program models testbench behavior and executes after all modules to prevent race conditions.

In short:

  • module → hardware

  • program → testbench

Quick Comparison

​Feature                       Module                       Program
Purpose                      Hardware modeling  Testbench modeling
Synthesizable             Yes                               No
Simulation Region     Active                          Reactive
Can Race with DUT   Yes                               No
Intended Use             Design                        Verification

4. Why This Distinction Matters (The Core Problem)

In simulation:

  • The design samples inputs on clock edges
     

  • The testbench drives those inputs
     

If both execute at the same simulation time, then:

  • Sometimes the design sees the old value
     

  • Sometimes the new value
     

  • The language gives no guarantee
     

This situation is called a race condition.

A race condition means behavior depends on execution order, not logic.

5. Where initial Fits (Important Clarification)

initial:

  • Runs once when simulation starts
     

  • Does not control priority
     

  • Executes according to where it is declared
     

Key rule:

initial defines what runs,
module vs program defines when it runs.

6. Unsafe Code Example — Works but Is NOT Safe

This code may appear to work, but it is not guaranteed by the language.

❌ NOT SAFE — Testbench as module

module dut(input logic clk, input logic a, output logic y);

  always_ff @(posedge clk)

    y <= a;

endmodule

 

module testbench;

  logic clk, a, y;

 

  dut d1(.clk(clk), .a(a), .y(y));

 

  initial clk = 0;

  always #5 clk = ~clk;

 

  initial begin

    a = 0;

    #10 a = 1;   // changes exactly at posedge

    #1  if (y !== 0) $error("RACE: y sampled new value");

    #10 $finish;

  end

endmodule

 

Why This Is Unsafe

  • DUT and testbench are both module
     

  • Both execute in the same simulation region
     

  • Order is not guaranteed
     

  • Behavior depends on simulator scheduling
     

No syntax error.
No compile error.
But language-level unsafe.

7. Safe Code Example — Deterministic and Correct

Now we fix only one thing:
Move the testbench into a program.

✅ SAFE — Testbench as program

module dut(input logic clk, input logic a, output logic y);

  always_ff @(posedge clk)

    y <= a;

endmodule

 

program testbench;

  logic clk, a, y;

 

  dut d1(.clk(clk), .a(a), .y(y));

 

  initial clk = 0;

  always #5 clk = ~clk;

 

  initial begin

    a = 0;

    #10 a = 1;   // same timing as before

    #1  if (y !== 0) $error("THIS SHOULD NEVER HAPPEN");

    #10 $finish;

  end

endprogram

 

Why This Is Safe

  • DUT (module) executes first
     

  • Testbench (program) executes after
     

  • Sampling happens before driving
     

  • Execution order is guaranteed by SystemVerilog
     

Same logic.
Different scheduling.
Only one is correct.

8. Key Takeaway (This Is the Core Insight)

  • Code can work and still be unsafe
     

  • Race conditions do not always show visible errors
     

  • System Verilog program exists to remove undefined behavior
     

This is why verification engineers care about this distinction.

9. Common Beginner Mistakes

  1. Writing entire testbench using module
     

  2. Assuming delays (#) fix race conditions
     

  3. Believing initial controls execution order
     

Mixing synthesizable logic inside program

10. Interview Perspective

Interviewers are not asking:

“Does the code run?”

They are asking:

“Is the behavior guaranteed by the language?”

Strong one-line answer:

Program prevents race conditions between testbench and RTL.

11. Knowledge Check

  1. Why can two modules race in simulation?
     

  2. What guarantee does program provide?
     

  3. Does initial decide execution order?
     

If you can answer these clearly, you understand the topic.

12. What’s Next

Now that execution order and structure are clear, the next confusion beginners face is:

“Which data types should I use, and why?”

👉 Next Article:
System Verilog Data Types — logic, bit, reg, wire

This topic is both:

  • Highly interview-relevant
     

Critical for writing correct code

bottom of page