Skip to content

SystemVerilog · Module 10

pre_randomize & post_randomize Callbacks

Setup-before-solve vs derive-and-log-after-success; mandatory super chaining; post_randomize skipped on failure; canonical UVM patterns for sub-object construction, scenario gating, and CRC computation.

Module 10 · Page 10.12

The Call Sequence — Where Each Hook Fires

Both hooks are built-in virtual functions inherited by every class. They have empty default implementations — you override only the ones you need. The full sequence on every randomize() call is:

StepWhat runsCalled even on failure?
1pre_randomize() — your hook before the solverYes — always
2Constraint solver finds valid values (or fails)N/A
3post_randomize() — your hook after successNo — only called when solver succeeds
4randomize() returns 1 (success) or 0 (failure)N/A

Syntax — Overriding the Hooks

Both hooks are declared as virtual function void. Override them inside your class body with the same signature. No arguments, no return value.

SystemVerilog — overriding pre and post randomize
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Packet;
    rand bit [7:0]  data[4];
    rand bit [1:0]  prio;
    bit [7:0]       crc;          // NOT rand — computed in post_randomize
    int             call_count;   // NOT rand — tracked in pre_randomize
 
    // ── Called BEFORE the solver ───────────────────────────────────
    virtual function void pre_randomize();
        call_count++;
        $display("[pre]  randomize() call #%0d", call_count);
    endfunction
 
    // ── Called AFTER the solver succeeds ──────────────────────────
    virtual function void post_randomize();
        crc = data[0] ^ data[1] ^ data[2] ^ data[3];  // simple XOR CRC
        $display("[post] prio=%0d  crc=0x%02h", prio, crc);
    endfunction
endclass

pre_randomize() — What to Put Here

pre_randomize() runs with the object in its current state — before any field is updated. It is the right place for anything that must happen before the solver sees the constraint set.

Constraint steering

Turn constraints on/off based on external state or a test counter before each solve. More readable than doing it outside the class.

Setting non-random bounds

Update plain (non-rand) variables that are referenced as constants inside constraint blocks — so the solver sees the fresh values on this call.

Transaction sequencing

Increment a call counter, advance a state machine, or derive a seed for the next transaction based on where you are in the test sequence.

Logging / debug

Print the pre-randomise state, record which constraints are active, or set a debug flag before the solver runs.

SystemVerilog — pre_randomize: constraint steering by phase
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class PhasedTxn;
    rand bit [31:0] addr;
    int             phase = 0;   // set by the test, not by the solver
 
    constraint phase0_range { addr inside {[32'h0000_0000:32'h0FFF_FFFF]}; }
    constraint phase1_range { addr inside {[32'h1000_0000:32'h1FFF_FFFF]}; }
 
    virtual function void pre_randomize();
        // Only one range constraint active at a time — no contradiction
        phase0_range.constraint_mode(phase == 0);
        phase1_range.constraint_mode(phase == 1);
    endfunction
endclass
 
PhasedTxn t = new();
 
t.phase = 0;  assert(t.randomize());  // addr in 0x0..0x0FFF_FFFF
t.phase = 1;  assert(t.randomize());  // addr in 0x1..0x1FFF_FFFF

post_randomize() — What to Put Here

post_randomize() runs after the solver has successfully assigned values to all rand fields. The solved values are readable and stable. It is the right place for anything that must happen using the newly randomised values.

Computed fields

CRC, checksum, parity, length, header packing — any field whose value is derived from the randomised fields but cannot be expressed as a constraint.

Type conversion

Convert a solved integer field into a string, enum, or packed struct. Randomise sub-objects that depend on the solved scalar fields (see page 10.11).

Display / coverage sampling

Print the randomised transaction, update statistics, or trigger a covergroup sample — guaranteed to run only on successful solves.

Post-solve validation

Assert extra invariants that are hard to express as constraints — for example, verifying that a packed CRC field matches computed CRC.

SystemVerilog — post_randomize: CRC, header packing, logging
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class EtherFrame;
    rand bit [47:0] src_mac;
    rand bit [47:0] dst_mac;
    rand bit [15:0] ethertype;
    rand bit [7:0]  payload[];
 
    // NOT rand — computed after solve
    bit [31:0] fcs;           // Frame Check Sequence
    int        frame_len;     // total frame length in bytes
 
    constraint payload_sz { payload.size() inside {[46:1500]}; }
    constraint no_loopback { src_mac != dst_mac; }
 
    virtual function void post_randomize();
        // Compute frame length (headers + payload + FCS placeholder)
        frame_len = 6 + 6 + 2 + payload.size() + 4;
 
        // Simple FCS placeholder (real CRC32 would go here)
        fcs = 32'h0;
        foreach (payload[i]) fcs ^= payload[i];
 
        $display("[Frame] src=%h dst=%h len=%0d fcs=%08h",
                 src_mac, dst_mac, frame_len, fcs);
    endfunction
