SystemVerilog · Module 10
Soft Constraints
Overridable defaults the solver drops when contradicted, per-expression granularity, distinct from constraint_mode and inline with, and the danger of marking protocol legality soft.
Module 10 · Page 10.7
Hard vs Soft — The Core Difference
Every constraint you have seen so far is a hard constraint — the solver must satisfy it or fail. A soft constraint is written with the soft keyword and behaves differently: the solver tries its best to satisfy it, but if it conflicts with any hard constraint, the soft constraint is silently dropped and the solver continues without it. randomize() still returns 1.
class Packet;
rand bit [7:0] len;
// Hard constraint — solver MUST satisfy this or return 0
constraint min_len { len >= 64; }
// Soft constraint — solver TRIES to satisfy this
// If it conflicts with a hard constraint, it is dropped silently
constraint prefer_short { soft len <= 128; }
endclass
Packet p = new();
// No conflict — both satisfied: len in 64–128
assert(p.randomize());
// Hard inline constraint contradicts the soft one:
// soft prefer_short is dropped, hard inline wins
assert(p.randomize() with { len > 500; }); // len in 501–255? No—
// len in 501..65535 for wider type
// For 8-bit: len in 132–255
// randomize() returns 1 in both cases — no failureSyntax — Where soft Goes
The soft keyword appears inside a constraint block, directly before the expression it applies to. Each expression in a block is independently hard or soft — you can mix them freely.
class BusTxn;
rand bit [31:0] addr;
rand bit [7:0] data;
rand bit write;
// ── A block where every expression is soft ────────────────────
constraint defaults {
soft addr == 32'h4000_0000; // prefer this address
soft write == 1; // prefer write transactions
}
// ── Mixed block — one hard, one soft ─────────────────────────
constraint mixed {
addr[1:0] == 2'b00; // HARD — alignment always enforced
soft data != 8'h00; // SOFT — prefer non-zero, can drop
}
// ── Soft inside a named block — the block name still applies ──
constraint prefer_range {
soft addr inside {[32'h4000_0000:32'h4000_FFFF]};
}
endclassThe soft keyword can also appear inside a with inline constraint:
BusTxn t = new();
// Add a soft preference inline — only for this call
assert(t.randomize() with {
soft addr == 32'h4000_0100; // prefer this address, but not required
});Overriding Soft Constraints
The primary purpose of soft constraints is to define sensible defaults in a base class that subclasses and tests can override with hard constraints — without triggering a constraint contradiction. This is the design pattern that makes them powerful.
Override with a hard inline constraint
class Pkt;
rand bit [1:0] prio;
constraint default_prio { soft prio == 0; } // prefer low priority
endclass
Pkt p = new();
// Normal call — soft satisfied, prio = 0
assert(p.randomize());
$display("prio = %0d", p.prio); // prio = 0
// Hard inline contradicts soft — soft is dropped, hard wins
assert(p.randomize() with { prio == 3; });
$display("prio = %0d", p.prio); // prio = 3 (no error, no failure)
// Next normal call — soft is active again, prio = 0
assert(p.randomize());
$display("prio = %0d", p.prio); // prio = 0Override in a child class with a hard constraint
// Base class defines a soft default
class BasePkt;
rand bit [1:0] prio;
constraint default_prio { soft prio == 0; }
endclass
// Child class ALWAYS uses high priority — hard constraint wins over soft
class HighPriPkt extends BasePkt;
constraint force_hi { prio == 3; } // hard — overrides parent's soft
endclass
// No contradiction error — the soft is silently dropped in HighPriPkt
HighPriPkt h = new();
assert(h.randomize()); // prio = 3, alwaysMultiple Soft Constraints — Priority
When multiple soft constraints are active and some of them conflict with each other or with hard constraints, the solver drops as few as possible to find a valid solution. The LRM does not mandate a specific priority ordering between soft constraints — the solver is free to choose which soft constraints to keep.
class Config;
rand bit [3:0] x;
// Three soft preferences — may not all be satisfiable together
constraint s1 { soft x > 8; } // prefer x in 9–15
constraint s2 { soft x < 5; } // prefer x in 0–4
constraint s3 { soft x == 7; } // prefer x = 7
// s1 and s2 contradict each other — solver drops one of them
// s3 is compatible with neither s1 nor s2 — may also be dropped
// Result is tool-dependent: could be 9–15, 0–4, or 7
endclassPractical Patterns
Pattern 1 — Reusable base class with overridable defaults
Write a single transaction class that works for both the default test scenario and specialised edge-case tests — without subclassing or constraint_mode() calls.
class AhbTxn;
rand bit [31:0] addr;
rand bit [2:0] size; // 0=byte, 1=half, 2=word, 3=dword...
rand bit [2:0] burst;
// Hard rules — always enforced
constraint aligned {
(size == 0) || (addr[0] == 0);
(size == 1) || (addr[1:0] == 0);
}
// Soft defaults — typical traffic pattern, tests can override
constraint typical {
soft size == 3'h2; // prefer word transfers
soft burst == 3'h0; // prefer single burst
}
endclass
AhbTxn t = new();
// Normal test — typical traffic (size=word, burst=single)
assert(t.randomize());
// Burst test — override size and burst inline, no class change
assert(t.randomize() with {
size == 3'h3; // hard: force dword — soft size is dropped
burst == 3'h4; // hard: force INCR16 — soft burst is dropped
});Pattern 2 — Steering coverage without closing doors
Use a soft constraint to bias randomisation towards an under-covered region while still letting the solver occasionally pick other values. Unlike constraint_mode(0/1), no switching is needed — the solver naturally includes values outside the hint when the combined constraint set requires it.
class OpGen;
rand bit [3:0] opcode;
// Bias towards rarely-hit opcodes 12–15
constraint coverage_push {
soft opcode inside {[12:15]};
}
endclass
OpGen g = new();
// Most calls: opcode in 12–15 (soft satisfied)
// If an inline or inherited hard constraint forces opcode < 12,
// the soft is dropped and that value is used instead — no failure
assert(g.randomize());
assert(g.randomize() with { opcode == 4'h5; }); // 5, soft dropped
assert(g.randomize()); // 12–15 againsoft vs the Alternatives
| Tool | What it guarantees | Best for |
|---|---|---|
| Hard constraint | Value always satisfies the rule or solver fails | Protocol-level invariants (alignment, range, legality) |
| soft constraint | Value satisfies rule when possible; dropped silently if conflicted | Default values in base classes, typical traffic bias |
| dist (page 10.8) | Weighted probability across values — some always possible | Statistical bias: "70% reads, 30% writes" |
| constraint_mode(0/1) | Rule is explicitly on or off — you control it in code | Switching between test phases programmatically |
| with { } inline | Extra rule active for exactly one call | Per-call steering, corner-case injection |
Common Pitfalls
Expecting soft to be probabilistic
soft x == 0 does not mean "x is 0 most of the time." It means "x is 0 whenever nothing forces it elsewhere." For weighted probabilities use dist.
Debugging disappeared soft constraints
If a soft constraint is being silently dropped and you don't know why, temporarily make it hard to see which other constraint is causing the conflict. Revert to soft once you understand the interaction.
Two conflicting softs with no hard tie-breaker
When two soft constraints cannot both be satisfied and there is no hard constraint to break the tie, the solver's choice is tool-defined. Never rely on which of two conflicting softs wins.
Using soft to avoid writing hard constraints
If a rule must always hold — alignment, legality, protocol compliance — it must be a hard constraint. Soft is not a "gentler" version of hard; it genuinely means the rule can be ignored.
Quick Reference
// ── Declare soft inside a constraint block ────────────────────
constraint name { soft <expr>; }
// ── Mix hard and soft in one block ───────────────────────────
constraint name {
hard_expr; // hard — must be satisfied
soft soft_expr; // soft — satisfied if possible
}
// ── Soft inside a with clause ─────────────────────────────────
assert(obj.randomize() with { soft field == value; });
// ── Override soft with a hard inline ─────────────────────────
// Hard always wins over soft — no contradiction error
assert(obj.randomize() with { field == other_value; }); // soft dropped
// ── Override soft in a child class ───────────────────────────
class Child extends Parent;
constraint force_it { field == specific_value; } // hard wins
endclass
// ── What soft does NOT do ─────────────────────────────────────
// ✗ Not probabilistic — use dist for weighted probability
// ✗ Not "sometimes" — it's satisfied fully or dropped entirely
// ✗ Not a replacement for hard constraints on protocol rulesVerification Usage — Where soft Pays Off in Real VIP
Production VIPs use soft in three distinct architectural roles. Each is a clean expression of "default that may be overridden"; mixing the roles or applying soft outside them produces unmaintainable behaviour.
Overridable defaults on protocol-shaped fields
The canonical use: a transaction class wants to declare "the typical address range," "the preferred burst length," "the default transaction kind" — values that real tests almost always accept, but that scenario tests may override.
<code>class apb_xact extends uvm_sequence_item;
rand bit [31:0] paddr;
rand bit [3:0] burst_len;
rand pwrite_e pwrite;
constraint c_defaults {
soft paddr inside {[32'h4000_0000 : 32'h4000_FFFF]}; // default range
soft burst_len inside {[0:7]}; // default short
soft pwrite == PWRITE_WRITE; // default write
}
endclass
// Default test gets the soft defaults
if (!tx.randomize()) `uvm_fatal("","");
// Scenario test overrides one without disabling others
if (!tx.randomize() with { paddr inside {[32'h5000_0000:32'h5FFF_FFFF]}; })
`uvm_fatal("","");
// burst_len and pwrite still use their soft defaults</code>Reusable VIP base classes with customer-overridable preferences
Vendor-shipped VIPs use soft extensively on the base transaction class. The vendor's defaults work out-of-the-box; customers override the specific defaults their project needs without forking the source.
Configurable error-injection scenarios
Soft constraints simplify error-injection: the class ships with the legal-stimulus defaults marked soft; error tests just add inline constraints that contradict the defaults, and the solver drops the conflicting softs automatically — no constraint_mode bookkeeping required.
Simulation Behavior — How the Solver Resolves Soft Conflicts
Constraint set assembly with soft expressions
At randomize() call time, the solver collects every constraint expression — hard and soft — from every active class block plus the inline with clause. Each expression is tagged with its priority (hard = required, soft = preferred).
Conflict detection and resolution
The solver first attempts to satisfy every expression simultaneously. If that's satisfiable, it picks an assignment and returns. If not, it identifies the minimal set of soft expressions that, when dropped, makes the remaining set satisfiable — and tries again. The drop is granular: only soft expressions that actually participate in a contradiction are dropped; soft expressions on unrelated fields are preserved.
No partial-priority levels
Soft expressions don't have a numeric priority — every soft is equally "droppable." If two soft expressions are in mutual conflict (rare), the solver chooses one to drop based on implementation-defined criteria. For most production code, this doesn't matter: real soft conflicts come from "inline with contradicts class default," not from "two class defaults contradict each other."
Cost characteristics
Soft constraints add a small cost over hard constraints: the solver must attempt the full set first, detect any conflict, identify the drop candidates, and re-solve. For a class with many softs and no conflicting inline with, the cost is identical to hard constraints. For a class with frequent inline conflicts, the cost is modestly higher.
<code>class xact;
rand bit [31:0] addr;
constraint c {
soft addr inside {[0:'hFFFF]}; // soft default
addr[1:0] == 2'b00; // hard alignment
}
endclass
// Inline asks for a high address;
// soft drops, hard still applies.
tx.randomize() with { addr inside {[32'h1_0000:32'hFFFF_FFFF]}; };
// Result: addr in [0x10000:0xFFFFFFFC],
// still aligned (hard preserved).</code><code>class xact;
rand bit [31:0] addr;
constraint c {
addr inside {[0:'hFFFF]}; // hard — no escape
addr[1:0] == 2'b00;
}
endclass
// Inline asks for high address; hard
// blocks → randomize() returns 0.
tx.randomize() with { addr inside {[32'h1_0000:32'hFFFF_FFFF]}; };
// → solve fails. Need constraint_mode(0)
// or refactor the class.</code>Waveform Analysis — Verifying Soft Behaviour Is What You Intended
Soft drops are silent — the resulting transaction looks identical to one where the soft constraint was never present. The way to verify softs are behaving correctly is to instrument post_randomize with the field values and the expected vs actual relationships.
The instrumentation pattern
<code>class apb_xact extends uvm_sequence_item;
rand bit [31:0] paddr;
rand bit [3:0] burst_len;
constraint c_defaults {
soft paddr inside {[32'h4000_0000 : 32'h4000_FFFF]};
soft burst_len inside {[0:7]};
}
function void post_randomize();
bit addr_in_default = (paddr inside {[32'h4000_0000:32'h4000_FFFF]});
bit blen_in_default = (burst_len inside {[0:7]});
`uvm_info("SOFT",
$sformatf("paddr=0x%08h (default=%0d) burst_len=%0d (default=%0d)",
paddr, addr_in_default, burst_len, blen_in_default),
UVM_HIGH)
endfunction
endclass</code>ASCII view — soft defaults respected vs dropped
<code> 100ns SOFT paddr=0x4000_0010 (default=1) burst_len=3 (default=1)
200ns SOFT paddr=0x4000_0044 (default=1) burst_len=5 (default=1)
300ns SOFT paddr=0x5000_1234 (default=0) burst_len=4 (default=1) ← softs split
400ns SOFT paddr=0x5000_5678 (default=0) burst_len=12 (default=0) ← both dropped
↑ ↑
inline overrode paddr's soft error test also overrode
(burst_len's soft still applied) burst_len's soft</code>Diagnosing "the test didn't generate what I expected"
When a test fails to produce stimulus in the expected range, the question is: was a soft default dropped? With per-field "in default range" booleans in the log, the answer is one line. Without it, you re-read every inline with and every constraint block to figure out which override fired.
Industry Insights — Hard-Won Lessons About Soft Constraints
Debugging Academy — Five Real Bugs From soft Misuse
"Soft protocol-alignment dropped silently, DUT NAKs everything"
DEBUGA test runs cleanly with no randomize() failures, but the DUT NAKs every transaction with "alignment violation." Coverage of legal stimulus is 0%.
<code>class apb_xact;
rand bit [31:0] paddr;
constraint c {
soft paddr[1:0] == 2'b00; // BUG: alignment is hard protocol, not preference
}
endclass
// Some upstream code does inline override:
tx.randomize() with { paddr == 32'h4000_0001; } // unaligned
// Soft drops silently; DUT rejects all transactions</code>Alignment is a hard protocol rule but was marked soft. The inline override drops it silently; the DUT enforces the rule downstream and rejects the result. The test infrastructure can't see the bug because both randomize and the driver report success.
Remove the soft keyword: constraint c { paddr[1:0] == 2'b00; }. Now any inline override that produces an unaligned address makes randomize return 0 — the failure surfaces immediately with the actionable "constraint infeasible" diagnostic instead of silently downstream.
"Soft expression never overridden — same as hard"
DEBUGA constraint is marked soft "for future override flexibility" but no test ever overrides it. After six months of regression, the soft never yielded.
<code>class xact;
rand bit [31:0] paddr;
// Soft "for flexibility" but no one ever overrides
constraint c { soft paddr inside {[0:'hFFFF]}; }
endclass</code>Speculative softness costs nothing in solver behaviour (no override = same as hard), but it costs in code clarity — the next maintainer wonders why soft is there and whether it's load-bearing. If nothing overrides it, the marker is noise.
Remove soft unless and until you have an actual override scenario. Mark a constraint soft when a derived class or test scenario actually needs to override it — not as a "just in case." YAGNI applies to constraint blocks too.
"Soft yields when I expected it to stay"
DEBUGA test sets up an inline with clause expecting the class's soft default to also apply. The resulting transaction shows the soft default was dropped.
<code>class xact;
rand bit [31:0] paddr;
rand bit [31:0] pwdata;
constraint c {
soft paddr inside {[32'h0:32'hFFFF]};
soft pwdata inside {[32'h0:32'h00FF]};
}
endclass
// Inline only mentions paddr:
tx.randomize() with { paddr inside {[32'h10000:32'h1FFFF]}; };
// User expects: paddr overridden, pwdata still in [0:0xFF]
// Actual: paddr in [0x10000:0x1FFFF], pwdata...?
// Depends on whether pwdata's soft was dropped (it shouldn't be)</code>Actually this works correctly: only paddr's soft conflicts with the inline, so only that soft is dropped. pwdata's soft is independent and stays in effect. If the user observes pwdata outside [0:0xFF], the cause is elsewhere — usually another inline clause they forgot, or a derived class that also relaxed pwdata.
Verify with the diagnostic from §11 (log soft-respect flags) before changing code. Soft drops are surgical — they touch only the conflicting expression. If you see broader-than-expected behaviour, look for other overrides in the call chain.
"Conflicting soft defaults in parent and child — child's wins?"
DEBUGA child class redeclares a soft default with a different value than the parent's. The developer expected the child's soft to "override" the parent's; actually both are present and the joint constraint set is unsatisfiable.
<code>class parent_xact;
rand bit [31:0] paddr;
constraint c { soft paddr inside {[0:'hFFFF]}; }
endclass
class child_xact extends parent_xact;
constraint c_child { soft paddr inside {[32'h1_0000:32'h1_FFFF]}; }
endclass
child_xact c = new();
c.randomize(); // Both softs are active; they don't overlap
// Solver drops one (implementation-defined which)
// Result: arbitrary which range the addr lands in</code>Inheritance doesn't override constraint blocks — it adds them. Both softs are in the active set; their satisfying sets don't intersect; the solver drops one. Which one gets dropped is implementation-defined, so the resulting behaviour is non-portable.
Two options. (a) If the child wants a different default range, mark the parent's constraint soft and let the child add a hard constraint that overrides: constraint c_child { paddr inside {[32'h1_0000:32'h1_FFFF]}; } — the hard child wins, the soft parent yields. (b) If both ranges should genuinely apply alternately, restructure with explicit modes via constraint_mode. Two softs on the same field is almost always a design smell.
"Inline with uses a value that should have triggered soft drop — but the soft is still active"
DEBUGA test's inline with picks a value within the soft default's range; the developer expects the soft to remain active. Actually, the inline doesn't contradict the soft, so the soft is preserved (correctly).
<code>class xact;
rand bit [31:0] paddr;
constraint c { soft paddr inside {[0:'hFFFF]}; }
endclass
// Inline picks a value INSIDE the soft default's range
tx.randomize() with { paddr == 32'h1234; };
// User expected: soft "yields to inline" because inline mentioned paddr
// Reality: 0x1234 is inside [0:0xFFFF], so soft is satisfied — no yield needed</code>The soft only yields when there is an actual contradiction. paddr == 0x1234 is consistent with paddr inside {[0:'hFFFF]}, so no conflict exists, and the soft stays active (and happens to also be satisfied). This is correct behaviour, not a bug.
Understand the model: soft is a "preference unless contradicted." A value inside the soft range doesn't contradict it; only a value outside does. If you wanted to force the soft to yield even on consistent values, the right tool is constraint_mode(0), not inline with.
Interview Q&A — Twelve Questions You Will Be Asked
A constraint expression marked with the soft keyword inside a constraint block. Soft expressions act as preferences: the solver tries to satisfy them along with every other constraint, but if a hard constraint or an inline with contradicts the soft, the solver drops the conflicting soft and re-solves. Soft constraints exist to support overridable defaults.
Best Practices — Ten Rules for soft Discipline
- Mark something
softonly when an override scenario actually exists. Speculative softness is just noise. - Never mark protocol legality as
soft. Alignment, byte-strobe consistency, address-map containment — these are hard contracts the DUT will enforce. Usesoftfor preferences, never for rules. - Comment every soft default with the rationale. Future readers need to understand whether the default reflects a measured typical case, a coverage convenience, or arbitrary choice.
- Use
softinstead ofconstraint_modewhen the scenario is "default unless overridden." Saves bookkeeping, eliminates leak risk. - Per-expression granularity matters. Mark each expression individually; mix soft and hard within the same block as needed.
- Don't put two conflicting softs on the same field across a parent/child chain. Implementation-defined behaviour is non-portable. Make the parent soft and the child hard.
- Log soft-respect flags in
post_randomizeduring bring-up. One bit per soft-constrained field; the log line tells you which softs dropped on which calls. - Use simulator's "report soft drops" mode during initial class design. Verifies your softs yield only when you intend.
- Don't confuse
softwithdist. Soft is "preferred default, yields on contradiction." Dist is "probabilistic shaping with weights." They serve different needs. - If you find yourself adding many softs to "make a class flexible," reconsider. Excessive softness produces a class whose behaviour depends entirely on call-site inline clauses — at which point a clear inline-only approach may be cleaner.