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 Path
  • Cache Coherency Glossary
  • The Shared Course Model
  • Cache Coherency Revision Sheet
  • Why Coherence Exists8
  • States and Protocols12
  • Transactions12
  • Races and Controller Design8
  • Progress and the Fabric16
  • Ordering and Synchronization12
  • Performance and Scalability12
  • Real Protocols12
  • SoC Integration12
  • Power, Clocking and Reliability12
  • Verification12
    • Coherency Scoreboards and Reference Models
    • Formal Properties for Coherency Safety
    • Coverage Closure and Waiver Governance
      • State and Transition Coverage
      • Race and Cross Coverage
      • Directed Race Stress
      • Closure Evidence
  • Debug and Bring-up12
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹Coverage Closure and Waiver Governance
  1. Topics
  2. Cache Coherency
  3. Verification
  4. Coverage Closure and Waiver Governance
  5. Directed Race Stress

Part 11 · Verification · All levels

Directed Race Stress

Open race windows on purpose: hot lines, more cores than lines, class delays, back-pressure and eviction pressure.

On this page

    How do you make a race happen on purpose?

    You cannot schedule a race message by message in a large system, and a hand-written directed test breaks as soon as the latency of any path changes. Instead, change the traffic so that race windows are wide and same-line conflicts are frequent, and let constrained random do the rest. Each knob targets a factor of the race probability: how often two requests meet the same line, how long the transient window lasts, and whether the conflicting message arrives inside it.

    Knobs and the races they provoke

    Knob

    What it changes

    Race cells it provokes

    Few addresses (hot spot): 1 to 8 lines, all with the same home

    Same-line conflicts per window

    (SM^D, Inv); requests queued behind a pending one at H

    More cores than lines

    Several sharers per line, several simultaneous upgraders

    (SM^D, Inv) with more than one ack to collect; long queues at H

    Random delay per message class (request, forward, response), with PutM delayed separately

    Which message wins at H; no FIFO across classes

    Order B of the eviction race (delay PutM), (MI^A, Fwd-GetS), (MI^A, Fwd-GetM); a PutM from a non-holder at H

    Back-pressure injection: withhold credits, fill H's queues

    Holds requests at the sender, widens every window

    Stall cells, queue-full and retry paths; checks that responses still sink

    Eviction pressure: tiny caches, one or two ways, addresses in one set

    Constant PutM and PutS traffic

    All MI^A, SI^A and II^A cells; (SI^A, Inv), a PutS crossing an Inv

    Store-heavy mix

    More GetM, more upgrades

    SM^D cells, Fwd-GetM paths

    Three knobs, few lines with many cores, a tiny cache and a delayed writeback class, each widen one condition, and together they make a conflicting request arrive inside the eviction window.
    Each knob widens one factor of a race, and the cell needs all of them at once.

    Worked case: forcing (MI^A, Fwd-GetS) on the course model

    Target: order B of Eviction Versus Intervention, where C1's GetS reaches H while C0's PutM is still in flight. Recipe, keeping that lesson's roles (C0 evicts, C1 reads): give C0 a one-line cache; let C0 store to X (C0 M / 1, H exclusive(C0), memory = 0), then store to a second line Y in the same set, which evicts X into the WB entry and sends PutM W (C0 MI^A / 1 in WB); add a random delay of up to 16 cycles to PutM only; and give C1 a load-heavy stream over just X and Y. Each knob alone barely helps. Together they make the cell frequent, and the value checker confirms that C1 reads 1, not memory's 0.

    The block below abstracts that recipe: C0 stores and evicts with an LRU cache, C1 reads, and a hit is a GetS for a line whose PutM is still in flight. It counts hits of (MI^A, Fwd-GetS) under each knob. Measured on seed 3 with 20,000 cycles, the counts are 4 (baseline: 1,024 lines, 64-line cache, no delay), 0 (hot spot only: 8 lines fit in the cache, so nothing is evicted), 5 (tiny cache only), 21 (slow writebacks only), 217 (hot spot and tiny cache) and 1,405 (all three).

    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
    import random
    from collections import OrderedDict
    def stress(lines, capacity, wb_delay, cycles=20000, seed=3):
        """Count (MI^A, Fwd-GetS) hits: C1's GetS finds C0's PutM for that line still in flight."""
        rng, cache, inflight, hits = random.Random(seed), OrderedDict(), {}, 0
        for _ in range(cycles):
            if rng.random() < 0.5:                       # C0 stores to a line (acquiring M abstracted)
                x = rng.randrange(lines)
                if x not in inflight:                    # a line in MI^A stalls C0's store
                    cache[x] = True; cache.move_to_end(x)
                    if len(cache) > capacity:            # eviction pressure: PutM enters the network
                        victim, _ = cache.popitem(last=False)
                        inflight[victim] = rng.randint(1, wb_delay)
            if rng.random() < 0.3:                       # C1 reads a line
                x = rng.randrange(lines)
                if x in inflight: hits += 1              # H still records C0: Fwd-GetS meets MI^A
                cache.pop(x, None)                       # otherwise C0 loses M to the read (simplified)
            inflight = {x: t - 1 for x, t in inflight.items() if t > 1}
        return hits
    base = stress(lines=1024, capacity=64, wb_delay=1)
    knobs = {"hot spot": stress(8, 64, 1), "tiny cache": stress(1024, 2, 1),
             "slow writeback class": stress(1024, 64, 16)}
    tiny_hot = stress(8, 2, 1)
    together = stress(8, 2, 16)
    assert base <= 5 and knobs["hot spot"] == 0          # few lines alone: the 64-line cache never evicts
    assert knobs["tiny cache"] >= base and knobs["slow writeback class"] > 4 * base
    assert tiny_hot > 40 * base and together > 5 * tiny_hot      # knobs multiply when combined
    # failing case: a configuration that cannot evict can never cover the cell, however long it runs
    assert stress(lines=8, capacity=8, wb_delay=16, cycles=200000) == 0
    print("PASS: eviction pressure plus few lines plus slow writebacks is what reaches MI^A, Fwd-GetS")

    The hot-spot result is the lesson in miniature: a knob that is right for one race (upgrades) does nothing for another (evictions) unless the cache is also small enough to evict. Stress profiles are therefore named per target cross, and a regression mixes them.

    Rules for stress knobs

    Stay inside the protocol's assumptions. Random delays may reorder messages only where the protocol allows it: the course model assumes no FIFO, so any reordering is legal here, but a protocol that requires an ordered channel must keep it ordered under stress, or the test reports false failures. Back-pressure on the response class may delay responses for a bounded time, never withhold them indefinitely, because the deadlock-avoidance rule says responses always sink; withholding them for ever tests a system the design never promised. Finally, keep unbiased profiles in the mix: a regression that only runs hot spots stops covering the plain paths and the address-dependent logic.

    Common pitfall

    Shrinking caches with a parameter that the RTL does not really support, so the stressed configuration is not the shipped one. Prefer set-conflicting addresses on the real geometry, or check that the reduced configuration is a supported build.

    Common pitfall

    Treating a timeout under heavy back-pressure as a design hang without checking the injection. Stimulus may delay responses for a bounded time, never withhold them indefinitely: if it withheld credits for ever on a class the protocol requires to drain, the hang is in the test.

    Interview

    Asked how you would hit a PutM crossing a forward: say tiny cache or set conflicts to force evictions, few lines so another core asks for the evicted line, and a delay on the writeback class so the PutM is still in flight when the forward arrives. Then say how you would know it happened: the race cross bin, sampled only on runs the value checker passed.

    Check yourself Your hot-spot profile runs 8 cores on 4 lines with 32 KB caches and never hits (SI^A, Inv). Why, and which knob is missing?

    (SI^A, Inv) needs a shared line being evicted with PutS while another core's GetM makes H invalidate it. With 4 lines in a 32 KB cache nothing is ever evicted, so no cache enters SI^A. Add eviction pressure: map the hot lines and some filler lines to one set, or use a supported small-cache build, so S lines are evicted while others upgrade.

    Related topics

    • Shared course model

    • The race crosses being targeted

    • Writeback buffers

    • Credits and back-pressure

    • Coverage-driven stimulus (First Principles of Verification Strategy)

    Previous

    132 / 167

    Next: Closure Evidence

    Up next

    Up nextClosure Evidence

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • State and Transition Coverage