A core-side MSHR, or miss-status holding register, records an outstanding miss and the local operation that awaits completion. H's pending entry records system work for a line: requester, current holders, required revocations, data source and grant progress. They can refer to the same X without being the same record.
Use the two-core model with private write-back caches and 64-byte X initially 0. The rows below are separate checkpoints, not a message sequence. Assume H has one active slot for X and can keep one waiting request. C0's read A takes the active slot; C1's write B waits without obtaining permission.
Step | C0 state / value | C1 state / value | H directory | Memory | Newest data |
|---|---|---|---|---|---|
C0 miss accepted | IS^D / -, MSHR A | I / - | U, pend GetS C0 | 0 | memory: 0 |
C1 waits behind A | IS^D / -, MSHR A | IM^D / -, MSHR B | U, pend GetS C0, queued GetM C1 | 0 | memory: 0 |
H has read memory for A | IS^D / -, MSHR A | IM^D / -, MSHR B | U, pend GetS C0, queued GetM C1 | 0 | H buffer and memory: 0, Data(0) in flight to C0 |
A's Data at C0 | S / 0, MSHR A freed | IM^D / -, MSHR B | U, pend GetS C0, queued GetM C1 | 0 | C0 and memory: 0 |
(assumed) grant delivered | S / 0 | IM^D / -, MSHR B | S{C0}, queued GetM C1 | 0 | C0 and memory: 0 |
MSHR field (core side) | Why it is needed |
|---|---|
Line address | Matches incoming Data, Inv and Fwd to this miss. |
Request type | GetS, GetM or upgrade: decides which grant completes it. |
Transient state | IS^D, IM^D, SM^D, IS^D_I: decides how each incoming event is handled. |
Waiting loads and stores, with store data | The core operations to perform when the grant arrives. |
Transaction id or generation | Tells this miss's responses from a delayed response to an older one on the same line. |
Merged secondary misses | Later loads or stores to the same line that join this miss instead of sending a second request. |
H pending-entry field | Why it is needed |
|---|---|
Requester | Where the grant goes. |
Request | GetS, GetM or Put, and the identity that responses must match. |
Acks needed and received | When every required Inv-Ack has arrived. |
Data valid and data buffer | Whether authoritative data has arrived, and where it is protected until sent. |
Queued next request | The waiting request for the same line, served when this one completes. |
The waveform below is simulation output for a private controller with two MSHRs. It misses on line X (address 0x40), then on line Y (address 0x80) while X is still pending. Each allocation sends one request to H, and each returning Data frees the entry it matches. One slot is one clock cycle.
How to read it: the miss to X appears in slot 1. In slot 2 entry 0 becomes valid, req pulses with req_line = 40, and mshr_count is 1. The miss to Y in slot 4 finds entry 0 busy, so in slot 5 entry 1 becomes valid, req pulses again with req_line = 80, and mshr_count is 2: two misses are outstanding at once. Data for line 40 arrives in slot 7 and entry 0 is free in slot 8 (count 1) while entry 1 still waits. Data for line 80 arrives in slot 10 and entry 1 is free in slot 11 (count 0). req_line simply keeps the last address after each one-cycle request.
Before accepting A, H needs a place to keep its identity and obligations. Before accepting dirty data, H needs protected storage for it. If those resources are unavailable, the controller must apply backpressure or refuse the request by a stated rule; it cannot accept and overwrite the active record. With a wait queue, a conflicting request for the same line can be accepted without touching the active entry.
ACTIVE_SLOTS = 1
QUEUE_SLOTS = 1
active = {}
queue = {}
def offer(line, request):
"""Return 'active', 'queued' or 'refused'."""
if line not in active and len(active) < ACTIVE_SLOTS:
active[line] = {"request": request, "data": None}
return "active"
if line in active and len(queue.get(line, [])) < QUEUE_SLOTS:
queue.setdefault(line, []).append(request)
return "queued"
return "refused"
def complete(line):
done = active.pop(line)
if queue.get(line):
active[line] = {"request": queue[line].pop(0), "data": None}
return done["request"]
assert offer("X", "A") == "active"
a_record = dict(active["X"])
assert offer("X", "B") == "queued" # B waits, it does not take the active slot
assert active["X"] == a_record and queue["X"] == ["B"]
assert offer("X", "C") == "refused" # one-slot queue is full
assert active["X"] == a_record and queue["X"] == ["B"]
assert offer("Y", "D") == "refused" # no active slot left for another line
assert complete("X") == "A"
assert active["X"]["request"] == "B" and queue["X"] == []
print("PASS: B queues behind A, a third request is refused, A's record is unchanged")The check covers three outcomes: A takes the active slot, B enters the wait queue without changing A's record, and a third request for X is refused because the queue is full. A request for another line Y is also refused, because the only active slot is in use. When A completes, B moves into the active slot. A merge path may attach compatible readers to a miss, but a queued writer has different permission requirements and may require invalidating A's eventual copy.
Freeing C0's MSHR after its read does not imply that every home queue or accepted writeback buffer is free. Conversely, H may have completed permission work while the requester still awaits delivered Data. Define a release condition for each resource instead of one global done bit.
The local entry tracks C1's outstanding operation, not a permission grant or ownership of H's active slot. B can wait in a defined queue while A remains the only active transaction for X.