Skip to content

UVM

Randomization Planning

Designing the constrained-random engine to reach the coverage plan's targets — choosing what to randomize, designing the random variables (rand versus randc), shaping distributions toward the corners, layering constraints into always-on legality and per-test steering, and structuring the stimulus so the solver can and does produce every coverage target.

Constrained-Random Verification · Module 20 · Page 20.2

The Engineering Problem

Stimulus strategy (Module 20.1) chose coverage-driven constrained-random as the methodology — but "randomize the stimulus" is not a plan. What should be random, and what fixed? Which random variables, of what type? Should the values be uniform, or weighted toward the interesting corners? Which constraints are protocol law that must always hold, and which are per-test biasing toward a scenario? Get these wrong and the random engine fails in silence: under-randomize (fix a field that should vary) and a whole coverage region becomes unreachable; use uniform randomness and the common center floods while the corners starve (20.1's lesson); bake a scenario constraint into the always-on base and every test in the regression is silently restricted to one kind of stimulus. The random engine will dutifully generate legal transactions — but the wrong ones, forever, with no error. The problem this chapter solves is randomization planning: designing the random variables, distributions, and constraint layers so the engine can and does produce the coverage plan's targets.

Randomization planning is the design of the constrained-random stimulus so the solver can and does reach the coverage plan's targets. It has four parts. Choosing what to randomize: the fields whose variation the coverage plan cares about (op, size, address, data, delays, ordering) are random; structural constants are fixedunder-randomizing makes targets unreachable, over-randomizing wastes the solver on noise. Designing the random variables: declare rand (independent each draw) or randc (cyclic — every value once before repeating, good for exhaustively sweeping a small enum) of the right type and width — the variables define the reachable space. Shaping distributions: plain rand is roughly uniform and floods the common center, so dist weights the probability toward the interesting values (boundaries, errors) — aligning the probability mass with the coverage targets. Layering constraints: legality constraints (protocol law — always on, in the base item) versus steering constraints (per-test biasing toward a scenario — layered on top); separating them keeps the base minimal and legal and the steering per-test. The governing principle: plan the randomization from the coverage plan — for every target, ensure a random variable and a constraint path that can reach it. This chapter is randomization planning: what to randomize, the variables, distributions, constraint layering, and reaching the coverage targets.

How do you plan randomization — choose what to randomize, design the random variables (rand versus randc) and their distributions, layer constraints into always-on legality and per-test steering, and structure the stimulus so the constrained-random engine can and does produce the coverage plan's targets?

Motivation — why randomization must be designed, not just declared

Declaring a few rand fields is not a randomization plan — and the gap between "it's random" and "it reaches the targets" is where verification silently fails. The reasons:

  • The random variables define what's reachable at all. If a coverage target depends on a field that isn't random (fixed, or absent), the engine can never produce it — the target is unreachable by construction, no matter how many seeds.
  • Uniform randomness starves the corners. Plain rand follows the legal space's density — the common center — so the boundaries and rare combinations (the coverage plan's hardest targets) are hit almost never. Distributions are how you aim the randomness at them.
  • Legality and steering serve opposite purposes. Legality makes every transaction protocol-correct (always on); steering biases a test toward a scenario (per-test). Conflating them — putting steering in the always-on base — restricts the entire regression to one scenario silently.
  • The randomization must span the coverage space. A cross target (op × size × error) requires the stimulus to randomize all three together in one transaction and bias toward the combination — the structure of the random variables and constraints must span what the coverage plan measures.
  • The solver is powerful but literal. It finds a legal random solution to the active constraintsexactly what you wrote, no more. If your constraints don't allow a scenario, the solver won't invent it; if they're contradictory, it fails. The plan must keep the constraints solvable and capable.

The motivation, in one line: "randomize the stimulus" is not a plan — the random variables define what's reachable, uniform randomness starves the corners, legality and steering serve opposite purposes (and conflating them restricts the whole regression), and the structure must span the coverage space — so randomization must be designed from the coverage plan: what to randomize, which variables, what distributions, which constraint layers — so the literal, powerful solver can and does produce every target.

Mental Model

Hold randomization planning as designing and loading dice — the variables are the faces, legality removes the illegal faces, and distributions weight the dice toward the rare, interesting outcomes:

