Skip to content
VLSI Mentor

CXL · Module 8

Coherent Accelerators

What changes inside an accelerator when its cache participates in host coherence: miss-status tracking, request merging, lane arbitration, outstanding-limited bandwidth, and stall counters that blame the right thing. Seven RTL models simulated, nineteen mutations, nineteen killed.

The previous three chapters built the coherence machinery. This one puts it underneath a compute pipeline and asks the question an architect actually has to answer: what does it cost, and where does the time go?

1. The Engineering Problem — Two Clients, One Cache

An accelerator's cache exists to serve the accelerator. Once it is coherent, that stops being true.

There is now a second client — the coherence path from 8.3 — and it differs from the first in every way that matters:

AcceleratorCoherence
Arrival rate set byyour workloadthe host's activity
Can be deferredyes, it just stallsonly briefly
Backs off under loadyesno
You control its timingyesno

The second client does not run your workload and does not care about your throughput. Every structure in this chapter exists because of that asymmetry: the cache port has to be shared, misses have to be tracked so the pipeline can continue past them, and the stall counters have to be able to say which client caused a given idle cycle.

2. The One-Sentence Model

Coherence is a second client of your cache, and it does not run your workload. So the accelerator must be built to make progress while another agent it does not control takes the cache port, answers on its own schedule, and occasionally takes a line away — which means outstanding-miss tracking, request merging, and stall accounting that can tell the two clients apart.

Call it the second client. Everything below follows from admitting it exists.

3. What This Chapter Owns

QuestionOwned by
What a borrowed line obliges8.1
Finding the authoritative copy8.2
Answering inbound coherence actions8.3
The accelerator built on top of all threethis chapter
Moving writable ownership8.5
Address translationnot this module
Generic coherency theoryModule 13
CXL performance analysis in depthModule 18

4. The Accelerator, End to End

A command processor feeds compute lanes, which issue loads and stores to a load-store unit. The load-store unit arbitrates among lanes and presents requests to the device cache. The cache is also reached by the device coherence agent, which serves inbound host coherence activity, so the two contend for one port. Misses from the cache enter a miss-status tracker, which issues external fetches over CXL.cache to the host and later wakes the waiting lanes.command processorwork dispatchcompute lanesissue loads andstoresload/store unitlane arbitrationdevice cacheone port, two clientscoherence agentthe second clientmiss trackerMSHRs: misses inflightCXL.cacheexternal fetchesone portcontendsmissexternal fetch12

The edge worth staring at is coherence agent → cache, because it is the only arrow in the picture whose rate the accelerator's designer does not control. Everything upstream of the cache is your workload; that one arrow is somebody else's.

5. Teaching-model boundary

6. RTL 1 — Misses in Flight, and the Second Miss That Costs Nothing

An accelerator with many lanes will miss the same line more than once — that is what shared data means. The structure that handles it is the miss-status tracker, and its most valuable behaviour is merging.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    // A second miss to a line already in flight MERGES: one fetch, two waiters.
    for (k = 0; k < NENT; k = k + 1)
      if (live_q[k[1:0]] && (line_m[k] == miss_line)) begin
        line_hit = 1'b1; hit_id = k[1:0];
      end
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP1: a second miss to the same line MERGES ===
  two lanes, one line : correct allocs=1 merges=1 occupancy=1
                       no-merge variant allocs=2 occupancy=2
  one fetch serves both lanes                        : ok
  the no-merge variant spent two entries and two fetches: ok
  MSHR peak occupancy was exactly 1                  : ok
  fill wakes mask=00100010 (lanes 1 and 5 merged)
  the fill woke both merged lanes, from one fetch    : ok

Merging saves two scarce resources at once: a tracker entry, and an external fetch. On shared data the saving is large and it is invisible in a hit-rate number — the second lane's access was a miss, and it cost no external traffic at all.

The entry is released at the fill, never at the response:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP1b: an entry is held until the FILL, not the response ===
  after response only : live=1000 occupancy=1
  the entry survived the response                    : ok
  and was released by the fill                       : ok

The reason is sharper here than in earlier chapters: the entry holds the waiter set. Releasing at the response discards the record of which lanes are waiting, so the fill wakes nobody and those lanes hang forever.

