Both qualifiers hand a field to the solver, with different promises. rand means an independent uniform draw each call, so repeats are normal.
randc means random-cyclic: the solver shuffles all legal values into a permutation and deals one per call. It reshuffles only when the permutation is exhausted.
randc field. Calls 4 and 5 both return 2. Is the solver broken?No. Call 4 ends one permutation and call 5 starts a new, independent one, so the two can match. A repeat is impossible only inside a cycle of four.
The listing below makes eight calls and prints both fields. It checks that c deals 0 to 3 once in each block of four calls, and counts how often r repeats back to back.
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 tb_rand_randc;
demo d = new();
bit [3:0] dealt;
int repeats_r = 0;
bit [1:0] last_r;
initial begin
for (int call = 1; call <= 8; call++) begin
if (!d.randomize()) $fatal(1, "FAIL: randomize failed");
$display("call %0d: r=%0d c=%0d", call, d.r, d.c);
if (call > 1 && d.r == last_r) repeats_r++;
last_r = d.r;
if (call % 4 == 1) dealt = 0;
if (dealt[d.c]) $fatal(1, "FAIL: c repeated %0d inside one cycle", d.c);
dealt[d.c] = 1;
if (call % 4 == 0 && dealt != 4'hF) $fatal(1, "FAIL: a cycle of 4 missed a value");
end
$display("PASS: c dealt 0..3 once in each block of 4 calls; r repeated back to back %0d times", repeats_r);
$finish;
end
endmodulecall 1: r=3 c=2
call 2: r=1 c=1
call 3: r=2 c=3
call 4: r=1 c=0
call 5: r=1 c=1
call 6: r=1 c=3
call 7: r=1 c=0
call 8: r=0 c=2
PASS: c dealt 0..3 once in each block of 4 calls; r repeated back to back 3 timesThe extra randc bookkeeping explains its limits. The solver must remember which values of the current permutation are used, which is why randc has vendor-dependent width limits and constraint restrictions.
Aspect | rand | randc |
|---|---|---|
Promise | Independent uniform draw each call | One random permutation dealt out, then reshuffled |
Repeats | Expected at any time | Never within a cycle; possible at a boundary |
Solver state | None kept between calls | Used-value set per variable per object |
Restrictions | None | Solved before rand; no dist weights; width limit is vendor-dependent |
Use | General stimulus | Cover-each-value-once sweeps inside one object |
The period counts legal values, not representable ones. A constraint that restricts a 4-bit randc field to 5 values makes the cycle length 5.
Back-to-back duplicates can occur exactly at a cycle boundary. The new permutation is independent of the old one, so the last value of one cycle may equal the first of the next.
class limits;
randc bit [7:0] small_id; // narrow randc: portable
rand bit [7:0] data;
// ILLEGAL: dist on randc contradicts the exactly-once contract
// constraint c_bad { small_id dist { 0 := 9, [1:255] :/ 1 }; }
// Legal: inside restricts the cycle to 5 values, so the period is 5
constraint c_set { small_id inside {10, 20, 30, 40, 50}; }
endclass
module tb_randc_set;
limits l = new();
int seen[int];
initial begin
for (int call = 0; call < 20; call++) begin
if (call % 5 == 0) seen.delete();
if (!l.randomize()) $fatal(1, "FAIL: randomize failed");
if (!(l.small_id inside {10, 20, 30, 40, 50})) $fatal(1, "FAIL: %0d is outside the set", l.small_id);
if (seen.exists(int'(l.small_id))) $fatal(1, "FAIL: %0d repeated inside a cycle of 5", l.small_id);
seen[int'(l.small_id)] = 1;
end
$display("PASS: 20 calls made 4 full cycles, each dealing 10, 20, 30, 40 and 50 once");
$finish;
end
endmodulePASS: 20 calls made 4 full cycles, each dealing 10, 20, 30, 40 and 50 onceThe solver builds the permutation only over the inside set. Each group of five consecutive calls then yields those five values in some order.
The standard lets implementations cap randc width, but the cap must be at least 8 bits (IEEE 1800 18.4.2). Wider limits are vendor-dependent, so keep randc fields narrow and shuffle an explicit list for wide spaces.
The guarantee is per object, per variable, across consecutive calls of that object. Three situations quietly break the all-values-once expectation that people reach for randc to get.
Re-randomizing for other fields still advances the randc cycle. Every call consumes one permutation slot, even if only another field mattered.
Two objects hold independent cycle state. Ten packets with a randc id each cover the range separately, never collectively.
Constraints that change mid-cycle restart the cycle. IEEE 1800 18.4.2 recomputes the permutation when the constraints on the variable change, so a value can repeat before every value was used.
// snippet: send() stands for driving the packet
class pkt;
randc bit [1:0] id; // intent: use each id once before repeating
rand bit [7:0] data;
endclass
module top;
pkt p = new();
initial begin
// Intent: 4 packets, ids 0..3 each exactly once. Looks right...
repeat (4) begin
if (!p.randomize()) $error("randomize failed");
if (p.data == 0) begin
if (!p.randomize()) $error("randomize failed"); // ALSO consumes an id slot
end
send(p);
end
$finish;
end
endmodule
// Sent ids may read e.g. 1,0,2,1: a value repeated and one missing.
// The cycle itself never repeated; one of its elements was discarded.The solver cannot know a result was discarded. Each call deals the next permutation element, so the re-roll burns one id. The fix is a field list from the next lesson: p.randomize(data) re-rolls only data and leaves id untouched.
The interview sweep below runs in this course's checks. A randc field with no constraints needs no constraint solver, so Verilator runs it. It asserts that each 16-call window is a permutation. An explicit shuffle fallback for wide fields follows.
class walker;
randc bit [3:0] val; // 16 calls deal 16 distinct values, random order
endclass
module tb_walker;
walker w = new();
bit seen[16];
initial begin
repeat (2) begin // two full cycles of 16 calls
foreach (seen[i]) seen[i] = 0;
repeat (16) begin
if (!w.randomize()) $fatal(1, "FAIL: randomize failed");
if (seen[w.val]) $fatal(1, "FAIL: value %0d repeated inside one cycle", w.val);
seen[w.val] = 1;
end
foreach (seen[i]) if (!seen[i]) $fatal(1, "FAIL: value %0d missing from the cycle", i);
end
$display("PASS: two 16-call randc cycles each dealt all 16 values once");
$finish;
end
endmodulePASS: two 16-call randc cycles each dealt all 16 values once// snippet: use_value() stands for whatever consumes each value
// Wide-field alternative: explicit shuffle, no randc width limit.
task automatic drive_wide();
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]);
endtaskrandc bit [3:0] id. Do the ten packets together cover 0 to 15?Not necessarily. Each object keeps its own cycle, so ten objects each start their own permutation; nothing coordinates them. One object called sixteen times covers 0 to 15.
The LRM only guarantees randc widths up to 8 bits; wider limits are vendor-dependent. Shuffling an explicit 1024-element list gives the same guarantee with no width risk.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.
Bench output is from a fixed seed; another seed deals a different permutation with the same property.