Part 6 · Testbench Architecture · Intermediate
program Blocks: Use or Skip?
What program changes about scheduling, the race-avoidance theory, why much of industry skips it in favor of clocking blocks, and how to discuss it in interviews.
What program actually changes
A program block is a module-like container whose statements execute in the Reactive region of the SystemVerilog event scheduler — after all design (Active-region) activity for that time slot has settled. The intent: if testbench code runs strictly after design code, the TB always samples post-update values and a whole class of TB-vs-DUT races disappears by construction.
SIMPLIFIED EVENT REGIONS IN ONE TIME SLOT
┌────────────┐ design always/assign blocks evaluate
│ Active │ (module code lives here)
├────────────┤
│ NBA │ nonblocking assignment updates
├────────────┤
│ Observed │ property/assertion evaluation
├────────────┤
│ Reactive │ program-block code runs HERE
│ │ (testbench sees settled design state)
├────────────┤
│ Re-NBA │ NBA updates from reactive code
└────────────┘
│ (loop until quiescent, then advance time)
Theory: TB in Reactive region ⇒ TB can never race the design.Side effects of program
Blocking assignments to design signals from a program are scheduled into the Re-NBA region — write races also avoided.
When every initial block inside all programs completes, simulation ends implicitly (like an automatic $finish).
program blocks cannot contain always blocks; they are stimulus containers, not design containers.
The case for skipping it
Most modern flows — including UVM itself — do not use program blocks. The race problem program solves is solved more locally and more completely by clocking blocks : input sampling at #1step (just before the edge) and output driving with a specified skew (just after the edge) eliminate sample/drive races at the signal boundary, regardless of which region the calling code runs in.
// Approach A — program block (Spear-era style)
program automatic test (bus_if bif);
initial begin
base_test t = new();
t.vif = bif;
t.run(); // runs in Reactive region
end
endprogram // sim ends when this initial completes
// Approach B — plain module + clocking blocks (industry-common)
module tb_top;
// ... clock, reset, interface bif, DUT ...
initial begin
base_test t = new();
t.vif = bif;
t.run();
end
// Race safety comes from the interface clocking blocks:
// @(vif.drv_cb) vif.drv_cb.valid <= 1; // skewed drive
// data = vif.mon_cb.rdata; // #1step sample
endmoduleWhy industry largely moved on
Clocking blocks fix races at the pin boundary, which is where they occur — program fixes them globally with scheduling semantics few engineers can reason about precisely.
The implicit end-of-simulation when program initials finish surprises people and fights explicit end-of-test orchestration.
UVM runs entirely in modules; mixing program-region semantics with UVM phasing created confusion for no benefit.
Tool support and team familiarity: a plain module behaves identically everywhere; program corner cases historically varied.
How to frame it in an interview
This is a judgment question disguised as a syntax question. The strong answer covers both sides and lands on practice: program exists to schedule TB code into the Reactive region and kill TB/design races by construction; in practice, disciplined use of clocking blocks for all pin access achieves the same race freedom with simpler semantics, which is why UVM-era testbenches are module-based. Knowing why program exists matters more than using it.
If you do use program
Declare it automatic so tasks/functions get per-call stacks under concurrency.
Still use clocking blocks — program does not specify drive skew at the pin.
Remember the implicit $finish; add explicit control if the env owns end-of-test.
Key takeaways
program schedules TB code in the Reactive region so it always sees settled design values.
Clocking blocks solve the same race problem at the pin boundary, which is where races actually happen.
UVM and most modern flows use plain modules + clocking blocks; program is legal but increasingly rare.
Interview answer: explain the scheduling theory, then the practical reason it is commonly skipped.
Common pitfalls
Relying on program for race safety while bypassing clocking blocks — drive skew at pins is still unspecified.
Being bitten by the implicit end-of-sim when the program's initial finishes before drain time.
Claiming “program is required for testbenches” in an interview — instantly dates your knowledge.
Mixing program Reactive semantics with module-based components and then debugging “impossible” orderings.