Part 3 · Constraint Randomization · Intermediate
Q&A: dist, inside, soft, solve-before
dist := vs :/, inside vs dist, soft constraints and discard order, what solve-before changes and does not, implication vs if-else.
Q: In dist, what is the difference between := and :/?
Direct answer: both attach weights; they differ only on range bins. := gives the weight to each value in the range; :/ divides the weight across the range. On a scalar bin they are identical. Get asked this, do the arithmetic out loud — the interviewer wants the number.
rand bit [7:0] x;
// := each value weighted 40 → total = 5*40 + 60 = 260
constraint c1 { x dist { [1:5] := 40, 9 := 60 }; }
// P(x==3) = 40/260 ≈ 15.4% P(x==9) = 60/260 ≈ 23.1%
// :/ range shares 40 → each of 1..5 gets 8 → total = 40 + 60 = 100
constraint c2 { x dist { [1:5] :/ 40, 9 := 60 }; }
// P(x==3) = 8/100 = 8% P(x==9) = 60/100 = 60%Follow-up you should expect
“Which one keeps its meaning if the range grows?” — :/ : the bucket keeps its total share and dilutes per-value; := inflates the bucket’s total share as values are added. So spec language like “small packets 95% of the time” maps to :/ — the share is what the spec pinned, not the per-value weight.
Junior vs senior answer
Junior: “:= is per value, :/ is divided.” Correct rule, no numbers.
Senior: computes an actual probability on the spot and maps spec percentage language to :/ — showing the rule is operational, not memorized.
Q: What is the difference between inside and dist?
Direct answer: inside defines legality — set membership, with the solver uniform over the legal set. dist defines legality and shapes probability over it. Also: inside is a boolean expression usable anywhere (procedural if, implications, negation), while dist is a constraint-only construct that cannot be negated or nested in arbitrary expressions, and is illegal on randc variables.
// inside: all 11 values equally likely — 20 gets 1/11, not "half"
constraint c_in { x inside {[1:10], 20}; }
// dist: same legal set, weighted — 20 now actually gets half
constraint c_dist { x dist { [1:10] :/ 50, 20 :/ 50 }; }
// inside as a reusable boolean — dist cannot do this:
constraint c_cond { (mode == FAST) -> !(x inside {[200:255]}); }Follow-up you should expect
“Can you combine them?” — Yes, and you often should: inside (or plain relations) to legislate the legal envelope in the base class, dist in a test or subclass to bias within it. Layering legality and policy separately is what keeps base classes reusable.
Junior vs senior answer
Junior: “inside is a range check, dist adds weights.”
Senior: adds the expression-vs-constraint distinction (inside negatable and usable procedurally, dist not), the randc exclusion, and the layering pattern of inside-for-legality plus dist-for-policy.
Q: What is a soft constraint, and in what order are softs discarded?
Direct answer: a soft constraint holds only while consistent with all hard constraints and higher-priority softs; on conflict it is discarded silently instead of failing the solve. Priority: hard constraints always win; among softs, the later-defined wins — and the LRM ordering means constraints in derived classes and in inline with clauses have higher priority than softs in base classes. Lower-priority conflicting softs are dropped, not averaged.
class base_txn;
rand bit [7:0] len;
constraint c_dflt { soft len == 4; } // default policy
endclass
class big_txn extends base_txn;
constraint c_big { soft len inside {[64:128]}; } // derived soft wins
endclass
initial begin
big_txn t = new();
void'(t.randomize()); // len in 64..128
void'(t.randomize() with { len == 100; }); // hard inline beats all softs
endFollow-up you should expect
“Why soft instead of constraint_mode(0)?” — soft is self-relaxing : overriders need no knowledge of the base constraint’s existence or name; the default simply yields wherever a hard constraint speaks and still applies everywhere else. constraint_mode is an explicit, named, all-or-nothing switch the test must operate. Defaults that should melt away on contact belong in soft; constraints a test must consciously kill belong named, with constraint_mode.
Junior vs senior answer
Junior: “soft constraints can be overridden.”
Senior: states the discard (not blend) semantics, the later-defined/derived-class priority rule, and the design guidance of soft-for-defaults vs named-hard-for-policy.
Q: What does solve...before change — and what does it NOT change?
Direct answer: it changes probability distribution only . The default solver is uniform over legal solution tuples , so when one value of a variable admits many more solutions than another, that value soaks up probability. solve a before b partitions the solve: a is chosen uniformly over its legal values first, then b within that choice. It does NOT change which solutions are legal, cannot fix an infeasible set, and is not procedural sequencing.
class pkt;
rand bit err;
rand bit [7:0] code;
constraint c { err -> code inside {[1:255]}; // 255 (err,code) pairs
!err -> code == 0; } // 1 pair
// Default: P(err) = 255/256. With ordering: P(err) = 1/2.
constraint c_ord { solve err before code; }
endclassFollow-up you should expect
“If the constraints were contradictory, would solve...before help?” — No. Legality is fixed by the constraint set; ordering only reshapes how probability falls across the same legal set. Saying “solve-before never affects feasibility” crisply is the checkmark the interviewer is waiting for. Bonus: randc variables are implicitly solved before rand ones — you cannot solve a rand before a randc.
Junior vs senior answer
Junior: “it makes the solver solve one variable first.” — sounds procedural, which invites the trap.
Senior: leads with “probability only, legality never”, explains uniform-over-tuples as the cause of skew, and notes the implicit randc ordering rule.
Q: Implication (->) vs if-else in constraints — any real difference?
Direct answer: a -> b is logical implication — exactly equivalent to !a || b, and if (a) b; else c; is exactly (a -> b) plus (!a -> c). Same solver, same semantics — the practical difference is completeness pressure: if-else forces you to look at the else arm, while a lone implication makes it easy to forget that when the antecedent is false, the consequent variable is completely unconstrained .
// Incomplete — when kind != READ, addr is ANYTHING:
constraint c_half { (kind == READ) -> addr inside {[0:16'h3FFF]}; }
// Complete, either spelling:
constraint c_full {
if (kind == READ) addr inside {[16'h0000:16'h3FFF]};
else addr inside {[16'h8000:16'hFFFF]};
}Follow-up you should expect
“Is the implication bidirectional?” — The implication is one-directional as a logical statement, but the solver still solves both sides jointly: constraining addr into ROM space makes kind == READ more likely, because the solver is uniform over legal pairs. Distinguishing logical direction from solving direction is precisely the senior-level point — and the bridge to the solver-semantics round.
Junior vs senior answer
Junior: “they’re the same thing, different syntax.” True and incomplete.
Senior: gives the !a||b desugaring, names the unconstrained-else hazard, and separates one-way logic from two-way solving with the distribution consequence.
Key takeaways
:= weights per value; :/ shares across the range — spec percentages map to :/.
inside legislates the set; dist shapes probability over it; layer them base-vs-test.
soft discards on conflict with later-defined and derived softs winning; hard always beats soft.
solve...before reshapes probability only — legality and feasibility are untouched.
Implication and if-else desugar identically; the hazard is the forgotten else arm.
Common pitfalls
Reading dist weights as percentages without checking the total — only ratios are guaranteed.
Negating dist or using it procedurally — it is constraint-only; inside is the reusable boolean.
Expecting conflicting softs to blend — losers are discarded whole.
Reaching for solve...before to “fix” a randomize() failure — ordering cannot create solutions.