A contradiction is a property of a set of constraints, not of one constraint. Somewhere inside the full active set sits a minimal subset that cannot be satisfied together, and the job is to shrink the full set down to that core.
The fastest manual technique is binary search by disabling named constraint blocks. Each experiment halves the suspect set, so twenty blocks take about five tries.
class axi_txn;
rand bit [31:0] addr;
rand bit [3:0] len;
rand bit [2:0] size;
rand bit is_wrap;
constraint align_c { addr % (1 << size) == 0; }
constraint size_c { size inside {[3'd2:3'd3]}; } // planted: 4- or 8-byte beats
constraint wrap_c { is_wrap -> len inside {1, 3, 7, 15}; }
constraint page_c { addr[11:0] + ((len + 1) << size) <= 4096; }
constraint hot_c { addr inside {[32'h1001:32'h1003]}; } // no multiple of 4
endclass
module top;
initial begin
axi_txn t;
bit saved_align, saved_size, saved_wrap, saved_page, saved_hot;
t = new();
if (!t.randomize()) begin
// Binary search: disable half, test, then narrow the guilty half.
saved_align = t.align_c.constraint_mode();
saved_size = t.size_c.constraint_mode();
t.align_c.constraint_mode(0);
t.size_c.constraint_mode(0);
if (t.randomize()) $display("conflict involves the align/size half");
// Restore each block to its own saved state, never to a blanket 1.
t.align_c.constraint_mode(saved_align);
t.size_c.constraint_mode(saved_size);
// Step 2: disable the other half and test again.
saved_wrap = t.wrap_c.constraint_mode();
saved_page = t.page_c.constraint_mode();
saved_hot = t.hot_c.constraint_mode();
t.wrap_c.constraint_mode(0);
t.page_c.constraint_mode(0);
t.hot_c.constraint_mode(0);
if (t.randomize()) $display("the other half also takes part: the core spans both");
t.wrap_c.constraint_mode(saved_wrap);
t.page_c.constraint_mode(saved_page);
t.hot_c.constraint_mode(saved_hot);
// Next: split each half again. The core is align_c, size_c, hot_c.
end
else $fatal(1, "FAIL: the planted conflict solved");
$display("PASS: the planted conflict failed and both halves took part");
$finish;
end
endmodule%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t156.sv:7: Unsatisfied constraint: 'constraint align_c { addr % (1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t156.sv:8: Unsatisfied constraint: 'constraint size_c { size inside {[3'd2:3'd3]}; } '
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t156.sv:11: Unsatisfied constraint: 'constraint hot_c { addr inside {[32'h1001:32'h1003]}; } '
conflict involves the align/size half
the other half also takes part: the core spans both
PASS: the planted conflict failed and both halves took partTwo rules make this efficient. Every constraint needs a name, because anonymous blocks cannot be toggled. And each experiment must restore per-block saved state, since a blanket re-enable would turn on blocks the test had deliberately left off.
Halving by hand works, but a short loop does it better. The deletion filter switches each named block off in turn: if the rest still fails, that block is not needed for the conflict and stays off; if the rest now solves, the block is part of the core and goes back on. One pass leaves the smallest set that still fails.
hot_c (addresses 1001 to 1003) against align_c with size_c (4- or 8-byte alignment). Which blocks will the deletion filter keep?align_c, size_c and hot_c. Switching off wrap_c or page_c still fails, so they are not needed; switching off any of the other three lets the call solve.
class axi_txn;
rand bit [31:0] addr;
rand bit [3:0] len;
rand bit [2:0] size;
rand bit is_wrap;
constraint align_c { addr % (32'd1 << size) == 0; }
constraint size_c { size inside {[3'd2:3'd3]}; } // 4- or 8-byte beats
constraint wrap_c { is_wrap -> len inside {1, 3, 7, 15}; }
constraint page_c { 32'(addr[11:0]) + ((32'(len) + 1) << size) <= 4096; }
constraint hot_c { addr inside {[32'h1001:32'h1003]}; } // planted: no multiple of 4
// Switch one named block on or off by index.
function void set_block(int i, bit on);
case (i)
0: align_c.constraint_mode(on);
1: size_c.constraint_mode(on);
2: wrap_c.constraint_mode(on);
3: page_c.constraint_mode(on);
4: hot_c.constraint_mode(on);
endcase
endfunction
endclass
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:6: Unsatisfied constraint: 'constraint align_c { addr % (32'd1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:7: Unsatisfied constraint: 'constraint size_c { size inside {[3'd2:3'd3]}; } '
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:10: Unsatisfied constraint: 'constraint hot_c { addr inside {[32'h1001:32'h1003]}; } '
all five blocks on: randomize() returned 0
align_c off: randomize() returned 1 -> in the core
size_c off: randomize() returned 1 -> in the core
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:6: Unsatisfied constraint: 'constraint align_c { addr % (32'd1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:7: Unsatisfied constraint: 'constraint size_c { size inside {[3'd2:3'd3]}; } '
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:10: Unsatisfied constraint: 'constraint hot_c { addr inside {[32'h1001:32'h1003]}; } '
wrap_c off: randomize() returned 0 -> not needed
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:6: Unsatisfied constraint: 'constraint align_c { addr % (32'd1 << size) == 0; }'
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:7: Unsatisfied constraint: 'constraint size_c { size inside {[3'd2:3'd3]}; } '
%Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t157.sv:10: Unsatisfied constraint: 'constraint hot_c { addr inside {[32'h1001:32'h1003]}; } '
page_c off: randomize() returned 0 -> not needed
hot_c off: randomize() returned 1 -> in the core
minimal core: align_c size_c hot_c
PASS: five solves named the minimal core align_c, size_c and hot_cRead the warnings above the result. Verilator 5.048 printed an UNSATCONSTR warning for exactly the three blocks in the core, on every failing call, and the deletion filter agreed. On this tool the warning is the solver dump: read it first, and use the filter to confirm it or when the tool says less.
It involves the disabled half. Re-enable and split that half again until the smallest failing set remains.
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.
On every failed call Verilator 5.048 printed an UNSATCONSTR warning for exactly the three core blocks, and the deletion filter agreed.