The previous lesson shrank a failing constraint set by switching blocks off. Once you have a suspect, three more tools confirm it: a check-only call, a look at what the inline with is fighting, and a minimal repro to hand to the solver dump.
The standard's check-only call is obj.randomize(null): it treats every field as fixed and only asks whether the current values satisfy the constraints. Verilator 5.048 rejects it as unsupported.
// snippet
t.addr = 32'h1003; t.size = 3'd2; // plant the suspect values
if (!t.randomize(null)) $display("0x1003 with size 2 breaks a constraint");The same question works on every tool if each field is pinned with an inline with. The call can only succeed if those exact values satisfy every active block, so its return value is the verdict.
class axi_txn;
rand bit [31:0] addr;
rand bit [2:0] size;
constraint align_c { addr % (32'd1 << size) == 0; }
constraint size_c { size inside {[3'd2:3'd3]}; }
endclass
module tb_pin_check;
axi_txn t = new();
// Check-only: pin every rand field to the suspect values. The call can only
// succeed if those exact values satisfy every active constraint.
function automatic bit legal(bit [31:0] a, bit [2:0] s);
axi_txn probe = new();
return probe.randomize() with { addr == a; size == s; };
endfunction
initial begin
bit r1, r2, r3;
r1 = legal(32'h1004, 3'd2); // 4-byte aligned, 4-byte beats
r2 = legal(32'h1003, 3'd2); // not 4-byte aligned
r3 = legal(32'h1004, 3'd3); // 8-byte beats need an 8-byte boundary
$display("addr 1004 size 2: %0d addr 1003 size 2: %0d addr 1004 size 3: %0d", r1, r2, r3);
if (!(r1 && !r2 && !r3)) $fatal(1, "FAIL: a pinned check gave the wrong verdict");
$display("PASS: pinning every field with an inline with gave the three verdicts by hand");
$finish;
end
endmodule%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:4: Unsatisfied constraint: 'constraint align_c { addr % (32'd1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:14: Unsatisfied constraint: 'return probe.randomize() with { addr == a; size == s; };'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:14: Unsatisfied constraint: 'return probe.randomize() with { addr == a; size == s; };'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:4: Unsatisfied constraint: 'constraint align_c { addr % (32'd1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:14: Unsatisfied constraint: 'return probe.randomize() with { addr == a; size == s; };'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t158.sv:14: Unsatisfied constraint: 'return probe.randomize() with { addr == a; size == s; };'
addr 1004 size 2: 1 addr 1003 size 2: 0 addr 1004 size 3: 0
PASS: pinning every field with an inline with gave the three verdicts by handThe same trick validates a transaction read back from a log against today's constraints: build a fresh object, pin every field to the logged values, and check the return value.
The most common real-world contradiction is a test-level inline constraint silently conflicting with a forgotten base-class range. The solver gives both sources equal authority.
Fixes layer in order of preference: soften the class default where policy allows, disable the block for one call, or subclass with test intent. Never delete the legality constraint.
class eth_frame;
rand bit [13:0] length;
constraint legal_c { length inside {[64:1518]}; }
endclass
module top;
initial begin
eth_frame f = new();
// Wants a jumbo frame. Inline ANDs with legal_c, so this is empty.
if (!f.randomize() with { length == 9000; })
$display("inline length 9000 vs class range: no solution");
else $fatal(1, "FAIL: length 9000 should contradict legal_c");
$display("PASS: the inline jumbo request failed as expected");
$finish;
// Fix 1: soften the class default, then the hard inline wins.
// Fix 2: f.legal_c.constraint_mode(0) around this one call.
// Fix 3: subclass eth_frame with a jumbo constraint.
end
endmodule%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t159.sv:3: Unsatisfied constraint: 'constraint legal_c { length inside {[64:1518]}; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t159.sv:10: Unsatisfied constraint: 'if (!f.randomize() with { length == 9000; })'
inline length 9000 vs class range: no solution
PASS: the inline jumbo request failed as expectedVerilator 5.048 needs no switch: every failed call prints an UNSATCONSTR warning naming the blocks behind it, as the core-finder bench showed. Every major simulator can report the constraint subset behind an UNSAT result with file and line references. Treat the exact switches as capability pointers and look them up in the tool version at hand, since flags move between releases.
Run the dump on a minimal repro, never on the full environment. Reducing the class to its failing core usually reveals the bug before the dump finishes.
// minimal_repro: 3 constraints, 1 state value, no methodology code.
class repro;
rand bit [31:0] addr;
rand bit [2:0] size;
constraint align_c { addr % (1 << size) == 0; }
constraint hot_c { addr inside {[32'h1001:32'h1003]}; }
constraint size_c { size >= 3'd2; }
endclass
module top;
initial begin
repro r = new();
// UNSAT: size>=2 needs addr divisible by 4, but the hot window
// holds no multiple of 4. Three constraints, zero solutions.
if (!r.randomize()) $display("PASS: repro shows UNSAT as analyzed");
else $fatal(1, "FAIL: solved: addr=%h size=%0d", r.addr, r.size);
$finish;
end
endmodule%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t160.sv:5: Unsatisfied constraint: 'constraint align_c { addr % (1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t160.sv:6: Unsatisfied constraint: 'constraint hot_c { addr inside {[32'h1001:32'h1003]}; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t160.sv:7: Unsatisfied constraint: 'constraint size_c { size >= 3'd2; }'
PASS: repro shows UNSAT as analyzedThe full object drags in dozens of inherited and nested blocks that bury the core. The dump is most readable on the smallest failing set.
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.
randomize(null) is rejected as unsupported by Verilator 5.048, so the pinned inline with is the check used here.