The expression a -> b reads as follows: when a holds, b must hold. Formally it is (!a) || b, so three of the four truth-table rows satisfy it.
When a is false, the implication says nothing about b. The false branch stays fully open unless another constraint pins it.
a | b | a -> b holds? |
|---|---|---|
false | false | Yes: b is free when a is false |
false | true | Yes: b is free when a is false |
true | false | No: the only forbidden row |
true | true | Yes |
// snippet: checked in the bench below
class dma_txn;
rand bit small_mode;
rand bit [7:0] len;
constraint len_c { small_mode -> len <= 8; }
endclass
// small_mode==1 implies len in 0..8.
// small_mode==0 leaves len in 0..255: full range, NOT len above 8.The classic wrong answer claims the false branch means the opposite. It does not: the opposite needs an else arm or a second implication.
Constraints have no left-to-right direction. The solver treats a -> b as one relation over every variable on both sides and solves them together.
// snippet: checked in the bench below
class bi_demo;
rand bit flag;
rand bit [3:0] val;
constraint c1 { flag -> val == 9; }
constraint c2 { val inside {[0:7]}; } // 9 is impossible here
endclass
// flag==1 needs val==9, but c2 forbids 9, so every solution has flag==0.
// randomize still SUCCEEDS here: solutions exist, with flag forced to 0.Implication also skews probability: with c1 alone, one legal pair has the flag set and sixteen do not. Bidirectional Solving works through those numbers; this lesson is about what is legal.
An if/else constraint is sugar for a pair of implications. The statement if (a) b; else c; means (a -> b) && (!a -> c) exactly.
// snippet: checked in the bench below
class pkt;
rand bit jumbo;
rand bit [13:0] len;
// Both branches pinned down, with no open hole:
constraint len_c {
if (jumbo) len inside {[1501:9000]};
else len inside {[64:1500]};
}
// Identical meaning written as two implications:
constraint len_c2 {
jumbo -> len inside {[1501:9000]};
!jumbo -> len inside {[64:1500]};
}
endclassBoth forms stay bidirectional. A later inline constraint such as len == 5000 forces the solver to pick jumbo==1.
// snippet: a richer shape, same rules
class mem_txn;
typedef enum {READ, WRITE, ATOMIC} op_e;
rand op_e op;
rand bit [3:0] burst;
rand bit locked;
constraint shape_c {
// Atomic ops are single-beat and locked
op == ATOMIC -> (burst == 1 && locked == 1);
// Writes longer than 8 beats stay unlocked
(op == WRITE && burst > 8) -> locked == 0;
}
endclass
// (op==WRITE && burst>8) -> locked==0 is the flattened form of
// op==WRITE -> (burst>8 -> locked==0): identical solution set.Form | Constrained rows | Open rows |
|---|---|---|
| a true requires b true | a false leaves b free |
| Both branches pinned | No open hole |
| Flattened chain, one forbidden combo | All rows with |
The statement solve a before b changes probability only, never the solution set. The solver picks a first over its own legal values, then picks b among values consistent with that choice. With flag -> val == 9, the standard gives the flag about a 1 in 17 chance without the ordering and about 1 in 2 with it; Bidirectional Solving has the counting.
// snippet: the two classes, run in the bench below
class skew_demo;
rand bit flag;
rand bit [3:0] val;
constraint c { flag -> val == 9; }
endclass
class shaped_demo;
rand bit flag;
rand bit [3:0] val;
constraint c { flag -> val == 9; }
constraint order_c { solve flag before val; }
endclass// snippet: errors only at one address, made likely by ordering
class bus_txn;
rand bit inject_err;
rand bit [31:0] addr;
constraint err_c {
inject_err -> addr == 32'hDEAD_BEEF;
solve inject_err before addr;
}
endclassOrdering and dist compose: dist sets the flag ratio while solve...before stops the partner variable from drowning it. The ordering rules are strict and worth memorizing.
Rule | Consequence |
|---|---|
Only |
|
Cycles are illegal |
|
Lists give a partial order |
|
Independent variables ignore it | With no shared constraint, ordering changes nothing. |
Failures stay failures | An empty solution set cannot be fixed by any ordering. |
solve flag before val change which (flag, val) pairs are legal?No. The reachable pairs are bit-for-bit identical with and without it. Only the probability weights move.
Pair counting: one pair carries the flag set against about four billion without it. The interview model gives about 1 in 2^32, so error traffic effectively never appears.
skew_demo and shaped_demo 1,700 times each. Can either class ever produce flag == 1 with val != 9?No. Ordering never changes what is legal, so every solve of both classes is one of the 17 legal pairs; only how often each pair appears can change.
Three classes from this lesson solved 2,000 times, checking that each implication binds only its true branch and that solving runs both ways; then the two ordering classes, checking legality and printing the measured flag rates.
class dma_txn;
rand bit small_mode;
rand bit [7:0] len;
constraint len_c { small_mode -> len <= 8; }
endclass
class bi_demo;
rand bit flag;
rand bit [3:0] val;
constraint c1 { flag -> val == 9; }
constraint c2 { val inside {[0:7]}; } // 9 is impossible here
endclass
class pkt;
rand bit jumbo;
rand bit [13:0] len;
constraint len_c {
if (jumbo) len inside {[1501:9000]};
else len inside {[64:1500]};
}
endclass
module tb_implication;
dma_txn d = new();
bi_demo b = new();
pkt p = new();
int big_when_off = 0, flag_set = 0;
initial begin
repeat (600) begin
if (!d.randomize()) $fatal(1, "FAIL: dma randomize failed");
if (d.small_mode && d.len > 8) $fatal(1, "FAIL: small_mode with len %0d", d.len);
if (!d.small_mode && d.len > 8) big_when_off++; // the false branch is open
if (!b.randomize()) $fatal(1, "FAIL: bi_demo randomize failed");
if (b.flag) flag_set++; // c2 forces flag to 0
if (!p.randomize()) $fatal(1, "FAIL: pkt randomize failed");
if (p.jumbo != (p.len > 1500)) $fatal(1, "FAIL: jumbo %0d with len %0d", p.jumbo, p.len);
end
if (!p.randomize() with { len == 5000; }) $fatal(1, "FAIL: len == 5000 failed");
$display("small_mode == 0 with len > 8: %0d of 600; bi_demo flag set: %0d; len 5000 chose jumbo = %0d", big_when_off, flag_set, p.jumbo);
if (big_when_off == 0 || flag_set != 0 || p.jumbo != 1) $fatal(1, "FAIL: the solver did not solve both ways");
$display("PASS: the implication bound only its true branch, c2 forced flag to 0, and len 5000 forced jumbo");
$finish;
end
endmodulesmall_mode == 0 with len > 8: 538 of 600; bi_demo flag set: 0; len 5000 chose jumbo = 1
PASS: the implication bound only its true branch, c2 forced flag to 0, and len 5000 forced jumboclass skew_demo;
rand bit flag;
rand bit [3:0] val;
constraint c { flag -> val == 9; }
endclass
class shaped_demo;
rand bit flag;
rand bit [3:0] val;
constraint c { flag -> val == 9; }
constraint order_c { solve flag before val; }
endclass
module tb_solve_order;
skew_demo s = new();
shaped_demo h = new();
int n = 1700;
int sf = 0, hf = 0;
int pairs_s[int], pairs_h[int];
initial begin
repeat (n) begin
if (!s.randomize() || !h.randomize()) $fatal(1, "FAIL: randomize failed");
if ((s.flag && s.val != 9) || (h.flag && h.val != 9)) $fatal(1, "FAIL: an illegal pair");
sf += s.flag;
hf += h.flag;
pairs_s[{s.flag, s.val}]++;
pairs_h[{h.flag, h.val}]++;
end
$display("legal pairs reached: without solve-before %0d, with it %0d (17 exist)", pairs_s.num(), pairs_h.num());
$display("flag == 1: without solve-before %0d of %0d (%0.1f%%), with it %0d (%0.1f%%)", sf, n, 100.0 * sf / n, hf, 100.0 * hf / n);
$display("standard: about 5.9%% without solve-before, about 50%% with it");
$display("PASS: all %0d solves of both classes were legal pairs", 2 * n);
$finish;
end
endmodulelegal pairs reached: without solve-before 17, with it 16 (17 exist)
flag == 1: without solve-before 54 of 1700 (3.2%), with it 0 (0.0%)
standard: about 5.9% without solve-before, about 50% with it
PASS: all 3400 solves of both classes were legal pairsThe printed rates are this simulator's, not the standard's: it does not follow the solve...before probability rule (in this run the flag was never set with the ordering), while every pair it produced was legal. Interview answers use the standard's numbers.
Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.
Verilator 5.048 does not follow the standard's solve...before probabilities: in this lesson's bench the flag was never set with the ordering (the standard gives about 50%), and in another probe it reached about 30%. Every solve was legal, which is all the bench asserts.