Part 3 · Constraint Randomization · Intermediate
Randomization Basics: rand, randc & randomize()
Hub — rand/randc semantics, the randomize() call, pre/post hooks, std::randomize, nested objects, and random stability.
Overview
Everything in constrained-random verification starts with three primitives: the rand / randc qualifiers that mark class fields as random, the built-in randomize() method that invokes the constraint solver, and the seeding machinery that makes random runs reproducible. Get these semantics exactly right before touching constraint operators — most randomization bugs (and most interview misses) are basics-level: an unchecked return value, a null nested handle, or a seed-stability misunderstanding.
The key mental model: randomize() is a solver call, not an assignment . It either finds a legal value set for ALL rand fields simultaneously and returns 1, or finds a contradiction, changes nothing, and returns 0. Every sub-topic below builds on this one fact.
Sub-topics
rand vs randc — uniform sampling vs exhaustive cyclic permutation, and where randc breaks down.
The randomize() Call — return-value checking, what gets solved, check-only mode, field-list arguments.
pre_randomize() & post_randomize() — automatic hooks, call order through inheritance, deriving non-rand fields.
std::randomize() & Scope Randomization — randomizing local variables, with-clauses, and $urandom comparison.
Randomizing Nested Objects — recursion into rand class handles, the null-handle skip trap, arrays of objects.
Random Stability & Seeding — thread/object seed hierarchy, srandom(), and reproducing regression failures.
RANDOMIZATION BASICS — what happens on obj.randomize()
class packet;
rand bit [7:0] addr; ◄── solved fresh each call (uniform)
randc bit [1:0] kind; ◄── cycles through all values, no repeat
bit [7:0] crc; ◄── NOT rand: untouched by solver
rand payload pl; ◄── solver RECURSES into pl (if non-null)
endclass
obj.randomize()
│
├─ 1. pre_randomize() hooks (top-down: obj, then nested)
├─ 2. solver: pick legal values for all rand/randc fields
│ success → fields updated, returns 1
│ failure → fields UNCHANGED, returns 0
└─ 3. post_randomize() hooks (only on success)
└─ typical: derive crc from randomized payloadWhere the basics bite
THE THREE CLASSIC BASICS BUGS
1. UNCHECKED RETURN 2. NULL NESTED HANDLE
pkt.randomize(); rand payload pl; // never new()
// contradiction → returns 0 pkt.randomize(); // returns 1!
// pkt holds STALE data // pl silently skipped, stays null
// test passes on garbage
3. SEED INSTABILITY
adding an unrelated fork / object changes downstream random values
→ yesterday's failing seed no longer reproduces the failureAll three pass compilation and usually pass simulation — they corrupt stimulus quality silently. The sub-lessons cover each failure mode, why the LRM defines it that way, and the defensive idiom that prevents it.
Key takeaways
randomize() solves all rand fields simultaneously — it is a constraint-satisfaction call, not sequential assignment.
On failure randomize() returns 0 and leaves every field unchanged — always check the return value.
rand resamples uniformly; randc walks a random permutation of all values before repeating any.
Nested rand class handles are recursed into only if non-null — null handles are skipped without warning.