Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
    • Randomization Basics: rand, randc & randomize()
    • Constraint Operators & Expressions
    • Dynamic Constraint Control
    • Randomizing Arrays and Queues
    • How the Constraint Solver Works
    • Debugging Randomization Failures
      • Randomization Failure Taxonomy
      • Finding Contradictions
      • Testing Hypotheses and Minimal Repros
      • Wrong Distribution Debugging
      • The Randomization Debug Checklist
    • Interview Drills — Constraints and Randomization Q&A
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Debugging Randomization Failures
  1. Topics
  2. SystemVerilog
  3. Constraint Randomization — Interview-Critical Deep Dive
  4. Debugging Randomization Failures
  5. Finding Contradictions

Part 3 of 9, lesson 27 of 39Part 3 of 9/Constraint Randomization/Lesson 27 of 39Intermediate

Finding Contradictions

Shrink the UNSAT set with binary search by hand and with a deletion filter that names the minimal core.

An AXI test failed on one address window after a refactor. Twenty constraint blocks were active, and the conflict needed three of them at once.

After this lesson you can: find the smallest failing set of constraint blocks by binary search and by a deletion filter over named blocks.

Shrink the UNSAT set

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.

top.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
Terminal
%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 part

Two 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.

Predict first The class has five blocks, and the planted conflict is 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.

design.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
Terminal
%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_c

Read 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.

Anonymous constraint blocks cannot be disabled one by one, so binary search is impossible. Forgetting to re-enable blocks after an experiment is the matching trap: later tests run under-constrained and the results lie.

Check yourself Disabling half the blocks makes randomize succeed. Where is the contradiction?

It involves the disabled half. Re-enable and split that half again until the smallest failing set remains.

  • A contradiction belongs to a set of blocks, never to one block.

  • A deletion filter over named blocks leaves the minimal core in one pass.

  • Restore each block to its own saved state, never to a blanket 1.

Rule of thumb: name every constraint block, so each one can be switched off.

How this was checked

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.

Related topics

  • Next: Testing Hypotheses and Minimal Repros

  • Randomization Failure Taxonomy

Previous

90 / 275

Next: Testing Hypotheses and Minimal Repros

Up next

Up nextTesting Hypotheses and Minimal Repros

Sign in to save progress

Practice this lessonPrevious lessonReport an error

See also

  • Wrong Distribution Debugging
  • The Randomization Debug Checklist