Skip to content
VLSI Mentor

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:

PropertyValue
Detected by hardwareno
Detected by the requesterno
Reproducible on demandno
Correlates withhost 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

QuestionOwned by
What a borrowed line obliges8.1
The generic coherence conversation3.4
Finding the authoritative copy for a device readthis chapter
Answering inbound coherence actions8.3
Living inside a real accelerator8.4
Moving writable ownership8.5
Generic coherency theoryModule 13
End-to-end annotated read and write flowsModule 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

A sequence diagram with four lifelines: device, host coherence logic, a host cache holding a modified copy, and host memory. The device requests a line. The host coherence logic consults its directory and finds a modified owner, so instead of reading memory it obtains the line from the owning host cache. The owner returns the modified value. The host coherence logic returns that value to the device. A second exchange shows a line with no modified owner, where the coherence logic reads memory directly and returns it.Two device reads: one needs an owner, one does notdevicehost coherencehost cache(modified)host memoryrequest line Adirectory: A has amodified ownerobtain the currentvaluemodified valuevalue (from theowner)request line Bdirectory: no owner— read memoryvalue (from memory)

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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];
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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                : ok

Cached 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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                     : ok

The 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // 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;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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       : ok

Two 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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          : ok

The 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 cycles
Nine clock cycles traced from the RTL. The directory shows the line cached and dirty, so memory-authoritative is low throughout. At cycle one the device request is asserted and the source selector chooses the owner rather than memory. From cycle three the access is waiting on the owner. At cycle five the owner responds and the value is delivered in the same cycle. Memory is never selected.authority resolvedauthority resolvedwaiting on the ownerwaiting on the ownerdelivereddeliveredowner chosen — memory never selectedowner chosen — memory neverselectedthe modified value arrives from the cachethe modified value arrivesfrom the cacheclkdev_reqdirty_ownermem_authoritativesrc_memorysrc_ownerwaitingowner_rspdeliverdata----------5c------t0t1t2t3t4t5t6t7t8
Icarus Verilog 13.0. Architectural teaching waveform derived from the simplified RTL model; it is NOT CXL.cache message timing.

Read 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
stale_read_err=0 mismatch=0 wrong_source=0 from_owner=1 from_mem=0

12. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // 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;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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      : ok

The 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

A device read enters an access table which allocates an entry keyed by line and requester. The directory lookup feeds a source selector that chooses between memory and a cache owner. A line lock holds that decision for its lifetime. An owner wait bounds the time spent waiting for a cache owner. A return matcher delivers the value to the requester that asked and checks it came from the decided source. Counters observe the source split and the refusals.device readline, requesteraccess tableone entry per liveaccessdirectory lookupwho holds the valuesource selectormemory or owner —never bothline lockholds the decisionuntil data landsowner waitbounded, and names theaccessreturn matcheridentity and authoritydecisionif owner12

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
T_read ≈ table allocation
       + authority resolution
       + (owner interaction, if any)
       + data path

The measured split, with 15 memory-sourced reads at 6 cycles and 5 owner-sourced reads at 18:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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   : ok

Five 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

PropertyIntent
No stale readdirty_owner |-> !src_memory
Exactly one sourcereq |-> one of memory / owner
One access per lineno two live entries share a line
Return identitydeliver |-> data == expected[id]
Return authoritydeliver |-> from_owner == decided
No unknown retireretire |-> entry live
Decision holdsa locked line defers conflicting changes
Timeout names the late onetimeout |-> age[id] >= LIMIT

Liveness

PropertyAssumption
An access eventually retiresthe owner answers, or the bound expires
A refused same-line access eventually allocatesthe holding access retires

Performance goals

GoalMeasured by
Bounded owner waitmax_wait_q against LIMIT
Table sized for concurrencypeak_q, n_full_q
Sharing pressure visiblen_from_owner_q / n_from_mem_q

16. Mutation Testing

Seventeen mutations. Seventeen killed.

MutationResult
Memory always authoritativekilled
Modified owner with nothing cached not flaggedkilled
Owner never used as a sourcekilled
Memory read despite a dirty ownerkilled
Stale-read check disabledkilled
One-source check disabledkilled
Same-line accesses proceed concurrentlykilled
Both refusal reasons counted as table-fullkilled
Peak occupancy lags by onekilled
Returns matched by arrival orderkilled
Misdelivered data not detectedkilled
Value from the wrong source not detectedkilled
Any waiting access may be declared latekilled
Reported timeout id not checkedkilled
Owner-sourced reads not countedkilled
Conflicting change applied under a live decisionkilled
Stale delivery not detectedkilled

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 constructionsrc_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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  both-sources variant flagged multi_src           : ok
  abuse instance flagged the wrong data source      : ok

Three 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