7. Waveform — One Fetch, Two Lanes

Two lanes miss the same line; one external fetch serves both

10 cycles
Ten clock cycles traced from the RTL. Lane one misses at cycle one and allocates a tracker entry. Lane five misses the same line at cycle two and merges rather than allocating, so the allocation count stays at one and the merge count becomes one. Occupancy stays at one throughout. A response arrives at cycle five but the entry is still held. The fill at cycle seven raises a wake mask with bits one and five set, waking both lanes, and the entry is released at cycle eight.two misses, one linetwo misses, one lineone fetch outstandingone fetch outstandingfill wakes bothfill wakes bothsecond miss merges — no new fetchsecond miss merges — no newfetchresponse arrives, entry still heldresponse arrives, entrystill heldwake mask 0x22 = lanes 1 and 5wake mask 0x22 = lanes 1and 5clkmissmiss_lane0155555555mergedallocs0011111111merges0001111111occupancy0011111100responsefillwake_mask00000000000000220000t0t1t2t3t4t5t6t7t8t9
Icarus Verilog 13.0. Architectural teaching waveform derived from the simplified RTL model; it is NOT CXL.cache message timing.

allocs reaches one and stops. merges reaches one. occupancy never exceeds one. Two lane misses, one external fetch — and the wake mask at cycle 7 is 0x22, exactly lanes 1 and 5.

8. RTL 2 — Waking Exactly the Lanes That Waited

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP2: the fill wakes exactly the lanes that waited ===
  waiters=00100010 : correct wake=00100010 | wake-all wake=11111111
  exactly the waiting lanes were woken               : ok
  the wake-all variant woke lanes that never asked   : ok

Waking a lane that was not waiting delivers data to a lane that did not ask for it. On a wide machine the broken variant is not a small error — it wakes every lane on every fill, and each spurious wake is a lane resuming with data for an address it never requested.

9. RTL 3 — A Stall Has a Cause, and It Is Usually Not the One You Assume

This is the most valuable module in the chapter, because it changes what an architect does with the data.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      if (BLAME_COMPUTE) begin
        n_miss_q <= n_miss_q + 16'd1;          // everything blamed on misses
      end else begin
        if (cause_miss) n_miss_q <= n_miss_q + 16'd1;
        if (cause_coh)  n_coh_q  <= n_coh_q  + 16'd1;
        if (cause_mshr) n_mshr_q <= n_mshr_q + 16'd1;
      end

Thirty stall cycles, evenly caused:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP3: a stall has a cause, and it is not always the cache ===
  30 stall cycles : miss=10 coherence=10 mshr-full=10
  every stall cycle attributed to exactly one cause  : ok
  blame-compute variant : miss=30 coherence=0 mshr-full=0
  the broken variant blamed all 30 on cache misses   : ok
  it reports 100% miss-bound; the truth is 33%

The broken counter reports the accelerator as 100% miss-bound when only a third of its stalls are misses. The other two thirds are coherence taking the cache port and the miss tracker being full — and those have completely different fixes:

Dominant causeThe fix
Missesa bigger cache, or better locality
Coherencearbitration policy, or data placement
Tracker fullmore MSHRs

A team reading the inflated number buys a bigger cache. It does not help, because the cache was never the constraint.

10. RTL 4 — No Lane Is Structurally Last

Eight lanes, one cache port, forty-eight cycles of full contention:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP4: no lane is structurally last ===
  8 lanes, 48 cycles : round-robin grants=48 max_lane_wait=7
                       fixed-priority grants=48 max_lane_wait=48
  round-robin worst wait was exactly 7 = lanes minus 1: ok
  fixed priority made one lane wait 48 cycles        : ok
  and flagged lane starvation                        : ok

Both schedulers issued exactly the same number of grants — 48. Throughput is identical. What differs is the distribution: round-robin bounds the worst lane's wait at lanes minus one, while fixed priority left one lane waiting for the entire run.

Equal throughput, completely different behaviour. On an accelerator this matters because lanes usually have to converge — a kernel finishes when its slowest lane finishes, so the worst-case lane wait, not the average, sets the kernel's time.

