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. Testing Hypotheses and Minimal Repros
On this page

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

    Testing Hypotheses and Minimal Repros

    Pin suspect values with check-only calls, spot inline-with conflicts, read solver dumps and cut a minimal repro.

    A jumbo-frame test failed with no solution, and the base-class range had been forgotten. The inline constraint was ANDed with it, so no value could satisfy both.

    After this lesson you can: test a suspect value set with pinned inline with, explain why inline constraints conjoin, and cut a minimal repro for the solver dump.

    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.

    Test a hypothesis without solving

    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.

    design.sv
    // 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.

    design.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
    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
    Terminal
    %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 hand

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

    Inline constraints conjoin

    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.

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

    Dumps and the minimal repro

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

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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
    Terminal
    %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 analyzed

    “randomize returns 0, how do you find the conflict?” Answer in order: confirm the return value is checked, replay a fixed seed, binary-search named blocks, pin suspects with an inline with to test them, read the solver dump on a minimal repro. Add that inline with constraints conjoin and that soft constraints drop wholesale on conflict.

    Check yourself Why run the solver dump on the repro instead of the full bench?

    The full object drags in dozens of inherited and nested blocks that bury the core. The dump is most readable on the smallest failing set.

    • Pin every field with an inline with to ask whether exact values are legal.

    • An inline with is ANDed with the class constraints; it never overrides them.

    • Run the solver dump on a minimal repro, never on the full bench.

    Rule of thumb: reduce to the smallest failing class before reading any dump.

    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.

    randomize(null) is rejected as unsupported by Verilator 5.048, so the pinned inline with is the check used here.

    Related topics

    • Finding Contradictions

    • Wrong Distribution Debugging

    Previous

    91 / 275

    Next: Wrong Distribution Debugging

    Up next

    Up nextWrong Distribution Debugging

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Randomization Failure Taxonomy
    • The Randomization Debug Checklist