CXL · Module 8
Host Cache Access
Why a device cannot simply read DRAM: if a host cache holds a modified copy, memory is stale. Source authority, per-line serialisation, return identity, bounded owner waits and the decision race. Seven RTL models simulated, seventeen mutations, seventeen killed.
Chapter 8.1 established that a cached line is borrowed and that the host coordinates. This chapter takes the first consequence, and it is sharper than it sounds: when the device wants a line, the value in memory may simply be wrong.
1. The Engineering Problem — The Obvious Read Is the Wrong Read
A device needs a line of host memory. The obvious implementation is a read of that address.
That implementation is broken, and not in a corner case. If any host cache holds a modified copy of the line, then memory has not been updated and the bytes at that address are a previous version. The read succeeds. The data is well-formed. It is stale, and nothing anywhere reports an error.
This is the worst failure shape in the module:
| Property | Value |
|---|---|
| Detected by hardware | no |
| Detected by the requester | no |
| Reproducible on demand | no |
| Correlates with | host activity on the same lines |
A device that reads memory directly is not slightly wrong; it is silently wrong exactly when another agent is doing something interesting.
2. The One-Sentence Model
Ask the coherence authority, not memory blindly. The location of a line's bytes and the location of its current value are different questions — so a device access must resolve who holds the authoritative copy before it decides where to read, and must hold that decision until the data actually lands.
Call it source authority. The address tells you where the line lives; only the directory tells you where its value is.
3. What This Chapter Owns
| Question | Owned by |
|---|---|
| What a borrowed line obliges | 8.1 |
| The generic coherence conversation | 3.4 |
| Finding the authoritative copy for a device read | this chapter |
| Answering inbound coherence actions | 8.3 |
| Living inside a real accelerator | 8.4 |
| Moving writable ownership | 8.5 |
| Generic coherency theory | Module 13 |
| End-to-end annotated read and write flows | Module 14 |
Deliberately not repeated: 3.4 already builds the host-side directory, conflict serialisation and snoop-wait as a generic coherence point. This chapter is the same problem seen from the device's side of the link — what the device must track, decide and bound while it waits — and it uses 3.4's vocabulary rather than re-deriving it.
4. The Exchange
Architectural. The arrows are decisions, not messages — this repository's source policy does not permit naming CXL.cache messages, and inventing them would be worse than omitting them.
Three things are worth reading off it.
Memory is never consulted in the first exchange. Not "consulted and discarded" — not read at all. Reading it would produce a stale value that looks exactly like a good one.
The device does not choose the source. It asks for a line; the coordinator resolves authority. The device's job is to track the request and accept whatever arrives, which is why its side of this chapter is mostly tracking rather than deciding.
The two exchanges cost different amounts, and §11 measures the difference.
5. Teaching-model boundary
6. RTL 1 — What the Directory Knows
// Memory is authoritative exactly when no cache holds a modified copy.
// This is the whole chapter in one expression.
assign memory_authoritative = !dirty_m[rd_idx];=== EXP1: memory is not automatically authoritative ===
uncached line : host_cached=0 dirty_owner=0 memory_authoritative=1
nothing cached: memory holds the value : ok
cached, clean : host_cached=1 dirty_owner=0 memory_authoritative=1
a clean copy does not make memory stale : ok
cached, DIRTY : host_cached=1 dirty_owner=1 owner=2 memory_authoritative=0
a modified copy makes memory stale : okCached is not the same as stale. A clean cached copy is identical to memory, so memory remains a perfectly good source. Only a modified copy displaces it. Designs that treat "someone has it cached" as "memory is untrustworthy" are correct but needlessly slow — they consult an owner on every shared line.
The directory also refuses to represent a contradiction:
// A modified owner that is not cached anywhere is a contradiction.
if (upd_dirty_owner && !upd_host_cached) owner_without_cache_err <= 1'b1;7. RTL 2 — One Source, Chosen by Authority
// Exactly one source, chosen by who holds the newest value.
assign src_owner = req && !TRUST_MEMORY && dirty_owner;
assign src_memory = req && (TRUST_MEMORY ? 1'b1 : (BOTH_SOURCES ? 1'b1 : !dirty_owner));
...
// THE invariant of this chapter.
if (src_memory && dirty_owner) stale_read_err <= 1'b1;=== EXP2: choosing the source ===
dirty line : correct owner=1 memory=0 | trust-memory memory=1
the owner's copy was chosen : ok
the broken variant read stale memory : ok
clean line : owner=0 memory=1
with no dirty owner, memory is used : ok
sources used: from memory=1 from owner=1
exactly one source per access : okThe TRUST_MEMORY variant is the bug in Debug Lab 1, and it is worth noticing that it produces no error of its own — the stale-read flag is a checker, not a consequence. In silicon that flag does not exist unless someone built it, which is exactly why the failure is silent.
8. RTL 3 — One Line, One Access, and Two Reasons to Refuse
end else if (alloc) begin
// Two distinct reasons to refuse, and they need different fixes.
if (!found) n_full_q <= n_full_q + 8'd1;
else n_conflict_q <= n_conflict_q + 8'd1;
end=== EXP3: one line, one access in flight ===
two lines : occupancy=2 allocated=2
different lines proceed together : ok
same line : correct allocated=2 conflicts=1 | allow-same-line allocated=3
the same-line request was refused : ok
the permissive variant allowed both : ok
and it then held two live accesses on one line : ok
=== EXP4: the table fills; refusals have two reasons ===
4 entries : occupancy=4 peak=4 full-refusals=1 conflict-refusals=1
full and conflict refusals counted separately : ok"Refused" is not a diagnosis. A device refusing because its table is full needs a bigger table; a device refusing because the line is already busy needs nothing at all — that is correct serialisation, and a high count simply means the workload is hammering one address. One counter cannot separate them, and a team that sees only a total refusal rate will size the wrong resource.
9. RTL 4 — The Value Must Reach the Requester That Asked
// Identity: did the right requester get the right bytes?
if (ret_data != exp_m[eff_id]) mismatch_err <= 1'b1;
// Authority: did the value come from where the directory said?
if (ret_from_owner != exp_from_owner) wrong_source_err <= 1'b1;=== EXP5: the value must reach the requester that asked ===
return tag 2 : correct delivers id=2 | by-order delivers id=0
the tag chose the requester, not arrival order : ok
delivered=3 mismatches on correct=0 | by-order mismatched=1
every value reached the requester that asked : ok
matching by arrival order delivered wrongly : okTwo independent properties, and a design can satisfy one while violating the other. Identity asks whether the right requester got the right bytes. Authority asks whether those bytes came from the source the directory chose — a value that is correct by luck, fetched from memory that happened not to be stale yet, still indicates the decision path is broken and will produce corruption on the next line.
Checking only identity misses an entire class of bug, because most of the time memory and the owner agree.
10. RTL 5 — Waiting for an Owner, With a Bound That Names Names
=== EXP6: whose owner response is late ===
id 3 hung : correct timeouts=1 reported id=3 max_wait=11
the per-access timer named the access that hung : ok
one shared timer blamed a healthy access : okThe same argument 7.5 made about completions applies unchanged: a shared timer can say something is late but not which thing, and recovery needs identity. Here the consequence is sharper, because the access that hung is holding a line locked — so blaming the wrong one leaves the real blockage in place.
max_wait_q is the counter worth exposing. It is the observed worst case, and it is what tells you whether the ten-cycle limit is generous or marginal for a real workload.
11. Waveform — A Read That Needs an Owner
Device read of a line a host cache holds modified
9 cyclesRead src_memory across the whole trace: it is never asserted. That flat line is the chapter. The device asked for a line, the directory knew a cache held a newer copy, and memory was not merely deprioritised — it was never a candidate.
The measured tail confirms it:
stale_read_err=0 mismatch=0 wrong_source=0 from_owner=1 from_mem=012. RTL 6 — The Decision Must Hold Until the Data Lands
This is the subtlest module in the chapter, and it is why per-line serialisation is a correctness requirement rather than a scheduling nicety.
A source decision is taken at one instant. The data arrives later. If the directory can change in between, the access can be authorised to read memory and then deliver a value that became stale while it was in flight.
// While a decision is outstanding the line is locked: a conflicting change
// is deferred, not applied. NO_LOCK removes exactly that protection.
assign change_blocked = dirty_change && line_locked_q && !NO_LOCK;=== EXP7: the decision must hold until the data lands ===
decided from memory, line locked=1
line dirtied mid-flight : correct blocked=1 | no-lock blocked=0
the conflicting change was deferred, not applied : ok
the locked design delivered the value it chose : ok
without the lock a stale value was delivered : okThe lock is what makes the decision meaningful. Without it, "memory is authoritative" is a statement about the past — true when it was evaluated, and possibly false by the time it is acted on. This is the same shape as the read-passing-a-pending-write hazard in 7.5: a fact that was true when sampled, acted on after it stopped being true.
13. The Device-Side Structures
The line lock is the block that does not appear in a non-coherent read path at all, and it is the one §12 showed is load-bearing. Everything else has a plain-memory analogue: a table of outstanding reads, a return matcher, a timeout. The lock exists because the answer to a question has a shelf life here, which is not true when the only source is memory.
14. Quantitative Reasoning — Where a Device Read's Time Goes
Illustrative, using this chapter's teaching latencies. Decompose a device read:
T_read ≈ table allocation
+ authority resolution
+ (owner interaction, if any)
+ data pathThe measured split, with 15 memory-sourced reads at 6 cycles and 5 owner-sourced reads at 18:
=== EXP8: where the time goes ===
from memory: n=15 sum=90 max=6
from owner : n=5 sum=90 max=18
the source split matched an independent oracle : ok
15 memory reads and 5 owner reads cost the same : ok
but the worst owner read was 3x the memory read : okFive owner reads cost as much total time as fifteen memory reads. That is the number worth carrying: at a 25% owner-source rate, half the access time is spent on a quarter of the accesses.
The design consequence is that the owner-source rate is a first-class performance metric, not a curiosity — and it is a property of the workload's sharing pattern, not of the device. A device cannot make it smaller by being faster; it can only be sized for it. That is why n_from_owner_q against n_from_mem_q belongs in silicon.
15. Assertions
Icarus Verilog 13.0 does not support concurrent SVA here, so each property is synthesisable checker logic verified in simulation.
Safety
| Property | Intent |
|---|---|
| No stale read | dirty_owner |-> !src_memory |
| Exactly one source | req |-> one of memory / owner |
| One access per line | no two live entries share a line |
| Return identity | deliver |-> data == expected[id] |
| Return authority | deliver |-> from_owner == decided |
| No unknown retire | retire |-> entry live |
| Decision holds | a locked line defers conflicting changes |
| Timeout names the late one | timeout |-> age[id] >= LIMIT |
Liveness
| Property | Assumption |
|---|---|
| An access eventually retires | the owner answers, or the bound expires |
| A refused same-line access eventually allocates | the holding access retires |
Performance goals
| Goal | Measured by |
|---|---|
| Bounded owner wait | max_wait_q against LIMIT |
| Table sized for concurrency | peak_q, n_full_q |
| Sharing pressure visible | n_from_owner_q / n_from_mem_q |
16. Mutation Testing
Seventeen mutations. Seventeen killed.
| Mutation | Result |
|---|---|
| Memory always authoritative | killed |
| Modified owner with nothing cached not flagged | killed |
| Owner never used as a source | killed |
| Memory read despite a dirty owner | killed |
| Stale-read check disabled | killed |
| One-source check disabled | killed |
| Same-line accesses proceed concurrently | killed |
| Both refusal reasons counted as table-full | killed |
| Peak occupancy lags by one | killed |
| Returns matched by arrival order | killed |
| Misdelivered data not detected | killed |
| Value from the wrong source not detected | killed |
| Any waiting access may be declared late | killed |
| Reported timeout id not checked | killed |
| Owner-sourced reads not counted | killed |
| Conflicting change applied under a live decision | killed |
| Stale delivery not detected | killed |
The first run scored 15 of 17, and both survivors were the same two shapes 8.1 hit.
The one-source check was unreachable by construction — src_owner requires dirty_owner and src_memory requires !dirty_owner, so they could never both assert. A BOTH_SOURCES variant makes the checker earn its keep.
The wrong-source check needed illegal stimulus. A value arriving from a source the directory did not choose cannot be driven into the instance under test, so it got a dedicated DUT:
both-sources variant flagged multi_src : ok
abuse instance flagged the wrong data source : okThree chapters in a row have now needed an instance reserved for illegal stimulus. It should be the default structure for any checker whose condition a correct design cannot reach, not a repair applied after a mutation escapes.
17. Verification Plan
| Area | Approach |
|---|---|
| Authority | Uncached, cached-clean, cached-dirty — all three asserted |
| Source selection | Both outcomes, plus exactly-one and at-least-one |
| Same-line | Refused; permissive variant shown holding two |
| Capacity | Fill, refuse, and separate the two refusal reasons |
| Return matching | Out-of-order returns with distinguishable data per id |
| Source authority | Return from the wrong source, on an abuse instance |
| Owner wait | A hung access with healthy traffic present |
| Decision race | Directory changed mid-flight, locked and unlocked |
| Latency | Independent oracle on the source split |
The coverage model is a cross of directory state against access outcome: uncached / cached-clean / cached-dirty, crossed with returns-promptly / returns-late / never-returns / changes-mid-flight. The last column is the one that produced RTL 6, and no random stimulus generates it — the change has to be injected deliberately during a live decision.
18. Silicon Observability
| Counter | Diagnoses |
|---|---|
| from-memory vs from-owner | sharing pressure in the workload |
max_wait_q | whether the owner-wait bound is marginal |
| timeout count and reported id | which path hangs |
| full-refusals vs conflict-refusals | undersized table vs hot line |
peak_q | table sizing evidence |
deferred changes (n_blocked_q) | contention during live decisions |
| stale-read flag | a correctness alarm that must read zero forever |
The last row deserves emphasis: it is the one counter here that should be identically zero for the life of the product. A non-zero value is not a performance signal, it is silent data corruption already in flight — and without the counter, nothing else in the system will tell you.
19. Debug Lab
A device intermittently computes on a previous version of the data
STALE-MEMORY// Device needs a line — read it.
assign src_memory = req;Results are occasionally wrong, by an amount consistent with using an older value. It reproduces only when a CPU thread is actively writing the same buffers, disappears under a debugger, and never fails in isolation. No error is reported anywhere.
dirty line : correct owner=1 memory=0 | trust-memory memory=1The device read the address instead of resolving the value. When a host cache holds the line modified, memory holds the previous version — the read returns well-formed, stale bytes.
The reason it is so hard to catch is that nothing is broken from the memory system's point of view: the read was legal, the address was right, and the data returned is genuinely what is stored there. Only the coherence directory knows it is not the current value.
assign src_owner = req && dirty_owner;
assign src_memory = req && !dirty_owner;
if (src_memory && dirty_owner) stale_read_err <= 1'b1; // and prove it never firesResolve authority before choosing a source, and add the checker — because in silicon the failure has no other symptom.
Test with a host cache deliberately holding the line modified. This is the single most important directed case in the chapter, and a random-traffic testbench with no modified-owner state will never produce it.
Two data sources are fetched for one access
MULTI-SOURCEassign src_owner = req && dirty_owner;
assign src_memory = req; // not mutually exclusiveDuplicate traffic on the memory path, occasional double delivery to one requester, and a table entry that retires twice. Bandwidth is worse than modelled and the outstanding count drifts.
both-sources variant flagged multi_src : okThe two source expressions were written independently rather than as a partition. Each is individually reasonable; together they overlap for every line with a modified owner.
The deeper problem is that the design has two answers to a question that must have exactly one, so whichever value arrives second either overwrites a correct value or retires an entry that is already gone.
assign src_owner = req && dirty_owner;
assign src_memory = req && !dirty_owner; // a partition, by construction
assign hot = src_memory + src_owner;
if (hot > 1) multi_src_err <= 1'b1;Write the two as complements so the exclusivity is structural, and keep the checker for the refactor that separates them again.
Assert both "at most one source" and "at least one source". The second catches the opposite bug — a line that matches neither condition and silently produces no fetch at all.
Two accesses to one line interleave and corrupt its state
SAME-LINEassign alloc_ok = alloc && found; // no per-line checkRare wrong data under load, always on hot lines. The two accesses individually look correct in a trace; only their interleaving is wrong. It scales with how many requesters share addresses.
same line : correct allocated=2 conflicts=1 | allow-same-line allocated=3
and it then held two live accesses on one line : okBoth accesses resolve authority against the same directory entry and then act on it independently. The line's state is a single piece of storage, so the second access can read a decision the first is midway through invalidating.
Serialising per line costs almost nothing in throughput — different lines still proceed concurrently — but it is a correctness requirement, not an optimisation.
for (k = 0; k < NENT; k = k + 1)
if (live_q[k] && (line_m[k] == alloc_line)) line_busy = 1'b1;
assign alloc_ok = alloc && found && !line_busy;And assert the property, not the mechanism: no two live entries name the same line.
Direct a test at two requesters hitting one address simultaneously. A random address stream over a large space will essentially never collide, which is why this survives long random regressions.
A returned value is delivered to the wrong requester
ORDER-MATCH// Deliver to the oldest outstanding access.
deliver_id <= oldest_q;
oldest_q <= oldest_q + 1;Every counter balances — issued equals delivered, the table drains, no timeouts — and the data is wrong. It only appears when more than one access is in flight, and it is worse against targets with variable latency.
return tag 2 : correct delivers id=2 | by-order delivers id=0The design assumed returns arrive in issue order. They do not: a line sourced from memory and a line sourced from an owner take different paths with different latencies, so the second request issued can easily be the first answered.
This chapter makes reordering more likely than a plain memory system, because two accesses issued together may be resolved by completely different sources.
assign eff_id = ret_id; // the id decides
assign deliver = ret_valid && live_q[eff_id];
if (ret_data != exp_m[eff_id]) mismatch_err <= 1'b1;Match on identity and keep a per-entry expectation so misdelivery is detectable at all.
Give every outstanding access distinguishable data and deliberately return them out of order. A testbench model that returns in order cannot fail this test, and in-order is the easiest model to write.
A timeout fires and names a healthy access
SHARED-TIMERif (|wait_q) gtimer_q <= gtimer_q + 1;
if (gtimer_q >= LIMIT) begin timeout_valid <= 1; timeout_id <= any_waiting; endTimeout errors name accesses that were issued moments earlier and were progressing normally. Recovery tears down the wrong access; the genuinely hung one keeps its line locked, so the same error recurs against different, arbitrary ids.
id 3 hung : correct timeouts=1 reported id=3 max_wait=11
one shared timer blamed a healthy access : okOne shared deadline knows that something is late and cannot know which. The reported id is whichever the picker happened to select.
Here the consequence compounds: the hung access is holding a line locked against other requesters, so blaming the wrong one leaves the blockage in place and adds a spurious teardown.
for (k = 0; k < NENT; k = k + 1) if (wait_q[k]) age_m[k] <= age_m[k] + 1;
if (wait_q[k] && (age_m[k] >= LIMIT)) begin hit = 1; pick = k; end
if (hit && age_m[pick] < LIMIT) wrong_id_err <= 1;An age per access, and a checker written about the reported id's own age rather than restating the picker.
Test with a genuinely hung access and healthy traffic present at the same time. One outstanding access cannot distinguish the two designs.
A value is delivered that became stale while it was in flight
DECISION-RACE// Resolve the source, then fetch.
decided_memory <= !dirty_now; // nothing locks the line afterwardsExtremely rare stale data on a design that correctly consults the directory. The window is a few cycles per access, so it appears only at high contention and effectively never on the bench. Every source decision in the trace is individually correct.
line dirtied mid-flight : correct blocked=1 | no-lock blocked=0
without the lock a stale value was delivered : okThe decision was right when it was made and wrong by the time it was used. Between resolving "memory is authoritative" and the data landing, a host cache took the line and modified it — so the fetch returned a value that had since been superseded.
Consulting the directory is necessary and not sufficient. The decision has to remain true for as long as it is being acted on.
assign change_blocked = dirty_change && line_locked_q; // defer, do not apply
// line_locked_q is set at the decision and cleared when the data landsLock the line for the lifetime of the decision and defer conflicting changes rather than applying them.
Inject a directory change deliberately during a live decision. It is a one-line stimulus and it cannot be reached by random traffic, because it requires the change to land inside a specific few-cycle window.
Nobody can tell whether the access table is too small
MERGED-REFUSALSif (alloc && !alloc_ok) n_refused_q <= n_refused_q + 1; // one counterTelemetry shows a high refusal rate. The table is enlarged; the refusal rate does not move. The exercise repeats at the next size, and the actual behaviour was correct all along.
4 entries : occupancy=4 peak=4 full-refusals=1 conflict-refusals=1Two unrelated conditions were merged into one counter. A refusal because no entry is free means the table is undersized. A refusal because the line is already busy means correct serialisation and calls for no change at all — it is a property of the workload's address locality.
Merging them produces a number that cannot drive any decision, and the obvious response to it is the wrong one.
if (!found) n_full_q <= n_full_q + 1; // undersized
else n_conflict_q <= n_conflict_q + 1; // hot line — expectedTwo counters, because they have two different fixes. Expose peak_q alongside, so the sizing argument is evidence rather than opinion.
For every refusal path, ask what action the reader of the counter should take. If two paths imply different actions, they need different counters — and if a path implies no action, say so in its name.
20. Design Review
- What decides where a read is sourced from? If it is the address, the design is broken.
- Does a clean cached copy force an owner interaction? It should not.
- Is the source selection a partition? Assert at most one and at least one.
- Is the line locked for the lifetime of the decision? Consulting the directory is not sufficient.
- Do returns match on identity or on order? Order fails as soon as sources differ.
- Is the source of a returned value checked against the decision? Identity alone misses it.
- Is the owner wait bounded, and does the bound name the access?
- Are the two refusal reasons counted separately?
- Which counter would be non-zero if stale data were being delivered right now?
21. How This Appears in Real Engineering
Bring-up. The stale-read failure is the one that costs weeks, because it is silent, load-dependent and looks like an accelerator bug rather than a coherence bug. Teams look at the compute pipeline first.
Performance. The owner-source rate is a workload property the device cannot improve by being faster. Measuring it early changes sizing decisions; discovering it late changes schedules.
DV. The modified-owner directed case and the mid-flight directory change are the two stimuli that random traffic will not produce, and they are the two that find the worst bugs.
Post-silicon. The stale-read flag is a correctness alarm, not telemetry. It belongs in a register that is checked, not merely logged.
Firmware. Placement matters: buffers that a CPU thread actively writes are the ones that generate owner interactions, and moving them is often cheaper than any hardware change.
22. Common Misconceptions
| Belief | Correction |
|---|---|
| A device can read host memory directly | Not if a cache holds a modified copy |
| Cached means memory is stale | Only a modified copy makes memory stale |
| The device chooses the data source | It asks; the coordinator resolves authority |
| Consulting the directory is enough | The decision must hold until the data lands |
| Returns arrive in issue order | Different sources take different paths |
| Matching data is proof of correctness | It may be correct by luck from the wrong source |
| A refusal means the table is too small | It may mean correct same-line serialisation |
| One timeout is enough | It cannot say which access hung |
23. Interview Reasoning
24. Exercises
-
Analysis. A device reports
from_memory=10000,from_owner=0, and intermittent stale data on a workload where a CPU thread writes the same buffers. Nothing else is abnormal. Name the two implementation faults consistent with this and the one directed test that separates them. -
Design. Extend the directory so a line may have several clean sharers and at most one modified owner. State the new invariant, and say which existing check becomes stronger and which becomes unnecessary.
-
RTL task. Add a second decision to
decision_raceso two different lines can have live decisions simultaneously. State what must be replicated per line, and which property stops being true if the lock is made global instead of per line. -
DV task. Write the coverage cross for the device read path, then explain why two of its points can only be reached by directed injection.
-
Debug task. Owner-sourced reads are 4% of accesses but 60% of total read latency. Give your investigation order and the single ratio that decides whether this is a hardware or a data-placement problem.
-
Design review. A colleague proposes skipping the directory lookup for a memory region marked read-only by software. Give the strongest version of that argument, then name what enforces the promise and what happens the first time it is violated.
25. Summary
The address tells you where a line lives; only the directory tells you where its value is.
- Memory is authoritative exactly when no cache holds a modified copy — one expression, and the whole chapter.
- Cached is not stale. Only a modified copy displaces memory; a clean one is identical to it.
- The device asks; the coordinator resolves authority. The device's job is tracking, not deciding.
- One line, one access. Different lines proceed concurrently; the same line serialises, for correctness rather than fairness.
- Returns must match on identity, and the source must be checked against the decision — data that is correct by luck still means the decision path is broken.
- The decision must hold until the data lands. Consulting the directory is necessary and not sufficient; the line is locked for the decision's lifetime.
- Bound the owner wait, and name the access that hung — a shared timer blames a healthy one and leaves the real blockage in place.
- Two refusal reasons, two counters: table-full means undersized, line-busy means correct.
- Measured: five owner-sourced reads cost as much as fifteen memory-sourced ones. The owner-source rate is a workload property, not a device one.
- One counter here is a correctness alarm rather than telemetry: memory selected while a modified owner exists must be zero forever.
Chapter 8.3 turns the direction around: what the device owes when host coherence activity reaches a line the device is holding.
Standards & specifications
- Governing standard
- CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)
Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the CXL curriculum.
