Part 3 · Constraint Randomization · Intermediate
inside, Ranges & Set Membership
Set membership with inside, range syntax, negation, $ open ranges, and arrays as constraint sets.
inside defines a membership set
The inside operator is the workhorse of constraint writing: it restricts a variable to a set built from individual values, ranges, or both. To the solver, x inside {[1:5], 10} means exactly x ∈ {1, 2, 3, 4, 5, 10} — six legal values. Because the default solver distribution is uniform over the solution space, each of those six values has probability 1/6. That last sentence is the part interviewers check: inside changes the set , and the set size determines each value's probability.
class pkt;
rand bit [7:0] len;
rand bit [7:0] kind;
// Ranges and discrete values mix freely in one set
constraint len_c { len inside {[1:8], 16, 32, [64:67]}; }
// Negation: kind may be anything EXCEPT 0 and the range 200..255
constraint kind_c { !(kind inside {0, [200:255]}); }
endclassHere len has 8 + 1 + 1 + 4 = 14 legal values, each at 1/14. The negated form inverts the set: kind may take any of the 256 possible values except the 57 excluded ones, leaving 199 legal values. Negation is how you express “anything but reserved encodings” without listing the legal side.
Open ranges with $
Inside a range, $ stands for the minimum or maximum value of the variable's type. [$:100] means “from the type's smallest value up to 100” and [100:$] means “from 100 up to the type's largest value.” This makes constraints robust against width changes — widen the field and the open range follows.
class addr_txn;
rand bit [15:0] addr; // unsigned: $ low = 0, $ high = 65535
rand int delta; // signed: $ low = -2^31, $ high = 2^31-1
constraint hi_region { addr inside {[16'h8000:$]}; } // 0x8000..0xFFFF
constraint neg_only { delta inside {[$:-1]}; } // any negative int
endclassNote the signedness subtlety: for the unsigned addr, $ on the low side is 0; for the signed delta, it is the most negative representable value. Using $ on a signed type when you meant “down to zero” is a classic source of surprise negative values.
Arrays as the membership set
The set in an inside expression does not have to be a literal — it can be an array (fixed, dynamic, or queue). The solver treats the array's current element values as the membership set at the moment randomize() is called. This is the standard pattern for “pick one of these runtime-configured values,” such as legal addresses loaded from a memory map.
class cfg_txn;
int legal_ids[$] = '{3, 7, 11, 42}; // non-rand state: a runtime knob
rand int id;
constraint id_c { id inside {legal_ids}; }
endclass
module t;
initial begin
cfg_txn tx = new();
void'(tx.randomize()); // id is one of 3, 7, 11, 42 (1/4 each)
tx.legal_ids = '{100, 200}; // reconfigure at runtime
void'(tx.randomize()); // id is now 100 or 200 (1/2 each)
end
endmoduleBecause the array is a non-rand state variable, the solver reads its values rather than solving for them. Reconfiguring the queue between calls changes the solution space with zero constraint edits — this is how reusable transactions get per-test legal sets.
Visualizing the solution space
It helps to picture every inside constraint as carving regions out of the variable's full value line. Intersecting constraints (multiple constraint blocks, or inline with) shrink the legal region further — the solver picks uniformly from whatever remains.
SOLUTION SPACE for: len inside {[1:8], 16, 32, [64:67]};
value line 0 ........................................... 255
|#[1──8]#......#16......#32......#[64─67]#....|
8 vals 1 1 4 vals
total = 14 legal values
P(each value) = 1/14 (uniform default)
Add a second constraint: len % 2 == 0; (intersection!)
|.#2,4,6,8#....#16......#32......#64,66#......|
4 vals 1 1 2 vals
total = 8 legal values
P(each value) = 1/8
Constraints INTERSECT — they never relax each other.The intersection rule is universal: every additional active constraint can only keep or shrink the space. If the intersection is empty, randomize() fails and returns 0 — it never “picks the closest legal value.”
Interview angle
What interviewers ask
“What is the probability of each value in x inside {[1:5], 10}?” — 1/6 each; they are checking you know the default is uniform over the SET, not over the syntax items (not 1/2 for the range and 1/2 for the value).
“How do you exclude a range?” — !(x inside {[a:b]}). Follow-up: what is the resulting set size?
“Can the set come from a variable?” — yes, arrays/queues are read as state at solve time; this is the runtime-configurable legal set pattern.
“What does inside {[100:$]} mean for a signed type?” — up to the type maximum; tests whether you know $ is type-relative.
A strong answer always names the set size and the uniform-over-set default before discussing syntax. If they want non-uniform probability, that is the cue to bring up dist — the next sub-topic.
Key takeaways
inside builds a membership set from values, ranges, and arrays; solver picks uniformly over that set.
!(x inside {...}) inverts the set — the standard way to exclude reserved encodings.
$ in a range bound means the type's min or max — beware signed types.
Arrays in the set are read as current state at solve time, enabling runtime-configured legal sets.
Multiple constraints intersect; an empty intersection makes randomize() return 0.
Common pitfalls
Assuming each syntax item gets equal weight — a range of 1000 values vs a single value is 1000:1, not 1:1.
Using $ on signed fields and getting unintended negative values in the set.
Forgetting that an empty array in inside {arr} yields an empty set — randomize() fails.
Writing x != 5 chains instead of one negated inside — verbose and easy to get wrong.
Expecting a later constraint to widen an earlier one — constraints only intersect.