Skip to content
VLSI Mentor

CXL · Module 9

CXL.mem Write Flows

The write path end to end: two completions rather than one, same-address visibility ordering, partial writes that must not clobber unnamed bytes, a buffer that may stall but never lose, newest-wins forwarding, and failures that must reach the writer. Seven RTL models simulated, twenty-one mutations, twenty-one killed.

Chapter 9.4 walked the read path: one question, one answer. The write path reuses that machinery and adds the thing 9.3 flagged as harder — a read is finished when it has a value, and a write is not finished until someone else can see it.

1. The Engineering Problem — A Write Has Two Endings

Ask when a read is done and there is one answer: when the value arrives. Ask when a write is done and there are two, and they are separated by real time:

EndingMeaningWho cares
acceptedthe device owns it and will not lose itthe writer, for flow control
visibleanother observer reading that address sees iteveryone, for correctness

Reporting the first as though it were the second is not an optimisation. It is the bug that breaks every producer/consumer handshake in software, and it is invisible to any test that writes and then reads back from the same agent.

Four more things follow from the gap between those two endings, and each is a section below: writes to one address must become visible in order, a sub-line write must read before it writes, an accepted write must survive until it is visible, and a read arriving in the gap must see the pending value.

2. The One-Sentence Model

A write is not done until someone else can see it. Acceptance is a flow-control event and visibility is a correctness event; between them the write lives in a buffer that must never lose it, must not let a later write to the same address overtake it, must not destroy bytes it never named, and must be searched by any read of that address.

Call it the second ending. Everything in this chapter lives in the gap between the two.

3. What This Chapter Owns

QuestionOwned by
The device's window contract9.1
The host's path and tag pool9.2
Ordering, atomicity, visibility as guarantees9.3
The read path9.4
The write path end to endthis chapter
Latency/throughput cost of CXL memory9.6
Latency anatomy and modellingModule 18

4. The Write, End to End

A sequence diagram with four lifelines: a writer, the device front end, the device write buffer, and the media. The writer sends a write. The front end accepts it into the write buffer, which is a flow-control event only, and no completion is sent. A reader then reads the same address and the front end forwards the buffered value rather than reading stale media. Later the buffer drains to media and the write becomes visible, and only then is a completion sent to the writer. A note marks that reporting completion at acceptance would let the writer proceed before the value was observable.Accepted early, visible late, completed only at visibilitywriterdevice front endwrite buffermediawrite X = newaccepted — flowcontrol onlya read of X nowforwards from thebufferdrainstored — now visiblecompletion — onlynow

Architectural. The arrows are obligations, not named messages.

The distance between arrow two and arrow six is the whole chapter. Everything that can go wrong on a write path goes wrong in that gap, and a design that closes it by reporting completion at acceptance has not removed the gap — it has hidden it from the only party that could have accounted for it.

5. Teaching-model boundary

6. RTL 1 — Two Completions, and Only One Counts

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign cmp_valid = REPORT_ON_ACCEPT ? wr_accept : wr_visible;
  ...
      // Reporting completion on a cycle that is not the visibility cycle.
      if (cmp_valid && !wr_visible) early_completion_err <= 1'b1;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP1: a write completes when it is VISIBLE, not when accepted ===
  at acceptance : correct cmp_valid=0 | report-on-accept cmp_valid=1
  acceptance does not complete the write             : ok
  the broken variant reported it immediately         : ok
  in flight : accepted=1 visible=0 inflight=1
  at visibility : cmp_valid=1 id=2
  visibility completes it, with the right id        : ok
  and the write left the in-flight set              : ok
  report-on-accept variant flagged early completion : ok

Acceptance is a flow-control event. It tells the writer the device has taken ownership, which is what a credit-returning scheme needs. It says nothing about observability, and using it as a completion converts a correctness guarantee into a throughput optimisation the writer did not ask for.

The visible-without-acceptance check initially read:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (!acc_mask_q[vis_id]) visible_without_accept_err <= 1'b1;

