Skip to content

UVM

Random Traffic Generation

Generating varied, legal stimulus at scale — constrained-random sequences that randomize fields, counts, and selection under constraints that keep it legal and bias it toward interesting cases, driving coverage closure across seeds.

Sequences · Module 9 · Page 9.6

The Engineering Problem

The previous chapters built and composed sequences (Modules 9.1–9.5), but mostly ran directed traffic — items you chose explicitly. Directed stimulus doesn't scale: a modern design has an input space far too large to enumerate by hand, and the bugs hide in combinations no one thought to write a test for. The dominant verification methodology answers this with constrained-random stimulus — generate varied but legal traffic that explores the space — and the vehicle for it is the random sequence.

A random-traffic sequence produces a stream of randomized items under constraints: constraints that keep each item legal (a valid address, a consistent mode) and constraints that bias the mix toward interesting cases (80% reads, occasional boundary addresses). You randomize not just item fields but the count (how many), the selection (which sub-sequence runs next), and the timing — so the traffic varies along every axis. And because each seed produces a different random stream, a regression of many seeded runs samples the space broadly, accumulating coverage that closes the gaps directed tests would miss. This chapter is random traffic: the constrained-random sequence pattern, what you randomize (fields, counts, selection, weighting), the coverage-driven loop it feeds, and why over-constraining — or running one seed — collapses the diversity that makes it work.

How do you generate varied, legal stimulus at scale — constrained-random sequences that randomize fields, counts, and selection, biased by constraints toward interesting cases — and how does seeded randomization across runs drive coverage closure?

Motivation — why random, constrained, and seeded

Random traffic is the answer to three facts about real verification, and each shapes the technique:

  • The input space is too large to enumerate, so you sample it randomly. A design's legal input combinations vastly outnumber what anyone can hand-write. Random generation samples the space instead of listing it, so it reaches combinations no directed test would, finding the bugs that live in the unanticipated corners.
  • Stimulus must stay legal, so randomization is constrained. Pure randomness produces mostly illegal inputs the protocol rejects. Constraints bound the randomization to the legal space — and, beyond legality, bias it toward the interesting parts (boundaries, rare modes), so the samples are both valid and valuable.
  • Diversity needs many axes, so you randomize more than fields. Randomizing only an item's fields gives shallow variety. Randomizing the count, the selection of which sub-sequence runs, and the timing varies the traffic's shape, reaching scenarios (a long write burst, then a read storm) a fixed structure never would.
  • One run is one sample, so coverage needs many seeds. A single seeded run produces one random stream — one path through the space. Different seeds produce different streams, so a regression of many seeds accumulates broad coverage; the diversity lives across runs, not within one.
  • Coverage measures and directs it, so random is part of a loop. Random stimulus is not fire-and-forget: coverage measures what's been hit, exposes gaps, and feeds back into constraints and seeds — the coverage-driven loop that closes verification (Module 2/3).

The motivation, in one line: the space is too big to enumerate and mostly illegal if random, so you generate constrained-random traffic — legal, biased, varied along many axes, and seeded — and use coverage across many runs to measure and direct the sampling until the space is closed.

Mental Model

Hold random traffic as rolling weighted dice within the rules:

Generating random traffic is rolling weighted dice within the rules, many times, with each game (seed) a different sequence of rolls — and coverage is the map of which outcomes you've hit. The rules are the legality constraints: a roll must be a valid move (a real address, a consistent operation), so you never produce nonsense. The weighting is the biasing constraints: the dice are loaded toward the interesting outcomes (favor reads, occasionally land on a boundary), so your rolls concentrate where the value is. Each roll is one randomized item; a game (one seed) is a whole stream of rolls — one path through the possibilities. The crucial bit: one game doesn't see much of the space — to explore it you play many games with different seeds, each a different sequence of rolls, and you keep a map (coverage) of which outcomes have come up, playing more games (or re-weighting the dice) until the map is filled. Two ways to ruin it: load the dice so heavily they only ever roll one number (over-constraint — no diversity, or contradictory rules that make a legal roll impossible), or play the same game over and over (one seed — the same rolls every time, so the map never fills).

