Part 3 · Constraint Randomization · Intermediate
rand vs randc
Uniform resampling vs cyclic permutation, randc period behavior, width and solver limits, and the all-values-once interview question.
Two different sampling contracts
Both qualifiers mark a class field as solver-controlled, but they make different statistical promises. rand means sample uniformly from the legal values, independently each call — repeats are normal and expected (8 values can repeat on the very next call with probability 1/8). randc means random-cyclic : the solver conceptually shuffles all legal values into a random permutation, deals them out one per randomize() call, and only reshuffles after the permutation is exhausted. Within one cycle, no value repeats.
class demo;
rand bit [1:0] r; // 4 legal values, independent uniform picks
randc bit [1:0] c; // 4 legal values, dealt as a random permutation
endclass
module top;
demo d = new();
initial begin
repeat (8) begin
void'(d.randomize());
$display("r=%0d c=%0d", d.r, d.c);
end
end
endmodule
// Possible output:
// r=2 c=1 r: any value any time (repeats fine)
// r=2 c=3 c: first cycle is some permutation of {0,1,2,3}
// r=0 c=0
// r=3 c=2 ◄── cycle 1 complete: 1,3,0,2
// r=3 c=2 c reshuffles: new permutation begins
// r=1 c=0
// r=0 c=3
// r=2 c=1 ◄── cycle 2 complete: 2,0,3,1What the solver does: for r, each call is an independent uniform draw — the solver intersects constraints into a legal set and picks any member. For c, the solver maintains per-variable cycle state: it remembers which values of the current permutation are already used and excludes them from the next pick. That extra bookkeeping is exactly why randc has width limits and constraint restrictions.
The randc period, drawn out
randc bit [1:0] c; 4 legal values → period = 4
call#: 1 2 3 4 │ 5 6 7 8 │ 9 ...
┌────────────────┐│ ┌────────────────┐│
value: 1 3 0 2 │ 2 0 3 1 │ ...
└── permutation A┘│ └── permutation B┘│
│ │
reshuffle reshuffle
(new random (new random
permutation) permutation)
Guarantees WITHIN a cycle: Guarantees ACROSS cycles:
• every value exactly once • NONE — last value of cycle A
• no repeats may equal first value of cycle B
(e.g. calls 4 and 5 above: 2, 2)The period equals the number of legal values, not the number of representable values — if a constraint restricts a 4-bit randc field to 5 legal values, the cycle length is 5. Note the boundary subtlety in the diagram: back-to-back duplicates CAN occur exactly at a cycle boundary, because the new permutation is independent of the old one.
Width and solver limits
Cyclic bookkeeping must track which values were used, so implementations cap randc width — the LRM requires support for at least 8 bits, and typical simulators allow somewhere between 16 and 32 bits before erroring out. Beyond that, tracking 2^32 used-values is impractical. Two more hard rules: randc variables are solved before rand variables (an implicit solve-before), and a randc variable cannot be the target of dist — a distribution weighting contradicts the exactly-once-per-cycle contract.
class limits;
randc bit [31:0] big_id; // many tools: error or unsupported
randc bit [7:0] small_id; // fine everywhere (LRM minimum)
rand bit [7:0] data;
// ILLEGAL: dist on randc — weighting contradicts cyclic contract
// constraint c_bad { small_id dist { 0 := 9, [1:255] :/ 1 }; }
// Legal: inside restricts the cycle to 5 values → period 5
constraint c_set { small_id inside {10, 20, 30, 40, 50}; }
endclassWhat the solver does with c_set: the permutation is built only over {10, 20, 30, 40, 50}, so each group of five consecutive randomize() calls yields those five values in some random order. Because randc is solved first, the constraint set involving small_id is fixed before data is picked — if a constraint ties data to small_id, data's legal range is computed from the already-chosen small_id value.
When randc misleads
The cyclic guarantee is per object, per variable, across consecutive randomize() calls of that object . Three situations quietly break the all-values-once expectation people reach for randc to get:
Re-randomizing for OTHER fields still advances the randc cycle — every randomize() call consumes one permutation slot, even if you only cared about re-rolling a different field.
Two different objects each have independent cycle state — ten packet objects with randc id do NOT collectively cover 0..255; each object covers it separately.
Constraints that change between calls invalidate the simple period story — if the legal set shrinks mid-cycle, the solver must reconcile used-value tracking with the new set (tool-dependent corner).
class pkt;
randc bit [1:0] id; // intent: use each id once before repeating
rand bit [7:0] data;
endclass
pkt p = new();
initial begin
// Intent: 4 packets, ids 0..3 each exactly once. Looks right...
repeat (4) begin
void'(p.randomize()); // advances id cycle: OK so far
if (p.data == 0)
void'(p.randomize()); // re-roll for data — but this ALSO
// consumes an id slot! The cycle
// advances and one id is "burned"
send(p);
end
// Result: sent ids may be e.g. 1,0,2,1 — a value repeated and a
// value missing, despite randc. The cycle itself never repeated;
// we just discarded one of its elements.
endWhat the solver does: it has no idea you discarded a result. Each randomize() deals the next permutation element; the re-roll consumed id=3 (say) and you never sent it. The fix is to re-roll only the offending field — p.randomize(data) with a field list (covered in the randomize() lesson) leaves id untouched, or constrain data so re-rolls are unnecessary.
Interview angle
The canonical question: “Generate all values of an N-bit field exactly once, in random order.” The expected first answer is randc:
class walker;
randc bit [3:0] val; // 16 calls → 16 distinct values, random order
endclass
walker w = new();
initial begin
repeat (16) begin
void'(w.randomize());
$display("%0d", w.val); // permutation of 0..15
end
endStrong candidates then volunteer the limits before being asked: (1) it only works within one object's consecutive calls — any extra randomize() burns a slot; (2) width is implementation-limited, so for a 32-bit space you instead shuffle an explicit list; (3) the cycle reshuffles after exhaustion, so call 17 may repeat call 16's value. The fallback they want to hear for wide fields:
// Wide-field alternative: explicit shuffle, no randc width limit
int unsigned vals[];
vals = new[1024];
foreach (vals[i]) vals[i] = i;
vals.shuffle(); // random permutation, exactly once each
foreach (vals[i]) use_value(vals[i]);Rapid follow-ups to be ready for
“Can two consecutive randc values be equal?” — Yes, exactly at a cycle boundary; never within a cycle.
“Can you put dist on a randc variable?” — No; weighting contradicts the once-per-cycle contract, it is a compile error.
“Are randc variables solved with or after rand variables?” — Before; randc has an implicit solve-before over rand.
“Do two objects share a randc cycle?” — No; cycle state is per variable per object.
Key takeaways
rand = independent uniform draw each call; randc = random permutation dealt one value per call, reshuffled when exhausted.
randc period = number of LEGAL values under current constraints, and duplicates can occur only across a cycle boundary.
randc variables are solved before rand variables and cannot take dist weights; width is implementation-limited (LRM minimum 8 bits).
Every randomize() call advances every randc cycle in that object — re-rolling for another field burns permutation slots.
Common pitfalls
Using randc on wide fields (>16–32 bits) — tools error out or silently degrade; use an explicit shuffle() instead.
Expecting all-values-once across multiple objects — cycle state is per object, not global.
Re-randomizing an object in a loop and assuming the randc field still covers everything sent — discarded calls burn cycle slots.
Putting dist on a randc field — illegal, and conceptually contradictory.
Assuming call N+1 differs from call N — at a cycle boundary the same value can appear twice in a row.