Stable-state transitions are exercised by almost any test. The bugs found late are in races: a message for one transaction arriving while the cache or H is in the middle of another. A race cross is a coverage item that names three things: the transient state, the conflicting message that arrives in it, and which request caused that message. Where the result depends on which of two messages H sees first, the cross also names the order, and both orders are bins.
Race cross | Where it is taught | What a bug here looks like |
|---|---|---|
(SM^D, Inv) caused by the other core's GetM | Simultaneous Upgrades, step 3 | The losing upgrader keeps its S copy, or later receives permission-only Data with no bytes. |
(MI^A, Fwd-GetS) caused by GetS: a PutM crossing a forward | Eviction Versus Intervention, order B step 2 | The cache answers 'absent' from its tag while the WB entry holds the only current value. |
(MI^A, Fwd-GetM) caused by GetM | Same mechanism, writer instead of reader: C0 answers from the WB entry and goes to II^A | The cache answers from its array instead of the WB entry, or frees the WB entry before Put-Ack; or H treats the late PutM as a writeback and erases C1's exclusive record. |
(SI^A, Inv) caused by GetM: a PutS crossing an Inv | The cache ignores the Inv because it is evicting, and H waits for an Inv-Ack for ever. | |
At H: PutM from a non-holder after a forward | Eviction Versus Intervention, order B step 5 | H treats the late PutM as a writeback, overwrites memory or erases C1's sharer record. |
At H: GetS arrives while PutM is pending | Eviction Versus Intervention, order A step 2 | H serves the reader from stale memory before the writeback is written. |
At H: GetM arrives while GetS is pending | Racing Requests at the Home, step 2 | H invalidates C0 before its S grant is delivered. |
The two orders of the eviction race are separate bins: PutM reaches H first (order A) or the GetS does (order B). A regression that only ever produces order A has not tested the forward-from-WB path at all, even though both cells of the plain matrix (MI^A, Put-Ack) and (M, Fwd-GetS) look covered.
A race needs a second request for the same line inside a short window: the cycles a cache spends in one transient state. Suppose (illustrative numbers) each core issues a request every 20 cycles on average (rate 0.05), addresses are uniform over A lines, and the window is 20 cycles. The chance that one of the other N - 1 cores requests this line during the window is 1 - (1 - 0.05 / A)^((N - 1) x 20).
Traffic | Cores N | Lines A | P(conflict in one window) | Windows for 100 expected conflicts |
|---|---|---|---|---|
Spread, course model | 2 (C0, C1) | 4,096 | 0.024% | about 409,600 |
Hot spot, extended model | 8 (C0 to C7) | 4 | 82.8% | about 121 |
The second row extends the course model to eight cores to show the effect; the protocol is unchanged. Few lines and many cores raise the rate by a factor of about 3,400. The same factor applies to every race cross, since each one starts with a same-line conflict, and a specific cross then also needs the right states and order. A plain random regression with realistic address spread can run for days without one PutM crossing a forward.
import random
def p_conflict(cores, lines, rate, window):
"""P(at least one other core requests the same line during one transient window)."""
per_cycle = rate / lines # one other core hits this line in one cycle
return 1 - (1 - per_cycle) ** ((cores - 1) * window)
def simulate(cores, lines, rate, window, trials, seed=1):
rng, hits = random.Random(seed), 0
for _ in range(trials):
line = 0 # the line in its transient window
hits += any(rng.random() < rate and rng.randrange(lines) == line
for _ in range((cores - 1) * window))
return hits / trials
WINDOW, RATE = 20, 0.05 # illustrative: 20-cycle window, 1 request per 20 cycles
spread = p_conflict(cores=2, lines=4096, rate=RATE, window=WINDOW)
hot = p_conflict(cores=8, lines=4, rate=RATE, window=WINDOW)
assert abs(spread - 2.44e-4) < 1e-5 and abs(hot - 0.828) < 0.001
assert hot / spread > 3000 # hot spot: thousands of times more races per window
for cores, lines in [(2, 64), (8, 4), (8, 16)]:
model, sim = p_conflict(cores, lines, RATE, WINDOW), simulate(cores, lines, RATE, WINDOW, 4000)
assert abs(model - sim) < 0.03, (cores, lines, model, sim)
# failing case: an estimate that forgets the other cores (counts one competitor) is wrong at 8 cores
naive = p_conflict(cores=2, lines=4, rate=RATE, window=WINDOW)
assert abs(naive - simulate(8, 4, RATE, WINDOW, 4000)) > 0.3
# transient windows needed for 100 expected conflicts
assert round(100 / spread) == 409648 and round(100 / hot) == 121
print("PASS: few lines and many cores raise the per-window race probability from 0.02% to 83%")The formula assumes independent uniform requests; real traffic is burstier, and locks and shared counters produce natural hot spots (see Hot Lines and Locks). The point survives: race coverage has to be engineered, which is the next lesson.
No. Each cell can be hit in a run with no race: an eviction that completes alone, and a forward to a line that was never being evicted. The race is the cross (MI^A, Fwd-GetS), plus the H-side bin for a PutM from a non-holder, and the order bin that says the GetS reached H before the PutM. Only those show that the forward was answered from the WB entry.