So random traffic is constrained, weighted sampling across many seeds, mapped by coverage: rules keep rolls legal, weights aim them at value, seeds vary the games, and coverage tells you when you've explored enough. Over-tighten the rules or fix the seed, and you stop exploring.

Visual Explanation — directed vs constrained-random

The defining contrast is enumerate vs sample: directed stimulus lists specific items; constrained-random samples the legal space, reaching far more of it across runs.

Directed stimulus enumerates specific points; constrained-random samples the legal space across seedscovers exactly thesebound + biassamples (per seed)measured byDirected: hand-writtenlistspecific chosen itemsA few anticipated pointsonly what was thought ofConstraints (legal +bias)the rules + weightingConstrained-randomgeneratora random sequenceThe legal input spacesampled broadly across seedsCoveragewhat's been sampled12
Figure 1 — directed enumeration vs constrained-random sampling. Directed: a hand-written list of specific items covers exactly those points — limited to what was anticipated. Constrained-random: a generator samples the legal space (bounded by constraints, biased toward interesting regions), and different seeds sample different points, so across many runs it covers far more of the space — including unanticipated combinations. Coverage measures what's been sampled. Directed lists points; random explores the space.

The figure shows why constrained-random is the dominant methodology. Directed stimulus (top) is a hand-written list of specific items, so it covers exactly those points and only those — it can only test what the engineer anticipated, which is precisely where bugs don't hide (anticipated cases usually work). Constrained-random (bottom) is a generator: the constraints bound and bias it (legal moves, weighted toward interesting regions), the random sequence samples the legal space, and — crucially — different seeds sample different points, so across many runs it covers far more of the space than any list, including the unanticipated combinations where bugs live. Coverage (right) measures what's been sampled, turning "did we explore enough?" into a metric. The trade is that random stimulus needs constraints (to stay legal and aimed) and coverage (to know what's been hit) and many seeds (for the diversity), where directed needs none of that infrastructure — but directed simply cannot reach the space at scale. So the picture is enumerate a few anticipated points versus sample the whole legal space, broadly, measurably: the latter is what finds the bugs you didn't know to look for, which is the entire reason verification is constrained-random.

RTL / Simulation Perspective — a constrained-random traffic sequence

A random-traffic sequence is a loop that randomizes along every axis — count, fields, selection, weighting — under constraints. The code shows the pattern.

a constrained-random traffic sequence
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class random_traffic_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(random_traffic_seq)
  rand int unsigned num;                       // RANDOM COUNT: how many items
  constraint c_num { num inside {[20:200]}; }   // legal + reasonable range
  function new(string name="random_traffic_seq"); super.new(name); endfunction
 
  task body();
    bus_item req;
    for (int i = 0; i < num; i++) begin
      `uvm_do_with(req, {                       // RANDOM FIELDS, with constraints:
        addr inside {[0:'hFFF]};                //   legality: valid address range
        op   dist { READ := 80, WRITE := 20 };  //   bias: 80% reads, 20% writes (weighting)
        addr dist { 0 := 5, ['h001:'hFFE] := 90, 'hFFF := 5 };  // occasionally hit boundaries
      })
    end
  endtask
endclass
 
// ── RANDOM SELECTION: randomly pick which sub-sequence to run (Module 9.5 composition) ──
task mix_seq::body();
  int pick;
  for (int i = 0; i < rounds; i++) begin
    pick = $urandom_range(0, 2);
    case (pick)                                 // a random mix of sub-scenarios
      0: `uvm_do(burst_read_seq)
      1: `uvm_do(burst_write_seq)
      2: `uvm_do(idle_seq)
    endcase
  end
endtask

The pattern randomizes along every axis. Count: num is a rand field with a legality constraint (c_num), so each run drives a different number of items — the traffic's length varies. Fields with constraints: `uvm_do_with(req, {...}) randomizes each item in the window (Module 9.3) under inline constraints that do two jobs — legality (addr inside {[0:'hFFF]} keeps addresses valid) and bias (op dist { READ := 80, WRITE := 20 } weights the mix 80/20, and the addr dist occasionally lands on the boundary addresses 0 and 'hFFF where bugs cluster). Selection: the mix_seq randomly chooses which sub-sequence to run each round ($urandom_range + case), composing a random mix of sub-scenarios (Module 9.5) — read bursts, write bursts, idles — so the traffic's shape varies, not just its values. The dist operator is the workhorse of biasing: dist { A := 80, B := 20 } makes A roughly four times as likely as B, concentrating samples where they matter without forbidding the rare case. The whole sequence is constrained-random: every randomize is bounded to legal and weighted toward interesting, and the loop + selection vary the count and shape — so each run produces a different, legal, interesting stream.

Verification Perspective — the dials you randomize

Random traffic varies along several independent dials, and turning all of them — not just item fields — is what produces deep diversity. Knowing the dials is knowing how to make traffic genuinely varied.

The dials of random traffic: fields, count, selection, timing, all biased by weightingwhat each item ishow manywhich sub-sequencegapsbias all (dist)Item fieldsaddr, data, op per itemCounthow many itemsSelectionwhich sub-sequence nextTiminggaps / delaysWeighting (dist)bias toward interestingVaried random trafficdiverse legal stimulus12
Figure 2 — the dials of random traffic. Item fields: randomize address, data, operation per item (the most basic). Count: randomize how many items the sequence sends. Selection: randomly choose which sub-sequence runs next, varying the traffic's shape. Timing: randomize gaps and delays between items. Weighting (dist): bias every dial toward interesting cases rather than uniform. Randomizing only fields gives shallow variety; turning all the dials — and weighting them — gives deep, interesting diversity.

The dials are what separate shallow randomness from deep diversity. Item fields — address, data, operation — are the basic dial: randomizing them varies what each item is, but a stream of randomized-field items that's always the same length and shape is still narrow. Count varies how many items, so runs differ in length (a short burst vs a long storm). Selection randomizes which sub-sequence runs next (Module 9.5), varying the traffic's shape — a run that's mostly read-bursts vs one that's mixed vs one that's write-heavy — which reaches scenario-level diversity field-randomization alone can't. Timing randomizes gaps and delays, exercising the design under back-to-back traffic and sparse traffic (timing-dependent bugs hide in both). And weighting (dist) is the dial that aims all the others: rather than uniform randomization (which wastes most samples on uninteresting middle cases), dist biases each dial toward the interesting regions — boundaries, rare operations, stress patterns — so the samples concentrate where bugs cluster while still occasionally hitting everything. The lesson is to turn all the dials and weight them: a truly varied traffic generator randomizes the count, the selection, and the timing — not just the fields — and biases each toward what matters, which is what makes random stimulus explore deeply rather than just jitter the same shape.

Runtime / Execution Flow — the coverage-driven loop

Random traffic isn't run-and-done; it's part of a loop. Across seeded runs, coverage measures what's been sampled, exposes gaps, and feeds back into constraints and seeds until the space is closed.

The coverage-driven loop: run constrained-random with a seed, measure coverage, analyze gaps, adjust seeds or constraints, repeat until closedrun (seed) → sample + cover → analyze gaps → adjust (seeds/constraints) → repeatrun (seed) → sample + cover → analyze gaps → adjust (seeds/constraints) → repeat1Run constrained-random (a seed)the sequence samples the legal space; one seed = one stream = onepath.2Stimulus hits coverage pointsthe run exercises parts of the space; coverage records what washit.3Analyze coverage for gapsmeasure what's covered; find regions no run has reached yet.4Respond: more seeds / adjust constraintsrun more seeds (more samples), or re-weight constraints to aim atgaps; repeat until closed.
Figure 3 — the coverage-driven loop random traffic feeds. Run the constrained-random sequence with a seed; it samples the space and stimulus hits coverage points. Coverage is measured and analyzed for gaps. If gaps remain, you respond — run more seeds (more samples), or adjust constraints/weighting to aim at the gap, or add a directed nudge — then run again. The loop repeats until coverage closes. Random traffic is the engine; coverage is the steering; seeds and constraints are the controls.

The loop is what makes random traffic converge rather than wander. Step 1, you run the constrained-random sequence with a seed — one seed produces one random stream, one sample path through the space (Module 8.3's stimulus, now varied). Step 2, the stimulus hits coverage points — the run exercises some regions of the input/state space, and coverage records which. Step 3, you analyze the accumulated coverage for gaps — regions no run has reached. Step 4, you respond: most gaps close by simply running more seeds (more random samples reach more of the space), but stubborn gaps need adjusting the constraints or weighting to aim the randomization at the missed region (re-weighting a dist, adding a constraint that forces a rare combination), or occasionally a directed nudge for a corner pure random rarely hits. Then you run again, and the loop repeats until coverage closes. This is coverage-driven verification (Module 2/3), and random traffic is its engine: the random sequence generates the samples, coverage steers by exposing gaps, and seeds and constraints are the controls you turn in response. The key runtime insight is that most coverage comes from seeds, not cleverness — running the same well-constrained generator across many seeds closes most of the space automatically, and you only hand-tune constraints for the residual gaps. So the loop is: generate broadly, measure, aim at what's left, repeat.

Waveform Perspective — varied traffic vs uniform

Random traffic's value is variety, and it's visible on a timeline: a constrained-random stream mixes operations, addresses, and gaps, where directed traffic is uniform.

Constrained-random traffic: varied operations, addresses, and gaps

12 cycles
Constrained-random traffic: varied operations, addresses, and gapsmixed ops: mostly reads, some writes (dist 80/20)mixed ops: mostly read…addr hits a boundary (0xFFF) — biased corner caseaddr hits a boundary (…varied gaps: randomized timing between itemsvaried gaps: randomize…another seed → a completely different streamanother seed → a compl…clkitemopRD--RDWR----RD--RDWR--RDaddr040--0A4FFF----010--7C0000--1B0t0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — varied random traffic on a timeline. The op trace mixes reads and writes (weighted ~80/20 by dist), the addr trace varies (including a boundary value 0xFFF), and the gaps between items vary (randomized timing) — item is active intermittently. This is one seed's stream; a different seed produces a different mix. Contrast with directed traffic, which would be a uniform, repeating pattern. The variety along every axis — op, addr, timing — is what lets random traffic explore the space.

The waveform shows the diversity random traffic produces. The op trace mixes reads and writes — mostly reads, with occasional writes, reflecting the dist { READ := 80, WRITE := 20 } weighting — so the operation type varies run to run. The addr trace takes varied values across the legal range, including the boundary 0xFFF (the addr dist deliberately biases toward such corners). And the item activity is intermittent — the gaps between items vary, exercising the design under both back-to-back and sparse traffic (randomized timing). The key point is that every axis varies: operation, address, and timing all differ, and this is one seed's stream — a different seed would produce a completely different mix of the same legal, weighted kind. Contrast this mentally with directed traffic, which would be a uniform, repeating pattern (always the same op, marching addresses, fixed gaps): it covers a thin, predictable slice. Random traffic's visual signature is exactly this irregularity — mixed, weighted, varied along every dial — and that irregularity is functional: it's what reaches the unanticipated combinations. The waveform is the methodology made visible: stimulus that looks varied because it is sampling broadly, biased toward where bugs hide.

DebugLab — the "random" traffic that wasn't random

A random sequence that drove identical items because over-constraint made randomize fail

Symptom

A regression of a constrained-random traffic sequence ran for many seeds, but coverage stayed flat — it barely grew beyond a single run. Inspecting the traffic, every item looked identical: the same address and operation, run after run, seed after seed, as if the sequence were directed. There were no obvious errors, just suspiciously uniform "random" traffic and coverage that refused to climb.

Root cause

A layered constraint contradicted an existing one, so randomize() failed every iteration — and because its return value wasn't checked, each item kept its default values, producing a stream of identical items:

why 'random' traffic was identical every time
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
base_traffic_seq:   constraint c_base { addr inside {[0:'h0FF]}; }     // addr ≤ 0xFF
derived_seq (adds): constraint c_more { addr inside {['h200:'h2FF]}; } // addr ≥ 0x200
 
→ the two constraints are CONTRADICTORY: no addr satisfies both
  body: void'(req.randomize());     // ✗ randomize FAILS (unsatisfiable) — return DISCARDED
        finish_item(req);            // item keeps DEFAULT addr/op every iteration
 
result:  every item is the default → "random" traffic is identical → coverage flat across all seeds
         (no error surfaced because the failed randomize return was ignored)
 
fix — resolve the contradiction AND check the randomize return:
  // make the constraints consistent (e.g. derived widens/overrides, not contradicts):
  constraint c_more { addr inside {[0:'h2FF]}; }
  if (!req.randomize()) `uvm_fatal("RANDFAIL", "constraints unsatisfiable")  // loud on failure

This is an over-constraint failure compounded by an unchecked randomize. The derived sequence added a constraint that contradicted the base's — the two address ranges don't overlap — so the constraint set was unsatisfiable, and randomize() failed on every call. A failed randomize() leaves the object unchanged (at its defaults), so every item came out identical; the "random" traffic was actually a stream of default items, which is why coverage never grew no matter how many seeds ran (every seed produced the same degenerate stream). Crucially, the failure was silent because the randomize() return value was discarded (void'(...)) — had it been checked, a uvm_fatal would have fired immediately, naming the unsatisfiable constraints. The fix has two parts: resolve the contradiction (make the derived constraint consistent with the base — widen or override rather than contradict) so a legal solution exists, and check the randomize return so any future unsatisfiability fails loudly instead of silently degenerating the traffic. The general lessons are the two failure modes of random generation: over-constraint (too tight → no diversity, or contradictory → randomize fails) collapses the very diversity random traffic exists to provide, and an unchecked randomize() hides that collapse — so constrain to legal-and-interesting without contradiction, and always check the randomize return.

Diagnosis

The tell is flat coverage with suspiciously uniform "random" traffic. Diagnose diversity-collapse bugs:

  1. Check the randomize() return. A discarded (void'(...)) or unchecked randomize hides failures. Wrap it in a check; if it fires, the constraints are unsatisfiable (over-constrained/contradictory).
  2. Look for contradictory constraints. Layered constraints (base + derived, or multiple blocks) that can't both hold make randomize fail. Trace all active constraints on the field and check they have a common solution.
  3. Confirm items are at defaults. If every item has the default value (not a randomized one), randomize isn't taking effect — the failure signature.
  4. Distinguish from a fixed seed. If items are randomized but identical across runs, suspect a fixed seed (every run is the same sample); if items are identical within a run too, suspect over-constraint/randomize failure.
Prevention

Keep randomization legal, diverse, and checked:

  1. Always check the randomize() return. if (!x.randomize()) uvm_fatal(...) so an unsatisfiable constraint set fails loudly and immediately, never silently degenerating the traffic.
  2. Constrain to legal-and-interesting, not into contradiction. Add constraints to keep stimulus valid and biased, but ensure the set always has a solution — when layering (base + derived), make derived constraints consistent with base.
  3. Vary the seed across runs. Each run should use a different seed so the regression samples broadly; a fixed seed makes every run the same one sample and stalls coverage.
  4. Watch coverage as the diversity check. Flat coverage across many seeds is the symptom of collapsed diversity (over-constraint or fixed seed) — treat stalled coverage as a randomization problem, not just a "need more tests" problem.

The one-sentence lesson: over-constraining a random sequence — especially with contradictory layered constraints — makes randomize() fail, and an unchecked failure leaves every item at its defaults, so the "random" traffic is identical and coverage stays flat; constrain to legal-and-interesting without contradiction, always check the randomize() return, and vary the seed, because diversity is the whole point of random traffic.

Common Mistakes

  • Not checking the randomize() return. A failed (over-constrained) randomize silently leaves defaults, degenerating the traffic; check it and fatal on failure.
  • Over-constraining into contradiction or rigidity. Too-tight or contradictory constraints kill diversity (or make randomize fail); constrain to legal-and-interesting with a solution always available.
  • Under-constraining into illegality. Too few constraints produce illegal items the DUT rejects, causing spurious failures; include legality constraints.
  • Running one seed (or a fixed seed). A single/fixed seed produces one sample every run, so coverage stalls; vary the seed across the regression.
  • Randomizing only fields. Field-only randomization gives shallow variety; also randomize count, selection, and timing for deep diversity.
  • Uniform randomization without weighting. Uniform wastes samples on uninteresting cases; use dist to bias toward boundaries and rare modes.

Senior Design Review Notes

Interview Insights

Constrained-random traffic generation is producing varied but legal stimulus by randomizing under constraints, so a sequence generates a stream of randomized items that explores the input space rather than testing a fixed hand-written list. It's used because a modern design's legal input space is far too large to enumerate by hand, and the bugs hide in combinations no one anticipated — exactly the cases directed tests miss, because engineers tend to test what they expect to work. Random generation samples the space instead of listing it, reaching unanticipated combinations. The randomization is constrained for two reasons: legality constraints keep each item valid, since pure randomness would mostly produce illegal inputs the protocol rejects; and biasing constraints, typically with the dist operator, weight the randomization toward interesting cases like boundaries and rare modes, so the samples concentrate where bugs cluster while still occasionally hitting everything. You randomize along multiple axes — the item fields, the count of how many items, the selection of which sub-sequence runs next, and the timing of gaps — so the traffic varies in shape, not just values. And because each seed produces a different random stream, a regression of many seeded runs samples the space broadly, accumulating coverage that measures what's been hit. This is the coverage-driven methodology: random traffic generates the samples, coverage steers by exposing gaps, and you run more seeds or adjust constraints until coverage closes. So it's used because it scales to spaces too big to enumerate and finds the bugs you didn't know to look for.

Beyond the item fields — address, data, operation — you randomize the count, the selection, and the timing, and you bias all of them with weighting. The count is how many items the sequence sends: making it a rand field with a legality constraint means each run drives a different number of items, so the traffic's length varies. The selection is which sub-sequence runs next: randomly choosing among, say, a burst-read, a burst-write, and an idle sub-sequence composes a random mix of sub-scenarios, which varies the traffic's shape — one run mostly reads, another mixed, another write-heavy — reaching scenario-level diversity that field randomization alone can't. The timing is the gaps and delays between items: randomizing them exercises the design under both back-to-back and sparse traffic, where timing-dependent bugs hide. And weighting, via the dist operator, aims all of these: rather than uniform randomization that wastes most samples on uninteresting middle cases, dist biases each dial toward the interesting regions — boundaries, rare operations, stress patterns. The reason to turn all the dials rather than just fields is that randomizing only fields gives shallow variety: the same length, the same shape, just different values. Varying the count, selection, and timing, and weighting them toward what matters, is what makes random stimulus explore deeply rather than just jitter the same pattern. So a good random traffic generator randomizes along every axis and weights each toward the cases that find bugs.

Exercises

  1. Write a random traffic sequence. Sketch a sequence that sends a random count (20–200) of items, each with a legal address and an 80/20 read/write mix, using uvm_do_with and dist.
  2. Turn the dials. List four axes you'd randomize for deep diversity beyond item fields, and what each varies about the traffic.
  3. Diagnose the flat coverage. A random sequence produces identical items and coverage won't grow. Give the two likely causes and how to tell them apart.
  4. Explain the loop. Describe the coverage-driven loop random traffic feeds, and what you do when most of the space is covered but a few gaps remain.

Summary

  • Random traffic generation produces varied, legal stimulus at scale: a constrained-random sequence loops a randomized number of times, each iteration randomizing an item (or selecting a sub-sequence) under constraints that keep it legal and bias it (dist) toward interesting cases.
  • You randomize along every dial — fields, count, selection, timing — not just item fields, so the traffic varies in shape as well as values; field-only randomization is shallow.
  • Because each seed yields a different stream, many seeded runs sample the space broadly and accumulate coverage — the coverage-driven loop where random traffic is the engine, coverage the steering, and seeds/constraints the controls, until the space closes.
  • Diversity is the whole point, and two things collapse it: over-constraint (too tight → no diversity, or contradictory → randomize fails, which an unchecked return hides) and a fixed seed (every run the same sample) — both flatten coverage.
  • The durable rule of thumb: constrain to legal-and-interesting without contradiction, weight with dist toward boundaries and rare cases, randomize count/selection/timing as well as fields, always check the randomize() return, and vary the seed across the regression — because random traffic only works when its diversity is preserved and measured by coverage.

Next — Sequence Design Patterns: with composition, control, and random generation in hand, the next chapter collects the reusable patterns that recur in sequence design — layered sequences, reactive sequences, sequence libraries, and the idioms that turn these mechanics into maintainable stimulus architectures.