A zero acknowledgement count means the specified revocations are complete. It does not make missing data appear. The converse is equally important: receiving the current value does not prove another cache has lost permission. This example keeps both dependencies at H, so the final Data still combines a usable value with the M grant.
Warm-up from X = 0: a previous write of 1 was completed and cleaned to memory, and C0 then obtained S / 1. Now C0 S / 1, C1 I / -, H S{C0}, memory = 1, MSI. C1 requests GetM A. H starts a memory read and sends an invalidation, then receives Inv-Ack before the memory read returns. Only A is active; a conflicting request would wait. Memory is authoritative because the old holder is clean S, not a dirty exclusive cache.
Step | C0 state / value | C1 state / value | H directory | Memory | Newest data |
|---|---|---|---|---|---|
Initial | S / 1 | I / - | S{C0} | 1 | C0 and memory: 1 |
1. GetM A at H | S / 1 | IM^D / - | S{C0}, pend GetM C1, acks 0/1, data pending | 1 | C0 and memory: 1 |
2. Inv at C0 | I / - | IM^D / - | S{C0}, pend GetM C1, acks 0/1, data pending | 1 | memory: 1 |
3. Inv-Ack at H | I / - | IM^D / - | U, pend GetM C1, acks 1/1, data pending | 1 | memory: 1 |
H reads memory | I / - | IM^D / - | U, pend GetM C1, acks 1/1, data valid | 1 | H buffer and memory: 1, Data(1) in flight to C1 |
4. Data at C1 | I / - | M / 1 | U, pend GetM C1, acks 1/1, data valid | 1 | C1 and memory: 1 |
(assumed) grant delivered | I / - | M / 1 | exclusive(C1) | 1 | C1 and memory: 1 |
L. Local store | I / - | M / 2 | exclusive(C1) | 1 | C1: 2 |
Authoritative data ready? | Required ack set empty? | Issue grant? |
|---|---|---|
No | No | No: two obligations remain. |
No | Yes | No: do not invent a payload. |
Yes | No | No: readers may still be usable. |
Yes | Yes | Eligible if identity, state and protected output capacity are also correct. |
Not the course's convention: in the Primer's directory protocol, invalidated caches send Inv-Acks straight to the requester, and Data carries the number of acks to expect. The requester waits in IM^AD or SM^AD. Because there is no FIFO order, an Inv-Ack can arrive before the Data that says how many to expect, so the requester keeps a signed counter: each early Inv-Ack decrements it below zero, and the ack count in Data adds back. The requester completes when Data has arrived and the counter is zero.
def receive(entry, transaction, kind, responder=None, value=None):
if transaction != entry["id"]:
return False
if kind == "ack":
if responder not in entry["waiting"]:
return False
entry["waiting"].remove(responder)
elif kind == "data":
if value is None or entry["data"] is not None:
return False
entry["data"] = value
else:
return False
return True
def ready(entry):
return not entry["waiting"] and entry["data"] is not None
for order in [("ack", "data"), ("data", "ack")]:
entry = {"id": "A", "waiting": {"C0"}, "data": None}
assert not ready(entry)
assert not receive(entry, "old", "ack", responder="C0")
assert not receive(entry, "old", "data", value=99)
for position, kind in enumerate(order):
assert receive(entry, "A", kind, responder="C0", value=0)
assert ready(entry) == (position == 1)
assert not receive(entry, "A", "ack", responder="C0")
assert not receive(entry, "A", "data", value=99)
assert entry["data"] == 0 and ready(entry)
print("PASS: both arrival orders require matching acks and present data")The waveform below is simulation output for H's pending entry in a three-core variant of the model: C0 and C2 hold X in S, memory is authoritative, and C1 sends GetM. H collects two Inv-Acks and the memory data. Here the data arrives between the two acks, whereas in the table above the ack came first. One slot is one clock cycle.
How to read it: C1's GetM is accepted in slot 1. In slot 2 the entry is pending, inv_sent pulses (Inv to C0 and C2) and acks_left is 2. C0's Inv-Ack in slot 4 brings acks_left to 1 in slot 5. The memory read returns in slot 6, so data_valid is high in slots 7 and 8, but grant stays low: one ack is still missing. C2's Inv-Ack in slot 8 brings acks_left to 0 in slot 9, and grant (Data with M permission to C1) pulses in that same slot while pending and data_valid clear. store_done rises in slot 10: C1's store completes only after the grant.
The code checks both arrival orders and rejects stale identities and duplicate responses without changing the accepted value. A counter can replace the expected-responder set only if each required response is counted exactly once.
H has not yet read it. The 1 in the table stands for the whole 64-byte line, which H must read from memory and hold in its buffer before it can send it. Knowing the value in a worked example is not the same as having the data.