Why Learn the Program Block? As testbenches grew more sophisticated, keeping design and testbench code side by side in ordinary modules created a real problem: both could execute in the same scheduling region of a simulation time step, and whether the testbench saw a signal's old or new value could depend on scheduling order that varies between simulators. The program block exists specifically to give the testbench a dedicated home that's guaranteed to run after the design has settled, plus a built-in way to know when a test has actually finished.

A module is the fundamental construct for building designs — it can contain hierarchies of other modules, nets, variables, and procedural blocks describing hardware functionality. A testbench, on the other hand, is a complete environment for verifying that design, and needs its own care taken in how it's modeled: it must be properly initialized and synchronized, avoiding race conditions with the design it's testing.

7 min read | Intermediate Level

What You'll Learn

  • Why the program block exists, and the specific race condition it avoids
  • The program block syntax, and how multiple program blocks can share local variables
  • What "simulation complete" means for a program block, and why always blocks aren't allowed inside one
  • How $exit and a final block fit into ending a test cleanly

What Is the Need for a Program Block?

The program block was introduced for a few specific reasons:

  • To provide a clear entry point for the execution of testbenches
  • To create a container that holds testbench data — tasks, class objects, and functions
  • To avoid race conditions with the design, by executing in the reactive region of a simulation cycle

The reactive region is one of the last phases before simulation time advances — by the time it runs, every design statement for that time step has already executed, so the testbench is guaranteed to see fully updated values. Having this clear separation between when design code runs and when testbench code runs is what makes output more deterministic across different simulators, rather than depending on scheduling details that could vary from one tool to the next.

Syntax


program [name] [port_list];
	...
endprogram

Examples


program test1 (input clk, reset);
	initial ...
endprogram

program test2 (interface wb_if);
	initial ...
endprogram

program test3;
	initial ...
endprogram

A program block can be nested within modules and interfaces, and multiple programs within the same module can share variables local to that scope. Below, mode is a local variable within tb that both p1 and p2 can access:


module tb;
	bit [3:0] mode;

	program p1;
		...
	endprogram

	program p2;
		...
	endprogram
endmodule

Quick Check

Q: In the example above, could p1 write a value to mode and have p2 read that same value?

Show Answer

Yes. Since mode is declared in the enclosing module tb, both nested program blocks share direct access to that one variable — it isn't copied or duplicated per program block.

How Does a Program Block End Simulation?

A program block has a built-in notion of completion: once every initial block inside it has finished executing, that program block is considered to have exited — as if $exit had been called on it automatically. Once every program block in the simulation has exited this way, the simulation implicitly ends, exactly as if $finish had been called — even if your testbench never calls $finish at all.

This is especially convenient for automated regression testing: a test that's written entirely inside initial blocks in a program block will naturally stop the simulation the moment it's actually done, with no risk of the simulator hanging indefinitely waiting for something that will never happen.

Why always Blocks Aren't Allowed in a Program Block

A program block can't contain an always block (or a module, interface, or another program). This isn't an arbitrary restriction — it follows directly from how completion works. An always block runs forever by design and never reaches a natural end on its own. If one were allowed inside a program block, that block could never "finish," which would permanently block the exactly the completion mechanism described above.

If you need behavior that repeats continuously, use a forever loop inside an initial block instead — unlike an always block, a forever loop can be exited (with disable, a break, or simply reaching the end of its enclosing process), so the program block can still complete normally when the test decides it's done.

Quick Check

Q: Why does a forever loop inside an initial block work fine in a program block, while an always block doesn't?

Show Answer

Because a forever loop runs inside a process (the initial block) that can still be terminated from the outside — via disable, for instance — letting that initial block eventually reach completion. An always block has no equivalent way to be stopped; it's designed to run for the entire simulation, which is incompatible with a program block's completion model.

Ending a Program Early: $exit

$exit lets one specific program block signal that it's done, without necessarily ending the entire simulation if other program blocks are still active. $finish, by contrast, ends the whole simulation immediately, regardless of what any program block is doing. A final block — allowed in both modules and programs — runs once, right at the very end of simulation, and is commonly used for printing a summary of errors and warnings; it can't contain delays or other timing controls, since it has to execute in zero time.

Where Is a Program Block Used?

A typical testbench structure uses a single top-level program block to house the main test sequence — class objects, the top-level task calls, and the overall flow — while the DUT lives in its own module, connected through an interface. This keeps the "what the test does" code cleanly separated from "what the hardware does," with the reactive-region scheduling guarantee applying automatically to everything inside the program.

Common Beginner Mistakes

Mistake #1: Putting an always block inside a program block

As explained above, this isn't allowed and won't compile. Why? An always block never terminates on its own, which is fundamentally incompatible with a program block's simulation-completion model — use a forever loop inside an initial block instead.

Mistake #2: Assuming $finish must always be called explicitly

Once every program block's initial blocks have completed, simulation ends on its own. Why? It's still perfectly fine to call $finish explicitly — for clarity, or to end a test early — but it's not required just to bring a normal test to a close.

Mistake #3: Trying to instantiate a design module inside a program block

A program block can't contain a module, an interface, or another program. Why? Program blocks exist to hold testbench code — tasks, functions, class objects, and initial blocks — not design hierarchy; the DUT and any interfaces need to be instantiated in an enclosing module instead, with only a handle or port connection passed into the program block.

Practice Exercise

Challenge: Predict When Simulation Ends

A single program block contains two initial blocks: one that runs for 50ns before finishing, and another that runs for 80ns before finishing. Assuming neither one calls $finish explicitly, at what time does the simulation end?

Hint

Simulation only ends once every initial block in every program block has completed — not just the first one.

Solution

80ns. The program block as a whole doesn't exit until both of its initial blocks have finished, and the slower of the two determines when that happens — the 50ns block finishing first doesn't end anything on its own.

Summary

In this tutorial, you learned:

  • A program block gives testbench code a dedicated home that executes in the reactive region, after all design activity for that time step has settled — avoiding race conditions between design and testbench code
  • Program blocks can accept ports or interfaces, and multiple program blocks nested in the same module can share that module's local variables
  • A program block implicitly exits once all of its initial blocks complete; once every program block has exited, simulation ends automatically, without needing an explicit $finish
  • always blocks (along with modules, interfaces, and other programs) aren't allowed inside a program block, since they never terminate on their own — use a forever loop inside an initial block instead
  • $exit ends one specific program block; $finish ends the whole simulation; a final block runs once at the very end, commonly for summary reporting

Continuing the Testbench Components Chapter

Continue with SystemVerilog Command Line Arguments.

Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5