Part 3 · Constraint Randomization · Intermediate
unique Constraints
unique { arr } semantics, mixing scalars and arrays, generating shuffled permutations, unique vs randc, and what uniqueness costs the solver.
unique: pairwise inequality in one keyword
The unique constraint declares that every listed value is pairwise distinct . unique { arr }; means no two elements of arr are equal — semantically identical to writing arr[i] != arr[j] for every i ≠ j pair, but vastly more readable and easier for the solver to recognize as an all-different problem. The braces can mix scalars and arrays: every scalar and every element of every listed array must differ from all the others.
class distinct_ids;
rand bit [3:0] id[6];
rand bit [3:0] master_id;
rand bit [3:0] a, b, c;
constraint c {
unique { id }; // 6 array elements all distinct
unique { master_id, id }; // master differs from every element too
unique { a, b, c }; // three scalars pairwise distinct
}
endclass
// unique { id } on bit [3:0] id[6]:
// 16 possible values, 6 slots -> satisfiable.
// If id were bit [2:0] id[10]:
// 8 values, 10 slots -> PIGEONHOLE: unsatisfiable, randomize()=0.The pigeonhole check is the first thing to verify: the element type must offer at least as many values as there are entries in the unique set, after intersecting with any range constraints. unique combined with foreach (id[i]) id[i] inside {[0:5]} on a 6-element array leaves exactly one solution family: a permutation of 0..5.
Generating a shuffled permutation
That last observation is the standard interview recipe for a random permutation of 0..N-1 : fix the size to N, restrict every element into [0, N-1], and require uniqueness. With N values squeezed into N distinct slots, the only legal assignments are permutations — and a conforming solver picks uniformly among them.
class perm_gen #(int N = 8);
rand int unsigned p[];
constraint c {
p.size() == N;
foreach (p[i]) p[i] inside {[0:N-1]};
unique { p };
}
endclass
module top;
initial begin
perm_gen #(8) g = new();
repeat (3) begin
void'(g.randomize());
$display("%p", g.p); // e.g. '{3,0,6,1,7,4,2,5}
end
end
endmodule
// Procedural alternative (no solver): arr.shuffle()
// - cheaper, but NOT constraint-aware: you cannot say
// "permutation where p[0] != 0" with shuffle alone.
// - the constraint version composes with any extra rule:
constraint c_extra { p[0] != 0; } // derangement-at-0 permutationMention shuffle() as the cheap procedural alternative when no additional constraints apply — knowing when not to use the solver is part of the expected answer.
unique vs randc
Both produce non-repeating values, but on different axes. unique enforces distinctness within one randomize() call, across the listed variables/elements . randc enforces non-repetition across successive randomize() calls of one variable — it cycles through a permutation of its value space over time. They answer different questions and are not interchangeable.
UNIQUE vs RANDC — TWO AXES OF NON-REPETITION
one call, many values many calls, one value
(spatial) (temporal)
---------------------- ----------------------
keyword unique { arr } randc bit [3:0] v;
guarantees all listed values v cycles all 16 values
differ in THIS solve before any repeat
scope across variables/ across successive
elements randomize() calls
resets when every call re-solves cycle exhausted ->
new random permutation
call 1: unique arr = {3,7,1} randc v = 5
call 2: unique arr = {7,7? NO randc v = 2 (not 5)
-> always distinct} ...
call 16: randc v = last unseen value
call 17: new permutation beginsclass compare;
rand bit [3:0] arr[4];
randc bit [3:0] chan;
constraint c { unique { arr }; }
// arr: each CALL gives 4 distinct values; values may repeat
// between calls (call1 {1,5,9,2}, call2 {5,3,1,8} is fine).
// chan: one value per call; guaranteed to visit all 16 values
// across 16 calls before any repeat.
endclassSolver cost of uniqueness
unique over N elements implies on the order of N²/2 pairwise inequalities. Modern solvers special-case all-different sets, but cost still grows with N and with how tightly other constraints squeeze the value space — a unique set that exactly fills its range (the permutation case) is the hardest version, since almost every partial assignment constrains the rest. Keep unique sets as small as the requirement allows, and widen value ranges when you can.
Interview angle
What interviewers probe
"Generate a random permutation of 0..N-1" — expected: size==N, inside [0:N-1], unique; bonus for naming shuffle() as the unconstrained alternative.
"unique vs randc?" — expected: unique is within-one-call across elements; randc is across-calls for one variable cycling its space.
"What if the range is smaller than the array?" — expected: pigeonhole, unsatisfiable, randomize() returns 0 — check value-count vs slot-count first.
"What does unique cost?" — expected: O(N²) pairwise inequalities conceptually; tight ranges make it harder; permutation is the worst case.
A subtle senior-level point: randc cannot participate in dist or solve...before, so when an interviewer asks for "non-repeating but weighted" values, the answer is unique-style constraints plus history kept in the class, not randc.
Key takeaways
unique { ... } = pairwise distinct across all listed scalars and array elements, within one solve.
Permutation recipe: size == N, elements inside [0:N-1], unique — only permutations remain.
unique is spatial (one call), randc is temporal (across calls) — different tools for different requirements.
Check the pigeonhole condition before anything else: value-space size must be >= unique-set size.
Uniqueness cost grows roughly quadratically and worsens as ranges tighten — keep sets small.
Common pitfalls
unique set larger than the available value space — silent unsatisfiability, randomize() returns 0.
Expecting unique to prevent repeats BETWEEN randomize() calls — that is randc territory (or manual history).
Using randc when distribution weighting is also needed — randc is illegal in dist; restructure with rand + constraints.
Reaching for the solver to shuffle an unconstrained array — arr.shuffle() is free; save the solver for constrained permutations.
Forgetting that range constraints shrink the value space the pigeonhole check applies to — unique passes alone, fails once inside [a:b] is added.