Part 3 · Constraint Randomization · Intermediate
randc Internals & Limits
The permutation-cycling implementation model, per-variable cycle independence, vendor width limits, randc-before-rand ordering, and the dist/solve-before restrictions.
The cycling model
A randc (random-cyclic) variable visits every value in its legal range exactly once before any value repeats. The implementation model to hold in your head: when a cycle starts, the tool conceptually shuffles the variable's legal values into a random permutation , then successive randomize() calls deal values off that permutation one at a time. When the permutation is exhausted, a new, independently shuffled permutation begins — so the last value of one cycle may equal the first value of the next (the only place a back-to-back repeat can appear).
class channel_walker;
randc bit [1:0] ch; // 4 values: cycles visit all of 0..3
endclass
module demo;
initial begin
channel_walker w = new();
repeat (8) begin
void'(w.randomize());
$write("%0d ", w.ch);
end
// possible output: 2 0 3 1 1 3 0 2
// ^cycle 1^ ^cycle 2^
// within each group of 4: all distinct. Across the
// boundary (1 then 1) a repeat IS possible.
end
endmoduleRANDC CYCLE MODEL (bit [1:0] -> 4 values)
cycle 1: shuffle {0,1,2,3} -> [2,0,3,1]
call1=2 call2=0 call3=3 call4=1
|
cycle 2: NEW shuffle -> [1,3,0,2] v
call5=1 call6=3 call7=0 call8=2
guarantees: non-guarantees:
- all values seen each - cycle boundary repeats possible
cycle - permutation order is random,
- no repeat WITHIN a not the same each cycle
cycle - no uniformity claim on the
ORDER within a cycleConstraints still apply: a constrained randc cycles over the legal subset only. If constraints change between calls (state variables moved), tools may restart the cycle — another reason to keep randc ranges stable.
Independence, width limits, and solve order
Per-variable independence
Each randc variable owns its own private cycle . Two randc variables in one class do not coordinate — their permutations are independent, and there is no "joint cycling" over tuple combinations. If you need all (a, b) pairs visited without repeats, make one wider randc variable and slice it.
class pair_cycler;
// WRONG for full pair coverage: independent cycles can
// revisit pairs while each variable still honors its own cycle.
randc bit [1:0] a;
randc bit [1:0] b;
// RIGHT: one 4-bit cyclic variable = 16 unique (a,b) pairs
randc bit [3:0] ab;
function bit [1:0] get_a(); return ab[3:2]; endfunction
function bit [1:0] get_b(); return ab[1:0]; endfunction
endclassWidth limits
Cycling requires the tool to track which values were used — conceptually a permutation table of 2^width entries. The LRM permits implementations to cap the supported randc width (the LRM mandates at least 8 bits; typical tools support somewhere in the 16- to 32-bit range, vendor-dependent). A randc bit [63:0] cannot have its 2^64-entry cycle tracked and will be rejected or degraded depending on the simulator. For wide non-repeating sequences, use unique-style history in the class instead.
randc solves before rand
Within one randomize() call, randc variables are solved first , in isolation, taking the next value from each one's cycle. Then the rand variables are solved with the randc results frozen as constants . The consequence: a constraint linking a rand variable to a randc variable can only push on the rand side — and if the randc's dealt value leaves the rand variable with no legal choice, randomize() fails, because the solver will not "re-deal" the randc to rescue the rand variable.
class ordering_trap;
randc bit [3:0] slot; // dealt first, frozen
rand bit [3:0] data;
constraint c { data > slot; } // only data can bend
endclass
// When the cycle deals slot = 15, no 4-bit data satisfies
// data > 15 -> randomize() returns 0 that call.
// The solver does NOT skip 15 in the cycle to save the call.What randc cannot join
dist — a weighted distribution contradicts mandatory full-cycle visiting; illegal on randc variables.
solve...before — randc already has fixed ordering (before all rand); naming it in solve-before is illegal.
Wide widths beyond the tool cap — compile or elaboration error, vendor-specific limit.
When to use randc — and when not to
randc shines for exhaustive small-space sweeps with random order : register addresses in a block, channel IDs, opcode selectors, page indexes. It is the wrong tool when you need weighting (illegal), cross-call constraints relating consecutive values (the cycle is oblivious to your constraints between calls), or wide spaces (width cap). The standard alternatives:
// Alternative 1: queue-based history for wide / weighted needs
class wide_norepeat;
rand bit [31:0] addr;
bit [31:0] used[$];
constraint c_new { !(addr inside { used }); }
function void post_randomize();
used.push_back(addr);
if (used.size() > 64) used.delete(0); // sliding window
endfunction
endclass
// Alternative 2: shuffle a list once, iterate — cheapest full sweep
int order[16];
initial begin
foreach (order[i]) order[i] = i;
order.shuffle();
foreach (order[i]) run_test_on(order[i]);
endThe queue-history pattern composes with dist and any other constraint — it trades a little solver work for the flexibility randc lacks.
Interview angle
What interviewers probe
"rand vs randc?" — expected: rand draws independently each call (repeats allowed); randc cycles a permutation, every legal value once per cycle.
"Can two randc variables repeat a PAIR?" — expected: yes; cycles are per-variable independent. Concatenate into one randc for pair-exhaustive behavior.
"Why no dist on randc?" — expected: weights conflict with mandatory once-per-cycle visiting — the cycle already fixes long-run frequency at exactly once each.
"What solves first, rand or randc?" — expected: randc first, frozen, then rand; a dealt randc value can strand the rand side and fail the call.
"randc bit [63:0] — fine?" — expected: no; cycle tracking needs 2^width state, tools cap width; use constraint-based history instead.
The randc-before-rand ordering trap (the data > slot example) is a favorite follow-up because it tests whether you know the cycle is dealt blindly, not negotiated with the rest of the constraint set.
Key takeaways
Model randc as: shuffle legal values into a permutation, deal one per call, reshuffle when exhausted.
Repeats can only occur across cycle boundaries; within a cycle every legal value appears exactly once.
Cycles are per-variable and independent — concatenate fields into one randc for exhaustive pair/tuple sweeps.
randc solves before rand with its value frozen — constraints can strand the rand side and fail the call.
randc cannot take dist or appear in solve-before, and width is vendor-capped — use queue-history constraints beyond its limits.
Common pitfalls
Expecting two independent randc variables to cover all pairs — pairs repeat; only a combined variable guarantees it.
Putting dist or solve-before on a randc variable — illegal; restructure with rand + history.
Wide randc declarations — exceeds vendor width caps; fails at compile/elaboration or silently degrades.
Constraints tying rand vars to a randc — intermittent randomize() failures when the cycle deals an extreme value.
Assuming no repeats EVER — cycle boundary back-to-back repeats are legal and do happen.