The baseline failed on correct stimulus: a write accepted and made visible in the same cycle is entirely legal — a device with a fast path can do exactly that — and the acceptance mask is still being set by that edge. The corrected condition admits it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // Becoming visible without ever being accepted is impossible. A write
        // accepted and made visible in the SAME cycle is legal, so the mask --
        // which this edge is still setting -- cannot be the whole condition.
        if (!acc_mask_q[vis_id] && !(wr_accept && (wr_id == vis_id)))
          visible_without_accept_err <= 1'b1;

Same state-versus-transition shape as 9.3's premature-visibility checker, in the opposite direction: there the check was too narrow to fire, here it was too broad and fired on correct behaviour.

7. Waveform — Accepted Early, Visible Late, Out of Order

Two writes accepted, made visible in the opposite order

9 cycles
Nine clock cycles traced from the RTL. Write A with id two is accepted at cycle one and write B with id three at cycle two. Through cycles three and four both are in flight with the accepted count at two and the visible count at zero, and no completion has been sent. At cycle five write B becomes visible and a completion is sent carrying id three. At cycle six write A becomes visible and a completion carries id two. The in-flight count falls to zero at cycle seven.two writes acceptedtwo writes acceptedin flight — no completion sentin flight — nocompletion sentvisible, B before Avisible, B before Aaccepted=2, visible=0 — nothing completedaccepted=2, visible=0 —nothing completedB visible first: completion carries id 3B visible first: completioncarries id 3then A: id 2then A: id 2clkacceptaccept_id023333333visiblevis_id000003222completioncmp_id000003222accepted001222222visible_cnt000000122inflight001222100t0t1t2t3t4t5t6t7t8

Two things to read.

Between cycles 3 and 5, accepted is 2 and visible_cnt is 0. Two writes the device owns, neither observable, no completion sent. That interval is the accepted-but-invisible window 9.3 measured, drawn.

The completions come back in the opposite order to acceptance — id 3 then id 2. That is legal because they are different addresses; §8 is about the case where it is not.

8. RTL 2 — Same-Address Writes Become Visible in Order

For reads, ordering decides which value is returned. For writes it decides which value survives, and getting it wrong is permanent.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A write may become visible only if every earlier write to this address
  // already has. Sequence numbers make that checkable.
  assign in_order = (vis_seq == expect_seq_q);
  assign allowed  = vis_valid && (NO_WAW_ORDER || in_order);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP2: same-address writes become visible in order ===
  the first write in sequence proceeds              : ok
  seq 2 before seq 1 : correct allowed=0 | no-WAW allowed=1
  a later write cannot overtake an earlier one      : ok
  the no-WAW variant let it overtake                 : ok
  the correct next write proceeds                   : ok
  no-WAW variant flagged a write-after-write violation: ok

The asymmetry with reads is worth dwelling on. A read served out of order returns a stale value once — bad, and recoverable by reading again. A write made visible out of order means an older value lands after a newer one and the newer value is gone. No subsequent read recovers it.

That is why the ordering here is expressed as a sequence check rather than a simple busy flag: the device must know not just that an earlier write exists but that it has already become visible.

9. RTL 3 — A Partial Write Must Read Before It Writes

