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.

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

systemverilog
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
endclass

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

diagram
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 gen

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