SystemVerilog · Module 10
Inline Constraints — the with Clause
Per-call constraint additions inside randomize() with { … }, scope and local-variable capture, the AND-not-substitute combination with class constraints, and the three-sites refactor signal.
Module 10 · Page 10.5
What the with Clause Does
The with clause is an anonymous constraint block appended directly to a randomize() call. It is active for that one call only and discarded immediately after. The class definition is never modified.
This is the right tool whenever you need a specific value range for a single test scenario — a corner-case transaction, a directed stimulus amid a random test — without creating a subclass or temporarily toggling constraint modes.
class Packet;
rand bit [7:0] src;
rand bit [7:0] dst;
rand bit [15:0] len;
constraint valid_len { len inside {[64:1518]}; }
endclass
Packet p = new();
// ── Normal call — only class constraints apply ─────────────────
assert(p.randomize()); // len: 64–1518, src/dst: free
// ── With clause — adds an extra constraint for THIS call only ──
assert(p.randomize() with { src == 8'hA0; }); // src fixed to 0xA0
// valid_len still active — len stays in 64–1518
// after this call: src is free again on the next randomize()
// ── Multiple inline expressions ───────────────────────────────
assert(p.randomize() with {
src == 8'hA0;
dst != src;
len <= 128;
});Scoping — What Variables You Can Reference
Inside a with { } block you can reference:
Class properties
Any rand, randc, or plain property of the object being randomised. This is the most common use.
Local variables in scope
Variables visible at the call site — local task/function variables, loop variables, module-level variables. They are treated as constants by the solver.
Parameters and constants
Any compile-time constant, parameter, or enum value accessible at the call site.
task automatic send_to_region(
input bit [31:0] base,
input bit [31:0] size
);
MemAccess m = new();
// 'base' and 'size' are local task args — used as constants in with
assert(m.randomize() with {
addr >= base;
addr < base + size;
});
// drive_bus(m); ...
endtask
// Call with different regions each time — no class changes needed
send_to_region(32'h0000_0000, 32'h0001_0000);
send_to_region(32'h4000_0000, 32'h0010_0000);How with Combines With Class Constraints
The full constraint set the solver sees on every call is:
| Source | Active when | Scope |
|---|---|---|
| Class constraint blocks | Always, unless turned off via constraint_mode(0) | Every randomize() call on this object |
| Inherited parent constraints | Always, unless overridden by same name in child | Every randomize() call on this object |
with { } inline constraint | Only the single call it appears on | That one call only — discarded after |
class BusTxn;
rand bit [31:0] addr;
rand bit [7:0] data;
rand bit write;
constraint aligned { addr[1:0] == 2'b00; } // always active
constraint in_range { addr inside {[32'h4000_0000:32'h4FFF_FFFF]}; }
endclass
BusTxn t = new();
// Call 1 — force a write to a specific sub-region for one call
assert(t.randomize() with {
write == 1;
addr inside {[32'h4000_0000:32'h400F_FFFF]}; // narrows in_range
});
// Solver satisfies: aligned AND in_range AND write==1 AND sub-region
// Call 2 — back to normal random, no inline constraint
assert(t.randomize());
// Solver satisfies: aligned AND in_range onlywith Combined With randomize() Argument List
You can combine a selective argument list (from page 10.3) with a with clause in one call. The argument list restricts which fields are randomised; the with block constrains the values.
BusTxn t = new();
// Randomise only 'data', constrain it to non-zero, keep addr/write unchanged
assert(t.randomize(data) with { data != 8'h00; });
// Randomise only 'addr', keep it in a tighter sub-range for this call
assert(t.randomize(addr) with {
addr inside {[32'h4000_0000:32'h4000_FFFF]};
addr[1:0] == 2'b00; // word-aligned (redundant with class constraint, harmless)
});with on std::randomize()
std::randomize(), introduced in page 10.3, has no class constraints to draw on — the with clause is its only source of constraints. This makes it ideal for quick one-off constrained values in tasks and functions without any class overhead.
task automatic inject_delay(input int min_ns, max_ns);
int delay_ns;
assert(std::randomize(delay_ns) with {
delay_ns >= min_ns;
delay_ns <= max_ns;
delay_ns % 10 == 0; // must be a multiple of 10 ns
}) else $fatal(1, "delay randomization failed");
#(delay_ns * 1ns);
endtask
// Generate a delay between 50–200 ns, in 10 ns steps
inject_delay(50, 200);Practical Patterns
Pattern 1 — Corner-case stimulus in a random test
Run 99 fully random transactions, then inject one deliberate corner case — all using the same class, no subclassing needed.
Packet p = new();
// 99 fully random packets
repeat (99) begin
assert(p.randomize());
drive(p);
end
// 1 deliberate maximum-size packet
assert(p.randomize() with { len == 1518; });
drive(p);
// 1 deliberate minimum-size packet
assert(p.randomize() with { len == 64; });
drive(p);Pattern 2 — Loop-driven parameter sweep
Use a loop variable as a constant inside with to systematically sweep through a set of values while keeping all other fields random.
BusTxn t = new();
// Test every burst length (0–7) with random addresses
for (int burst = 0; burst < 8; burst++) begin
assert(t.randomize() with {
burst_len == burst; // loop var used as constant — solver sees it as fixed
});
drive(t);
endPattern 3 — Narrowing without a subclass
Instead of creating a ShortPacket extends Packet class just to test short frames, use with for that one test sequence. Subclasses are better when the constraint is permanent and reused across many tests; inline is better when it is one-off.
// ✓ Use with for a one-off scenario — no new class needed
assert(p.randomize() with { len <= 128; });
// ✓ Use a subclass when the constraint defines a reusable test object
class ShortPacket extends Packet;
constraint short { len <= 128; } // used across 20 different test files
endclassCommon Pitfalls
Contradicting a class constraint
If the inline constraint conflicts with an active class constraint, the solver fails and returns 0. Either disable the class constraint first with constraint_mode(0) or make the inline constraint compatible.
Using the handle prefix inside with
p.src == 0 inside a with block is illegal. Property names are unqualified inside the block. Write src == 0.
Expecting with to persist
The inline constraint applies to exactly one call. The very next randomize() without a with clause operates under class constraints only. Nothing carries over.
Overconstraining by accident
Adding an inline constraint on a field that a class constraint already pins tightly can leave no valid solution. Always reason about the intersection of both constraint sets before writing a tight inline constraint.
Inline vs Class Constraint — Decision Table
| Situation | Use |
|---|---|
| Constraint applies to every test that uses this class | Class constraint block |
| Constraint applies to one specific test scenario | with inline |
| Constraint is used in 3+ test files | Subclass with a named constraint block |
| Corner-case value injected once among random traffic | with inline |
| Need to turn the constraint on/off across many calls | Named class constraint + constraint_mode() (page 10.6) |
| Quick random value in a task with no class | std::randomize(var) with { ... } |
Quick Reference
// ── Basic form ────────────────────────────────────────────────
assert(obj.randomize() with { <expr>; });
// ── Multiple inline expressions (all AND'd) ───────────────────
assert(obj.randomize() with { expr1; expr2; expr3; });
// ── With argument list (selective randomisation) ──────────────
assert(obj.randomize(field_a) with { field_a > 10; });
// ── Properties are unqualified — no handle prefix ─────────────
// CORRECT: with { src == 0; }
// WRONG: with { p.src == 0; } ← compile error
// ── Local variables are treated as constants ──────────────────
assert(obj.randomize() with { addr >= local_base; });
// ── std::randomize() with inline constraint ───────────────────
assert(std::randomize(x) with { x inside {[1:100]}; });
// ── Combined with class constraints ───────────────────────────
// with block is AND'd with all active class constraint blocks
// active for THIS call only — no persistenceVerification Usage — Where Inline with Earns Its Place in Real UVM
Every UVM environment uses inline with in three predictable places. Outside these three, the inline form usually points at a refactor opportunity — most often "promote to a derived class."
Inside ``uvm_do_with` macros in sequences
The most common use: sequences narrow the legal space per call using the inline form. The base transaction stays generic, the sequence chooses the scenario.
<code>class apb_burst_seq extends uvm_sequence #(apb_xact);
task body();
repeat (8) begin
`uvm_do_with(req, {
pwrite == 1;
paddr inside {[32'h4000_0000 : 32'h4000_00FF]};
})
end
endtask
endclass</code>Per-call corner-case stimulus inside a directed-random hybrid
A "directed-random" pattern is a test that drives some specific transactions deliberately then fills the rest with random stimulus. The deliberate transactions use inline with to pin specific fields while letting others vary.
<code>// Drive a specific corner case, then resume random
if (!tx.randomize() with { paddr == 32'h0; pwdata == 32'hFFFF_FFFF; })
`uvm_fatal("CORNER", "boundary case infeasible — check class constraints")
drive(tx);
// ... return to fully random ...</code>Test-layer relaxations through inline negation
An error-injection test can negate a class constraint inline (when the constraint is soft) to deliberately generate illegal stimulus and verify the DUT's response.
<code>// Class has: constraint c_align_soft { soft addr[1:0] == 2'b00; }
// Error test forces unaligned:
if (!tx.randomize() with { addr[1:0] != 2'b00; })
`uvm_fatal("ERR", "could not generate unaligned address")</code>Simulation Behavior — What the Solver Actually Does With with
Constraint set assembly with inline expressions
At randomize() call time, the solver collects (a) every enabled class constraint block from every class in the inheritance chain, and (b) every expression in the inline with clause. All are AND-ed into a single conjunction. Local procedural variables referenced in the with clause are captured by reference; their values at call time are snapshotted as constants.
No precedence between inline and class constraints
The solver treats every expression equally — there is no "class constraints win" or "inline overrides class." If the conjunction is satisfiable, every constraint holds in the solution. If not, the call returns 0. The mental model "inline overrides class" is a frequent source of confusion: inline adds, never replaces. To remove a class constraint, you must call constraint_mode(0) on it before the randomize call.
Soft constraint interaction (preview of 10.7)
If a class constraint is declared soft, the solver treats it as a preference rather than a requirement: when the inline with contradicts it, the soft constraint is dropped (only that conflicting one), and the solver finds a solution satisfying the inline plus all hard class constraints. This is how protocol-relaxing error tests work without explicit constraint_mode manipulation — the class constraint is soft, the inline says "not aligned," and the soft yields.
Cost characteristics
Adding inline expressions has roughly the same cost as adding equivalent class constraints — the solver doesn't distinguish provenance. A complex inline with with foreach loops or implication chains can be just as solver-expensive as the same expressions in a class block.
<code>class xact;
rand bit [31:0] addr;
constraint c_align { addr[1:0] == 2'b00; }
endclass
// Inline says addr in [0:7], class says [1:0]==0
// Joint solution: addr ∈ {0, 4}
tx.randomize() with { addr inside {[0:7]}; };</code><code>class xact;
rand bit [31:0] addr;
constraint c_align { addr[1:0] == 2'b00; }
endclass
// Inline says addr is 1 (unaligned)
// Class says addr must be aligned
// Joint solution: none → randomize() = 0
tx.randomize() with { addr == 1; };</code>Waveform Analysis — Tracing Where Inline Constraints Came From
Inline constraints exist only at the call site; they do not appear in the class definition and cannot be queried at runtime. The way to make them traceable is to log the call site explicitly alongside the constrained values.
The instrumentation pattern
<code>// Inside a sequence, log which inline constraint scenario fired:
`uvm_info("SCN", "Running aligned-write scenario", UVM_HIGH)
if (!tx.randomize() with {
pwrite == 1;
paddr inside {[32'h4000_0000 : 32'h4000_00FF]};
}) `uvm_fatal("SCN", "aligned-write scenario infeasible")
drive(tx);</code>ASCII view — scenario logging alongside transaction values
<code> 100ns SCN Running aligned-write scenario
100ns RAND paddr=0x4000_0010 pwrite=1
200ns SCN Running aligned-write scenario
200ns RAND paddr=0x4000_0044 pwrite=1
300ns SCN Running corner-case scenario
300ns RAND paddr=0x0000_0000 pwrite=1 pwdata=0xFFFF_FFFF
↑ ↑
scenario name matches the inline
(from `uvm_info) with clause that ran</code>Reconciling failures back to the inline constraint that caused them
When a test fails, the post-mortem question is often "which inline with at which call site produced this transaction?" Without the scenario log, the answer requires reading every with in the sequence file. With the log, the answer is one line above the randomisation.
Industry Insights — Hard-Won Lessons About Inline with
Debugging Academy — Five Real Bugs From Inline with Misuse
"with { addr == 1; } always fails to solve"
DEBUGA test wants to drive a single transaction to address 1. tx.randomize() with { addr == 1; } returns 0 every call; UVM fatals.
<code>class xact;
rand bit [31:0] addr;
constraint c_align { addr[1:0] == 2'b00; } // class requires alignment
endclass
if (!tx.randomize() with { addr == 1; })
`uvm_fatal("RAND", "couldn't drive addr=1");</code>The class's c_align requires addr[1:0] == 2'b00. The inline clause requires addr == 1 (i.e., addr[1:0] == 2'b01). The two are jointly unsatisfiable — the inline doesn't override the class, it adds.
Two options. (a) If the test legitimately wants to violate alignment for error injection, disable the class constraint first: tx.c_align.constraint_mode(0);, then randomize. (b) If alignment is required by the protocol and the test shouldn't violate it, pick an aligned value: with { addr == 0; }. Either way, the inline form alone cannot remove a class constraint.
"Local variable referenced in inline with gets shadowed by class field"
DEBUGA test computes a local int addr_max and uses it inside with { addr inside {[0:addr_max]}; }; the constraint behaves as if addr_max is always 0xFFFF_FFFF.
<code>class xact;
rand bit [31:0] addr;
bit [31:0] addr_max = 32'hFFFF_FFFF; // class field with the SAME NAME
endclass
task body();
xact tx = new();
int addr_max = 32'h00FF; // local, intended for the with clause
if (!tx.randomize() with { addr inside {[0:addr_max]}; })
`uvm_fatal("","");
// addr ends up across the full 32-bit range, not [0:0x00FF]
endtask</code>Inside the with body, name resolution starts in the class scope — so addr_max resolves to the class field (0xFFFF_FFFF), shadowing the local variable. The local is unused; the constraint uses the class field's value.
Either (a) qualify the local explicitly using local::addr_max (when the syntax is supported), or (b) rename the local to avoid the collision — int local_addr_max. Convention: never name a local variable the same as any field on the class being randomised.
"Inline with used to "freeze" a field still randomises it"
DEBUGA test sets tx.kind = T_READ then calls tx.randomize() with { /* no kind */ }; expecting kind to stay T_READ. After the call, kind is a different value.
<code>class xact;
rand xact_kind_e kind;
rand bit [31:0] addr;
endclass
tx.kind = T_READ;
if (!tx.randomize() with { addr inside {[0:255]}; }) `uvm_fatal("","");
$display("kind=%s", tx.kind.name()); // Sometimes T_WRITE, T_ATOMIC, etc.</code>The bare randomize() form (including with an inline clause that doesn't mention kind) randomises every rand field, including kind. The pre-call assignment tx.kind = T_READ is overwritten by the solve.
Use the named-argument form to limit the solve to specific fields: tx.randomize(addr) with { addr inside {[0:255]}; };. Now only addr is randomised; kind keeps its pre-call value. Alternatively, add kind == T_READ to the inline with so the solver pins it explicitly.
"with { addr == base; } in a loop produces the same value every iteration"
DEBUGA test increments base in a loop and calls tx.randomize() with { addr == base; }; the resulting tx.addr stays at the loop's first base value forever.
<code>int base = 0;
for (int i = 0; i < 10; i++) begin
base = i * 16;
if (!tx.randomize() with { addr == base; }) `uvm_fatal("","");
// BUG: addr stuck at base's initial value? or randomized correctly?
end</code>In the canonical case, this works correctly — base is snapshotted at each randomize call, so each iteration uses the current value. If you observe the field stuck, the cause is usually elsewhere: base is being shadowed by a class field, or the test is actually printing the wrong field, or the loop body has a bug. Verify by adding $display("iter=%0d base=%0d addr=%0d", i, base, tx.addr); before the next call.
Confirm the diagnostic before changing code. Inline with does correctly capture local-variable values at call time, per iteration. If the field is genuinely stuck, look for shadowing (lab 2) or a corrupted test scaffold.
"Inline with works alone, fails when run with the regression's verbosity settings"
DEBUGA test that passes locally with default verbosity fails in regression with the high-verbosity flag set. The failure points at a randomize call inside an inline with.
<code>// Sequence calls a UVM macro:
`uvm_do_with(req, { paddr inside {[lo:hi]}; })
// Verbosity-high regression also enables a tracing callback
// that mutates `lo` and `hi` during the call.</code>A tracing callback or environment configuration is modifying the local variables that the with clause references — at exactly the moment the solver is reading them. Because the solver snapshots local values at call time, this is normally race-free, but a callback that fires before the snapshot can change the values that get captured. The verbosity setting was the trigger that enabled the callback.
Find the callback that mutates the local variables (grep the regression configuration for verbosity-conditional callbacks). Either disable it for this test, or copy lo and hi to fresh locals immediately before the randomize call so the callback's mutations don't affect the snapshot. Cleaner long-term: don't reference local variables in with that any callback might mutate — pass them as explicit class fields the test layer assigns before randomization.
Interview Q&A — Twelve Questions You Will Be Asked
An extension to the randomize() call that adds extra constraints for one specific call only. Syntax: obj.randomize() with { expr1; expr2; ... };. The expressions inside the braces are conjuncted with the class's existing constraints for this call only — they do not modify the class. The next call without a with clause uses only the class's built-in constraints.
Best Practices — Ten Rules for Inline with Discipline
- Use inline
withfor one-off scenarios; class constraints for recurring patterns. Three uses of the same inline = refactor signal. - Always assert the
randomize()return value. Inlinewithcan fail just as silently as class constraints; theif (!...)wrapper is mandatory. - Inline
withnever removes a class constraint — only adds. Useconstraint_mode(0)orsoftto relax class constraints. - Use the named-argument form when you need to keep some fields fixed.
tx.randomize(addr) with { ... };randomises only the named fields. - Don't shadow class fields with local variable names. Inline
withresolves to the class field first; the local is silently ignored. - Tag inline-with call sites with a scenario name. A short ``uvm_info` above the call makes failure logs immediately traceable to the right inline clause.
- Prefer ``uvm_do_with
over manualrandomize() with` in sequences. The macro bundles create + randomize + send with the assertion built in. - Don't use inline
withas a workaround for bad class design. If twenty sequences need the same inline patch, fix the class instead. - Local variables in the
withclause are captured at call time. Loops with varying locals work correctly; mutations between snapshot and solve do not. - Profile inline-with the same way you profile class constraints. Complex inline expressions (foreach, implication chains) can dominate runtime just as surely.