Media writes whole lines. A sub-line write is therefore a read-modify-write inside the device, and skipping the read is the classic corruption.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A full-line write needs no merge read; a partial one does.
  assign needs_merge_read = wr_valid && (byte_en != 8'hFF);
  assign can_write = wr_valid && (NO_MERGE_READ || !needs_merge_read || old_valid);
  assign merged_line = (wr_data & mask) | (old_line & ~mask);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP3: a partial write must not clobber unnamed bytes ===
  full-line write : needs_merge_read=0 can_write=1
  a full-line write needs no merge read             : ok
  partial, no old data : needs_merge_read=1 correct can_write=0 | no-merge can_write=1
  it waits for the old line                         : ok
  the no-merge variant wrote anyway                 : ok
  old data available : can_write=1 merged=0x11111111AAAAAAAA
  named bytes written, unnamed bytes preserved     : ok
  no-merge variant flagged a clobber                : ok

Read the merged value: 0x11111111AAAAAAAA. The low four bytes came from the write (AAAAAAAA), the high four from the old line (11111111). The bytes the request never named survived, which is the entire requirement.

The broken variant writes wr_data straight through, so the unnamed bytes become whatever the merge buffer held — usually zeros, sometimes another request's data. It corrupts memory the writer never touched.

This is also a performance fact worth stating plainly. A partial write costs a media read and a media write, so on a write path dominated by media it is roughly twice a full-line write. Software that writes whole lines is not being fastidious; it is halving its media traffic.

10. RTL 4 — Stall, Never Lose

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP4: an accepted write is never lost ===
  12 pushes into depth 8 : correct level=8 pushed=8 stalled=4
  four were stalled, none lost                      : ok
  peak buffer level was exactly 8                   : ok
  the drop-on-full variant lost accepted writes     : ok
  and the buffer drained completely                 : ok
  everything pushed was drained exactly once        : ok

Same contract as 9.1's inbound path, with a harsher consequence. A dropped read hangs a requester — bad, and detectable, because someone is waiting. A dropped write produces no waiter at all: the data is simply gone, and the next read of that address returns the old value with no error anywhere.

n_drain_q == n_push_q is the conservation law that makes it checkable, and it is the one property worth asserting continuously rather than at the end of a test.

11. RTL 5 — A Read in the Gap Must See the Newest Write

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    if (NEWEST_WINS) begin
      // Scan so the LAST match wins -- the newest write to this address.
      for (k = 0; k < DEPTH; k = k + 1)
        if (vld_q[k] && (addr_m[k] == rd_addr)) begin sel = k[3:0]; found = 1'b1; end
    end
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP5: a read must see the NEWEST buffered write ===
  two writes to one address : correct data=0x2222 | oldest-wins data=0x1111
  the NEWEST buffered write was forwarded           : ok
  the oldest-wins variant returned the stale value  : ok
  an unbuffered address falls through to media      : ok

9.3 required forwarding; this is where it lives. The write buffer is precisely the reason a write can be accepted and not yet in the media, so the buffer is what a read must search.

The subtle part is which match wins. With several writes to one address buffered, the newest is the correct answer — and the difference between a scan that keeps the last match and one that stops at the first is one loop direction. Both are one line, both look right, and one silently returns stale data.

12. RTL 6 — A Failed Write Must Reach the Writer

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign cmp_is_error = drain && media_fail && !SILENT_FAIL;
  ...
      // The media could not store it and the writer was told it succeeded.
      if (media_fail && !cmp_is_error) silent_loss_err <= 1'b1;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP6: a failed write must be reported ===
  media failed : correct is_error=1 | silent variant is_error=0
  the failed write was reported as an error         : ok
  the silent variant reported success               : ok
  silent-fail variant flagged a silent data loss    : ok

A failed write is harder to notice than a failed read, and the asymmetry is worth stating. A failed read has nothing to return, so a device that does not report it must fabricate data — at least there is a value to be suspicious of. A failed write has nothing to return either way: success and failure look identical on the wire, so a device that omits the error is indistinguishable from one that worked.

The data is gone and every party believes it was stored. This is the single most dangerous failure in the write path.

13. RTL 7 — Write Accounting

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP7: write accounting, against an oracle ===
  accepted=36 buffered=36 visible=32 failed=2 max_uncommitted=4
  partial=9 merge-reads=9 forwards=6
  accept/visible matched an independent oracle      : ok
  nothing became visible that was not accepted      : ok
  peak uncommitted was exactly 4                    : ok
  every partial write took a merge read             : ok

partial == merge-reads is the invariant that catches §9's defect in telemetry rather than in data: every partial write must have taken a merge read, so a mismatch means some partial write went straight to media.

max_uncommitted is the write path's most important number. It is the size of the accepted-but-invisible window — how many writes the device owes visibility for at the worst moment — and it bounds both how wrong a report-on-accept design would be and how long a fence must wait.

A write is accepted into an order gate that holds it until every earlier write to the same address is visible. Full-line writes go straight to the write buffer; partial writes first take a merge read of the old line and combine it with the named bytes. The buffer stalls when full and never drops, and it is searched by every read so the newest buffered write is forwarded. Draining to media makes the write visible, and only then is a completion sent, carrying an error if the media failed. Counters observe acceptance, visibility, partial writes and merge reads.write acceptedflow control onlyorder gaterisks: newer valuelostmerge readrisks: bytes clobberedwrite bufferrisks: silent lossread forwardrisks: stale valuemedia — visiblerisks: silent failurecompletionsent only atvisibilitypartialif full linenewestdrainvisible12

Read it as a list of losses rather than a datapath. Every stage between acceptance and completion can destroy data, and each does so without a waiter: the order gate loses the newer value, the merge read loses unnamed bytes, the buffer loses the write outright, the forward path returns a value already superseded, and the media loses it while reporting success. That is why §18 lists four alarms — the write path earns them.

14. Quantitative Reasoning — What the Write Path Costs

Illustrative, from the measured runs.

Partial writes cost double

A full-line write is one media operation. A partial write is a media read plus a media write:

Write mixMedia operations per 100 writesRelative media load
all full-line1001.00×
measured mix (25% partial)1251.25×
all partial2002.00×

At 9.1's 100 ns media latency and 4-deep media concurrency, moving from all-partial to all-full-line halves the media occupancy of the write stream. That is a software alignment decision with a hardware-scale payoff.

The accepted-but-invisible window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  accepted=36 visible=32 max_uncommitted=4

Four writes owed visibility at the peak. Two consequences:

A fence issued at that moment waits for all four — at 100 ns media latency and 4-deep concurrency, roughly one media round trip.

A report-on-accept design would be wrong by up to four writes, which is exactly how many producer/consumer handshakes could observe a flag before its data.

Buffer sizing

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  12 pushes into depth 8 : pushed=8 stalled=4

The buffer must cover the acceptance rate times the drain latency — the same bandwidth-delay reasoning as every other resource in this module. Undersizing it converts write acceptance into a stall the writer sees, which on a store-heavy workload is a core stall.

15. Assertions

Icarus Verilog 13.0 does not support concurrent SVA here, so every property is synthesisable checker logic verified in simulation.

Safety

PropertyIntent
Completion at visibilitycmp_valid |-> wr_visible
No visibility without acceptancevisible |-> accepted (or accepted this cycle)
Same-address write ordervisible |-> all earlier same-address writes visible
No clobberpartial write |-> old line present
No lost writepush && ready |-> stored
Newest forwarda read returns the newest buffered write to that address
Failure reportedmedia_fail |-> cmp_is_error
Conservationvisible <= accepted, drained == pushed

Liveness

PropertyAssumption it needs
An accepted write eventually becomes visiblethe buffer drains
A stalled write is eventually acceptedthe buffer has room
A blocked visibility eventually proceedsthe earlier same-address write becomes visible

The first is the one that matters most: an accepted write that never becomes visible is silent data loss with no waiter — nothing anywhere is blocked on it, so nothing reports it. That is why buffer drain conservation is a continuously-checked safety property and not merely an end-of-test tally.

Performance goals

GoalMeasured by
Partial writes rarepartial vs full counts
Buffer sized to drain latencypeak level, stall count
Forwarding effectiveforward hits
Visibility window boundedmax_uncommitted

16. Mutation Testing

Twenty-one mutations. Twenty-one killed.

MutationResult
Write completes at acceptancekilled
Early completion not flaggedkilled
Visibility without acceptance not flaggedkilled
Write in-flight counted with two assignmentskilled
Same-address writes may reorderkilled
Write-after-write violation not flaggedkilled
Blocked writes not countedkilled
Partial write proceeds without the old linekilled
The merge discards unnamed byteskilled
Clobbering unnamed bytes not flaggedkilled
Buffer accepts when fullkilled
Stalled pushes not countedkilled
Buffer peak lags by onekilled
Oldest buffered write forwardedkilled
Write buffer never searched on a readkilled
Failed write reported as successkilled
Silent write loss not flaggedkilled
Acceptances counted as visiblekilled
Peak uncommitted lags by onekilled
Merge reads not countedkilled
Visible conservation law disabledkilled

The first run scored 18 of 21, and the escapes were the shapes this batch has established: visibility-without-acceptance and the conservation law both needed instances reserved for illegal stimulus, and the in-flight counter needed accept and visible in the same cycle — the ninth appearance of that defect family in this track.

Two repairs came from the baseline rather than from mutation testing, and both were mine.

The visible-without-acceptance checker fired on correct behaviour. A write accepted and made visible in the same cycle is legal, and the acceptance mask is still being set by that edge. The checker was too broad, which is the mirror image of 9.3's premature-visibility checker being too narrow.

My testbench accepted the same id four times and made it visible four times. An id may be accepted once and become visible once; the stimulus was illegal and the design was right to complain. Distinct ids fixed it. Across this batch the baseline found four defects the mutation suite could not, because mutation testing verifies the checkers and the baseline verifies the design.

17. Verification Plan

AreaApproach
CompletionAcceptance and visibility driven separately; report-on-accept variant
ConcurrencyAccept and visible in the same cycle, with distinct ids
OrderingSequence skipped and then supplied; no-WAW variant
PartialFull line, partial without old data, partial with old data; merged value asserted
BufferOverfill, stall count, complete drain, push/drain conservation
ForwardingTwo writes to one address; newest asserted, oldest-wins variant
FailureGood and failed drain; silent variant compared
CountersIndependent oracle; partial == merge_reads invariant

The coverage cross is write size × buffer state × same-address relationship: full / partial, crossed with room / full / draining, crossed with unique / repeated address. The repeated-address-with-buffer-full point is where ordering, forwarding and back-pressure interact, and it must be directed.

18. Silicon Observability

CounterDiagnoses
accepted vs visiblethe size of the observability gap
max_uncommittedthe worst-case gap, and what a fence would wait for
buffer peak and stallswhether the buffer is sized for the drain rate
partial vs full writesmedia load from sub-line traffic
merge readsconfirms every partial took its read
forward hitsreads served from the buffer
blocked visibilitiessame-address write contention
write failuresmedia health
lost_writea correctness alarm — must be zero forever
silent_lossa correctness alarm — must be zero forever
waw_violationa correctness alarm — must be zero forever
clobbera correctness alarm — must be zero forever

Four alarms — more than any other chapter in this module, and that is the honest signature of the write path: it has more ways to lose data silently than the read path has to return it wrongly. A lost write, a silently failed write, an out-of-order visibility and a clobbered byte all destroy data with no waiter and no error.

The most useful tuning ratio is partial writes over total writes, because it converts directly into media load, and it is usually fixable in software.

19. Debug Lab

1

A consumer sees the flag set and the data stale

COMPLETE-ON-ACCEPT
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign cmp_valid = wr_accept;      // completion at acceptance
Symptom

A producer/consumer handshake fails intermittently. The consumer reads the flag as set and the guarded data as stale. Inserting any delay in the consumer hides it completely, which sends the investigation to the consumer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  at acceptance : correct cmp_valid=0 | report-on-accept cmp_valid=1
  report-on-accept variant flagged early completion : ok
Root Cause

Acceptance was reported as completion. The producer's data write was owned by the device but not observable, so the producer proceeded to the flag and the flag became visible first.

Acceptance is a flow-control event — useful for returning credit — and using it as a correctness signal converts a guarantee software depends on into a throughput optimisation it never asked for.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign cmp_valid = wr_visible;
if (cmp_valid && !wr_visible) early_completion_err <= 1'b1;
Prevention

Probe the address between acceptance and visibility and assert the old value is returned. A test that probes after visibility passes on both designs.

2

An older value overwrites a newer one, permanently

WAW-REORDER
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign allowed = vis_valid;      // any buffered write may drain
Symptom

A location holds a value that was written and then overwritten — the earlier value survives. It appears only when two writes to one address are buffered together, so it scales with write locality, and no read ever recovers the lost value.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  seq 2 before seq 1 : correct allowed=0 | no-WAW allowed=1
  no-WAW variant flagged a write-after-write violation: ok
Root Cause

Two writes to one address became visible out of acceptance order, so the older one landed last and won.

The asymmetry with reads is what makes this severe: an out-of-order read returns a stale value once and a re-read fixes it, while an out-of-order write destroys the newer value permanently.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign in_order = (vis_seq == expect_seq_q);
assign allowed  = vis_valid && in_order;
if (allowed && !in_order) waw_violation_err <= 1'b1;

A sequence per address, so the device knows not just that an earlier write exists but that it has already become visible.

Prevention

Buffer two writes to one address and drain them out of order deliberately. A random address stream essentially never buffers two writes to the same line.

3

A write corrupts bytes the writer never touched

NO-MERGE-READ
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
media_line <= wr_data;      // partial write straight to media
Symptom

Data adjacent to a written field is destroyed — usually zeroed, sometimes replaced with another request's data. It happens only for sub-line writes, so structures written field-by-field are corrupted while whole-line writes are fine.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  old data available : can_write=1 merged=0x11111111AAAAAAAA
  named bytes written, unnamed bytes preserved     : ok
Root Cause

Media writes whole lines, so a sub-line write must first read the line and merge. Writing wr_data straight through replaces the unnamed bytes with whatever the buffer held.

The corruption is adjacent to the intended write, which makes it look like a pointer or bounds bug in software rather than a memory-device defect.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign needs_merge_read = wr_valid && (byte_en != 8'hFF);
assign can_write   = wr_valid && (!needs_merge_read || old_valid);
assign merged_line = (wr_data & mask) | (old_line & ~mask);

Read, merge, then write — and assert that a partial write never proceeds without the old line.

Prevention

Write a partial line over a known non-zero background and assert the untouched bytes are unchanged. A test over a zeroed background cannot see the bug.

4

A write disappears and nothing is waiting for it

DROPPED-WRITE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign ready = 1'b1;                    // always accept
if (push && !full) store_write();       // ...but only store when there is room
Symptom

Data written is simply absent later. There is no hang, no error and no waiter — the next read of that address returns the old value as though the write never happened. It correlates with write bursts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  12 pushes into depth 8 : pushed=8 stalled=4
  the drop-on-full variant lost accepted writes     : ok
Root Cause

ready and "actually stored" disagreed. The device claimed ownership of a write and discarded it.

This is worse than a dropped read, and the reason is structural: a dropped read leaves a requester waiting, which something eventually notices. A dropped write leaves nobody waiting at all, so the loss is discovered only when someone reads the address and gets an old value they cannot explain.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign ready   = !full;
assign do_push = push && ready;
// and assert continuously
if (n_drain_q != n_push_q) /* investigate */ ;
Prevention

Assert push/drain conservation continuously, not at end of test. A transient loss that self-corrects in the totals is exactly what an end-of-test tally misses.

5

A read returns a value that was already overwritten

OLDEST-FORWARD
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for (k = DEPTH-1; k >= 0; k = k - 1)
  if (vld[k] && addr[k] == rd_addr) begin sel = k; found = 1; end   // first match
Symptom

A read returns a stale value even though the newer write was accepted and acknowledged. It requires two buffered writes to one address, so it appears only under write bursts to hot locations.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  two writes to one address : correct data=0x2222 | oldest-wins data=0x1111
  the oldest-wins variant returned the stale value  : ok
Root Cause

The buffer scan kept the first match rather than the newest. With several writes to one address buffered, the first found is the oldest, so the read is answered with a value that has already been superseded.

The two loop directions differ by one character and both look correct in review.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for (k = 0; k < DEPTH; k = k + 1)
  if (vld[k] && addr[k] == rd_addr) begin sel = k; found = 1; end   // last match wins

Or track the newest explicitly rather than relying on scan direction, which is what a real design with a wrapping pointer should do.

Prevention

Buffer two writes to one address with different data and assert the newer is forwarded. A single buffered write cannot distinguish the implementations.

6

Data is lost and every party believes it was stored

SILENT-WRITE-FAIL
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign cmp_is_error = 1'b0;      // completion never carries a failure
Symptom

Writes to an ageing region of media are silently lost. Reads return old values. No error, no machine check, no counter. Every diagnostic reports the system healthy, and the corruption is discovered by application-level checksums if at all.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  media failed : correct is_error=1 | silent variant is_error=0
  silent-fail variant flagged a silent data loss    : ok
Root Cause

The media could not store the write and the completion did not say so.

A failed write is harder to notice than a failed read. A failed read has nothing to return, so a device that hides it must fabricate a value — there is at least something to be suspicious of. A failed write has nothing to return either way, so success and failure are indistinguishable on the wire.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign cmp_is_error = drain && media_fail;
if (media_fail && !cmp_is_error) silent_loss_err <= 1'b1;
Prevention

Inject media write failures and assert the completion is classified as an error, not merely that a completion arrived. Checking "a completion came back" passes on the broken design.

7

The in-flight count drifts and a fence passes early

TWO-ASSIGN-INFLIGHT
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (wr_accept)  inflight_q <= inflight_q + 1;
if (wr_visible) inflight_q <= inflight_q - 1;
Symptom

The reported in-flight write count falls under steady traffic until it reads near zero while writes are demonstrably outstanding. Anything gated on it — a fence, a drain check, a power-down handshake — then acts too early. Burst-then-drain tests pass.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  3 cycles of accept+visible together : inflight=4
  the case-based counter held steady                : ok
Root Cause

Two non-blocking assignments to one variable: the second wins, so a cycle with an acceptance and a visibility decrements instead of holding.

Ninth appearance of this defect family in this track, and the consequence here is the same as 9.3's: a counter defect becoming a correctness violation, because something waits on the count reaching zero.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
case ({wr_accept, wr_visible})
  2'b10: inflight_q <= inflight_q + 4'd1;
  2'b01: inflight_q <= inflight_q - 4'd1;
  default: ;                                  // both or neither: hold
endcase
Prevention

Test the cross of the two events with distinct ids — a write accepted and made visible in the same cycle is legal and must be exercised.

8

A correct device fails its own regression

CHECKER-TOO-BROAD
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (!acc_mask_q[vis_id]) visible_without_accept_err <= 1'b1;
Symptom

The regression fails on a design that is behaving correctly. The error fires exactly when a write is accepted and made visible in the same cycle — a legal fast-path completion.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  3 cycles of accept+visible together : inflight=4
  the case-based counter held steady                : ok
Root Cause

The acceptance mask is set by the same edge on which visibility is being evaluated, so on a same-cycle completion the mask is still zero and the checker sees an impossible state that is in fact legal.

It is the mirror of 9.3's premature-visibility checker: that one was too narrow to fire on a real violation, this one is too broad and fires on correct behaviour. Both come from reading state that the transition under test is still creating.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (!acc_mask_q[vis_id] && !(wr_accept && (wr_id == vis_id)))
  visible_without_accept_err <= 1'b1;

Admit the same-cycle case explicitly.

Prevention

For every checker, ask both questions: can it fire when it should, and can it fire when it should not? Mutation testing answers the first; only running legal corner-case stimulus answers the second.

20. Design Review

  1. Which event does a write completion report — acceptance or visibility?
  2. Can two writes to one address become visible out of order?
  3. Does a partial write read the line first, and what asserts it?
  4. Can an accepted write be dropped, and what conservation law would catch it?
  5. Does a read search the write buffer, and does the newest match win?
  6. Does a media write failure reach the writer?
  7. Is the in-flight count correct when an acceptance and a visibility coincide?
  8. What is max_uncommitted under real traffic, and what waits on it?
  9. What fraction of writes are partial, and what does that cost in media?
  10. What happens on reset with accepted-but-invisible writes?

21. How This Appears in Real Engineering

Architecture. The partial-write fraction is a software-visible lever with a hardware-scale payoff — up to 2× media load on the write stream.

RTL. The write buffer is a small structure carrying four of this chapter's alarms: lost writes, out-of-order visibility, clobbered bytes and stale forwards.

DV. Two writes to one address, a partial write over a non-zero background, and a same-cycle accept-and-visible are all directed. None arises naturally.

Post-silicon. Four correctness alarms is more than any other chapter here, and each catches a failure whose only symptom is wrong data much later.

Software. Whole-line writes halve media traffic and avoid the merge-read path entirely, which makes alignment a performance decision rather than a stylistic one.

22. Common Misconceptions

BeliefCorrection
A write is done when acceptedIt is done when others can see it
Write ordering is like read orderingAn out-of-order write destroys data permanently
A partial write is cheaper than a full oneIt costs a read and a write
A dropped write is like a dropped readNobody is waiting, so nothing reports it
Forwarding just needs an address matchThe newest match must win
A write failure is obviousSuccess and failure look identical on the wire
Counter bugs are statistics bugsHere one makes a fence pass early
A checker that never fires is safeIt may also fire when it should not

23. Interview Reasoning

24. Exercises

  1. Calculation. A workload issues 10,000 writes, 60% of them sub-line. Compute the media operations with and without a merge read, and the media time at 100 ns per operation. Then state the saving from aligning the workload to full lines.

  2. Analysis. A device reports accepted=10^6, visible=10^6, max_uncommitted=0, and a producer/consumer handshake that fails intermittently. Explain why those three counters are suspicious together and name the defect.

  3. RTL task. Extend write_order to track sequences per address rather than globally. State the storage cost and the new failure mode a shared sequence counter would have had on a multi-address workload.

  4. Assertion task. Write the property that catches a partial write proceeding without its merge read, and explain why it must be expressed over the byte enables rather than over the write's size field.

  5. Debug task. Data adjacent to a written field is being zeroed. Give your investigation order and the single directed test that distinguishes a device merge bug from a software bounds bug.

  6. Design review. A colleague proposes reporting write completion at acceptance to improve throughput, noting the device never loses an accepted write. Give the strongest version of that argument, then name exactly what it breaks and what would have to be true system-wide for it to be safe.

25. Summary

A write is not done until someone else can see it.

  • A write has two endings: accepted (flow control) and visible (correctness). Reporting the first as the second breaks every producer/consumer handshake.
  • Same-address writes must become visible in acceptance order. An out-of-order write destroys the newer value permanently, unlike an out-of-order read.
  • A partial write must read before it writes, or it corrupts bytes the writer never named — and it costs two media operations, up to 2× media load.
  • Stall, never lose. A dropped write has no waiter, so nothing reports it; push-equals-drain is the law that catches it.
  • A read in the gap must see the newest buffered write. The scan direction that returns the oldest is one character away and silently stale.
  • A failed write must reach the writer, because success and failure are otherwise indistinguishable on the wire.
  • Measured: peak accepted-but-invisible was exactly 4 — the fence cost and the bound on how wrong a report-on-accept design would be.
  • Four correctness alarms — lost write, silent failure, write-after-write violation, clobber — more than any other chapter in this module.
  • The two-assignment counter defect appeared for the ninth time, and again turned a counter bug into a correctness violation.
  • Verification lesson: the baseline found two defects mutation testing could not — a checker that fired on legal same-cycle completion, and a testbench driving illegal stimulus the design was right to reject. Mutation testing verifies the checkers; the baseline verifies the design.

That completes the read and write flows. Chapter 9.6 — CXL.mem Performance Implications turns these obligations into their latency and throughput cost, and it is not part of this batch.

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.