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
  • Part 4: Stimulus11
    • Directed Tests
    • Why Random Works
    • Constrained Random
    • Distributions and Bias
    • The Long Tail
    • Scenarios and Layered Stimulus
    • Error Injection
    • Timing and Back-Pressure
    • Seeds and Regressions
    • Hunting the Zoo with Random
    • Drills: Stimulus
  • 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 4: Stimulus
  1. Topics
  2. First Principles of Verification Strategy
  3. Part 4: Stimulus
  4. Why Random Works

Part 4 · Stimulus · Working (W)

Why Random Works

Random stimulus finds cases nobody listed, because it does not share the test writer's assumptions, and every random packet also exercises timing and combinations for free. Its cost is the coupon collector's slow finish: DCRouter's 128 header kinds need about 695 random packets to see them all, and the last 10 cost more than four times the first 64.

On this page

    Note

    Level: Working (W) · Answers: How do we drive the design into the states that matter?

    Before this: Directed Tests

    Lesson 4.1's directed suite missed BUG_13 because its one bad-CRC packet was low priority, and BUG_21 because it woke the router from input 0. No reviewer would call either choice wrong; they were simply the choices the writer made. A generator that picks priority and port at random does not make the writer's choices. Over a few hundred packets it tries both priorities and all four ports without anyone deciding to.

    Why does random stimulus find bugs that directed tests miss, and what does it cost?

    The obvious answer, and why it breaks

    One obvious view is that random testing is unprincipled: it wastes cycles on cases that do not matter and repeats cases already seen. The opposite view is that random, run long enough, covers everything. Both miss the shape of the curve. Random is very efficient at first, because almost every packet is a case not yet seen. It becomes very inefficient at the end, because the last unseen cases are found only by luck. Knowing where on that curve a regression sits is what makes random a strategy rather than a hope.

    The idea: no shared assumptions, and the coupon collector

    • Independence from the writer: random choices do not share the blind spots of the person who wrote the tests (the other priority, the other port).

    • Combinations for free: every random packet also has a random length, destination, timing and neighbour activity, so interactions are exercised without being designed.

    • The cost: drawing uniformly from n kinds, the expected number of draws to see all of them is n·H_n (fact M2).

    E[draws to see all n]=nHn​=n(1+21​+⋯+n1​)≈nlnn+0.577nE[kinds seen after t]=n(1−(1−n1​)t)

    Build it: DCRouter's 128 header kinds

    A DCRouter header has 4 destinations, 2 priorities and 16 lengths: 128 kinds. The model computes how many kinds uniform random packets see after t packets, checks it by simulation, and splits the time to see all 128 into the first half and the last ten.

    example.py
    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
    """Why random works, and why it slows down: the coupon collector on DCRouter headers.
    
    A DCRouter header has 4 destinations x 2 priorities x 16 lengths = 128 kinds. Directed tests
    can hit all 128 with 128 packets, if someone writes all 128. Uniform random packets hit a
    new kind quickly at first and ever more slowly at the end: the expected number of packets to
    see every kind is n * H_n (the coupon collector, fact M2). Part 1 prints the expected number
    of kinds seen after t random packets, n * (1 - (1 - 1/n)^t), and the expected time to see
    them all. Part 2 checks both by simulation. Part 3 splits the wait: how many packets go to
    the first half of the kinds, and how many to the last ten.
    """
    import random
    
    n = 4 * 2 * 16
    H = sum(1 / k for k in range(1, n + 1))
    ts = [64, 128, 256, 512, 1024, 2048]
    print(f"header kinds n = {n}; expected packets to see all of them n*H_n = {n * H:.0f}")
    print("random packets:      " + " ".join(f"{t:>6d}" for t in ts))
    expect = [n * (1 - (1 - 1 / n) ** t) for t in ts]
    print("kinds seen (formula) " + " ".join(f"{e:6.1f}" for e in expect))
    
    rng = random.Random(7)
    runs = 2000
    seen_at = [0.0] * len(ts)
    all_at = []
    for _ in range(runs):
        seen, t, firsts = set(), 0, []
        while len(seen) < n:
            t += 1
            k = rng.randrange(n)
            if k not in seen:
                seen.add(k)
                firsts.append(t)
            if t in ts:
                seen_at[ts.index(t)] += len(seen) / runs
        for j, tt in enumerate(ts):          # runs that finished early saw all n from then on
            if t < tt:
                seen_at[j] += n / runs
        all_at.append(firsts)
    print("kinds seen (sim)     " + " ".join(f"{s:6.1f}" for s in seen_at))
    mean_all = sum(f[-1] for f in all_at) / runs
    half = sum(f[n // 2 - 1] for f in all_at) / runs
    last10 = mean_all - sum(f[n - 11] for f in all_at) / runs
    print(f"simulated packets to see all {n}: {mean_all:.0f} (formula {n * H:.0f})")
    print(f"first {n // 2} kinds: {half:.0f} packets; last 10 kinds: {last10:.0f} packets")
    
    assert all(abs(a - b) < 1.0 for a, b in zip(expect, seen_at))
    assert abs(mean_all - n * H) / (n * H) < 0.03
    assert last10 > 4 * half
    print(f"PASS: random needs about {n * H / n:.1f} times as many packets as kinds; the last 10 kinds cost more than four times the first {n // 2}")

    It prints:

    Diagram
    header kinds n = 128; expected packets to see all of them n*H_n = 695
    random packets:          64    128    256    512   1024   2048
    kinds seen (formula)   50.5   81.1  110.8  125.7  128.0  128.0
    kinds seen (sim)       50.5   81.1  110.8  125.7  128.0  128.0
    simulated packets to see all 128: 703 (formula 695)
    first 64 kinds: 88 packets; last 10 kinds: 382 packets
    PASS: random needs about 5.4 times as many packets as kinds; the last 10 kinds cost more than four times the first 64
    The model's numbers: 50.5 kinds after 64 packets, 81.1 after 128, 110.8 after 256, 125.7 after 512 and all 128 by 1,024.

    How to read it: 128 random packets see only 81 kinds, because some kinds repeat while others are missed; it takes about 695 packets (5.4 per kind) to see all 128. The first 64 kinds cost 88 packets; the last 10 cost 382. The simulation agrees with the formula at every point.

    Random against directed, honestly

    Directed

    Uniform random

    Packets to cover 128 header kinds

    128, if someone writes all 128

    About 695 on average

    Cases nobody listed

    Never

    Yes, in proportion to their probability

    Timing and combinations

    Only those designed

    Exercised as a side effect

    Rare corners (probability p)

    If someone writes them

    About 1/p packets (lesson 4.5)

    Cost to add a case

    Engineering time per case

    Simulation time, which is cheap

    Random trades simulation time for engineering time and imagination. That trade is excellent for the many common cases and poor for rare ones, which is why lessons 4.3 to 4.5 shape the randomness and Part 6 measures what it reached.

    How real teams use random stimulus

    Practice

    Purpose

    Random by default, directed for known corners

    Breadth from random, precision where it is needed

    Functional coverage on every random regression

    Shows which kinds were reached and which are still missing (Part 6)

    Watch the closure curve flatten

    The slow finish is when to bias or direct stimulus, not to run longer

    What breaks

    A team (illustrative) planned "one random packet per header kind" for a block with 128 kinds and ran 128 packets per test. Their coverage report showed about 81 of 128 kinds per test, as the model predicts, and the remaining kinds varied from seed to seed. Running each test for 1,024 packets, or tracking coverage across many seeds, closed the gap.

    Why this matters

    Senior lens. Use random where its arithmetic works: wide spaces of individually common cases, and combinations nobody will enumerate. Expect the slow finish, measure it with coverage, and switch to bias or directed tests when the curve flattens instead of buying more simulation.

    Interview

    Asked: "Why does random testing find bugs that directed tests miss?"

    Weak answer: Because it runs more tests.

    Strong answer: Because its choices don't share the test writer's assumptions: it tries the other priority, the other port, the next packet with a different header, without anyone deciding to. And each random transaction also randomises length, timing and neighbour activity, so it exercises combinations nobody would enumerate. The cost is the coupon-collector finish: covering n equally likely cases takes about n·H_n draws, so 128 header kinds need around 695 random packets, and the last few kinds cost most of that. That's why I pair random with coverage and switch to bias or directed tests when the curve flattens.

    Follow-ups: (1) How many draws to see all of 128 kinds? (2) What do you do when coverage plateaus?

    Check yourself After 128 uniform random packets, about how many of DCRouter's 128 header kinds have been seen, and why not all?

    About 81: random draws repeat some kinds and miss others; the expected count is 128·(1 − (1 − 1/128)^128).

    Check yourself Why do the last 10 kinds cost more packets than the first 64?

    When only a few kinds are unseen, each packet has a small chance of hitting one, so the expected wait for each new kind grows as the number left shrinks.

    Key takeaways

    • Random stimulus does not share the test writer's blind spots and exercises combinations for free.

    • Covering n equally likely cases takes about n·H_n random draws: fast at first, slow at the end.

    • Pair random with coverage, and bias or direct stimulus when the curve flattens.

    Keep going

    Next: Lesson 4.3, Constrained Random. Random inside the legal space: constraints as a model of the environment.

    Related topics

    • Part 4 overview: Stimulus

    Previous

    41 / 160

    Next: Constrained Random

    Up next

    Up nextConstrained Random

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Distributions and Bias
    • The Long Tail
    • Scenarios and Layered Stimulus