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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • Meet DCRouter
  • Glossary
  • Part 0: What Verification Is8
  • Part 1: What Can Go Wrong11
  • Part 2: Specifications as Checkable Rules8
  • Part 3: Observability and Controllability7
    • Controllability
    • Observability
    • Bug Propagation and Masking
    • Black Box, White Box, Grey Box
    • Hooks and Test Modes
    • Design for Verification
    • Drills: Observability and Controllability
  • Part 4: Stimulus11
  • Part 5: Checking11
  • Part 6: Coverage10
  • Part 7: Formal and Simulation10
  • Part 8: Verification Levels and Reuse8
  • Part 9: Planning and Risk9
  • Part 10: Emulation and Prototyping8
  • Part 11: Post-Silicon Validation8
  • Part 12: Bug Economics8
  • Part 13: Debug7
  • Part 14: Sign-Off7
  • Part 15: Verification in Context8
  • Part 16: Case Studies5
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 3: Observability and Controllability
  1. Topics
  2. First Principles of Verification Strategy
  3. Part 3: Observability and Controllability
  4. Bug Propagation and Masking

Part 3 · Observability and Controllability · Senior (S)

Bug Propagation and Masking

A test catches a bug only if three things happen: the stimulus activates it, the wrong value propagates to something observed, and a check detects it. A runnable example where a test fires the bug twice and still passes, because a mux masks the wrong value, and what each of the three conditions asks of a testbench.