11. RTL 5 — Hit Rate Is Not the Whole Story

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP5: external traffic is misses MINUS merges ===
  requests=40 hits=30 misses=10 merged=5 external=5
  traffic split matched an independent oracle        : ok
  every miss either merged or went external          : ok
  hit rate 75%, but external traffic is only 12% of requests

A 75% hit rate suggests 25% of requests go out to the host. The measured external traffic is 12% — half the misses merged onto fetches already in flight.

The relationship worth remembering:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
external fetches = requests × (1 − hit rate) − merged misses

The merge term is invisible in a hit-rate number and grows with lane count and with data sharing. A wide accelerator on shared data can have a mediocre hit rate and modest external traffic, and an architect who sizes the link from hit rate alone will over-provision it.

12. RTL 6 — The Counter That Has Now Broken Four Times

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      if (TWO_ASSIGNS) begin
        // Both assign outstanding_q; the second one wins outright.
        if (alloc) outstanding_q <= outstanding_q + 5'd1;
        if (free)  outstanding_q <= outstanding_q - 5'd1;
      end else begin
        case ({alloc, free})
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP6: allocate and free in the same cycle ===
  4 allocated : correct=4 | two-assign variant=4
  3 cycles of alloc+free together : correct=4 | two-assign=1
  the case-based counter held steady                 : ok
  the two-assign variant drifted down to 1           : ok

This defect has now appeared in an event queue, a traversal work queue, an outstanding-transaction count and here — four times in this track. Two non-blocking assignments to one variable in one cycle: the second wins, so allocate-and-free together decrements instead of holding.

It survives fill-then-drain testing perfectly and appears only under steady-state traffic, which is the operating point an accelerator spends its life in.

13. RTL 7 — What Outstanding Capacity Buys

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP7: outstanding capacity bounds throughput ===
  4 outstanding, latency 8 : issued=36 stalled=44
  unlimited                : issued=80 stalled=0
  the outstanding limit held every cycle             : ok
  more lanes cannot beat outstanding/latency         : ok
  achieved 36 issues in 102 cycles = 1 per 2 cycles (limit/latency = 1 per 2)
  every offered cycle either issued or stalled       : ok

Four outstanding misses and eight cycles of latency bound throughput at one issue per two cycles, and the measurement lands exactly there. Adding lanes changes nothing once that bound binds — the lanes simply stall earlier.

Illustrative worked example. Take 64 lanes, each issuing a 64-byte access every 4 cycles at 1 GHz:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
offered      = 64 / 4 = 16 accesses per cycle
              = 16 × 64 B × 1e9 = 1024 GB/s offered
at 75% hit   → 4 misses per cycle
with merging → measured 12% external ≈ 1.9 external fetches per cycle

That last number is the one to size the link against, and it is less than half what the hit rate alone suggests. Then the outstanding bound applies on top: to sustain 1.9 fetches per cycle at 200 cycles of round-trip latency needs roughly 380 outstanding misses. If the design has 64, it will sustain 64/200 ≈ 0.32 per cycle — a sixth of what the workload offers.

That arithmetic, not the hit rate, is what decides whether the accelerator is memory-bound.

14. Reading a Stall Cycle

An accelerator lane request can stall for three distinct reasons. It may lose the cache port to the coherence agent, it may hit in the cache and be waiting for a fill on a line already missed, or it may miss and find the tracker full so no entry can be allocated. Each reason routes to its own counter, and the three counters sum to the total stall count.lane stalledone cycle of noprogresslost the portcoherence had itwaiting on a fillthe line is in flighttracker fullno entry to allocatefix: arbitrationor data placementfix: capacitycache size or localityfix: more MSHRsoutstanding budget12

The bottom row is why the attribution matters. Three causes, three unrelated fixes — and a merged counter picks one of them for you, usually the middle column, usually wrongly.

15. Assertions

Icarus Verilog 13.0 does not support concurrent SVA here; each property is synthesisable checker logic verified in simulation.

Safety

