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

  1. rand vs randc — uniform sampling vs exhaustive cyclic permutation, and where randc breaks down.

  2. The randomize() Call — return-value checking, what gets solved, check-only mode, field-list arguments.

  3. pre_randomize() & post_randomize() — automatic hooks, call order through inheritance, deriving non-rand fields.

  4. std::randomize() & Scope Randomization — randomizing local variables, with-clauses, and $urandom comparison.

  5. Randomizing Nested Objects — recursion into rand class handles, the null-handle skip trap, arrays of objects.

  6. Random Stability & Seeding — thread/object seed hierarchy, srandom(), and reproducing regression failures.

diagram
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 payload

Where the basics bite

diagram
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 failure

All 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.