When a load returns a stale value or a core hangs, the question is always about one line: which transactions touched it, in which order did H serialize them, and which message did not arrive. A coherence trace answers that only if three choices were made at design time: where the taps are, what each record holds, and how the limited buffer is spent.
Tap H's input and output (every request, response and forward it receives or sends) and each cache controller's coherence port. H's tap shows the serialization order, because H decides which transaction for X is active. The cache taps show what each cache actually received and when, which matters because the course model assumes no FIFO order: a record order at H can differ from the delivery order at C0. A common time base (a global timestamp, or per-tap timestamps with a known offset) is what makes records from different taps comparable.
Field | Illustrative size |
|---|---|
Line address (mandatory): 42 bits covers a 48-bit physical address with 64-byte lines | 6 B |
Message type and direction or capture point (mandatory) | 1 B |
Source and destination (mandatory): 4 bits each | 1 B |
Transaction id (mandatory) | 2 B |
Timestamp (mandatory) | 4 B |
State before and after (optional): at a cache the line state, at H a compressed directory and pending-entry code | 2 B |
Payload word or hash (optional) | 4 B for a 32-bit hash, more for a word |
Why each field is needed, and what you lose without it: see Trace-First Debug.
The five mandatory fields are 14 bytes; this lesson's illustrative record adds the 2-byte state field for 16 bytes, and leaves the payload out. A payload word or hash enlarges every record, and it raises the security question covered in Debug Hooks in Controllers, as do the addresses and timestamps themselves. The state fields are the expensive design choice, because they need wires from the state arrays to the tap, but they are what let you check a trace against the protocol table without a reference model.
Take the write miss from S{C0} in Write Miss and Ownership: C0 S / 0, C1 I / -, H S{C0}, memory = 0, MSI, C1 stores 1. That lesson has the message sequence and the snapshot table. Here is the same transaction, labelled A, as records from three taps (illustrative generic format, illustrative cycle numbers).
Cycle | Tap | Dir | Type | Txn | Src to dst | State before to after |
|---|---|---|---|---|---|---|
96 | C1 | out | GetM(X) | A | C1 to H | I to IM^D |
100 | H | in | GetM(X) | A | C1 to H | S{C0} to S{C0}, pend GetM C1, acks 0/1 |
103 | H | out | Inv(X) | A | H to C0 | unchanged |
110 | C0 | in | Inv(X) | A | H to C0 | S to I |
112 | C0 | out | Inv-Ack(X) | A | C0 to H | I |
120 | H | in | Inv-Ack(X) | A | C0 to H | S{C0}, pend GetM C1, acks 0/1 to U, pend GetM C1, acks 1/1 |
160 | H | out | Data(X, M) | A | H to C1 | unchanged until the grant is delivered |
168 | C1 | in | Data(X, M) | A | H to C1 | IM^D to M |
The gap from 120 to 160 is H's memory read, which is an internal event, not a message; a tap can log it as a separate internal record if the designers chose to. Read the trace the way a checker would: the Data with M permission at cycle 160 must come after the Inv-Ack at 120, and C0's state must be I before C1's is M. If the H tap showed the Data before the Inv-Ack, that would be the bug the linked lesson warns about, visible in two records.
# not-run: illustrative arithmetic
peak records at H = 2 per cycle (one in, one out), 1 GHz clock
record size = 16 B (14 B mandatory + 2 B state, illustrative)
fill rate = 2 x 16 B = 32 B per cycle = 32 GB/s
window wanted = 10 us = 10,000 cycles
buffer needed, no filter = 32 B x 10,000 = 320,000 B, about 312.5 KiB
on-chip buffer available = 16 KiB = 1,024 records
window, no filter = 1,024 / 2 per cycle = 512 cycles = 0.512 us
filtered to one line = 1 record per 50 cycles (illustrative)
window, filtered = 1,024 x 50 = 51,200 cycles = 51.2 usThe unfiltered buffer covers half a microsecond, which rarely includes the start of the transaction that went wrong. Filtering to the suspect line stretches the same storage a hundredfold. That is why per-address filtering matters: coherence is defined per location, so the full history of one line is worth more than a short history of every line. The filter is an address match and mask (one line, or a set of lines), optionally combined with a message-type mask and a source mask.
Triggers decide which window you keep. A circular buffer always holds the most recent records; the trigger stops it a set number of records after an event, so you get history before the event and a little after. Good triggers for coherence are a pending-entry timeout at H (the hang case), a checker or parity event, or the first request to a chosen line.
Two transactions on the same line can overlap at H, and a late response for an old one can arrive after a new one has started. Address and time cannot say which transaction an Inv-Ack belongs to; the id can. Without it a trace can show an apparently correct ack that actually completed the wrong transaction.