PropertyIntent
No entry reuse while livealloc |-> !live[entry]
Fill wakes only waiterswake |-> waiters
No orphan fillfill |-> live[entry]
One cause per stallexactly one of miss / coherence / tracker
Outstanding never negativefree && zero |-> error
Outstanding within limitoutstanding <= MAXO
Traffic conservationmisses == merged + external

Liveness

PropertyAssumption
Every waiting lane is eventually wokenthe fill arrives
Every lane eventually gets the portround-robin, not fixed priority
A merged miss completes with its host entrythe entry is not released early

Performance goals

GoalMeasured by
Worst lane wait boundedmax_lane_wait_q = lanes − 1
Throughput at the outstanding boundissues per cycle vs MAXO/latency
Merge rate visiblemerged vs external
Stalls attributablethe three cause counters

16. Mutation Testing

Nineteen mutations. Nineteen killed — but this page needed the most repair of the batch.

MutationResult
Second miss to a live line allocates againkilled
Merged lane not recorded as a waiterkilled
Entry freed at the response, not the fillkilled
Fill for a dead entry not flaggedkilled
Miss refused for lack of an entry not countedkilled
MSHR peak occupancy lags by onekilled
Fill wakes every lanekilled
Spurious wakes not detectedkilled
Coherence stalls not countedkilled
Unattributed stall not flaggedkilled
Stall with two causes not flaggedkilled
Round-robin degenerates to fixed prioritykilled
Round-robin pointer never advanceskilled
Max lane wait lags by onekilled
Miss = merged + external law disabledkilled
Every miss counted as externalkilled
Outstanding counted with two assignmentskilled
Outstanding limit not enforcedkilled
Stalls waiting for a slot not countedkilled

The first run scored 9 of 19 — the weakest result in the batch, and it was correct to be. The testbench was exercising the modules without checking most of what they produce. The eight escapes were:

  • Three signals never asserted at all: MSHR peak, max lane wait, and the bandwidth model's stall count. The designs computed them; nothing looked.
  • Two paths never stimulated: the response-before-fill sequence, and the merged waiter set surviving to the wake.
  • Three checkers unreachable by construction: unattributed stall, two-cause stall, and the traffic conservation law — all true by construction under legal stimulus.

Two of the repairs are worth naming.

A bound became an equality, for the second time in this batch. The lane-wait assertion said "no more than 8"; the design has an exact answer of 7. 8.3 hit the identical problem on its coherence-wait counter. Twice in one batch is a pattern, not an accident: when a design has a computable exact value, asserting a bound is settling.

The merge test was checking the wrong end. It verified that a merge happened — allocation count stayed at one — without ever verifying that the merged lane was woken. A design that merges correctly and forgets the waiter passes the first check and hangs a lane forever. Asserting the wake mask closed it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  fill wakes mask=00100010 (lanes 1 and 5 merged)
  the fill woke both merged lanes, from one fetch    : ok

17. Verification Plan

AreaApproach
MergingTwo lanes, one line; wake mask asserted, not just the count
Entry lifetimeResponse and fill driven separately
CapacityFill the tracker; refusals counted
WakeCorrect mask against a wake-all variant
Stall causesAll three, plus no-cause and two-cause on an abuse instance
Lane fairnessSustained contention; exact worst-wait assertion
TrafficIndependent oracle; conservation proven reachable
OutstandingSimultaneous allocate and free
ThroughputMeasured against the outstanding/latency bound

The coverage cross is lane concurrency against line sharing against coherence activity. The middle axis is the one that produces merging, and a testbench where every lane touches a distinct line will never exercise it — which is exactly the testbench most people write, because distinct addresses are easier to generate.

18. Silicon Observability

CounterDiagnoses
stalls split by causewhether the cache, coherence or the tracker is the constraint
MSHR peak and full-refusalswhether outstanding capacity is the bound
merge ratehow much external traffic sharing is saving
external fetches vs missesthe real link demand
max lane waitfairness, and kernel convergence time
spurious wakesa lane-return bug, which is otherwise silent
outstanding count and peakthe bandwidth-delay working point

The single most valuable is the stall split, because it is the one that redirects effort. Everything else refines a decision; that one decides which decision you are making.

19. Debug Lab

1

The accelerator runs beautifully and the rest of the system degrades

