SystemVerilog · Module 10
Weighted Distributions — dist
Probabilistic stimulus shaping with relative weights, the := vs :/ operator semantics on ranges, the two-stage filter-then-sample model, and pairing every dist with a coverpoint.
Module 10 · Page 10.8
What dist Does
Without dist, every value in the legal range is equally probable. The dist operator lets you assign relative weights to individual values or ranges. The solver picks values in proportion to those weights — heavier items are chosen more often.
dist is a constraint expression, not a separate keyword. It appears inside a constraint block or a with clause, applied to a rand variable.
class BusTxn;
rand bit write;
// Without dist: write=0 and write=1 equally likely (50% each)
// With dist: write=0 three times more likely than write=1
constraint rw_ratio {
write dist { 0 := 3, 1 := 1 };
}
// 75% reads (write=0), 25% writes (write=1)
endclassTwo Weight Operators — := and :/
There are two ways to assign a weight to an item inside a dist list — and they behave very differently when applied to ranges.
| Operator | Applied to a single value | Applied to a range |
|---|---|---|
:= | Weight assigned to that one value | Weight assigned to each individual value in the range |
:/ | Weight assigned to that one value | Weight split equally across all values in the range |
class WeightDemo;
rand bit [3:0] x;
// ── := assigns weight to EACH value in the range ──────────────
// Each of 0,1,2,3,4 gets weight 10. 6 gets weight 1.
// Total weight = 5×10 + 1 = 51
// P(x in 0–4) = 50/51 ≈ 98% P(x = 6) = 1/51 ≈ 2%
constraint c_colon_eq {
x dist { [0:4] := 10, 6 := 1 };
}
// ── :/ splits weight ACROSS the range ────────────────────────
// Range [0:4] has 5 values, weight 10 split → each gets 10/5 = 2.
// 6 gets weight 1.
// Total weight = 5×2 + 1 = 11
// P(x in 0–4) = 10/11 ≈ 91% P(x = 6) = 1/11 ≈ 9%
constraint c_colon_slash {
x dist { [0:4] :/ 10, 6 :/ 1 };
}
endclassHow Weights Translate to Probabilities
Weights are relative, not absolute percentages. The probability of any item is its weight divided by the sum of all weights. You can use any positive integers — the actual values don't matter, only their ratios.
// Three values: weights 7, 2, 1 → total = 10
// P(x=0) = 7/10 = 70%
// P(x=1) = 2/10 = 20%
// P(x=2) = 1/10 = 10%
constraint traffic {
x dist { 0 := 7, 1 := 2, 2 := 1 };
}
// Equivalent — only ratios matter, not the actual numbers
constraint traffic_scaled {
x dist { 0 := 70, 1 := 20, 2 := 10 }; // same probabilities
}
// Weight 0 effectively excludes a value
// (same as not listing it at all)
constraint exclude_two {
x dist { 0 := 7, 1 := 2, 2 := 0 }; // x=2 never chosen
}Mixing Values and Ranges
A single dist list can contain any combination of individual values and ranges. Each item gets its own weight and operator.
class PacketLen;
rand bit [15:0] len;
constraint length_dist {
len dist {
64 := 20, // min-size packets: 20/57 ≈ 35%
[65:511] :/ 30, // short range as a bucket: 30/57 ≈ 53%
[512:1517] :/ 6, // medium range as a bucket: 6/57 ≈ 11%
1518 := 1 // max-size packets: 1/57 ≈ 2%
};
}
endclass
// Total = 20 + 30 + 6 + 1 = 57
// Corner cases (min and max) are explicitly weighted
// Ranges are treated as single buckets with :/ so range size doesn't dominatedist Combined With Other Constraints
dist is a constraint expression — it combines with all active constraint blocks. The solver first finds the set of values that satisfies every other constraint, then applies the weighted distribution over that reduced set.
class Txn;
rand bit [7:0] addr;
rand bit write;
// Hard: address must be in valid peripheral space
constraint in_range { addr inside {[8'h10:8'h7F]}; }
// Weighted: within that range, bias heavily towards the base region
constraint addr_bias {
addr dist { [8'h10:8'h1F] :/ 60,
[8'h20:8'h7F] :/ 40 };
}
// Weighted: 3:1 read-heavy traffic
constraint rw_mix {
write dist { 0 := 3, 1 := 1 };
}
endclassdist in Inline with Constraints
dist works inside a with clause exactly like inside a class constraint block, making per-call traffic shaping straightforward.
Txn t = new();
// Normal 3:1 read/write from the class constraint
// Override inline for a write-heavy phase
assert(t.rw_mix.constraint_mode(0)); // disable class dist
assert(t.randomize() with {
write dist { 0 := 1, 1 := 9 }; // 90% writes inline
});
t.rw_mix.constraint_mode(1); // restorePractical Traffic Patterns
Pattern 1 — Protocol traffic mix
class AxiTxn;
rand bit write;
rand bit [2:0] burst_type; // 0=FIXED, 1=INCR, 2=WRAP
rand bit [3:0] burst_len; // 0=1beat, 15=16beats
// 60% reads, 40% writes
constraint rw {
write dist { 0 := 6, 1 := 4 };
}
// INCR dominates (most real traffic), FIXED and WRAP as minorities
constraint burst_t {
burst_type dist { 0 := 1, 1 := 8, 2 := 1 };
}
// Short bursts most common, medium occasional, long rare
constraint burst_l {
burst_len dist {
[0:3] :/ 70, // 1–4 beats: 70% of traffic
[4:7] :/ 20, // 5–8 beats: 20%
[8:15] :/ 10 // 9–16 beats: 10%
};
}
endclassPattern 2 — Corner-case emphasis
class FifoDepth;
rand bit [5:0] depth; // 0–63
// Hit boundary conditions often, middle occasionally
constraint boundary_focus {
depth dist {
0 := 25, // empty: 25%
63 := 25, // full: 25%
[1:62] :/ 50 // rest: 50% split across 62 values
};
}
endclassdist vs soft — Choosing the Right Tool
| Scenario | Use |
|---|---|
| You want exactly 70% reads, 30% writes over many calls | dist |
| You want reads to be the default unless a test forces writes | soft write == 0 |
| You want corner values hit often but not to the exclusion of others | dist with high weights on corners |
| You want a base class to have defaults a child can freely override | soft |
| You want all values equally likely but only within a set | inside (no dist needed) |
Common Pitfalls
Confusing := and :/ on ranges
[0:99] := 1 gives 100 values each with weight 1 — total weight from this item alone is 100. [0:99] :/ 1 gives the entire range a total weight of 1. The difference is enormous when other items have weight 1 too.
dist values outside other constraints
If a value in the dist list is excluded by another hard constraint, the solver fails. Keep dist ranges aligned with all other active constraints.
Expecting exact percentages in small runs
Weights are proportional probabilities, not guarantees per call. Over 10 calls, a 70/30 split might produce 8/2 or 6/4. The ratio holds statistically over hundreds of calls.
Using dist when randc gives better coverage
If you just need all values hit before any repeats, use randc. Reserve dist for when you genuinely need unequal probabilities, not just "visit everything."
Quick Reference
// ── Basic syntax ──────────────────────────────────────────────
x dist { val1 := w1, val2 := w2, ... };
// ── := on a range: weight per value ──────────────────────────
x dist { [0:3] := 10 }; // 0,1,2,3 each get weight 10 → total 40
// ── :/ on a range: weight for the whole range ─────────────────
x dist { [0:3] :/ 10 }; // 0,1,2,3 share weight 10 → each gets 2.5
// ── Mixed — values + ranges ───────────────────────────────────
x dist { 0 := 20, [1:10] :/ 50, 11 := 30 };
// ── Weight 0 = exclude value ──────────────────────────────────
x dist { 0 := 9, 1 := 0, 2 := 1 }; // x=1 never generated
// ── Probability formula ───────────────────────────────────────
// P(item) = item_weight / sum_of_all_weights
// Weights are relative — only ratios matter
// ── dist inside a with clause ─────────────────────────────────
assert(obj.randomize() with { x dist { 0 := 7, 1 := 3 }; });Verification Usage — Where dist Earns Its Keep in Real CRV
Across production UVM environments, weighted distributions cluster around four reliable patterns. Each addresses a coverage-driven need that pure uniform randomisation handles poorly.
Corner-case biasing on payload fields
The canonical use: skew payload values toward boundary corners (zero, all-ones, alternating bit patterns) that often expose RTL bugs but are statistically rare in uniform random.
<code>class apb_xact;
rand bit [31:0] pwdata;
constraint c_data_dist {
pwdata dist {
32'h0000_0000 := 5, // 5% — boundary low
32'hFFFF_FFFF := 5, // 5% — boundary high
32'hAAAA_AAAA := 3, // 3% — alternating pattern
32'h5555_5555 := 3, // 3% — alternating pattern (inverse)
[32'h1 : 32'hFFFF_FFFE] := 84 // 84% — fills the rest
};
}
endclass</code>Read/write/atomic transaction mix
Real workloads aren't 50/50 reads/writes. Cache-heavy benchmarks are 80% reads; streaming writes are 90% writes; coherency tests emphasise atomics. The dist shapes the protocol-level mix per test scenario.
<code>typedef enum bit [1:0] { READ, WRITE, ATOMIC_RMW, BARRIER } kind_e;
class apb_xact;
rand kind_e kind;
constraint c_kind_mix {
kind dist {
READ := 65,
WRITE := 30,
ATOMIC_RMW := 4,
BARRIER := 1
};
}
endclass</code>Inter-transaction delay shaping
The driver's idle gap between transactions affects bus utilisation and exposes timing-related bugs. Realistic test mixes emphasise both back-to-back (gap=0) and long-idle (gap=large) patterns over pure uniform.
<code>class apb_driver_seq;
rand int unsigned idle_gap;
constraint c_idle {
idle_gap dist {
0 := 40, // 40% back-to-back
[1:5] := 30, // 30% short gap
[6:100] := 25, // 25% medium gap
[101:1000] := 5 // 5% long gap
};
}
endclass</code>Burst-length distribution for AXI/AHB/PCIe
Protocols allow many burst lengths but typical traffic clusters at small power-of-2 sizes. dist shapes the burst-length distribution to match realistic traffic without losing the ability to occasionally exercise edge cases.
Simulation Behavior — How the Solver Samples From a dist
Two-stage solve: feasibility then weighted sampling
The solver first computes the satisfying set (all values that meet every active constraint). It then applies the dist weights to that satisfying set: items present in both the satisfying set and the dist list keep their relative weights; items in the dist list excluded by other constraints have weight 0; values in the satisfying set not mentioned in the dist list get weight 0 too (unless an explicit "else" range covers them).
Weight semantics
Weights are relative, not percentages. The probability of any item is item_weight / sum_of_all_weights. { 0 := 1, 1 := 1 } and { 0 := 50, 1 := 50 } produce identical distributions. The convention of using percentages (weights summing to 100) is purely a readability tool, not a solver requirement.
:= vs :/ semantics
x := w assigns weight w to every value in x. x :/ w assigns weight w to x as a whole, with each value getting w / N (where N is the number of values in x). For single values, both are identical. For ranges, the operators are dramatically different — [10:19] := 5 gives the range weight 50 (each of 10 values gets 5); [10:19] :/ 5 gives the range weight 5 (each value gets 0.5).
Cost of dist-driven sampling
Distribution sampling adds a small per-call cost over uniform random: the solver computes the cumulative weight distribution, picks a uniform random number in [0, total_weight), and finds the corresponding item. Cost is O(number of dist items), typically negligible against the solve itself. For very large dist lists with implicit "else" ranges, the cost is dominated by the satisfying-set computation, not the sampling.
<code>class xact;
rand bit [7:0] kind;
// Equal probability for individual values vs ranges
constraint c {
kind dist {
0 := 25, // 25%
255 := 25, // 25%
[1:254] := 50 // 50% spread over middle
// (each middle value: 50/254 ≈ 0.2%)
};
}
endclass</code><code>class xact;
rand bit [7:0] kind;
constraint c {
kind dist {
0 := 25,
255 := 25,
// Wanted "50% in middle" — used := so each
// middle value gets weight 25, ratio:
// total = 25 + 25 + 254*25 = 6400
// 0 gets 25/6400 = 0.4% (not 25%!)
[1:254] := 25
};
}
endclass</code>Waveform Analysis — Verifying the Distribution Matches Intent
A dist describes the long-run frequency of values. Verifying it produces the intended distribution requires statistical observation, not single-transaction inspection. The discipline: pair every dist with a coverpoint that bins the field by intended weight category.
The coverage-driven verification pattern
<code>class apb_xact;
rand bit [31:0] pwdata;
constraint c_data {
pwdata dist {
32'h0 := 5,
[32'h1 : 32'hFFFE_FFFE] := 90,
32'hFFFF_FFFF := 5
};
}
covergroup cg_dist;
cp_data : coverpoint pwdata {
bins zero = {0};
bins middle = {[1 : 32'hFFFE_FFFE]};
bins all_ones = {32'hFFFF_FFFF};
}
endgroup
endclass</code>ASCII view — coverage report validating the dist
<code>After 10,000 randomize() calls:
Coverpoint Hits Expected Status
-----------------------------------------------------
cp_data.zero 487 500 ✓ (~5%)
cp_data.middle 8941 9000 ✓ (~90%)
cp_data.all_ones 572 500 ✓ (~5%)
-----------------------------------------------------
Total: 10000 10000 Dist OK</code>Diagnosing "the distribution isn't what I expected"
If the coverage shows the zero-bin at 1% instead of the intended 5%, three likely causes: (a) the :=/:/ operator was wrong, scaling the weights incorrectly; (b) another constraint is excluding part of the dist list, re-normalising weights toward other items; (c) the iteration count is too low to converge statistically — at 100 calls, even a correct dist can show 1% on a 5% bin from pure randomness. Distinguish by running with 10x more iterations.
Industry Insights — Hard-Won Lessons About Weighted Distributions
Debugging Academy — Five Real Bugs From dist Misuse
"Boundary case appears 0.4% of the time, not 25%"
DEBUGCoverage after 10,000 transactions shows the all-ones boundary at 0.4% hits, not the intended 25%.
<code>class xact;
rand bit [7:0] pwdata;
constraint c {
pwdata dist {
0 := 25, // 25% zero
[1:254] := 25, // BUG: meant "25% middle" but := gives weight to each
255 := 25 // 25% all-ones
};
}
endclass</code>:= assigns the weight to each value in a range. [1:254] := 25 gives 254 items × 25 = 6350 total weight to the middle, vs 25 for zero and 25 for all-ones. Ratios: zero = 25/6400 ≈ 0.4%, middle ≈ 99.2%, all-ones ≈ 0.4%. Not what was intended.
Use :/ for the range: pwdata dist { 0 := 25, [1:254] :/ 50, 255 := 25 };. The range gets weight 50 shared across all values (each gets 50/254 ≈ 0.2). Total = 25+50+25 = 100. Now zero = 25%, middle = 50%, all-ones = 25%.
"dist appears to do nothing — distribution is uniform"
DEBUGA dist declared with weights 50/50 between two values produces ~50/50, but a 90/10 dist between the same two values still produces ~50/50.
<code>class xact;
rand bit [3:0] kind;
constraint c_dist { kind dist { 0 := 90, 1 := 10 }; }
constraint c_kind { kind inside {0, 1}; }
endclass
// Some pre-existing constraint:
// constraint c_kind_force { kind != 0; } // forces non-zero
// (added elsewhere, forgotten)
// Result: 0% zero, 100% kind=1, dist appears ignored</code>A hard constraint elsewhere excludes kind = 0. The solver computes the satisfying set first (which is {1}), then applies the dist — but only value 1 is in both the dist list and the satisfying set, so probability collapses to 100% one. The dist weights become irrelevant when the hard constraint zeros out one of them.
Find and either remove or relax the conflicting hard constraint. If the intent is "usually zero, sometimes one," the conflicting constraint defeats the purpose entirely. Distribution debugging always starts with "what's the satisfying set?" — print it explicitly and verify it includes every value the dist mentions.
"foreach with dist takes forever to solve"
DEBUGA constraint with foreach (data[i]) data[i] dist { ... } on a 32-element array makes randomize take 200ms per call; a regression that should run in 5 minutes takes 4 hours.
<code>class burst_xact;
rand bit [31:0] data[];
constraint c_size { data.size() == 32; }
constraint c_data {
foreach (data[i])
data[i] dist { 0 := 5, [1:'hFFFE] := 90, 'hFFFF_FFFF := 5 };
}
endclass</code>The solver computes the joint distribution over 32 array elements simultaneously. The cost grows roughly as 3^32 across possible boundary-mix configurations — astronomical for the solver to enumerate exactly.
Move per-element randomisation to post_randomize: function void post_randomize(); foreach (data[i]) begin int w = $urandom_range(0, 99); data[i] = (w < 5) ? 0 : (w < 95) ? $urandom_range(1, 'hFFFE) : 'hFFFF_FFFF; end endfunction. Same statistical distribution; the per-element work is O(1) instead of joint-solved across 32 fields.
"Two dist constraints on the same field — only one takes effect"
DEBUGA class has two constraint blocks each containing a different dist on the same field. The resulting distribution matches one of them but not the other.
<code>class xact;
rand bit [7:0] kind;
constraint c1 { kind dist { 0 := 60, 1 := 40 }; }
constraint c2 { kind dist { 0 := 30, 1 := 70 }; }
endclass
// What's the actual distribution? Neither 60/40 nor 30/70.
// It's the intersection of both, weights multiplied/normalised
// — usually implementation-defined.</code>Two dist clauses on the same field don't combine into a defined joint distribution — the LRM allows implementations to pick one, multiply weights, or anything in between. The result is non-portable across simulators.
Never have two dist constraints on the same field. Consolidate into one block with the intended distribution. If the two were meant to model different test scenarios, use constraint_mode to switch between them, or build a single combined dist whose weights reflect both intents.
"dist with implicit else range omitted — values outside the dist appear with no weight"
DEBUGA test expected zero, all-ones, and 0xCAFE_BABE to each appear ~33% of the time. Actual: zero appears, all-ones appears, but values between them appear at random — apparently with no respect to the dist.
<code>class xact;
rand bit [31:0] pwdata;
// BUG: dist lists three values; everything else has weight 0 in dist —
// but the field still has those values in its type range
constraint c {
pwdata dist {
0 := 1,
'hCAFE_BABE := 1,
'hFFFF_FFFF := 1
};
}
endclass</code>Actually this is the correct interpretation: dist lists exactly three values, so the satisfying set is restricted to those three values, each with probability 1/3. If the user observes values between them, the source is elsewhere — usually another randomize path that doesn't include this constraint, or a constraint disabled via constraint_mode(0).
Verify by printing the satisfying set explicitly. If the constraint is active, pwdata can only take the three values listed. If pwdata takes other values, either: (a) the constraint is disabled (check constraint_mode()), (b) there's a parent class constraint widening it, or (c) some other code path is overriding. The dist itself is working correctly.
Interview Q&A — Twelve Questions You Will Be Asked
It declares a weighted probability distribution over the values a rand/randc field can take. The solver finds a satisfying assignment respecting all hard constraints, then samples from that satisfying set with the given weights. x dist { 0 := 50, [1:9] := 50 } means "50% of the time pick 0, 50% pick uniformly in [1:9]."
Best Practices — Ten Rules for dist Discipline
- Use weights that sum to 100 for readability. Each weight then reads directly as a percentage; no mental arithmetic required.
- Choose
:=for "each value in this range gets this weight." Choose:/for "this entire range shares this weight." Verify the choice matches intent at code-review time. - Pair every
distwith a coverpoint that mirrors its weight categories. The coverage confirms the dist is producing the intended frequencies. - Comment every weight with its rationale. Measured workload? Coverage convergence target? Spec requirement? Future maintainers need the answer in the source.
- Don't put two
distconstraints on the same field. Behaviour is implementation-defined and non-portable. Consolidate. - Avoid
foreachwithdiston large arrays. Joint distribution solving is exponentially expensive. Use procedural per-element randomisation inpost_randomizeinstead. - Choose between
dist,soft, and hard constraints by intent. Dist for probabilistic shaping; soft for overridable defaults; hard for required rules. Mixing the wrong tool produces silent surprise. - Verify dist behaviour during class onboarding. A 10,000-call sanity test with coverage validation catches operator and weight bugs before they distort months of regression data.
- Encode real-workload statistics into dist weights. Uniform random verifies a fantasy workload; weighted random verifies the workload silicon will actually see.
- Remember dist samples from the satisfying set, not the type range. When other constraints exclude values, the dist re-normalises silently — diagnose distribution surprises by printing the satisfying set first.