Part 6 · Testbench Architecture · Intermediate
Generator Patterns
Randomize-in-loop, blueprint cloning, scenario generators, weighted transaction mixes, and stop conditions by count, coverage, or event.
The baseline: randomize-in-loop with a blueprint
The simplest correct generator randomizes a blueprint object in a loop and puts a clone of it into the mailbox. The blueprint matters: because tests can replace it with a derived-class object carrying extra constraints, the generator becomes retargetable without edits — a hand-rolled version of the UVM factory override.
class generator;
mailbox #(bus_txn) out;
int unsigned n;
bus_txn blueprint; // tests may swap in a derived txn
event done;
function new(mailbox #(bus_txn) out, int unsigned n);
this.out = out; this.n = n;
blueprint = new();
endfunction
virtual task run();
repeat (n) begin
if (!blueprint.randomize())
$fatal(1, "GEN: randomize failed");
out.put(blueprint.clone()); // clone! handle semantics
end
-> done;
endtask
endclass
// a test retargets stimulus with zero generator edits:
class low_addr_txn extends bus_txn;
constraint c_low { addr < 16'h0100; }
endclass
// in the test: low_addr_txn b = new(); e.gen.blueprint = b;Scenario generators and weighted mixes
Single random transactions rarely trigger interesting DUT behavior; sequences of related transactions do. A scenario generator emits coherent multi-transaction patterns — write-then-readback to the same address, bursts to consecutive addresses — while a weighted kind selector controls the mix of scenarios.
class scenario_gen extends generator;
typedef enum {SINGLE, WR_RD_BACK, BURST4} scen_e;
rand scen_e scen;
constraint c_mix { scen dist {SINGLE :/ 50,
WR_RD_BACK :/ 30,
BURST4 :/ 20}; }
virtual task run();
int unsigned sent = 0;
while (sent < n) begin
if (!this.randomize()) $fatal(1, "GEN: scen pick failed");
case (scen)
SINGLE: sent += do_single();
WR_RD_BACK: sent += do_wr_rd_back();
BURST4: sent += do_burst4();
endcase
end
-> done;
endtask
task automatic put_one(bus_txn t);
out.put(t);
endtask
function int do_single();
void'(blueprint.randomize());
out.put(blueprint.clone());
return 1;
endfunction
function int do_wr_rd_back();
bus_txn wr, rd;
void'(blueprint.randomize() with { kind == WRITE; });
wr = blueprint.clone();
rd = wr.clone();
rd.kind = READ; // same addr, read it back
out.put(wr); out.put(rd);
return 2;
endfunction
function int do_burst4();
void'(blueprint.randomize() with { kind == WRITE; addr[3:0] == 0; });
for (int i = 0; i < 4; i++) begin
bus_txn b = blueprint.clone();
b.addr = blueprint.addr + 4 * i;
b.gap = 0; // back-to-back inside the burst
out.put(b);
end
return 4;
endfunction
endclassPattern summary
Scenario = a named, parameterized sequence of related transactions — the hand-rolled ancestor of UVM sequences.
The dist constraint on scen is the stimulus mix knob; tests override it to bias toward one scenario.
Derived scenarios construct related transactions by cloning and editing — readback shares the write's address by construction.
Intra-scenario gaps are forced to 0 for bursts — scenarios control their own pacing intent (the driver still obeys the protocol).
Stop conditions
When does a generator stop? Three idioms cover practice, and real testbenches often combine them.
GENERATOR STOP CONDITIONS
1. COUNT repeat (n) ... simplest; n from test knob
deterministic length, no feedback
2. COVERAGE while (cg.get_coverage() < 95.0) ...
stimulus runs until measurement says enough
needs a handle to the coverage object
3. EVENT while (!stop_req.triggered) ...
env/test fires stop_req (error budget hit,
time budget hit, external phase change)
Combined: while (sent < max_n &&
cov.get_coverage() < goal &&
!stop_req.triggered) ← typical regression genInterview angle
“How does your generator know when to stop?” separates count-only thinkers from closure thinkers. Mention coverage-driven stopping with a max-count safety cap — run until the covergroup reports the goal, but never past a hard transaction budget, so a coverage bug cannot hang the regression.
Key takeaways
Randomize a blueprint, put a clone — the blueprint swap is your hand-rolled factory override.
Scenarios (write-readback, bursts) trigger DUT behavior single random transactions never reach.
A dist constraint over scenario kinds is the stimulus mix knob tests tune.
Stop on count, coverage, or event — production generators combine all three with a safety cap.
Common pitfalls
Putting the blueprint itself instead of a clone — every queued transaction mutates on the next randomize.
Scenario steps generated independently — the readback randomizes its own address and checks nothing.
Coverage-driven stop without a max-count cap — an unreachable bin spins the simulation forever.
Generator waiting on clock edges to pace stimulus — pacing belongs to gap fields and the driver.