A constrained-random generator is a set of dice. The random variables are the dice and their faces — they define the whole space of outcomes that can be rolled. The legality constraints permanently remove the illegal faces, so every roll lands on a protocol-correct outcome — that's the fixed shape of the dice, always in effect. The distributions weight the dice — a fair die rolls every face equally and so almost never lands on a rare corner, while a loaded die favors the outcomes you care about. Planning randomization is designing dice that can roll every outcome the coverage plan needs, and loading them toward the rare corners — and doing the loading per-test, on top of the permanent legal shape, never by reshaping the dice themselves. Picture the constrained-random engine as a set of dice you design. The random variables are the dice and their faces: declaring rand bit [6:0] size is a die with 128 faces; the set of random variables defines the entire space of outcomes the engine can roll. The legality constraints permanently remove the illegal faces: size inside {[1:64]} strikes off faces 65–127, so every roll lands on a legal size — this is the fixed shape of the die, always in effect (it makes the stimulus protocol-correct). The distributions weight the dice: a fair die (plain rand) rolls every legal face equally, so it almost never lands on a rare corner (the boundary, the error) — while a loaded die (dist) favors the outcomes you care about (size dist { [60:64] := 10 } loads toward the large sizes). Planning randomization is two acts: first, design dice that can roll every outcome the coverage plan needs (the right variables, with legal shapes that don't accidentally exclude a target); second, load them toward the rare corners (distributions and scenario constraints that bias toward the hard targets). And the crucial discipline: the loading is per-test, on top of the permanent legal shape — you load the dice differently for different scenarios (a write-heavy test loads toward writes, a boundary test loads toward boundaries), but you never reshape the die itself (you never bake a scenario's loading into the permanent legal shape, or every test would be stuck with it). A well-planned generator is a set of dice that can roll every target, with per-test loading toward the corners, on a fixed legal shape.

So randomization planning is designing and loading dice: the random variables are the faces (the reachable space), the legality constraints remove the illegal faces (the permanent legal shape — every roll protocol-correct), and the distributions weight the dice toward the rare corners the coverage plan needs (since a fair die rarely rolls them). Design dice that can roll every target, load them toward the corners, and do the loading per-test on top of the fixed legal shapenever reshape the die (bake steering into legality), or every test is stuck with it. Design the dice to reach every target, load them toward the corners, and keep the loading separate from the legal shape.

Visual Explanation — the anatomy of planned randomization

The defining picture is the layering: random variables define the space, legality constraints carve the legal subset, and distributions weight within it — together producing the reachable, biased stimulus.

Randomization anatomy: variables define space, legality carves legal subset, distributions weight itRandom variables (the space)rand / randc fields of the right type and width — define the full set of reachable values (the dice and their faces)rand / randc fields of the right type and width — define the full set of reachable values (the dice and their faces)Legality constraints (the legal subset)protocol law, always on — every generated transaction is legal (remove the illegal faces)protocol law, always on — every generated transaction is legal (remove the illegal faces)Distributions (the weighting)dist weights the probability toward the interesting corners — plain rand floods the common center (load the dice)dist weights the probability toward the interesting corners — plain rand floods the common center (load the dice)Result: legal, aimed stimulusalways protocol-correct and biased toward the coverage plan's targets — can and does reach themalways protocol-correct and biased toward the coverage plan's targets — can and does reach them
Figure 1 — the anatomy of planned randomization. The random variables define the full space of possible values. Legality constraints carve out the legal subset — every generated transaction is protocol-correct. Distributions weight the probability within the legal subset, biasing toward the interesting corners rather than the common center. The result is stimulus that is always legal and aimed at the coverage targets. Each layer is a design decision: the variables decide what's reachable, legality decides what's allowed, and distributions decide what's likely.

The figure shows the anatomy of planned randomization as layers. Random variables (the brand-colored top) define the full spacerand/randc fields of the right type and width, the set of reachable values (the dice and their faces). Legality constraints (the success-colored layer) carve out the legal subsetprotocol law, always on, so every generated transaction is legal (the illegal faces removed). Distributions (the warning-colored layer) weight the probability within the legal subset — biasing toward the interesting corners rather than the common center (since plain rand floods the center). The result (the brand-colored bottom) is stimulus that is always legal and aimed at the coverage targets — it can and does reach them. The crucial reading is that each layer is a distinct design decision: the variables decide what's reachable at all (a target depending on a non-random field is unreachable), legality decides what's allowed (every transaction protocol-correct), and distributions decide what's likely (aimed at the corners). The layers compose: the space (variables) is narrowed to the legal subset (legality) and then weighted (distributions) — and all three must be right for the engine to produce the targets. If the variable is missing or fixed, no legality or distribution can reach the target (it's outside the space). If the legality is too tight, it excludes a legal target (it's carved out of the subset). If the distribution is absent (plain rand), the corners are reachable but rarely reached (un-weighted). The diagram is the design stack: variables (reachable) → legality (allowed) → distributions (likely) → legal, aimed stimulusthree decisions that together make the random engine capable of and biased toward the coverage plan's targets. Variables decide what's reachable, legality what's allowed, distributions what's likely — design all three from the coverage plan.

RTL / Simulation Perspective — variables, distributions, and constraint layers

In code, the plan is random variables, distributions, and separated constraint layers. The example shows a base transaction (legality only) and a test (steering on top).

planned randomization: rand/randc variables, dist weighting, legality in base, steering in test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// === BASE transaction: random variables + LEGALITY constraints ONLY (always on, protocol-correct) ===
class bus_txn extends uvm_sequence_item;
  rand  op_e       op;                 // rand: independent each draw
  rand  bit [6:0]  size;
  randc bit [3:0]  id;                 // randc: cycles through all 16 IDs before repeating (exhaustive)
  rand  bit [31:0] addr;
 
  constraint legality_c {              // LEGALITY ONLY — must hold for EVERY transaction
    size inside {[1:64]};              // legal size range
    addr[1:0] == 0;                    // legal alignment
  }
  // NOTE: NO op/size BIASING here — biasing is per-test steering, not base legality (DebugLab)
endclass
 
// === TEST A: steer toward LARGE sizes and ERROR-prone ops (reach the boundary/error coverage) ===
class boundary_test extends base_test;
  // steering layered ON TOP of legality — biases the probability mass toward the corners
  constraint steer_c {
    size dist { [1:8] := 1, [9:55] := 2, [56:64] := 10 };   // load the dice toward LARGE sizes
    op   dist { READ := 2, WRITE := 2, RMW := 6 };           // load toward the rarer RMW
  }
endclass
 
// === TEST B: a DIFFERENT scenario steers differently — same legal base, different loading ===
class smallio_test extends base_test;
  constraint steer_c { size dist { [1:4] := 10, [5:64] := 1 }; }  // load toward SMALL sizes
endclass
// the BASE stays legal-and-unbiased; each TEST loads the dice for ITS scenario → all targets reachable

The code shows the planned structure. The base transaction (bus_txn) declares the random variablesrand op_e op and rand bit [6:0] size (independent each draw), randc bit [3:0] id (cyclic — sweeps all 16 IDs before repeating, exhaustively), rand bit [31:0] addr — and only legality constraints (size inside {[1:64]}, addr[1:0]==0) that must hold for every transaction. Crucially, it has no op/size biasingbiasing is per-test steering, not base legality (the DebugLab). Test A (boundary_test) layers steering on top: size dist { ... [56:64] := 10 } loads the dice toward large sizes, and op dist { ... RMW := 6 } toward the rarer RMW — to reach the boundary/error coverage. Test B (smallio_test) steers differentlysize dist { [1:4] := 10, ... } toward small sizessame legal base, different loading. The shape to carry: the base declares the variables (the reachable space — rand for independent, randc for exhaustive sweep) and the legality (always-on protocol law), and each test layers its steering (dist weighting toward its scenario's corners) on top. Because the base stays legal-and-unbiased, each test can load the dice for its scenario, and across tests all targets are reachable. The randc for id is a planning choice — for a small enum where you want every value exercised, randc guarantees the sweep (no value missed by chance), where plain rand might take many draws to hit them all. The separation — legality in the base, steering in tests — is what keeps the base reusable across all scenarios. Declare the variables and legality in the base; load the dice per-test with distributions.

Verification Perspective — legality versus steering, and reaching every target

The central planning skill is layering constraints rightlegality always-on, steering per-test — and ensuring every coverage target has a reachable path. Seeing the two constraint roles and the target-to-capability mapping shows the plan.

Legality (base, always-on) vs steering (test, per-scenario), mapped to coverage targetsneeds a random variableneeds arandom…needs a constraint pathneeds aconstraint…declared in baselegality allows itsteering biases toitreachableand legaland likelyCoverage targeta bin/cross to hitRandom variablecan take the valueConstraint pathcan reach the valueBase itemvariables + legalityLegality (always on)allows the targetSteering (per test)biases toward itTarget reachablelegal + likely12
Figure 2 — legality versus steering, and mapping coverage targets to randomization capability. Legality constraints live in the base item, are always on, and make every transaction protocol-correct — they must never exclude a legal coverage target. Steering constraints live in the tests, are per-scenario, and bias the probability toward a target's corner. For every coverage target, the plan must provide a random variable that can take the needed value and a constraint path that can reach it — a target with no reachable path is a testbench limitation. Legality is the fixed shape; steering is the per-test loading.

The figure shows the two constraint roles and the target-to-capability mapping. A coverage target (a bin or cross to hit) needs two things: a random variable that can take the needed value, and a constraint path that can reach it. The variable is declared in the base item (alongside legality); the path requires that legality allows the value (doesn't exclude it) and that steering can bias toward it. The verification insight is the division of labor: legality (the default-colored always-on base constraints) makes every transaction protocol-correct and must never exclude a legal coverage target (if legality forbids a value the coverage plan requires, the target is permanently unreachable — a contradiction between the legality model and the coverage plan). Steering (the warning-colored per-test constraints) biases the probability toward a target's corner. So the plan must, for every coverage target, provide a random variable that can take the value (else unreachable by construction) and a constraint path that legality allows and steering can bias to (else unreachable by constraint). A target with no reachable path is a testbench limitation (Module 19.6) — surfaced at planning time by checking each coverage target against the randomization capability. The brand-colored target maps through the success-colored required capabilities (variable, path) to the success-colored reachable outcome — legal (legality allows) and likely (steering biases). The figure is the planning check: walk the coverage plan, and for each target, confirm a random variable can take it and a constraint path can reach itlegality the fixed shape (allows it), steering the per-test loading (biases to it). Every coverage target needs a random variable that can take it and a constraint path that legality allows and steering can reach.

Runtime / Execution Flow — how the solver produces a transaction

At run time, each randomization call invokes the constraint solver, which finds a legal random solution to the active constraints, weighted by the distributions. The flow shows it.

Solver flow: gather constraints, find solution space, pick weighted by distributionsrandomize() → gather active constraints (legality + steering) → solver finds the legal solution space → pick one, weighted by distributions → one legal, biased transactionrandomize() → gather active constraints (legality + steering) → solver finds the legal solution space → pick one, weighted by distributions → one legal, biased transaction1randomize() is calledthe engine gathers all active constraints: the base's legality plusthe current test's steering.2Solver finds the solution spacethe set of all value assignments satisfying every active constraint— legal and scenario-consistent.3Pick one, weighted by distributionschoose a random solution from the space, biased by the dist weightstoward the interesting values.4One legal, biased transactionthe produced transaction is always legal and aimed at the scenario;contradictory constraints would fail instead.
Figure 3 — how the solver produces one randomized transaction. The randomize call gathers all active constraints — the always-on legality from the base plus the steering from the current test. The solver finds the space of solutions satisfying all of them. It picks one solution at random, weighted by the distributions. The result is one legal transaction, biased toward the scenario. More constraints shrink the solution space; contradictory constraints leave no solution and fail. The solver is literal — it produces exactly what the active constraints allow, weighted as the distributions specify.

The flow shows how the solver produces a transaction. Call (step 1): randomize() gathers all active constraints — the base's legality plus the current test's steering. Solve (step 2): the solver finds the solution space — the set of all value assignments satisfying every active constraint (legal and scenario-consistent). Pick (step 3): choose one random solution from the space, weighted by the dist weights toward the interesting values. Result (step 4): the produced transaction is always legal and aimed at the scenario. The runtime insight is that the solver is literal and constraint-bounded: it produces exactly what the active constraints allow, weighted as the distributions specifyno more, no less. This has three planning consequences. First, more constraints shrink the solution space — each constraint removes solutions, so over-constraining leaves few (or no) legal options (Module 20.3's concern). Second, contradictory constraints leave no solution — if legality says size <= 64 and a steering constraint says size > 100, the solver fails (a runtime randomization failure). Third, the solver won't invent what the constraints don't allow — if no active constraint permits a target value, the solver never produces it (the unreachable-by-construction case). So the plan must ensure the active constraint set for each test is solvable (not contradictory) and permits the targets that test aims at (legality allows them, steering biases to them). The distributions then shape which of the legal solutions are pickedweighting toward the corners. The flow is the solver's job: gather (legality + steering) → find the legal solution spacepick weighted by distributionsone legal, biased transaction — and the plan's job is to make that solution space both solvable and target-reaching. The solver produces exactly what the active constraints allow, weighted by the distributions — plan the constraints to be solvable and to permit every target.

Waveform Perspective — distributions shaping the output over time

The effect of a distribution is visible over many transactions: plain rand clusters in the common center (corners rarely hit), while a dist-weighted stream frequently hits the cornerthe same legal variable, differently loaded. The waveform shows both.

Plain rand clusters in the common center; a dist-weighted stream reaches the boundary corner

12 cycles
Plain rand clusters in the common center; a dist-weighted stream reaches the boundary cornersize_uniform: mid-range values (12..51) — boundary (64) almost never occurssize_uniform: mid-rang…size_dist (loaded toward large): hits 64 → cov_dist_bnd boundary bin fillssize_dist (loaded towa…uniform boundary bin (cov_unif_bnd) still empty after 12 txns — corner starveduniform boundary bin (…dist boundary bin hit repeatedly → the distribution aimed the engine at the cornerdist boundary bin hit …clksize_uniform301245285119334022374825size_dist625864306164566359645760cov_unif_bndcov_dist_bndt0t1t2t3t4t5t6t7t8t9t10t11
Figure 4 — distributions shape which legal values actually occur. Two streams generate the same legal size field (1 to 64). The plain-rand stream (size_uniform) produces mostly mid-range values and only rarely lands on the boundary (size 64), so the boundary coverage bin stays mostly empty. The dist-weighted stream (size_dist, loaded toward large sizes) frequently produces the boundary, so cov_boundary fills. Same legal variable, same legal range — the distribution decides whether the corner is reached often or almost never. Planning the distribution is how you aim the random engine at the coverage plan's corners.

The waveform shows distributions shaping the output. Two streams generate the same legal size field (1–64). The plain-rand stream (size_uniform) produces mostly mid-range values (12–51) and only rarely lands on the boundary (size 64), so the boundary coverage bin (cov_unif_bnd) stays empty. The dist-weighted stream (size_dist, loaded toward large sizes) frequently produces the boundary (62, 58, 64, 64...), so cov_dist_bnd fills. The crucial reading is that both streams are equally legalsame variable, same legal range (1–64) — yet the uniform stream starves the boundary corner (after 12 transactions, cov_unif_bnd is still empty) while the weighted stream hits it repeatedly. This is the visual proof of the distribution's role: it decides whether a legal corner is reached often or almost never. The uniform stream isn't wrong — it's just un-aimed: it follows the legal space's density, which is the mid-range (most of 1–64 is not near the boundary), so the boundary — a narrow slice — is hit only by chance (rarely). The dist re-weights the probability toward the boundary slice, aiming the engine at the corner. The picture to carry is that planning the distribution is aiming the random engine: without a distribution, the engine fires at the common center (and the corners starve — 20.1's lesson); with a planned distribution, the engine fires at the coverage plan's corners. Reading the waveform this way — does the stream reach the corner, or cluster in the center? — is reading the distribution's effect. The dist-weighted stream filling the boundary bin while uniform leaves it empty is the signature of distribution planning: the same legal variable, aimed. Plan the distribution to aim the random engine at the coverage corners, or the legal-but-narrow corners stay starved.

DebugLab — the read path that no test could ever exercise

Read coverage stuck at 0% across the whole regression — a steering constraint baked into the base

Symptom

A team's read-path coverage was at 0%no read transaction had ever been generated, in any test, across the entire regression of thousands of seeds. The write path was fully covered. The team assumed a stimulus gap (Module 19.6) and added tests specifically targeting reads — new sequences with op == READ steering. The reads still didn't appear: even the read-targeted tests produced only writes, and the randomization silently ignored the read steering. No test, however written, could produce a read — yet there was no error, no solver failure, just writes, always writes.

Root cause

A steering constraint — op == WRITE, added to the base transaction's legality during early write-focused debug and never removed — was baked into the always-on base, so it silently restricted every test in the regression to writes only, overriding (contradicting, and winning over, or simply co-constraining) any per-test read steering:

why no test could produce a read — a scenario constraint in the always-on base
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
✗ STEERING baked into the BASE's always-on legality:
  class bus_txn extends uvm_sequence_item;
    rand op_e op;
    constraint legality_c { size inside {[1:64]}; op == WRITE; }  // ← op==WRITE is STEERING, not legality!
  endclass
  // op==WRITE is ALWAYS active (it's in the base) → EVERY transaction in EVERY test is a WRITE
  // a read-targeting test adds op==READ → CONTRADICTION with base op==WRITE → solver fails or base wins
  //   → reads NEVER generated, read coverage stuck at 0% across the WHOLE regression, no obvious error
 
✓ SEPARATE legality from steering — base has ONLY legality; steering lives in tests:
  class bus_txn extends uvm_sequence_item;
    rand op_e op;
    constraint legality_c { size inside {[1:64]}; addr[1:0] == 0; }  // legality ONLY — op is free
  endclass
  // now op is unconstrained in the base → tests steer it freely:
  //   write_test:  constraint { op == WRITE; }     read_test: constraint { op == READ; }
  //   mixed_test:  constraint { op dist { READ:=1, WRITE:=1, RMW:=1 }; }
  // every op is reachable; each test loads the dice for its scenario → read coverage closes

This is the steering-in-the-base bug — the cardinal randomization-planning structural failure. A scenario constraint (op == WRITE) — legitimate steering for a write-focused test — was placed in the base transaction's legality_c, where it became always active, so every transaction in every test across the whole regression was forced to be a WRITE. A test trying to target reads (op == READ) contradicts the base's op == WRITE — so the solver either fails (if it detects the contradiction) or the base constraint wins — and reads are never generated. The insidious part is the silence: there's no obvious error (the engine dutifully produces legal writes), and the read coverage sits at 0% with the team chasing it via more tests that can't possibly work — because the restriction is in the base, invisible to the test-level debugging. The root error is conflating steering with legality: op == WRITE is not a legality rule (reads are perfectly legal) — it's steering for one scenario, which belongs in a test, not the always-on base. The fix is to separate them: the base legality_c holds only true legality (size inside {[1:64]}, addr[1:0] == 0) and leaves op free, and each test steers op for its scenario (write_test → WRITE, read_test → READ, mixed_test → a dist). Now every op is reachable, each test loads the dice for its scenario, and read coverage closes. The general lesson, and the chapter's thesis: separate legality from steeringbase constraints encode what's always legal (must hold for every transaction), and test/sequence constraints encode what this scenario wants (steering, per-test); baking a scenario constraint into the always-on base silently restricts every test in the regression to one kind of stimulus, making whole coverage regions unreachable with no obvious error — so keep the base minimal and legal, put scenario biasing in tests, and when a coverage region is unreachable across the entire regression, suspect an over-constrained base, not a stimulus gap. A whole coverage region that no test can reach is usually not a missing test — it is a steering constraint hiding in the always-on base, restricting every test at once.

Diagnosis

The tell is a coverage region unreachable across the entire regression, not just one test. Diagnose steering-in-the-base:

  1. Check whether any test can reach it. If no test, however targeted, produces the scenario, the restriction is global — suspect the base, not a stimulus gap.
  2. Inspect the base item's constraints for steering. A scenario constraint (specific op, mode, or value) in the always-on base restricts every test; only legality belongs there.
  3. Look for solver failures or silently-overridden test constraints. A test constraint that contradicts the base either fails to solve or is overridden, producing the base's value regardless.
  4. Separate the concern. Legality must hold for every transaction; if a constraint is true only for some scenarios, it's steering and belongs in a test.
Prevention

Keep the base legal and the steering per-test:

  1. Put only true legality in the base. Constraints that must hold for every legal transaction; nothing scenario-specific.
  2. Put steering in tests and sequences. Per-scenario biasing — distributions, specific values — layered on top of the legal base.
  3. Review base constraints for hidden steering. A specific op, mode, or value in the base is a red flag; ask whether it's legality or a leftover scenario bias.
  4. Treat regression-wide unreachability as an over-constrained base. When no test can reach a legal region, look in the base before writing more tests.

The one-sentence lesson: separate legality from steering — base constraints encode what's always legal (must hold for every transaction), test constraints encode what this scenario wants (steering, per-test), so baking a scenario constraint into the always-on base silently restricts every test in the regression to one kind of stimulus with no obvious error, and a coverage region unreachable across the whole regression is usually an over-constrained base, not a missing test.

Common Mistakes

  • Baking steering into the always-on base. A scenario constraint in the base restricts every test; keep only true legality in the base and put steering in tests.
  • Under-randomizing a coverage-relevant field. Fixing or over-narrowing a field a coverage target depends on makes the target unreachable by construction; randomize what the coverage plan cares about.
  • Relying on uniform rand to reach corners. Plain rand floods the common center; weight distributions toward the boundaries and rare values the coverage plan needs.
  • Using rand where randc fits (or vice versa). For a small enum you want exhaustively swept, randc guarantees every value before repeating; rand may miss some by chance.
  • Not mapping coverage targets to randomization capability. Every target needs a random variable that can take it and a constraint path that reaches it; check the plan against the coverage plan.
  • Over-constraining into contradiction. Too many or conflicting constraints shrink the solution space to nothing and fail; keep the active set solvable.

Senior Design Review Notes

Interview Insights

Planning randomization means designing the constrained-random stimulus so the solver can and does reach the coverage plan's targets, and it has four parts: choosing what to randomize, designing the random variables, shaping the distributions, and layering the constraints. First, choosing what to randomize: the fields whose variation the coverage plan cares about — operation, size, address, data, delays, ordering — are random, while structural constants are fixed. Under-randomizing makes targets unreachable; over-randomizing wastes the solver on noise. Second, designing the random variables: you declare rand or randc fields of the right type and width. rand draws independently each time; randc cycles through all values before repeating, which is good for exhaustively sweeping a small enum like IDs or modes. The variables define the reachable space — a target depending on a non-random field can never be produced. Third, shaping distributions: plain rand is roughly uniform and floods the common center, so the boundaries and rare combinations are hit almost never. You use dist to weight the probability toward the interesting corners, aligning the probability mass with the coverage targets. Fourth, layering constraints: legality constraints encode protocol law and are always on in the base item, making every transaction correct; steering constraints are per-test and bias toward a scenario. You separate them so the base stays minimal and legal and the steering stays per-test. The governing principle tying it together is to plan the randomization from the coverage plan: for every target, ensure a random variable that can take the value and a constraint path that legality allows and steering can reach. A target the randomization can't produce is a testbench limitation. The mental model is designing and loading dice: the variables are the faces, legality removes the illegal faces, and distributions weight the dice toward the rare corners. You design dice that can roll every target and load them toward the corners, per-test, on top of the fixed legal shape.

Legality constraints encode what's always legal — protocol law that must hold for every transaction — and live in the base item always on; steering constraints encode what a particular scenario wants — biasing toward a target — and live in the tests per-scenario. You separate them because conflating them silently breaks the whole regression. Legality is things like the legal size range, address alignment, valid encodings — rules the DUT requires of any correct transaction. These belong in the base sequence item's constraints because every transaction, in every test, must satisfy them. Steering is things like bias toward large sizes, focus on writes, weight error responses higher — choices about which legal scenarios this particular test should exercise. These belong in the test or sequence, because different tests want different biasing on the same legal base. The reason separation matters is the failure mode when you violate it. Suppose you put a steering constraint like op equals WRITE into the base item's legality, maybe during early write-focused debug. Now op equals WRITE is always active, so every transaction in every test across the entire regression is forced to be a write. Reads are perfectly legal, but no test can ever produce one — a read-targeting test adds op equals READ, which contradicts the base, so the solver either fails or the base wins, and reads are never generated. Read coverage sits at zero percent across the whole regression with no obvious error, because the engine dutifully produces legal writes. The team chases it as a stimulus gap, writing more read tests that can't possibly work, because the restriction is in the base, invisible to test-level debugging. The fix is to keep only true legality in the base — leaving op free — and put op steering in each test: write_test constrains op to WRITE, read_test to READ, mixed_test to a distribution. So the rule is: if a constraint must hold for every legal transaction, it's legality and goes in the base; if it's true only for some scenarios, it's steering and goes in a test. And when a coverage region is unreachable across the entire regression rather than just one test, suspect an over-constrained base, not a missing test.

You use distributions because plain rand follows the legal space's density and floods the common center, so the boundaries and rare values the coverage plan needs are hit almost never — distributions reweight the probability to aim the random engine at those corners. When you declare a rand field with a legal range, the solver picks roughly uniformly across the legal solutions. For a size field from 1 to 64, most of that range is mid-range values, and the boundary — say exactly 64 — is a narrow slice, so uniform randomization lands on it only by chance, rarely. The same is true for error responses versus normal ones, or any rare combination: the common cases dominate the legal space, so random stimulus churns them while the corners starve. This is the volume-isn't-coverage problem from the strategy chapter, at the variable level. Distributions fix it. The dist construct lets you assign weights: size dist with the large-size range weighted ten times higher than the mid-range biases the engine to produce large sizes frequently, so the boundary coverage bin fills instead of staying empty. The relationship to coverage is direct: the coverage plan names the corners you need to hit, and the distribution is how you make the random engine actually hit them. You plan the distribution from the coverage plan — wherever the coverage plan has a hard-to-reach target, you weight the distribution toward it. Without distributions, the engine fires at the common center and the corners go unexercised; with planned distributions, it fires at the coverage plan's corners. The waveform view makes it concrete: a uniform stream produces mid-range values and leaves the boundary bin empty after many transactions, while a dist-weighted stream hits the boundary repeatedly — same legal variable, same legal range, but the distribution decides whether the corner is reached often or almost never. Distributions are also typically steering, so they live in tests, letting different tests aim at different corners on the same legal base. So distributions are the aiming mechanism: they convert a legal-but-uniform engine into one that targets the specific scenarios coverage requires.

You use randc when you want to exhaustively sweep all values of a variable before any repeats — typically a small enumeration where every value is a distinct case you need to exercise — and rand for independent draws everywhere else. rand draws each value independently, so over many randomizations it produces a roughly uniform spread but with no memory: it can draw the same value twice in a row and might take many draws to hit every value of a set at least once, purely by chance. randc, the cyclic random qualifier, cycles through all possible values of the variable in a random order, visiting each exactly once before repeating any. So for a 4-bit id field, randc produces a random permutation of all 16 values, then another permutation, and so on — guaranteeing every id is exercised within each cycle of 16. The use case is a small set where you want coverage of every value and don't want to rely on chance to eventually hit them all. IDs, modes, opcodes, source ports, small indices — anything where the coverage plan says exercise every value of this enumeration. With plain rand, you'd lean on distributions or many seeds and still might leave one value under-hit; with randc, the sweep is guaranteed. The trade-offs and cautions: randc only makes sense for variables with a small domain, because cycling through all values of a wide field is meaningless and expensive — you'd never use randc on a 32-bit field. It also interacts with constraints — randc still respects constraints, cycling through the legal values — but heavy constraints on a randc variable can complicate the cyclic guarantee. And randc applies per-variable independence, so it doesn't coordinate across variables. In practice, you reach for randc deliberately when a coverage goal is exercise every value of this small set and you want the exhaustive sweep guaranteed rather than probabilistic. For most fields, especially wide ones or where you want distribution shaping, rand with appropriate distributions is the right choice. So the decision is: small enum needing exhaustive per-cycle coverage, use randc; everything else, use rand.

You ensure it by planning the randomization from the coverage plan: for every coverage target, you confirm there's a random variable that can take the needed value and a constraint path — legality allowing it and steering able to bias toward it — that can reach it. This is the reachability check, and it's done at planning time by walking the coverage plan against the randomization design. For each target — a bin or a cross cell — you ask two questions. First, is there a random variable that can take the value? If the coverage plan requires exercising, say, a particular response code, but the response field isn't randomized, or is fixed, the target is unreachable by construction — no seeds will ever produce it. So every coverage-relevant field must be a random variable of a type and width that spans the needed values. For cross targets, the variables involved must be randomizable together in one transaction, because a cross cell needs the values to co-occur. Second, is there a constraint path that reaches it? Legality must allow the value — if a legality constraint excludes a value the coverage plan requires, you have a contradiction between the legality model and the coverage plan, and the target is permanently unreachable; that's a flag to resolve, maybe the coverage target is actually illegal, or the legality is too strict. And steering must be able to bias toward it — there must be a way for some test to weight the distribution or constrain toward that corner. A target with no reachable path is a testbench limitation, which is exactly the kind of gap that, if not caught at planning, shows up later as a permanently unhit bin that more stimulus can't close. By doing this mapping up front, you catch reachability problems before running thousands of seeds against them. This is the concrete form of the two-halves principle: the coverage plan says what to exercise, the stimulus plan says how to produce it, and the reachability check is where you verify the how can actually deliver the what. If it can't, you fix the randomization — add a random variable, loosen an over-tight legality constraint, add a steering path — so that every target the coverage plan demands has a route the random engine can travel.

Exercises

  1. Classify the constraints. For a transaction with size, alignment, op, and a write-focused test, label each constraint as legality or steering and place it.
  2. Aim a distribution. Coverage needs the maximum size hit often. Write a dist that weights a size field toward the boundary.
  3. Choose rand or randc. For an 8-entry ID field where every ID must be exercised, pick rand or randc and justify it.
  4. Find the over-constrained base. Read coverage is 0% across all tests. Describe what you'd inspect and the likely cause.

Summary

  • Randomization planning is designing the constrained-random stimulus so the solver can and does reach the coverage plan's targets, across four decisions: what to randomize (the coverage-relevant fields; fix the rest), the random variables (rand vs randc, right type), the distributions (dist weighting toward the corners), and the constraint layers (legality vs steering).
  • Distributions aim the engine: plain rand follows the legal density and floods the common center, so the corners starvedist weights the probability mass toward the boundaries and rare values the coverage plan needs.
  • Separate legality from steering: legality (protocol law) is always on in the base item; steering (per-scenario biasing) lives in the testsbaking steering into the base silently restricts the whole regression to one kind of stimulus.
  • Plan from the coverage plan: for every target, ensure a random variable that can take the value and a constraint path (legality allows, steering biases) that can reach it — a target with no reachable path is a testbench limitation; the solver is literal and produces only what the active constraints allow.
  • The durable rule of thumb: design the dice to reach every target and load them toward the corners — randomize the coverage-relevant fields, weight distributions toward the boundaries and rare values, keep only true legality in the always-on base and put scenario steering in tests, and check every coverage target against a random variable and a constraint path that can reach it; when a coverage region is unreachable across the whole regression, suspect an over-constrained base, not a missing test.

Next — Constraint Quality: randomization planning designs the constraints; this chapter examines whether they are good — over-constraining that starves the solution space, contradictory constraints that fail to solve, constraints that bias unintentionally, solver performance and distribution skew, and how to write constraints that are legal, solvable, performant, and produce the spread the coverage plan needs.