On this page

    Note

    Level: Senior (S) · Answers: Can we see and steer what matters?

    Before this: Observability

    A bug report arrives from the lab. The team replays the failing scenario in simulation, adds a temporary internal check, and finds that their regression had triggered the bug: in 40 different tests, thousands of times. Every one of those tests passed. The wrong value was produced, travelled one stage, and was thrown away by a mux that happened to be selecting the other input every time.

    How can a test trigger a bug and still pass?

    The obvious answer, and why it breaks

    The obvious model of testing is that if the stimulus reaches the buggy logic, the test will fail. Reaching the logic is only the first of three conditions. The bug must also corrupt a value that travels to something the testbench observes, and a check must be looking at that thing at that time and know the right answer. Fail any one and the test passes with the bug fully exercised.

    The idea: activate, propagate, detect

    Condition

    Meaning

    Fails when

    Activation

    The stimulus drives the buggy logic into the case where it is wrong

    The triggering case is never reached (controllability, lesson 3.1)

    Propagation

    The wrong value travels to an observed point

    Later logic masks it: a mux selects another input, an AND with 0, an overwrite before use, a value never read

    Detection

    A check at that point knows the right answer and compares

    No check there, or the check is too weak (lesson 2.1)

    Software testing calls the same chain reachability, infection, propagation and revealability (fact V5). It is why a large number of tests can exercise a bug and none of them report it.

    Build it: a bug that fires and is masked

    A saturating adder feeds a mux, y = mode ? sum : a. The adder's overflow test is written on the 8-bit result, so it can never be true: 200 + 100 gives 44 instead of 255. Test A overflows the adder with mode = 0; test B repeats the same additions with mode = 1. An output check compares y with a model; an internal check compares sum.

    tb.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
    47
    48
    49
    50
    // Activation, propagation, detection. A saturating adder feeds a mux: y = mode ? sum : a.
    // The adder has a bug: it compares the 8-bit result with 255, so an overflow wraps instead
    // of saturating (200 + 100 gives 44, not 255). Test A (slots 1-3) overflows the adder with
    // mode = 0: the bug fires but the mux discards sum, so y is correct. Test B (slots 5-7)
    // overflows it with mode = 1: the wrong sum reaches y. An output check (black box) compares
    // y with a model; an internal check (white box) compares sum with the model's sum.
    module tb;
      logic clk = 0;
      always #5 clk = ~clk;
      initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
    
      logic       mode = 0;
      logic [7:0] a = 0, b = 0, sum, y;
      logic [8:0] wide;
      assign wide = a + b;
      assign sum  = (8'(a + b) > 8'd255) ? 8'd255 : 8'(a + b);    // BUG: should test wide > 255
      assign y    = mode ? sum : a;
    
      // stimulus: {mode, a, b} per slot
      logic [16:0] stim [0:8] = '{17'h0_0000, 17'h0_C864, 17'h0_FA0A, 17'h0_1020,
                                  17'h0_0000, 17'h1_C864, 17'h1_FA0A, 17'h1_1020, 17'h0_0000};
      int slot = 0;
      always @(posedge clk) begin
        {mode, a, b} <= stim[slot];
        slot <= slot + 1;
      end
    
      // checkers, sampled at the falling edge when inputs are stable
      function automatic logic [7:0] sat(input logic [7:0] x, input logic [7:0] z);
        return (9'(x) + 9'(z) > 9'd255) ? 8'd255 : 8'(x + z);
      endfunction
      int out_err_a = 0, out_err_b = 0, int_err_a = 0, int_err_b = 0;
      logic out_bad, int_bad;
      always @(negedge clk) begin
        out_bad = (y !== (mode ? sat(a, b) : a));
        int_bad = (sum !== sat(a, b));
        if (slot >= 2 && slot <= 4) begin out_err_a += out_bad; int_err_a += int_bad; end
        if (slot >= 6 && slot <= 8) begin out_err_b += out_bad; int_err_b += int_bad; end
      end
    
      initial begin
        repeat (10) @(posedge clk);
        $display("test A (mode 0): output check %0d errors, internal check %0d errors", out_err_a, int_err_a);
        $display("test B (mode 1): output check %0d errors, internal check %0d errors", out_err_b, int_err_b);
        if (out_err_a == 0 && int_err_a == 2 && out_err_b == 2 && int_err_b == 2)
          $display("PASS: in test A the bug fired twice but was masked; only the internal check saw it. In test B it propagated to y");
        else $display("FAIL: unexpected error counts");
        $finish;
      end
    endmodule

    It prints:

    Diagram
    test A (mode 0): output check 0 errors, internal check 2 errors
    test B (mode 1): output check 2 errors, internal check 2 errors
    PASS: in test A the bug fired twice but was masked; only the internal check saw it. In test B it propagated to y

    How to read it: values are in hex. In slots 1 and 2, a + b overflows (c8 + 64 and fa + 0a) and sum is wrong (2c, 4 instead of ff), so the bug is activated. mode is 0, so y shows a (c8, fa): the wrong value does not propagate, and the output check sees nothing. In slots 5 and 6 the same additions run with mode = 1, the wrong sums reach y, and the output check detects both. The internal check on sum catches the bug in both tests, because it observes the value before the mux can mask it.

    Where masking happens in real designs

    • Selection: a mux, arbiter or priority encoder picks a different source.

    • Overwrite: a register is rewritten before anything reads the wrong value.

    • Logical masking: AND with 0, OR with 1, a saturating clamp, a comparison that hides small errors.

    • Unused results: a speculative or predicted value that is discarded (a mispredicted branch, a cancelled request).

    • Error correction: ECC or retries silently repair a wrong value, hiding the logic bug that produced it.

    • Performance-only effects: DCRouter's BUG_9 loses credits, which only slows the output (lesson 3.2).

    What each condition asks of the testbench

    To improve

    Do this

    Activation

    Reach the triggering states on purpose: bias, directed sequences, coverage of the trigger (Part 6)

    Propagation

    Vary the conditions downstream (modes, selections, back-pressure) so the wrong value has a path out; read everything at the end

    Detection

    Check close to the source (internal assertions), check every output, and use a complete oracle

    How real teams handle it

    Practice

    Purpose

    Assertions on intermediate values

    Detect at the source, before masking

    Coverage of trigger conditions and of propagation conditions

    Know whether the bug could have been activated and observed

    Mutation testing of the testbench

    Plant bugs and check the testbench reports them (Part 6)

    What breaks

    The opening story is illustrative but common: a regression that triggered a bug thousands of times and never reported it. Coverage said the overflow case was hit, so the team believed it was tested. Coverage of activation is not evidence of detection; only a check that sees the value, or coverage that the wrong value reached an output, is.

    Why this matters

    Senior lens. When a bug escapes, ask which of the three conditions failed. Never reached is a stimulus gap; reached but masked is a propagation gap; reached and visible but unchecked is a checker gap. Each has a different fix, and fixing the wrong one changes nothing.

    Interview

    Asked: "Your test hit the bug but passed. How?"

    Weak answer: The test must have had a bug in it.

    Strong answer: Hitting the buggy logic is only activation. The wrong value also has to propagate to something observed, and a check there has to detect it. Here it was probably masked: a mux selected the other input, a value was overwritten before use, or the result was discarded. Or it reached an output nobody checked. I'd add an assertion close to the source, vary the downstream conditions so the value has a path out, and add coverage that the wrong value can reach an output, not just that the trigger happened.

    Follow-ups: (1) Give three ways a wrong value is masked. (2) Why is activation coverage not enough?

    Check yourself In the example, which condition failed in test A?

    Propagation: the bug was activated (sum was wrong) but the mux selected a, so the wrong value never reached y.

    Check yourself Why did the internal check catch the bug in both tests?

    It observes sum directly, before the mux, so masking downstream cannot hide the wrong value from it.

    Check yourself Coverage shows the adder overflowed 10,000 times. What else must be true before you trust the overflow logic?

    That a check compared the overflow result with the right answer, or that the wrong result would have reached a checked output; activation alone proves nothing.

    Key takeaways

    • A test finds a bug only if it activates it, the wrong value propagates, and a check detects it.

    • Masking by selection, overwrite, logic or discard lets a test trigger a bug and pass.

    • Check close to the source and vary downstream conditions; coverage of the trigger is not evidence of detection.

    Keep going

    Next: Lesson 3.4, Black Box, White Box, Grey Box. Where checkers should look: at the pins, inside, or in between.

    Related topics

    • Part 3 overview: Observability and Controllability

    Previous

    35 / 160

    Next: Black Box, White Box, Grey Box

    Up next

    Up nextBlack Box, White Box, Grey Box

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Controllability
    • Hooks and Test Modes
    • Design for Verification
    clkmodea0c8fa100c8fa100b064a20064a200sum02c43002c4300y0c8fa1002c4300
    Simulation output: slots 1 and 2 (test A) overflow the adder, sum shows 2c and 4 instead of ff, but y follows a; slots 5 and 6 (test B) select sum, and y shows the wrong 2c and 4.