AreaApproach
AuthorityUncached, cached-clean, cached-dirty — all three asserted
Source selectionBoth outcomes, plus exactly-one and at-least-one
Same-lineRefused; permissive variant shown holding two
CapacityFill, refuse, and separate the two refusal reasons
Return matchingOut-of-order returns with distinguishable data per id
Source authorityReturn from the wrong source, on an abuse instance
Owner waitA hung access with healthy traffic present
Decision raceDirectory changed mid-flight, locked and unlocked
LatencyIndependent 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

CounterDiagnoses
from-memory vs from-ownersharing pressure in the workload
max_wait_qwhether the owner-wait bound is marginal
timeout count and reported idwhich path hangs
full-refusals vs conflict-refusalsundersized table vs hot line
peak_qtable sizing evidence
deferred changes (n_blocked_q)contention during live decisions
stale-read flaga 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

1

A device intermittently computes on a previous version of the data

STALE-MEMORY
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Device needs a line — read it.
assign src_memory = req;
Symptom

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  dirty line  : correct owner=1 memory=0 | trust-memory memory=1
Root Cause

The 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 fires

Resolve authority before choosing a source, and add the checker — because in silicon the failure has no other symptom.

Prevention

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.

2

Two data sources are fetched for one access

MULTI-SOURCE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign src_owner  = req && dirty_owner;
assign src_memory = req;                 // not mutually exclusive
Symptom

Duplicate 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  both-sources variant flagged multi_src           : ok
Root Cause

The 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Prevention

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.

3

Two accesses to one line interleave and corrupt its state

SAME-LINE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign alloc_ok = alloc && found;    // no per-line check
Symptom

Rare 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  same line   : correct allocated=2 conflicts=1 | allow-same-line allocated=3
  and it then held two live accesses on one line    : ok
Root Cause

Both 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Prevention

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.

4

A returned value is delivered to the wrong requester

ORDER-MATCH
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Deliver to the oldest outstanding access.
deliver_id <= oldest_q;
oldest_q   <= oldest_q + 1;
Symptom

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  return tag 2 : correct delivers id=2 | by-order delivers id=0
Root Cause

The 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Prevention

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.

5

A timeout fires and names a healthy access

SHARED-TIMER
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (|wait_q) gtimer_q <= gtimer_q + 1;
if (gtimer_q >= LIMIT) begin timeout_valid <= 1; timeout_id <= any_waiting; end
Symptom

Timeout 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  id 3 hung   : correct timeouts=1 reported id=3 max_wait=11
  one shared timer blamed a healthy access          : ok
Root Cause

One 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Prevention

Test with a genuinely hung access and healthy traffic present at the same time. One outstanding access cannot distinguish the two designs.

6

A value is delivered that became stale while it was in flight

DECISION-RACE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Resolve the source, then fetch.
decided_memory <= !dirty_now;      // nothing locks the line afterwards
Symptom

Extremely 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  line dirtied mid-flight : correct blocked=1 | no-lock blocked=0
  without the lock a stale value was delivered      : ok
Root Cause

The 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 lands

Lock the line for the lifetime of the decision and defer conflicting changes rather than applying them.

Prevention

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.

7

Nobody can tell whether the access table is too small

MERGED-REFUSALS
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (alloc && !alloc_ok) n_refused_q <= n_refused_q + 1;   // one counter
Symptom

Telemetry 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  4 entries   : occupancy=4 peak=4 full-refusals=1 conflict-refusals=1
Root Cause

Two 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.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (!found) n_full_q     <= n_full_q     + 1;   // undersized
else        n_conflict_q <= n_conflict_q + 1;   // hot line — expected

Two counters, because they have two different fixes. Expose peak_q alongside, so the sizing argument is evidence rather than opinion.

Prevention

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

  1. What decides where a read is sourced from? If it is the address, the design is broken.
  2. Does a clean cached copy force an owner interaction? It should not.
  3. Is the source selection a partition? Assert at most one and at least one.
  4. Is the line locked for the lifetime of the decision? Consulting the directory is not sufficient.
  5. Do returns match on identity or on order? Order fails as soon as sources differ.
  6. Is the source of a returned value checked against the decision? Identity alone misses it.
  7. Is the owner wait bounded, and does the bound name the access?
  8. Are the two refusal reasons counted separately?
  9. 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

BeliefCorrection
A device can read host memory directlyNot if a cache holds a modified copy
Cached means memory is staleOnly a modified copy makes memory stale
The device chooses the data sourceIt asks; the coordinator resolves authority
Consulting the directory is enoughThe decision must hold until the data lands
Returns arrive in issue orderDifferent sources take different paths
Matching data is proof of correctnessIt may be correct by luck from the wrong source
A refusal means the table is too smallIt may mean correct same-line serialisation
One timeout is enoughIt cannot say which access hung

23. Interview Reasoning

24. Exercises

  1. 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.

  2. 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.

  3. RTL task. Add a second decision to decision_race so 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.

  4. 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.

  5. 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.

  6. 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.