Part 3 · Constraint Randomization · Intermediate

Inline Constraints: randomize() with {...}

Call-site constraints, the AND-only rule, contradiction failures, local:: scope resolution, and when inline beats subclassing.

Call-site constraints intersect with class constraints

obj.randomize() with { ... } injects extra constraint expressions for one call only. The solver gathers the class's active constraint blocks AND the inline expressions and solves them together — the inline block is ANDed , never substituted. The immediate corollary is the rule that drives every interview question here: inline constraints can only tighten the solution space. There is no syntax inside with {} that loosens, overrides, or disables a class constraint (the one nuance: a hard inline expression can displace a class-level soft constraint, because soft yields to any contradiction).

systemverilog
class bus_txn;
  rand bit [31:0] addr;
  rand bit [7:0]  len;
  constraint legal_c { len inside {[1:16]}; addr < 32'h1_0000; }
endclass

bus_txn t = new();

// Tighten: legal AND call-site — len ∈ [4:16], addr ∈ [0x8000:0xFFFF]
void'(t.randomize() with { len >= 4; addr >= 32'h8000; });

// THE CLASSIC CONTRADICTION:
if (!t.randomize() with { len == 32; })
  $display("FAILS: len==32 ∩ len inside {[1:16]} = empty set");
// randomize() returns 0; t's fields KEEP their previous values.

Two details of the failure path matter in practice: randomize() returning 0 leaves every rand field holding its prior value (a stale-transaction hazard if you ignore the return code), and well-run benches treat a 0 return as a fatal coding error, not a retry.


Name resolution and local::

Inside a with block, names resolve against the object being randomized first , then the enclosing scope. That is exactly what you want until a local variable shadows a class property — typically when your sequence has its own addr or len. The local:: prefix forces resolution to the scope containing the randomize() call, skipping the object.

systemverilog
class my_seq;
  bit [31:0] addr;          // sequence's OWN addr — same name as txn field!

  task body(bus_txn t);
    addr = 32'h4000;

    // BUG: both 'addr's resolve to t.addr → constraint is t.addr == t.addr
    void'(t.randomize() with { addr == addr; });        // tautology!

    // CORRECT: right-hand side pulled from the sequence scope
    void'(t.randomize() with { addr == local::addr; }); // t.addr == 32'h4000
  endtask
endclass
diagram
NAME RESOLUTION INSIDE  t.randomize() with { addr == local::addr; }

        with-block name lookup order
        ────────────────────────────
   addr ──► 1. object t's properties         t.addr   (rand, solved)
            2. enclosing scope (my_seq)      (shadowed, not reached)

   local::addr ──► SKIP object, resolve in the scope
                   containing the call       my_seq.addr (state, 0x4000)

   Result: constraint  t.addr == 32'h4000
   Without local:::    t.addr == t.addr    always true  addr fully random

The tautology bug is vicious because nothing fails — the transaction simply randomizes addr freely, and you discover it from coverage holes or scoreboard noise much later.


When inline beats subclassing (and when it loses)

Inline constraints shine for one-off, call-local intent: this loop wants short lengths, this call pins an address, this phase narrows to one opcode. Subclassing (next sub-lesson) wins when the modified behavior is a reusable personality — needed across many call sites, distributed via the factory, or involving relaxation that with cannot express.

systemverilog
// Inline: perfect for a directed sweep inside one sequence
foreach (targets[i])
  void'(t.randomize() with { addr == local::targets[i]; len == 1; });

// Inline CANNOT do this — needs constraint_mode or a subclass:
//   "generate len > 16"   ← contradicts legal_c; with cannot relax it.

// Subclass: a reusable personality, factory-substitutable everywhere
class short_bus_txn extends bus_txn;
  constraint short_c { len inside {[1:4]}; }   // still ANDs with legal_c
endclass

Interview angle

What interviewers ask

  • “Do inline constraints replace class constraints?” — no; they AND with all active class constraints. Tighten-only. This is the screening question.

  • “What happens if the inline constraint contradicts a class constraint?” — empty intersection, randomize() returns 0, fields keep prior values; soft class constraints are the exception (they yield).

  • “What is local:: for?” — forcing a name to resolve in the calling scope when it is shadowed by an object property; cite the addr == local::addr pattern.

  • “How would you make a transaction generate an illegal length?” — not with inline alone; disable the legality block (constraint_mode) or override in a subclass, THEN tighten inline.

  • “Inline with vs subclass?” — call-local intent vs reusable, factory-distributable personality.

Key takeaways

  • with {} expressions intersect with active class constraints — they can only tighten.

  • Contradictions fail randomize() (returns 0) and leave fields unchanged — always check the return.

  • Names in with resolve to the randomized object first; local:: forces the calling scope.

  • Hard inline expressions displace class soft constraints — the only “override” with can do.

  • Use inline for one-off call-site intent; use subclassing or constraint_mode for reusable or relaxing changes.

Common pitfalls

  • Trying to relax a class constraint inline — guaranteed contradiction failure.

  • addr == addr tautologies from shadowed names — silently random fields, no error anywhere.

  • Ignoring randomize()'s return value — driving a stale transaction after a failed solve.

  • Huge always-repeated with blocks — duplicated policy that belongs in a class or subclass.

  • Assuming with affects later calls — inline scope is exactly one randomize() invocation.