Part 3 · Constraint Randomization · Intermediate
pre_randomize() & post_randomize()
Automatic hooks around the solve, call order through inheritance and nesting, deriving non-rand fields, and recursion gotchas.
Hooks around the solve
Every class may define function void pre_randomize(); and function void post_randomize();. You never call them directly — randomize() invokes them automatically: pre before the solver runs, post after the solver succeeds. Their division of labor follows from the solver contract: pre is where you set up state the solver will READ (weights, mode flags, rand_mode/constraint_mode switches), post is where you compute values the solver cannot produce (CRCs, parity, timestamps, derived non-rand fields).
class eth_frame;
rand bit [7:0] payload[];
rand bit [11:0] vlan_id;
bit [31:0] fcs; // NOT rand — derived, not solved
bit vlan_en; // state knob set by the test
constraint c_size { payload.size() inside {[46:1500]}; }
function void pre_randomize();
// setup the solver's world BEFORE it runs
if (!vlan_en) vlan_id.rand_mode(0); // freeze field for this solve
endfunction
function void post_randomize();
// derive what the solver cannot: a function of the random result
fcs = crc32(payload);
endfunction
endclassWhat happens at f.randomize(): pre_randomize runs and possibly removes vlan_id from the solve set; the solver then picks payload (size and contents) in one solve; on success post_randomize computes fcs from the actual randomized payload. If the solve fails, post_randomize is not called — fcs would otherwise be derived from stale data. pre_randomize runs unconditionally, before success is known.
Call order across inheritance and nesting
Two ordering rules matter. Inheritance: the hooks behave virtually in practice — the most-derived override runs, and it should call super.pre_randomize() / super.post_randomize() to preserve base-class behavior. Nesting: when the solver recurses into rand class handles, each contained object's hooks run around the overall solve — pre hooks top-down before solving, post hooks after the global solve succeeds.
CALL ORDER for pkt.randomize()
(class derived_pkt extends base_pkt; rand payload pl inside)
┌────────────────────────────────────────────────────────┐
│ 1. pkt.pre_randomize() │
│ derived override runs; calls super.pre_randomize()│
│ 2. pl.pre_randomize() (nested objects next) │
├────────────────────────────────────────────────────────┤
│ 3. ONE GLOBAL SOLVE │
│ pkt fields + pl fields together │
│ (constraints may span both objects) │
├────────────────────────────────────────────────────────┤
│ on success only: │
│ 4. pkt.post_randomize() │
│ 5. pl.post_randomize() │
└────────────────────────────────────────────────────────┘
on solve FAILURE: steps 4–5 skipped, fields unchanged, returns 0
(pre hooks already ran — any side effects they made persist!)The virtual-ness folklore
Older material claims pre/post_randomize are “not virtual”. The reality: IEEE 1800 defines them as built-in virtual-behaving hooks — when randomize() invokes them, the most-derived override executes, exactly as you would expect from virtual dispatch. The pre-2012 confusion arose because early LRM wording did not use the keyword 'virtual' in their prototypes, and some early tools dispatched them non-virtually. Modern simulators all dispatch to the derived override. The interview-safe answer: “they dispatch virtually in all modern tools per 1800-2012 clarifications; declare them without the virtual keyword, override freely, and call super”.
class base_pkt;
rand bit [7:0] kind;
function void post_randomize();
$display("base post: kind=%0d", kind);
endfunction
endclass
class derived_pkt extends base_pkt;
bit [7:0] kind_log[$];
function void post_randomize();
super.post_randomize(); // keep base behavior — easy to forget
kind_log.push_back(kind);
endfunction
endclass
base_pkt h = new derived_pkt(); // base handle, derived object
initial begin
void'(h.randomize());
// prints "base post" AND logs — derived override ran via base handle
endWhat happens: randomize() on the base-typed handle still dispatches to derived_pkt::post_randomize — proof of virtual behavior. Omitting the super call is the classic bug: the base class's derivations (CRC, logging) silently stop happening in every derived transaction.
Typical uses — and the recursion trap
pre_randomize: select weight/mode knobs, flip rand_mode()/constraint_mode() for this solve, capture pre-state for later comparison.
post_randomize: derive non-rand fields (CRC, parity, ECC), enforce relationships too procedural for constraints, log or histogram the chosen values.
post_randomize fix-ups vs constraints: prefer constraints for anything the solver CAN express — post fix-ups bypass the solver, so other constraints cannot react to the fixed-up value.
class msg;
rand bit [7:0] data[4];
bit parity;
function void post_randomize();
parity = ^{data[0], data[1], data[2], data[3]}; // derive: fine
// THE TRAP — re-randomizing inside the hook:
// if (parity) void'(this.randomize());
// randomize() → post_randomize() → randomize() → ... recursion!
// Even when guarded, each inner call reruns hooks and re-solves
// everything, destroying the values the outer call just picked.
endfunction
endclassWhat goes wrong with randomize-inside-the-hook: the inner randomize() triggers the full sequence again — pre, solve, post — so post_randomize re-enters itself; without a termination condition this recurses until stack exhaustion, and with one it still silently replaces the outer solve's results and advances randc cycles. If a result needs rejection-and-retry, loop at the call site , or better, express the requirement as a constraint so the solver never produces a rejected value: constraint c_par { (^data[0] ^ ... ) == 0; } style.
Interview angle
These hooks are a favorite for layered questions because they connect solver mechanics, inheritance, and coding discipline in one topic.
“When does post_randomize NOT run?” — when the solve fails (returns 0). pre_randomize always runs.
“Where do you compute a CRC over random payload?” — post_randomize: it is a function of the solved values, so it cannot be a constraint input the same call.
“Why not fix illegal results in post_randomize instead of writing constraints?” — fix-ups bypass the solver: other constraints cannot see the fixed value, distribution is distorted, and randomize(null) checks will disagree with what you actually send.
“Are the hooks virtual?” — they dispatch to the most-derived override in all modern tools; always call super in overrides.
“What happens if you call randomize() inside post_randomize?” — re-entrant hook execution; recursion risk, clobbered results, burned randc slots. Retry at the call site instead.
// Call-site retry pattern (instead of hook recursion)
msg m = new();
int tries = 0;
initial begin
do begin
assert (m.randomize());
tries++;
end while (!external_filter_ok(m) && tries < 100);
if (tries == 100) $error("could not produce acceptable msg");
end
// Better still: encode external_filter_ok's rule as a constraint
// so the solver only ever produces acceptable messages.Key takeaways
pre_randomize sets up state the solver reads; post_randomize derives values from the solved result — and runs only on success.
Hooks dispatch to the most-derived override (virtual behavior in modern tools); always call super.pre/post_randomize().
For nested rand objects there is ONE global solve, with each object's pre hooks before it and post hooks after it.
Never call randomize() inside the hooks — recursion, clobbered results, advanced randc cycles. Retry at the call site or constrain it away.
Common pitfalls
Deriving CRC/parity in post_randomize but forgetting it is skipped on solve failure — stale derived fields follow stale random fields.
Overriding a hook without calling super — base-class derivations and logging silently vanish in derived transactions.
Using post_randomize fix-ups for rules the solver could enforce — distribution skew and constraint/check disagreement.
Side effects in pre_randomize (e.g. rand_mode(0)) that persist after a FAILED solve — the next call inherits the modified mode unexpectedly.
Calling randomize() from inside pre/post_randomize — re-entrancy and recursion.