Ordinary constraints are called hard constraints, because it's mandatory for the solver to always satisfy them — if it can't, randomization fails outright. A constraint declared soft gives the solver some flexibility instead: it doesn't have to be satisfied if some other, higher-priority constraint conflicts with it — either a hard constraint, or a soft constraint the solver has decided takes precedence. Soft constraints are commonly used to specify default values and distributions for random variables.
12 min read | Intermediate Level
What You'll Learn
- The difference between a hard constraint (must always be satisfied) and a soft one (a default that yields to conflicts)
- Why
softapplies to individual expressions, not to an entire constraint block, even when both appear together - What happens when a soft constraint genuinely conflicts with something else — a hard constraint, or an inline one
- Why the exact same conflicting inline constraint produces a completely different outcome depending on whether the original was soft or hard
- How
disable softcleanly replaces an inherited soft default, removing any ambiguity about which of two competing soft constraints actually takes effect
Example
Here, a soft constraint tells the solver to produce values between 4 and 12 for data — but only the first expression is actually soft:
class ABC;
rand bit [3:0] data;
// This constraint is defined as "soft"
constraint c_data { soft data >= 4;
data <= 12; }
endclass
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 5; i++) begin
abc.randomize();
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
As expected, the randomized values fall within 4 and 12:
ncsim> run abc = 0x4 abc = 0x8 abc = 0x4 abc = 0x7 abc = 0x7 ncsim: *W,RNQUIE: Simulation is complete.
soft applies only to the specific expression it directly precedes — here, data >= 4. data <= 12 has no soft keyword of its own, so it remains a hard constraint, even though it's sitting in the same block. Mixing soft and hard expressions within one constraint block is completely legal, and each expression's own priority is entirely independent of the others around it.
Now let's see how a contradicting inline constraint is handled:
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 5; i++) begin
abc.randomize() with { data == 2; };
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
The contradicting inline constraint successfully sets data to 2 — contradicting because the original constraint's soft part asked for data >= 4, but the inline constraint demands 2, which is outside that range:
ncsim> run abc = 0x2 abc = 0x2 abc = 0x2 abc = 0x2 abc = 0x2 ncsim: *W,RNQUIE: Simulation is complete.
Since the inline constraint (data == 2) outranks the soft one, the soft data >= 4 rule simply yields — no failure, no error, just a quietly overridden default. The hard part, data <= 12, is untouched by any of this, since 2 never conflicted with it in the first place.
Quick Check
Q: Would randomize() with { data == 15; }; succeed against this same class?
Show Answer
No. 15 conflicts with the hard part of the constraint, data <= 12 — and unlike the soft part, a hard constraint never yields. Randomization would fail here, exactly as if the entire constraint were hard.
How Is It Different From Hard Constraints?
Now the same class, with soft removed entirely — both expressions are hard — and the same conflicting inline constraint:
class ABC;
rand bit [3:0] data;
constraint c_data { data >= 4;
data <= 12; }
endclass
module tb;
ABC abc;
initial begin
abc = new;
for (int i = 0; i < 1; i++) begin
abc.randomize() with { data == 2; };
$display ("abc = 0x%0h", abc.data);
end
end
endmodule
This time the solver fails outright, because of the contradicting constraint, and data is left at 0:
ncsim> run
abc.randomize() with { data == 2; };
|
ncsim: *W,SVRNDF (./testbench.sv,14|18): The randomize method call failed. The unique id of the failed randomize call is 4.
Observed simulation time : 0 FS + 0
ncsim: *W,RNDOCS: These constraints contribute to the set of conflicting constraints:
constraint c_data { data >= 4; (./testbench.sv,4)
abc.randomize() with { data == 2; }; (./testbench.sv,14)
ncsim: *W,RNDOCS: These variables contribute to the set of conflicting constraints:
rand variables:
data [./testbench.sv, 2]
abc = 0x0
ncsim: *W,RNQUIE: Simulation is complete.
Same class, same conflicting inline value — but a completely different outcome, purely because data >= 4 is no longer negotiable. This is the entire practical value of soft: the exact same rule either yields gracefully or blocks the entire solve, depending only on whether it was declared soft.
Where Is This Used?
Soft constraints are the standard way to give a variable a reasonable default value or distribution — "most transactions should be reads," for instance — while still letting individual tests freely override that default with an inline constraint or a more specific hard constraint in a subclass, without needing to explicitly disable anything first.
Replacing an Inherited Soft Constraint With disable soft
A subclass can inherit a soft constraint from its parent and simply add a conflicting hard constraint of its own — as already shown, the soft one yields automatically, no extra syntax required. But what if the subclass wants to replace that soft default with a different soft default, rather than a hard rule? Adding a second soft constraint on the same variable, without removing the first, leaves two soft constraints competing on the same variable at once — which one actually takes effect then depends on the solver's own priority rules, rather than being something the subclass author explicitly controls.
disable soft removes that ambiguity. Placed inside a constraint block, it strips away any soft constraint currently active on a specific variable, so whatever comes next — soft or hard — is guaranteed to be the only one governing that variable, rather than one of two competing defaults:
class ABC;
rand bit [3:0] data;
constraint c_data { soft data >= 4;
data <= 12; }
endclass
class XYZ extends ABC;
constraint c_new {
disable soft data; // Remove the inherited soft default entirely
soft data inside {0, 1, 2}; // This is now the only soft rule on 'data'
}
endclass
XYZ inherits ABC's constraints, including the soft data >= 4 default and the hard data <= 12 rule. disable soft data; removes just the inherited soft default, leaving the hard data <= 12 constraint from the parent fully intact. The new soft rule, data inside {0, 1, 2}, is now the only soft constraint in play for data — there's no second soft default left to compete with it.
Quick Check
Q: After disable soft data;, does XYZ still respect the hard data <= 12 rule inherited from ABC?
Show Answer
Yes. disable soft only removes soft constraints on the named variable — it has no effect on hard constraints. data <= 12 stays fully enforced in XYZ, exactly as it was in ABC.
Common Beginner Mistakes
Mistake #1: Assuming soft applies to an entire constraint block
As the first example's note explains, soft only affects the specific expression it directly precedes. Why? A block can freely mix soft and hard expressions — assuming the whole block became soft the moment the keyword appeared anywhere in it would misread which parts are actually negotiable.
Mistake #2: Assuming a soft constraint can cause randomize() to fail
On its own, it can't — a soft constraint always yields rather than blocking a solution. Why? Its entire purpose is to describe a preference that's safe to override; only hard constraints (including inline ones) are capable of causing an outright solver failure.
Mistake #3: Forgetting a hard expression can sit right next to a soft one
As the first example shows, data <= 12 stays fully enforced even while data >= 4 is soft and gets overridden. Why? Each expression's priority is independent — a soft default doesn't weaken any of the hard rules declared alongside it.
Mistake #4: Assuming inline and soft constraints have equal priority
They don't — an inline constraint behaves like a hard one, and overrides a conflicting soft constraint rather than the other way around. Why? This is exactly why the inline data == 2 example succeeds against the soft rule but still has to respect the hard data <= 12 rule sitting right next to it.
Mistake #5: Adding a second soft constraint without disabling the first
Leaving both the inherited soft default and a new soft constraint active on the same variable means the solver decides between them, rather than the subclass author. Why? disable soft removes that ambiguity outright — if the intent is specifically to replace a soft default with a new one, disabling the old one first guarantees only the new rule is in play.
Practice Exercise
Challenge: Soft vs a Separate Hard Block
Given constraint c_default { soft len == 8; } and a second, separate block constraint c_override { len == 3; } in the same class, what value will len randomize to?
Hint
The conflict here works the same way as the inline example in this article — it just comes from a second constraint block instead of a with clause.
Solution
len will always be 3. c_override is a hard constraint, and it directly conflicts with the soft c_default — exactly like the inline data == 2 example, the soft constraint yields without causing any solver failure, regardless of whether the conflicting hard rule comes from an inline clause or a separate named block.
Summary
In this tutorial, you learned:
- A hard constraint must always be satisfied, or randomization fails; a soft constraint is a default that yields when something higher-priority conflicts with it
softapplies only to the specific expression it precedes, not to an entire constraint block, so soft and hard expressions can freely coexist in one block- A soft constraint never causes
randomize()to fail on its own — it simply gets overridden - An inline constraint behaves like a hard one, overriding a conflicting soft constraint while still having to respect any hard constraints that remain in play
disable softremoves an inherited soft constraint on a specific variable, guaranteeing a new soft (or hard) rule replaces it cleanly, rather than competing with it under the solver's own priority rules