COH-STARVED
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign grant_coh   = coh_req && !accel_req;   // accelerator first, always
assign grant_accel = accel_req;
Symptom

Accelerator throughput counters look excellent. Host threads touching shared data stall for long periods. No errors anywhere, and the device reports itself busy and productive.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  24 cycles of contention : fair accel=20 coh=4 max_coh_wait=4
                          : strict accel=24 coh=0 max_coh_wait=24
Root Cause

Strict accelerator priority on the shared cache port. A busy accelerator always has work, so coherence never wins — measured as zero grants in twenty-four cycles of contention.

The accelerator's own metrics cannot show this, because from its point of view everything is going well. The failure is visible only in the rest of the system, which is why it is usually reported as a host problem.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign promote   = (coh_wait_q >= COH_AGE_LIMIT);
assign grant_coh = coh_req && (!accel_req || promote);

Age the coherence request and promote it after a bounded wait. Measured cost: 24 accelerator grants falling to 20, in exchange for a stated worst-case coherence latency.

Prevention

Assert an exact bound on the worst coherence wait, and run sustained contention with both clients asserting every cycle. A test where the accelerator idles occasionally lets coherence through and hides the policy entirely.

2

Lanes hang forever waiting for a fill that already happened

MSHR-EARLY-FREE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (rsp_valid) live_q[rsp_id] <= 1'b0;   // released when answered
Symptom

Individual lanes stop making progress and the kernel never completes. The data did arrive — the cache holds the line — but the lanes that missed on it are still blocked. It worsens with lane count and with sharing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  after response only : live=1000 occupancy=1
  the entry survived the response                    : ok
Root Cause

The tracker entry holds the waiter set, and it was released at the response rather than at the fill. The waiter bitmap went with it, so when the fill completed there was no record of which lanes to wake.

This is worse than the equivalent bug in earlier chapters. There, an early release risked a late message landing on the wrong occupant; here it discards the list of who needs telling, so the lanes wait forever with nothing to time them out.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (rsp_valid && live_q[rsp_id]) answered_q[rsp_id] <= 1'b1;   // answered
if (fill_done)                   live_q[fill_id]    <= 1'b0;   // finished

Two events, two flags, and the waiter set lives until the fill.

Prevention

Drive the response and the fill as separate stimuli and assert occupancy after each. A testbench that models them as one event cannot distinguish the two designs.

3

A lane resumes with data for an address it never requested

