addr at 8000 while the rest of the object keeps randomizing?t.addr.rand_mode(0); t.addr = 16'h8000;. The field becomes a constant to the solver, and every other field still solves around it.
len at 64. A test calls randomize() with { len > 64; }. What happens, and what is the fix?The call returns 0, because the inline rule is ANDed with the cap. The fix is c_len.constraint_mode(0) for that test, or a subclass that replaces c_len.
c_len again with a new range, and c_hi as a new block. What does each do?The same name replaces the parent's c_len. The new name adds c_hi to every inherited rule.
Knob | Switches | Lasts |
|---|---|---|
| One field | Until |
| One named block | Until |
Inline | One call | That call; ANDed with every class rule |
Subclass block | One block by name | Every object of the subclass |
A knob is a plain field the test sets before the call. A dist whose weights read the knob, plus a pair of implications that turn the decision into a stimulus change, gives rate-controlled error injection. Keep the knob in range in the test, before the call.
The bench runs every answer above on Verilator: a frozen field over 100 calls, the inline contradiction and its fix, a subclass override, and error injection at 0, 25 and 100 percent.
class txn;
rand bit [15:0] addr;
rand bit [15:0] len;
constraint c_len { len inside {[1:64]}; }
constraint c_fit { int'(addr) + int'(len) <= 65536; }
endclass
class sub_txn extends txn;
constraint c_len { len inside {[1:16]}; } // same name: replaces the base rule
constraint c_hi { addr inside {[16'h8000:16'hFFFF]}; } // new name: adds to the base rules
endclass
class err_txn extends txn;
int unsigned err_pct; // state knob, set by the test
rand bit inject_err;
constraint c_len { len inside {[1:255]}; } // replaced, so len > 64 is reachable
constraint c_rate { inject_err dist { 1 := err_pct, 0 := 100 - err_pct }; }
constraint c_arm { inject_err -> len > 64; !inject_err -> len inside {[1:64]}; }
endclass
freeze: addr stayed 8000 for 100 calls while len kept changing (last 63)
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-K1WcxI/t65.sv:4: Unsatisfied constraint: 'constraint c_len { len inside {[1:64]}; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-K1WcxI/t65.sv:38: Unsatisfied constraint: 'ok = t.randomize() with { len > 64; };'
inline len > 64 against the class cap: randomize() returned 0
with c_len switched off: returned 1, len 32767
subclass: len stayed 1 to 16 and addr 8000 or above over 100 calls
err_pct 0: 0 errors in 400 packets
err_pct 25: 99 errors in 400 packets
err_pct 100: 400 errors in 400 packets
PASS: freeze, relax, subclass override and knob-rate injection all did what the answers sayThis bench replaces an earlier model on this page that clamped values and forced frozen fields to 0. Both pictures were wrong: an enabled constraint rejects solutions instead of clamping them, and a frozen field keeps its own value. The output above shows the real behaviour.
Constraints in the transaction, knobs in the sequence or config, seeds and plusargs in the test. Each override lives at the level that owns it.
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.
Error injection at 0, 25 and 100 percent gave 0, 99 and 400 errors in 400 packets.