endclass

Inheritance — super Calls Are Essential

When a child class overrides either hook, it is responsible for calling the parent's version using super.pre_randomize() or super.post_randomize(). If you omit the super call, the parent's hook logic is silently skipped — including any CRC computation, logging, or constraint steering the parent implements.

SystemVerilog — super calls in overridden hooks
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class Base;
    rand bit [7:0] x;
    bit [7:0] parity;
 
    virtual function void post_randomize();
        parity = ^x;    // base computes parity
        $display("Base:  x=%h  parity=%b", x, parity);
    endfunction
endclass
 
class Child extends Base;
    rand bit [7:0] y;
    bit [7:0] checksum;
 
    virtual function void post_randomize();
        super.post_randomize();  // ← MUST call parent first
        checksum = x + y;       // child adds its own derived field
        $display("Child: y=%h  checksum=%h", y, checksum);
    endfunction
endclass
 
// Without super.post_randomize():
//   parity is never computed — Base's logic is silently lost

Complete Practical Example

A real transaction class combining pre_randomize for phase-based constraint steering and post_randomize for CRC computation, object randomisation, and display.

SystemVerilog — full pre + post pattern in one class
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class CanFrame;
    // Randomised fields
    rand bit [10:0] id;
    rand bit         rtr;
    rand bit [3:0]  dlc;       // data length code (0–8)
    rand bit [7:0]  data[];   // dynamic — size driven by dlc in post
 
    // Derived fields (computed)
    bit [14:0] crc15;
    bit [7:0]  frame_count;
 
    // External state set by the test
    bit        extended_id_only = 0;
 
    constraint dlc_max { dlc <= 8; }
    constraint no_data_on_rtr { rtr -> dlc == 0; }
    constraint std_id { id < 11'h400; }    // standard 11-bit
    constraint ext_id { id >= 11'h400; }   // extended range
 
    virtual function void pre_randomize();
        // Steer ID range before solve
        std_id.constraint_mode(!extended_id_only);
        ext_id.constraint_mode( extended_id_only);
    endfunction
 
    virtual function void post_randomize();
        // Size the payload array to match solved DLC
        data = new[dlc];
        foreach (data[i]) data[i] = $urandom_range(0, 255);
 
        // Compute a simple CRC15 placeholder
        crc15 = 0;
        foreach (data[i]) crc15 ^= data[i][7:1];
 
        frame_count++;
        $display("[CAN #%0d] id=%03h rtr=%b dlc=%0d crc=%04h",
                 frame_count, id, rtr, dlc, crc15);
    endfunction
endclass

Common Pitfalls

Calling randomize() from inside a hook

Calling randomize() from inside pre_randomize() or post_randomize() on the same object causes infinite recursion. Use std::randomize() for local variables, or call randomize() on a different object.

Forgetting super in a child class

Omitting super.post_randomize() silently discards the parent's CRC, logging, and any other post-solve logic. Always call super unless you deliberately intend to replace the parent's behaviour entirely.

Reading rand fields in pre_randomize

In pre_randomize(), rand fields still hold their previous values from the last call. Do not base constraint steering on them expecting the new values — the solver hasn't run yet.

Relying on post_randomize after a failed solve

If the solver fails and you use void'(randomize()), post_randomize() is never called, but your derived fields (CRC, checksum) still hold stale values from the previous successful call. Always check the return value.

Quick Reference

SystemVerilog — pre/post_randomize cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Declaration ───────────────────────────────────────────────
virtual function void pre_randomize();  // override to run before solver
virtual function void post_randomize(); // override to run after solver
 
// ── Call sequence ─────────────────────────────────────────────
// pre_randomize()    → always fires (even on failure)
// solver runs        → may fail (returns 0)
// post_randomize()   → fires ONLY on success
 
// ── Inheritance — always call super ───────────────────────────
virtual function void post_randomize();
    super.post_randomize();   // parent's derived fields first
    // child's additions here
endfunction
 
virtual function void pre_randomize();
    // child's prep here
    super.pre_randomize();    // parent's base prep last
endfunction
 
// ── Good uses ─────────────────────────────────────────────────
// pre:  constraint_mode steering, non-rand bounds update, logging
// post: CRC/checksum, length computation, sub-object randomise, display
 
// ── Bad uses ──────────────────────────────────────────────────
// ✗ Do NOT call obj.randomize() on same object (infinite recursion)
// ✗ Do NOT read rand fields in pre expecting new values (not solved yet)

Verification Usage — Where pre/post Hooks Live in Production UVM

Across every UVM environment, pre_randomize and post_randomize cluster around five reliable patterns. Each addresses a specific need that pure constraint expressions cannot handle.

pre_randomize: sub-object construction

For classes containing rand arrays of class handles, pre_randomize is where the elements are allocated. The solver randomises fields of already-constructed objects; it does not construct.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class burst_xact extends uvm_sequence_item;
  rand int beat_count;
  rand beat_xact beats[];
 
  function void pre_randomize();
    super.pre_randomize();
    if (beats == null) beats = new[beat_count];
    foreach (beats[i])
      if (beats[i] == null)
        beats[i] = beat_xact::type_id::create($sformatf("b_%0d", i));
  endfunction
endclass</code>

pre_randomize: scenario-driven constraint mode

Toggle constraint blocks before each call based on test-layer scenario state. Common in error-injection scenarios where one transaction in N violates a specific rule.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>function void pre_randomize();
  super.pre_randomize();
  if (test_cfg.error_injection_active && ($urandom_range(0, 9) == 0)) begin
    c_align.constraint_mode(0);   // 10% of transactions misaligned
  end else begin
    c_align.constraint_mode(1);
  end
endfunction</code>

post_randomize: CRC and checksum computation

Most protocols carry a CRC, checksum, or parity computed from the randomised payload. These derived fields must match the payload — perfect post_randomize territory.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>function void post_randomize();
  super.post_randomize();
  crc = compute_crc32(payload);
  parity = ^payload;
endfunction</code>

post_randomize: building associative arrays from randomised lists

Associative arrays can't be randomised directly. The idiom: randomise parallel key/value lists, then assemble the associative array procedurally.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>function void post_randomize();
  super.post_randomize();
  mem.delete();
  foreach (keys[i]) mem[keys[i]] = vals[i];
endfunction</code>

post_randomize: logging the constrained outcome

The canonical place to log what the solver picked — transaction ID, field values, soft-default respect flags. The log line is invaluable for post-hoc failure forensics.

Simulation Behavior — When Each Hook Fires

Exact call sequence

The randomize() method conceptually executes: (1) call pre_randomize(); (2) run the constraint solver; (3) if solve succeeded, write the rand fields and call post_randomize(); (4) return 1 (success) or 0 (failure). On failure, step 3 is skipped entirely.

pre_randomize always runs

It runs before the solver attempts to find a satisfying assignment — independent of whether the solver eventually succeeds. Code in pre_randomize executes on every randomize call, period.

post_randomize conditional on success

Only invoked when the solver returns 1. On failure (solver returned 0), post_randomize is skipped, and any derived-field computation it contains does not execute. The fields hold their previous values (or defaults on the first failure).

Synchronous execution on host CPU

Both hooks execute synchronously on the host CPU. $time does not advance during them. They are pure procedural code — function calls, assignments, control flow — just like any class method. No simulation-time events fire during their execution.

Inheritance and virtual dispatch

Both hooks are declared virtual function void. Child class overrides participate in normal virtual dispatch — a parent-handle call parent_h.randomize() on a child object invokes the child's override. Each override must explicitly call super.<same-method>() to chain to the parent's hook.

✅ Proper hook usage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>function void pre_randomize();
  super.pre_randomize();         // chain to parent
  if (beats == null)
    beats = new[expected_size];  // setup sub-objects
endfunction
 
function void post_randomize();
  super.post_randomize();        // chain to parent
  crc = compute_crc(payload);    // derive
  `uvm_info("XACT",
            $sformatf("addr=0x%h", addr),
            UVM_HIGH)
endfunction</code>
❌ Anti-patterns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>function void pre_randomize();
  // No super chain — parent's setup lost
  $display("addr=%h", addr);
  // BUG: addr is the OLD value (previous
  //      call) — solver hasn't run yet
endfunction
 
function void post_randomize();
  // No super chain — parent's CRC lost
  if (!something) return;
  // BUG: conditional skips parent's derive,
  //      next test step expects it
endfunction</code>

Waveform Analysis — Making Hook Effects Visible

The hooks themselves don't fire waveform events — they're procedural functions. The way to verify they're behaving correctly is to log their effects: which derived fields got computed, which sub-objects got allocated, which constraint modes got set.

The pre/post instrumentation pattern

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class apb_xact extends uvm_sequence_item;
  rand bit [31:0] addr, data;
  bit [31:0] crc;
 
  function void pre_randomize();
    super.pre_randomize();
    `uvm_info("PRE",
              $sformatf("scenario=%0d c_align=%s",
                        scenario_id,
                        c_align.constraint_mode() ? "ON" : "OFF"),
              UVM_HIGH)
  endfunction
 
  function void post_randomize();
    super.post_randomize();
    crc = compute_crc32(data);
    `uvm_info("POST",
              $sformatf("addr=0x%h data=0x%h crc=0x%h",
                        addr, data, crc),
              UVM_HIGH)
  endfunction
endclass</code>

ASCII view — hook events bracketing each randomize

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>   90ns  PRE   scenario=2 c_align=ON
  100ns  POST  addr=0x4000_0010 data=0xDEAD_BEEF crc=0xA1B2_C3D4
  190ns  PRE   scenario=3 c_align=OFFscenario flipped
  200ns  POST  addr=0x4000_0011 data=0x12345678 crc=0x9876_5432
  290ns  PRE   scenario=2 c_align=ON
  ...
        ↑                                          ↑
   pre log shows setup            post log shows constrained values
   the hook performed             AND derived CRC</code>

Diagnosing "derived field is wrong" failures

When a DUT NAKs a transaction for "bad CRC," the immediate question is: was the CRC computed at all? With post_randomize logging, you see either "post fired, CRC=X" (computation ran, X is the value) or no post line (solve failed, CRC stayed at default). Either case directs the debugging — to the CRC algorithm if the value is wrong, or to the solver failure if post never fired.

Industry Insights — Hard-Won Lessons About pre/post Hooks

Debugging Academy — Five Real Bugs From Hook Misuse

1

"pre_randomize reads rand field expecting solver values"

DEBUG
Symptom

A debug print in pre_randomize shows the same value every call; the developer thinks the solver is broken.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [31:0] addr;
  constraint c { addr inside {[0:255]}; }
 
  function void pre_randomize();
    $display("[pre] addr=0x%h", addr);   // BUG: pre sees previous-call value
  endfunction
 
  function void post_randomize();
    $display("[post] addr=0x%h", addr);  // post sees current solve
  endfunction
endclass
 
// Log: pre and post show different values, confusing the developer</code>
Root Cause

pre_randomize runs before the solver; fields hold values from the previous solve (or defaults on first call). The values the developer expects to see only exist after post_randomize.

Fix

Move the logging to post_randomize. pre_randomize is for setup, not inspection. The only time to read a rand field in pre_randomize is when you genuinely need its previous-call value (rare).

2

"super.pre_randomize() omitted; sub-objects null"

DEBUG
Symptom

A child class's randomize appears to skip sub-object setup; downstream code crashes on null handles.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class parent_xact;
  rand beat_xact beats[];
  function void pre_randomize();
    if (beats == null) beats = new[8];
    foreach (beats[i])
      if (beats[i] == null) beats[i] = beat_xact::type_id::create($sformatf("b_%0d", i));
  endfunction
endclass
 
class child_xact extends parent_xact;
  rand bit [31:0] child_field;
  function void pre_randomize();
    // BUG: forgot super.pre_randomize()
    if ($urandom_range(0, 9) == 0)
      child_field = 32'hDEAD_BEEF;
  endfunction
endclass
 
child_xact c = new();
c.randomize();
foreach (c.beats[i]) drive(c.beats[i]);   // crash — beats[i] are null</code>
Root Cause

The child override doesn't call super.pre_randomize(), so the parent's sub-object allocation never runs. The array stays unsized; each access dereferences null.

Fix

Always call super.pre_randomize() as the first line of every child override. Same discipline as super.do_copy, super.build_phase, every UVM hook chain. Make it a style rule.

3

"post_randomize CRC missing on solver failure"

DEBUG
Symptom

A test sees occasional packets with CRC=0 despite the post_randomize computing it. Coverage shows the failures cluster around scenarios with strict constraints.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class pkt;
  rand bit [31:0] payload;
  bit [31:0] crc;
 
  function void post_randomize();
    crc = compute_crc32(payload);
  endfunction
endclass
 
task body();
  pkt p = new();
  p.randomize();          // BUG: return value discarded
  drive(p);                // sends a packet with crc=0 on solve failure
endtask</code>
Root Cause

post_randomize runs only on solver success. When the solve fails, post_randomize is skipped, crc stays at its initialised value (0). The test discards the return value, so the failure is silent.

Fix

Always assert the return value: if (!p.randomize()) uvm_fatal("RAND", "solve failed"). The fatal aborts on failure with a useful diagnostic. If you can tolerate the failure and want a fallback CRC, compute it outside the hook: if (p.randomize()) drive(p); else { p.crc = compute_default_crc(); drive(p); }`.

4

"post_randomize silently overrides solver's choice"

DEBUG
Symptom

A constraint addr inside {[0:255]} appears to be ignored — addr values are seen outside the range.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [31:0] addr;
  constraint c { addr inside {[0:255]}; }
 
  function void post_randomize();
    // "Fix" the address to a specific value for some scenarios
    if (some_flag) addr = 32'h1000_0000;   // BUG: overrides the constraint
  endfunction
endclass</code>
Root Cause

post_randomize can write to rand fields directly, overriding what the solver picked. The class now has a hidden side effect — the constraint contract isn't actually enforced. Downstream code that adds an inline with { addr inside {[64:128]}; } is confused when the post-hook overrides to 0x1000_0000.

Fix

Either (a) express the special case in the constraint set declaratively (constraint c2 { some_flag -> addr == 32'h1000_0000; }), or (b) document the post-hook override prominently at the top of the class so consumers know about it. Silent post-hoc fix-ups are class-API debt.

5

"Hook executes side effects but solver fails — partial state visible"

DEBUG
Symptom

A class's pre_randomize mutates a static counter; the developer expects "one counter increment per successful randomise" but sees increments per attempted randomise, including failures.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  static int call_count = 0;
  rand bit [31:0] addr;
  constraint c { addr inside {[0:7]}; }
 
  function void pre_randomize();
    call_count++;   // BUG: increments on every call, even failures
  endfunction
endclass</code>
Root Cause

pre_randomize runs on every randomize call regardless of outcome. Side effects (counter increments, static field updates) happen on failures too — which the developer didn't intend.

Fix

If the side effect should only happen on success, move it to post_randomize. If you need it on every attempt (e.g., counting calls), keep it in pre_randomize but make sure the rest of the code handles the failure case. The hook semantics are asymmetric — pre always fires, post only on success — and that asymmetry must be respected.

Interview Q&A — Twelve Questions You Will Be Asked

Virtual function void hooks called automatically by randomize(). pre_randomize runs before the solver attempts to find a satisfying assignment; post_randomize runs after a successful solve. Both are user-overridable; the default implementations do nothing.

Best Practices — Ten Rules for pre/post Hook Discipline

  1. Use pre_randomize for setup that affects the solver's input. Sub-object allocation, scenario-driven constraint mode, non-rand input computation.
  2. Use post_randomize for computation that depends on the solver's output. CRCs, derived fields, associative-array building, logging.
  3. Always call super.pre_randomize() or super.post_randomize() as the first line of every override. Forgetting silently drops parent hook logic.
  4. Don't read rand fields inside pre_randomize expecting current solve values. The fields hold the previous call's values; inspection belongs in post_randomize.
  5. Remember post_randomize is skipped on solver failure. For derived fields that must always be valid, compute them after explicit if (!obj.randomize()) checks, not inside the hook.
  6. Don't silently override rand fields in post_randomize. If you must, document the override prominently at the class top; better, express the override in constraints declaratively.
  7. Document the hook contract at class top. What each hook is responsible for; what it expects to find; what it produces.
  8. Pair pre_randomize and post_randomize logs during bring-up. The pairing diagnoses lifecycle issues — missing PRE means no randomize call; missing POST after PRE means solver failure.
  9. Don't put solver workarounds in post_randomize. If the constraint set doesn't produce what you want, fix the constraint set, don't fix up the values after the fact.
  10. Keep hook bodies short and focused. One responsibility per hook. If post_randomize grows beyond ~20 lines, factor into named helper methods called from the hook.