SPURIOUS-WAKE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign wake = fill ? {NLANE{1'b1}} : '0;   // wake everyone
Symptom

Silent wrong results that scale with lane count and miss rate — worst when the machine is busiest. Nothing in the coherence path is violated and no error is set. Reducing lane count makes it rarer, which misleads the investigation toward a parallelism bug.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  waiters=00100010 : correct wake=00100010 | wake-all wake=11111111
  the wake-all variant woke lanes that never asked   : ok
Root Cause

The fill woke every lane rather than the recorded waiters. Lanes that were not blocked on this line resume anyway and consume the fill data as though it answered their access.

It is silent because a wake is not a data-integrity event in any checker — the line is valid, the coherence state is right, and only the routing of the response is wrong.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign wake = fill ? waiters_m[fill_id] : '0;
if (wake[k] && !waiters[k]) spurious_wake_err <= 1'b1;

Wake the recorded set, and assert that no woken lane was outside it.

Prevention

Assert the wake mask against the waiter set on every fill, per lane. Counting wakes in aggregate passes on the broken design whenever the waiter count happens to match.

4

A second miss to a line already in flight allocates a second entry

NO-MERGE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign alloc_ok = miss && found;    // no in-flight check
Symptom

The miss tracker fills far sooner than the miss rate predicts, external traffic is roughly double the model, and both worsen sharply on shared data. Stall counters blame the tracker being full.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  two lanes, one line : correct allocs=1 merges=1 occupancy=1
                       no-merge variant allocs=2 occupancy=2
Root Cause

Each miss allocated its own entry and issued its own fetch, even when a fetch for that line was already outstanding. Two scarce resources are consumed where one would do, and the duplication scales with how many lanes share data — exactly the workload coherence is for.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
for (k = 0; k < NENT; k = k + 1)
  if (live_q[k] && (line_m[k] == miss_line)) begin line_hit = 1'b1; hit_id = k; end
assign merged = miss && line_hit;
// attach this lane to the existing entry rather than allocating
Prevention

Generate deliberately shared addresses. A testbench where every lane touches a distinct line exercises the merge path zero times while reporting high coverage.

5

A miss is refused and the request is lost

MSHR-FULL-DROP
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (miss && found) allocate();   // and if !found, nothing at all
Symptom

Lanes occasionally hang under heavy miss bursts. No counter moves, no error is set, and the tracker is not full by the time anyone looks. It correlates with burstiness rather than with average miss rate.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  5 distinct lines into 4 entries : occupancy=4 full-refusals=1
  the fifth miss was refused and counted             : ok
Root Cause

A miss arriving at a full tracker was silently discarded rather than stalled. The lane believes its access is in progress; nothing is tracking it.

The refusal must both stall the lane and increment a counter — the counter because a full tracker is a sizing signal, and the stall because the alternative is a lost request with no owner.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
end else if (miss) begin
  n_full_q <= n_full_q + 8'd1;   // refused: count it, and stall the lane
end
Prevention

Fill the tracker deliberately and assert that offered misses equal allocated plus merged plus refused. Conservation is what makes a silent drop visible.

6

Every performance investigation blames the cache

BLAME-COMPUTE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (stalled) n_miss_stall_q <= n_miss_stall_q + 1;   // one bucket
Symptom

Telemetry reports the accelerator as almost entirely miss-bound. The cache is enlarged; throughput barely moves. The exercise repeats at the next size.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  30 stall cycles : miss=10 coherence=10 mshr-full=10
  blame-compute variant : miss=30 coherence=0 mshr-full=0
  it reports 100% miss-bound; the truth is 33%
Root Cause

All stall cycles were attributed to cache misses regardless of cause. Two thirds were coherence holding the port and the tracker being full — neither of which a bigger cache addresses.

The counter is not merely imprecise; it points the entire optimisation effort at the wrong subsystem, and it does so confidently.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (cause_miss) n_miss_q <= n_miss_q + 1;
if (cause_coh)  n_coh_q  <= n_coh_q  + 1;
if (cause_mshr) n_mshr_q <= n_mshr_q + 1;
if (hot != 1)   attribution_err <= 1'b1;      // exactly one cause per cycle
Prevention

Assert that the cause counters sum to the stall count, and prove the no-cause and two-cause checkers can fire using an instance driven with illegal stimulus. On legal stimulus they are true by construction and verify nothing.

7

Outstanding count drifts to zero under steady traffic

TWO-ASSIGN-COUNTER
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (alloc) outstanding_q <= outstanding_q + 1;
if (free)  outstanding_q <= outstanding_q - 1;
Symptom

The reported outstanding count falls steadily under sustained traffic until it reads near zero while the tracker is demonstrably busy. Any throttling driven by the count then misbehaves. Fill-then-drain tests pass perfectly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  3 cycles of alloc+free together : correct=4 | two-assign=1
  the two-assign variant drifted down to 1           : ok
Root Cause

Two non-blocking assignments to one variable in one cycle: both read the pre-edge value and the second wins, so allocate-and-free together decrements instead of holding.

This is the fourth appearance of this defect in this track — after an event queue, a traversal work queue and an outstanding-transaction count. It recurs because each path is written independently and each looks correct alone.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
case ({alloc, free})
  2'b10: outstanding_q <= outstanding_q + 1;
  2'b01: outstanding_q <= outstanding_q - 1;
  default: ;                                  // both or neither: hold
endcase

Exhaustive by construction, so the both-at-once case cannot be forgotten.

Prevention

Test the cross of the two conditions, not each separately. Steady state is where accelerators live, and it is precisely where this defect shows.

20. Industry Roles

Accelerator architect. Sizes the cache, the MSHR count and the outstanding budget. The key realisation from §13 is that outstanding capacity, not cache size, is usually what binds on a latency-heavy link — and that the merge rate makes hit rate a poor proxy for link demand.

RTL engineer. Owns the tracker, the lane arbiter and the wake path. The two defects that bite are entry lifetime — release at fill, not response — and the waiter set surviving to the wake.

DV engineer. Must generate shared addresses to exercise merging, must drive coherence concurrently with compute, and must assert exact values where the design has them. A testbench of distinct addresses with an idle coherence path covers almost none of this chapter.

Performance engineer. Lives in the stall split and the outstanding/latency bound. The first question on any regression is which stall cause moved.

Firmware and runtime. Data placement decides the sharing pattern, which decides both the merge rate and the coherence traffic. It is often the cheapest lever available.

21. Design Review

  1. How many misses can be outstanding, and how does that compare to bandwidth × latency?
  2. Does a second miss to a line in flight merge, or allocate?
  3. When is an MSHR entry released — at the response or at the fill?
  4. Does the fill wake exactly the lanes that waited?
  5. Can coherence starve? Can a lane starve?
  6. What happens when the miss tracker fills — stall, or drop?
  7. Can a line be evicted while an MSHR references it?
  8. Which counters separate cache misses from coherence stalls?
  9. Is the worst lane wait bounded, and is the bound asserted exactly?
  10. What happens on reset with outstanding misses and waiting lanes?

22. Common Misconceptions

BeliefCorrection
The cache serves the acceleratorIt serves two clients; one is not yours
Hit rate predicts link trafficMerging can halve it
A stall means the cache is too smallA third of stalls here were coherence
More lanes means more throughputNot past the outstanding/latency bound
Fixed priority and round-robin differ in throughputIdentical throughput; different worst case
An MSHR entry frees when the response arrivesIt holds the waiter set until the fill
Merging is an optimisationIt is also what keeps the tracker from filling
Coherence traffic scales with your workloadIt scales with the host's

23. Interview Reasoning

24. Exercises

  1. Analysis. An accelerator reports 90% hit rate, 40% stall cycles, MSHR peak equal to its depth, and zero coherence stalls. Name the constraint, the one change that would help, and the change that would not.

  2. Design. Add per-lane fairness weighting so some lanes may be prioritised. State what breaks in the worst-lane-wait bound and what new counter is needed to detect it.

  3. RTL task. Extend the MSHR so a merged waiter can attach to an entry whose response has already arrived but whose fill has not. State the new race and the assertion that catches it.

  4. DV task. Write the address-generation policy that exercises merging, and explain why the natural policy — distinct addresses per lane — gives high coverage numbers while testing none of it.

  5. Debug task. Lanes intermittently resume with data for addresses they never requested. Give your investigation order and the single counter that confirms the cause.

  6. Design review. A colleague proposes removing merging because "it complicates the tracker and the hit rate is already good". Give the strongest version of that argument, then name the two resources it costs and the workload where it is most expensive.

25. Summary

Coherence is a second client of your cache, and it does not run your workload.

  • The cache now has two clients with opposite characteristics: one you control and one you do not.
  • MSHRs let the pipeline continue past a miss, and merging makes a second miss to an in-flight line cost nothing.
  • An entry is released at the fill, never at the response — it holds the waiter set, and losing that hangs lanes.
  • A fill must wake exactly the lanes that waited. Spurious wakes deliver data to lanes that never asked, silently.
  • Measured: a broken stall counter reported 100% miss-bound where the truth was 33%, with the rest split between coherence and a full tracker — three causes, three different fixes.
  • Measured: round-robin and fixed priority delivered identical throughput; the worst lane wait was 7 versus 48. Kernels converge, so the tail sets the runtime.
  • Hit rate is a poor proxy for link traffic. A 75% hit rate produced 12% external traffic because half the misses merged.
  • Measured: four outstanding at eight cycles of latency bounds throughput at one issue per two cycles, exactly. More lanes do not help past that.
  • The two-assignment counter defect appeared for the fourth time in this track.
  • Verification lesson: the first mutation run scored 9 of 19. Three signals the design computed were never named by any assertion, and the merge test verified the optimisation triggered without verifying the deferred work happened.

Chapter 8.5 takes the hardest question in the module: what must be proven before writable ownership of a line may move.

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.