Part 3 · Constraint Randomization · Intermediate
Ranges With & Without inside
Drills: bounded ranges without inside, disjoint ranges, excluding a range, and dynamic bounds where lo and hi are themselves rand.
Problem 1 — Value in [100:200] without using inside
Problem
“Constrain rand int unsigned x; to lie in [100:200]. Now do it again without the inside operator.”
Think it through
A closed range is the conjunction of two relational facts: x is at least 100 AND at most 200. Constraints in one block are implicitly ANDed, so two separate expressions work; so does one expression with &&. The trap is the chained-comparison spelling from math notation.
class range_drill;
rand int unsigned x;
// With inside — preferred in production
// constraint c_range { x inside {[100:200]}; }
// Without inside — two ANDed relations
constraint c_range_rel { x >= 100;
x <= 200; }
endclassWhy this works
Every expression inside a constraint block must independently hold; the solver intersects them. Note that inside and the relational pair give the same uniform distribution here — inside is set membership, not a distribution weighting, a distinction the interviewer will pivot to (see the dist drills).
Variation — the chained-comparison trap
// WRONG — looks like math, is not:
constraint c_bad { 100 <= x <= 200; }
// Parses as (100 <= x) <= 200 → (0 or 1) <= 200 → always TRUE.
// The constraint is vacuous: x is completely unconstrained.This compiles cleanly and never errors — the worst kind of bug. Symptom in practice: out-of-range values appear in regression and the constraint “looks right” in review.
Problem 2 — Multiple disjoint ranges, then excluding a range
Problem
“Constrain x to lie in [10:20] or [40:50] or be exactly 99. Then: constrain it to be anywhere in [0:1000] EXCEPT [100:200].”
Think it through
Disjoint sets are exactly what inside is for — its braces take a mix of scalars and ranges. Exclusion is negated membership: !(x inside {...}) ANDed with the outer bound. Without inside, both become OR-chains of relational pairs.
class set_drill;
rand int unsigned x;
// Disjoint union — inside takes scalars and ranges together
constraint c_union { x inside {[10:20], [40:50], 99}; }
// Without inside: explicit OR of conjunctions
// constraint c_union_rel {
// (x >= 10 && x <= 20) || (x >= 40 && x <= 50) || (x == 99);
// }
endclass
class hole_drill;
rand int unsigned x;
// Bounded with a hole
constraint c_hole { x inside {[0:1000]};
!(x inside {[100:200]}); }
endclassWhy this works
The two expressions in c_hole are ANDed: x must be in the big range AND not in the hole. Equivalently you could enumerate x inside {[0:99], [201:1000]} — say both; the negation form scales better when the hole is parameterized.
Variation — exclusion driven by a state variable
class hole_dyn;
rand bit [31:0] addr;
bit [31:0] excl_lo, excl_hi; // NOT rand — state, set by the test
constraint c_hole { !(addr inside {[excl_lo:excl_hi]}); }
endclassState variables are read as constants at the moment randomize() is called — the test sets the hole before randomizing. Interviewers use this to check you know the rand/state distinction.
Common wrong answers
x != [100:200] — ranges are not values; != cannot take a range. Does not compile.
Writing the OR-chain with single & and | — bitwise operators on multi-bit operands give arithmetic garbage, not logical OR of conditions.
x inside {[0:99]} || x inside {[201:1000]} as two separate constraint expressions on separate lines — separate expressions are ANDed, so x must be in both sets: contradiction, randomize() fails.
Problem 3 — Dynamic bounds: lo < x < hi where lo and hi are also rand
Problem
“All three of lo, x, hi are rand 8-bit values. Constrain them so that lo < x < hi always holds. What can go wrong?”
Think it through
State the relations directly — the solver is simultaneous, so it will choose all three together. The subtle question is solution-space existence: lo < x < hi requires hi >= lo + 2 (there must be room for x strictly between). If other constraints squeeze hi and lo together, randomize() fails. A senior answer adds the gap guarantee explicitly.
class window_drill;
rand bit [7:0] lo, x, hi;
constraint c_window { lo < x;
x < hi; }
// Senior addition: guarantee the window is wide enough to be useful
constraint c_gap { hi >= lo + 2; // room for at least one x
hi <= 8'd255; } // explicit, documents intent
endclassWhy this works
There is no “order of solving” to worry about — the solver treats {lo, x, hi} as one constraint satisfaction problem and picks a jointly-legal triple. Note the distribution effect: x values near the middle of [0:255] have more legal (lo, hi) pairs around them, so x is not uniform — it humps toward the center. Mentioning this unprompted is a strong senior signal.
Variation — interviewer follow-up
“Make x uniform regardless of the window.” Answer: decouple the solves — solve lo, hi before x does NOT make x uniform overall (it makes the window uniform first, x uniform within each window). True global uniformity on x requires randomizing x alone first (e.g. std::randomize(x)) and then constraining lo/hi around the now-state value — explain the trade, don’t just name a keyword.
Common wrong answers
lo < x < hi as one chained expression — same vacuous-parse trap as Problem 1.
“You must solve lo and hi before x or it won’t work” — false; the solver is simultaneous. solve...before only reshapes probability, never legality.
Forgetting that lo=254, hi=255 leaves no legal x — if a test later pins lo and hi, randomize() returns 0 and an unchecked call silently keeps stale values.
Key takeaways
A range is two ANDed relations; inside is set membership over scalars and ranges.
Exclusion is !(x inside {...}) ANDed with the outer bound.
rand-on-rand bounds solve simultaneously — but guarantee the window has room, and know the distribution is not uniform.
Common pitfalls
100 <= x <= 200 — parses as ((100<=x)<=200), always true, constraint vacuous.
Two inside expressions on separate lines hoping for OR — separate expressions AND together.
Bitwise &/| in place of logical &&/|| inside compound range conditions.
Never checking randomize() return — an infeasible window silently leaves stale values.