Skip to content
VLSI Mentor

CXL · Module 11

Capacity Scaling

Memory the host cannot address is not capacity. Region decode with exactly one target, interleave that keeps capacity reachable, latency class captured at issue, and why a longer round trip needs proportionally more requests in flight.

11.1 established the weld: capacity is a product of channels, slots and density, fixed when the board was designed, and surplus behind one socket cannot serve demand behind another.

This chapter asks what actually changes when capacity arrives over a link instead of over channels — and the first answer is not about bandwidth or latency. It is about naming.

1. The Engineering Problem — Capacity You Cannot Name Is Not Capacity

A device with a terabyte of DRAM is attached to a host. The link trains. The device enumerates. Software reports the same memory it had before.

Nothing failed. The memory is there, powered, and working. It has no address, so no instruction can reach it, and from the workload's point of view it does not exist.

That gap — between installed and addressable — is where capacity scaling actually happens, and it introduces three problems the local memory system never had:

Every address must resolve to exactly one place. With one memory controller this was trivial. With local memory and several expanded regions it is a decode problem, and getting it wrong means either an address that goes nowhere or, worse, an address two regions both claim.

Capacity now appears and disappears. A local DIMM is present from power-on. An expanded region is configured, enabled, and can be disabled again — under live traffic.

Capacity is no longer uniform. Two addresses in the same address space now have materially different service times, and the system has to know which is which for every request it issues.

2. The One-Sentence Model

Addressable capacity is the sum of live regions, and every address must resolve to exactly one of them. Installed capacity is a hardware fact; addressable capacity is a configuration fact; and the gap between them is memory that exists and cannot be used.

Call it name it or lose it. Everything below follows from the requirement that an address resolves — uniquely, to an enabled region, with a known latency class.

3. What This Chapter Owns

GroundOwner
Why the ceiling exists and what it costs11.1
Making expanded capacity addressable and reachablethis chapter
Sharing one device between hosts11.3
Server designs built on expansion11.4
What AI workloads demand of it11.5

Deferred:

Deferred groundOwner
The CXL.mem protocol itselfModule 9
What its guarantees cost9.6
The expander device's own architecture10.3
Latency anatomy and throughput modellingModule 18
Pools and many-host allocationModule 12

This chapter deliberately does not decompose latency. It establishes that a latency class exists and must be tracked per request — which is a correctness and accounting problem — and leaves the hop-by-hop decomposition to Module 18.

4. Teaching-model boundary

5. RTL 1 — Exactly One Target

The decode is the first thing expansion adds, and it has two failure modes that are opposites.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      // A disabled region must not match. Enable is part of the decode, not a
      // downstream filter -- a filtered match still consumed a priority slot.
      if ((IGNORE_ENABLE || enabled[r])
          && (acc_addr >= base[r]) && (acc_addr <= limit[r])) begin
        match[r] = 1'b1;
        if (nmatch == 0) first_idx = r[1:0];
        nmatch++;
      end
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP1: one address, exactly one target ===
  boundaries walked : local=4 expanded=4 unmapped=1
  independent oracle: local=4 expanded=4 unmapped=1
  local and expanded hits matched an independent oracle      : ok
  every region included both its own boundaries              : ok
  addr 250 with region 2 disabled : hit=0 unmapped=1 | ignore-enable hit=1
  a disabled region does not match, and the access is unmapped : ok
  overlapping regions, addr 260 : overlap flag=1
  two regions claiming one address was caught                : ok

Zero targets and two targets are both failures, and only one of them is loud.

An address matching nothing is unmapped — it has no correct answer, and the only correct response is to report it. An address matching two regions is worse: the priority logic silently picks one, the access succeeds, and whichever region lost the race has an address range that quietly belongs to someone else.

The overlap checker is written unguarded on purpose. Guarding it with the check-enable parameter would switch it off on the only configuration that can trip it — the self-disabling-checker defect this track has now seen four times, and it is always written by someone narrowing a check "sensibly".

Enable belongs inside the decode. Filtering a match afterwards looks equivalent and is not: a disabled region that still matches has consumed a priority slot, so an address it should never have claimed now resolves to it rather than falling through to a lower-priority region or reporting unmapped.

Every boundary was walked — first and last address of all four regions. That matters because the <= versus < limit comparator is invisible to any address comfortably inside a region, and it silently loses one line per region.

6. Waveform — A Region Disappearing Under an Address

Nine cycles of address decode. Addresses 50, 99, 100, 250, 299, 300, 500, 250 and 350 are presented. The first three hit local regions with target changing from 0 to 1 at the boundary between 99 and 100. Addresses 250, 299 and 300 hit expanded regions. Address 500 matches nothing and asserts unmapped. At cycle eight the enable mask changes and address 250, which previously hit, now asserts unmapped. At cycle nine address 350 hits an expanded region again.local regionslocal regionsexpanded regionsexpanded regionsunmapped, then disabledunmapped, thendisabledboundary 99 to 100: target changesboundary 99 to 100: targetchangessame address, now unmappedsame address, now unmappedclkacc_addr5099100250299300500250350enabled111111111111111111111111111110111111hittarget001223333is_expandedunmappedt0t1t2t3t4t5t6t7t8
Figure 1 — nine accesses through a four-region map. The target changes at the 99 to 100 boundary; addresses 250 and 300 classify as expanded; 500 matches nothing and is reported unmapped. At cycle 8 the same address 250 that resolved cleanly at cycle 4 becomes unmapped, because its region was disabled between the two accesses.

Teaching-model timing derived from the simplified RTL in this chapter. Not CXL wire timing.

Compare cycle 4 with cycle 8. The address is identical and the outcome is opposite, because the enable mask changed between them. Local memory never behaves this way — a DIMM does not stop being addressable while the machine runs — and it is the single biggest behavioural difference expansion introduces.

That is why §9's enable FSM exists: if capacity can be withdrawn, the withdrawal has to be sequenced.

7. RTL 2 — Interleave Decides Whether Capacity Is Reachable

Once capacity spans several devices, the address bits chosen to select between them determine both bandwidth and, less obviously, whether the capacity can be reached at all.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign way = HIGH_ORDER_SEL ? high_sel : low_sel;
  // Whatever selects the way must be removed from the per-device offset.
  assign dev_offset = KEEP_SELECT_BITS
                    ? acc_addr[13:0]
                    : {acc_addr[15:GRAN_LOG2+2], acc_addr[GRAN_LOG2-1:0]};
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP2: interleave decides whether capacity is reachable ===
  16 consecutive granules : ways=4/4/4/4
  the stream spread exactly evenly                           : ok
  high-order-select variant : way0=16 (a sequential stream never leaves it)
  addr 0 selects way 0 / addr 4 selects way 1 / addr 8 selects way 2 : ok
  addr 16 : way=0 offset=4 | keep-select-bits offset=16
  addr 16 wraps back to way 0                                : ok
  and its per-device offset advanced to 4                    : ok
  the keep-select-bits variant left the select bits in the offset : ok

Two independent failures, and the second is the quiet one.

Selecting on high-order bits makes a sequential stream stay on one device for an enormous run — 16 of 16 in the measured contrast — so three devices idle while one queues. That is a bandwidth failure, and it is at least visible as poor throughput.

Leaving the select bits in the per-device offset is a capacity failure. Each device then only ever sees offsets whose select bits match its own index, so it reaches a quarter of its own DRAM while the host believes all of it is addressable. Note the measured values: with the bits removed, address 16 maps to offset 4; with them left in, it maps to offset 16 — the offsets spread out and the device's address space is used sparsely.

An even distribution proves nothing

The distribution was 4/4/4/4 and matched the oracle — and that check alone would have passed a decode that shifts the select bits by one position, because such a decode is also perfectly even over a strided stream. It simply assigns the wrong addresses to each device.

Only the exact way and exact offset for known addresses pin a decode down. Evenness is a statistic; a decode is a function. This is the second batch in which that distinction has been the difference between a passing test and a real check.

8. RTL 3 — Latency Class Belongs to the Request

Expansion makes the address space non-uniform, and the moment it does, every performance counter has to answer a new question: which kind of memory was this?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  always_comb begin
    used_cls = RECLASSIFY_AT_DONE ? current_class : cls_q[retire_id];
  end
  ...
      if (issue) begin
        cls_q[issue_id]  <= issue_class;   // captured at issue, not at retire
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP3: latency class belongs to the request, not the map ===
  issued expanded, map changed, then retired : correct exp done=1 local done=0
  reclassify-at-done variant                : exp done=0 local done=1 stale flag=1
  the request was attributed to the class it actually traversed : ok
  the reclassifying variant charged it to the wrong class    : ok
  measured age of the expanded request : 5 cycles
  the expanded request accumulated exactly its real age      : ok

A request that traversed expanded memory must be charged to expanded memory, even if the map changed while it was in flight.

The reclassifying variant looks harmless — it reads the current configuration at completion, which is simpler and needs no per-slot storage. It charged an expanded access to the local bucket, and the consequence is not a rounding error: the local latency average is inflated by a request that never touched local memory, and the expanded average is deflated by its absence. Both numbers move in the direction that makes expansion look better than it is.

The class must be captured at issue, in the slot, alongside the age. That is a few bits per outstanding transaction, and it is what makes the two latency populations separable at all.

9. RTL 4 — A Longer Round Trip Needs More In Flight

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      case ({do_issue && !FREE_ON_ISSUE, do_free})
        2'b10: inflight_q <= inflight_q + 4'd1;
        2'b01: inflight_q <= inflight_q - 4'd1;
        default: ;                              // both or neither: hold
      endcase
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP4: a longer round trip needs more requests in flight ===
  6 issues, 8 slots  : inflight=6 peak=6 throttled=0
  same 6 into a 2-slot pool : issued=2 throttled=4 ready=0
  the 2-slot pool issued 2 and throttled 4                   : ok
  and it deasserted ready once full, so upstream can see the limit : ok
  3 cycles of issue+response together : inflight 6 -> 6
  simultaneous issue and response left the in-flight count unchanged : ok
  response for an unallocated slot : double-free flag=1

This is 9.6's concurrency argument arriving at the capacity problem. Expanded memory has a longer round trip, so sustaining the same bandwidth requires proportionally more requests in flight — and a pool sized for local latency throttles expanded traffic while reporting no error at all.

The 2-slot pool issued 2 and throttled 4 on identical demand. Nothing is broken; the capacity is addressable and the device is fine. The pool is simply too small for the latency, and the only visible symptom is bandwidth that will not rise.

ready must fall when the pool is full, or upstream cannot see the limit and the throttle is invisible outside the block. That output is how a concurrency limit becomes observable rather than merely effective.

The simultaneous issue-and-response test is there because the two-assignment counter defect has now appeared eleven times in this track. One issue and one completion in the same cycle must leave the count unchanged; two separate non-blocking assignments make it fall, and the in-flight count drifts downward until anything gated on it acts early.

10. RTL 5 — Capacity May Not Appear or Vanish Under Traffic

A region starts in the OFF state. When configuration completes it moves to CONFIG, which is not yet addressable. An enable request moves it to LIVE, the only addressable state. A disable request moves it to DRAIN, which is not addressable but still answers outstanding accesses. When outstanding reaches zero it returns to OFF. A separate path shows instant disable going straight from DRAIN to OFF with work still outstanding, which makes capacity vanish.cfg doneenabledisabledrainedno drainOFF — notaddressableCONFIG — still notaddressableLIVE — addressableDRAIN —answers,acceptsnothingOFF —outstanding iszeroinstantdisable:capacityvanishes
Figure 2 — the region lifecycle. A region is addressable only in LIVE. Configuration must complete before it can be enabled, and a disable request moves it to DRAIN, where it answers outstanding accesses but accepts nothing new. Only when outstanding reaches zero does it leave. The instant-disable path skips the drain and makes capacity vanish underneath live accesses.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP5: capacity may not appear or vanish under traffic ===
  a region is not addressable before configuration           : ok
  configured is still not addressable                        : ok
  only an enabled region is addressable                      : ok
  disable with 3 outstanding : state=3 addressable=0 wait=4 | instant variant state=0 vanish flag=1
  a draining region accepts nothing new                      : ok
  the instant-disable variant made capacity vanish under traffic : ok
  drained : state=0
  the region left only after outstanding reached zero        : ok

Configured is not addressable, and that distinction is the whole state machine. A region that has been described to the host but not enabled is dark capacity — it exists, its size is known, and no instruction can reach it.

Disable is a sequence, not a bit. This is the third structural appearance of the drain barrier in this curriculum, after 9.6's fence and 10.2's ownership handover, and it has the same shape every time: the cost is the work outstanding, and the fast variant that skips it corrupts something.

Here the corruption is specific. The instant-disable variant returns to OFF with three accesses still outstanding — those accesses were issued against memory that no longer answers, and they have no correct completion.

11. RTL 6 — Addressable Means Live

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      counted = COUNT_CONFIGURED ? configured[r] : live[r];
      ...
      // Capacity that exists and is configured but cannot be addressed.
      if (configured[r] && !live[r]) d_sum += size[r];
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP6: addressable capacity counts LIVE regions only ===
  sizes 128/128/512/512, live=0111 : addressable=768 local=256 expanded=512 dark=512
  addressable is 128+128+512 = 768                           : ok
  split 256 local and 512 expanded                           : ok
  the two halves account for the whole                       : ok
  512 GB is configured but dark                              : ok
  count-configured variant : addressable=1280 (512 GB software cannot use)
  double-count abuse : local=768 expanded=512 addressable=768 flag=1

768 addressable, 512 dark. The count-configured variant reports 1280 — a number that is arithmetically defensible and operationally false, because 512 GB of it cannot be reached by any instruction.

Dark capacity is the metric worth adding. It is the difference between "we installed a terabyte" and "software can use a terabyte", and the gap is usually a configuration or enablement problem rather than a hardware one. A platform reporting only installed capacity cannot distinguish a device that failed to enable from one that was never fitted.

The balance law — local plus expanded equals addressable — is what makes the split trustworthy. The DOUBLE_COUNT_EXP abuse instance counts expanded capacity into both halves and is caught immediately; without the law, a ledger can report a plausible total whose parts do not add up, and every derived percentage is then wrong.

12. RTL 7 — Count the Accepted Event

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== EXP7: count traffic on the accepted event ===
  offered=24 accepted local=8 expanded=8 stalled=8
  oracle : offered=24 local=8 expanded=8 stalled=8
  accepted local/expanded matched the oracle                 : ok
  accepted traffic is strictly less than offered             : ok
  count-offered abuse : offered=3 counted=3 flag=0
  and reports 100% acceptance on a path that accepted nothing : ok
  count-ready abuse : counted=3 offered=0 flag=1

The count-offered variant is the instructive one because it is self-consistent. Its own conservation law holds — it never counts more than it offered — and it still reports 100% acceptance on a path that accepted nothing. A metric can be internally coherent and measure the wrong event.

The rule is the same one AXI, PCIe and every credited interface teach: an event happened when valid && ready, not when valid. After expansion this matters more, because the local and expanded paths stall at different rates, and counting offers hides exactly the difference you added the counter to see.

A host physical address enters a region decode which selects one live region and classifies it as local or expanded. Local traffic goes directly to local memory. Expanded traffic passes through an interleave selector that chooses a device and reconstructs the per-device offset, then through an outstanding pool that limits concurrency, and finally to the expanded devices. Counters observe the accepted event at the pool.host addressone nameregion decodeexactly one livetargetlocal memoryshort round tripinterleavespread + rebuildoffsetoutstanding poolsized for the roundtripexpanded deviceslong round triplocalexpandedaccepted12
Figure 3 — the request path after expansion, and what each stage can get wrong. The decode must select exactly one live region; the interleave must both spread the stream and reconstruct the per-device offset; the pool must be sized for the longest round trip the map can produce. Each stage carries a failure that leaves every component reporting healthy.

Read it as three independent failure points on one path. A wrong decode sends the access nowhere or to two places; a wrong interleave strands three quarters of each device; an undersized pool throttles everything downstream of it. None of the three raises an error, and all three present as "expanded memory is disappointing".

13. Quantitative Reasoning

Illustrative, with stated assumptions. No figure below describes a real device.

Addressable versus installed

From §11:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
installed   = 128 + 128 + 512 + 512 = 1280 GB
addressable = 128 + 128 + 512       =  768 GB
dark        =                          512 GB   (40% of installed)

Forty percent of the memory is present and unusable. No hardware fault would show that; only a live-versus-configured ledger does.

Concurrency for a longer round trip

Using 9.6's relation, concurrency equals bandwidth × round-trip time ÷ transaction size. Take a local round trip of 80 ns and an expanded one of 250 ns, both at 32 GB/s with 64-byte accesses:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
local    : 32e9 x  80e-9 / 64 =  40 outstanding
expanded : 32e9 x 250e-9 / 64 = 125 outstanding
ratio    : 3.1x

A pool sized for local memory delivers roughly a third of the bandwidth on expanded memory — and reports no error, exactly as the 2-slot pool in §9 throttled 4 of 6 requests while behaving correctly.

Mixed-latency averages

With a fraction p of accesses served from expanded memory:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
L_avg = (1 - p) x L_local + p x L_expanded
      = (1 - p) x 80 ns   + p x 250 ns
Expanded fractionMean access
0%80 ns
10%97 ns
30%131 ns
100%250 ns

The mean is linear in placement, which is why placement policy is a first-class design concern rather than an optimisation — and why 11.5 returns to it for workloads whose p is not a free choice.

What interleaving is worth

From §7, a sequential stream across 4 ways:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
spread across 4 devices : 4 concurrent media streams
all on one device       : 1 concurrent media stream

A 4× throughput difference decided entirely by which address bits select the device — same capacity, same link, same demand.

Reachable capacity when select bits are retained

From §7's offsets: with the select bits left in, each device only sees offsets whose bits match its index.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ways                    = 4
fraction of each device reachable = 1/4
usable capacity         = 25% of installed

The host believes all of it is addressable, which is why this defect presents as data corruption at high addresses rather than as an out-of-memory condition.

14. Assertions

Icarus Verilog 13.0 is the only simulator installed here. It does not execute concurrent SVA — property blocks are unsupported and unique/priority case qualities are parsed but ignored — so every property below is synthesisable checker logic verified procedurally, not executed SVA. 52 assertions.

Safety

PropertyIntent
Decode exclusivitya valid address selects exactly one target
Enable in decodea disabled region never matches
Boundary inclusivityeach region includes its own first and last address
Unmapped reportedan address matching nothing is reported, never served
Offset bijectionselect bits are removed from the per-device offset
Class stabilitya request retires under the class it was issued with
Slot integritya response never frees an unallocated slot
In-flight conservationsimultaneous issue and response hold the count
Lifecycle legalityaddressable only in LIVE; OFF only after drain
Ledger balancelocal + expanded = addressable
Accountingaccepted never exceeds offered

Liveness

PropertyAssumption it needs
A throttled request eventually issuessome response returns
A draining region eventually reaches OFFoutstanding eventually drains

Performance goals — not correctness

GoalMeasured by
Interleave spread evenper-way counts
Throttle rate lowthrottled over offered
Expanded fraction as plannedexpanded over accepted
Dark capacity zeroconfigured minus live

15. Mutation Testing

Twenty-six mutations. Twenty-six killed.

MutationResult
Region limit excludes its own last addresskilled
Disabled region still matcheskilled
Region overlap not reportedkilled
Every hit classified as expandedkilled
Unmapped accesses not countedkilled
Interleave selects the wrong address bitskilled
Select bits left in the per-device offsetkilled
All traffic sent to one devicekilled
Latency class re-read at completionkilled
Issue class not capturedkilled
Stale-class attribution not reportedkilled
Expanded latency counted as one cycle per requestkilled
In-flight counted with two assignmentskilled
Throttled requests not countedkilled
Response to an unallocated slot not reportedkilled
Pool accepts past its slot countkilled
A configuring or draining region is addressablekilled
Region disabled without drainingkilled
Vanishing capacity not reportedkilled
Region goes live without an enable requestkilled
Dark capacity reported as addressablekilled
Dark capacity never reportedkilled
Ledger balance check disabledkilled
Traffic counted on the offered eventkilled
Stalled offers not countedkilled
Accounting law disabledkilled

First run: 22 of 26. The four escapes sorted the same way as the previous chapter's, which is now a reliable enough pattern to plan around:

Cause of escapeCount
Stimulus never reached the state2
Checker unreachable without an abuse instance2

Simultaneous issue and response was never driven, so the two-assignment in-flight counter survived — its eleventh appearance in this track, and it survives fill-then-drain stimulus every single time.

req_ready was never asserted on. The pool's issue behaviour was checked thoroughly and its output was not, so a mutation that leaves ready permanently high passed. A block's outputs are part of its contract even when internal behaviour is correct.

Two conservation laws were unreachable by construction — local + expanded always equals addressable, and accepted always ≤ offered, on any correct design. Rather than delete them, DOUBLE_COUNT_EXP and COUNT_READY_ONLY abuse instances were added, deliberately breaking each law on a separate instance so the checker fires without illegal stimulus reaching the instance under test.

Two testbench defects the baseline caught

A combinational output sampled after deassert. The disabled-region check read hit and unmapped after lowering acc_valid, so it read the idle value and reported a failure on a correct design. An isolated probe showed the decoder behaving perfectly. The fix samples while the input is still asserted — and this is the same defect class that appeared in 9.2.

An expectation asserted as a bound. The expanded request's age was checked as >= 6 when the true value is 5. A bound would have accepted a design that inflated the age arbitrarily; the assertion now checks the exact measured value.

16. Verification Strategy

AreaApproach
DecodeEvery region's first and last address; unmapped; disabled; a deliberate overlap
InterleaveStrided sweep against an oracle; exact way and offset per address; high-order and keep-bits variants
Latency classIssue, reconfigure mid-flight, retire; a reclassify-at-done variant
PoolUnder and over capacity; simultaneous issue/response; unallocated response; a free-on-issue variant
LifecycleEvery state transition; disable with work outstanding; an instant-disable variant
LedgerLive versus configured; a double-count abuse instance
CountersIndependent oracle; count-offered and count-ready abuse instances

The reference model is an address-map table, not a copy of the decode. The testbench resolves each address by a lookup over the intended map and compares against the design's priority loop. A testbench that reproduced the loop would agree with a wrong loop.

The coverage cross is region state × address position × traffic class: OFF/CONFIG/LIVE/DRAIN, crossed with first/interior/last/unmapped, crossed with local/expanded. The DRAIN × interior × expanded point is where an access is issued to a region that is about to stop answering, and it is unreachable without directed stimulus.

17. Synthesis and Implementation Reality

StructureImplementation consequence
Region compare2 comparators per region, all in parallel — width scales with region count
Priority encodea tree over match bits; depth is log(regions) and sits in the address path
Decode registered?if combinational, the compare tree is in the critical path to the interconnect
Interleave selectpure wire selection — free
Offset reconstructiona bit concatenation — also free, which is why omitting it is tempting
Per-slot class + agea few bits per outstanding transaction; scales with pool depth
Outstanding poola free-slot bitmap plus a priority encoder; the encoder is the timing risk
Region FSM2 flops per region plus a drain comparator
Ledgeran adder tree over enabled sizes, recomputed on configuration change

The decode is the timing-critical item. Every access pays for the compare tree, and adding regions widens it — which is the practical reason real systems favour a small number of large regions over many small ones, quite apart from any protocol consideration.

The free-slot priority encoder is the second. It sits between "a request arrived" and "a request issued", and its depth grows with pool size — so the pool you need for expanded latency is also the pool that is hardest to close timing on.

18. Silicon Observability

CounterDiagnoses
local vs expanded acceptedwhere traffic actually went
unmapped accessesa map that does not cover what software uses
overlap flaga configuration that double-claims an address
per-way counts and spreadwhether interleave is reaching every device
max per-device offsetwhether select bits leaked into the offset
in-flight, peakwhether the pool is sized for the round trip
throttled requeststhe pool is the limit, not the device
expanded cycles / expanded completionsthe real expanded latency
dark capacityconfigured memory software cannot reach
drain waitwhat a region disable costs
overlap, vanished, double_free, stale_classcorrectness alarms — must be zero forever

The pair to fight for is throttled requests against in-flight peak. Together they say whether the concurrency pool is the bottleneck, which is the single most common reason expanded capacity underperforms while every component reports healthy.

Max per-device offset is the cheap detector for the retained-select-bits defect — one register per device, and a value far above the device's real size is proof the offset was never reconstructed.

19. Debug Lab

1

A terabyte installed, and software reports nothing new

DARK-CAPACITY
Symptom

An expander is fitted and enumerates. Link is up. The device reports its size correctly. Total memory available to software is unchanged, and no error is logged anywhere.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  sizes 128/128/512/512, live=0111 : addressable=768 dark=512
  512 GB is configured but dark                              : ok
Evidence

Compare configured capacity against live capacity per region. Any region configured but not live is dark — present, described, and unreachable by any instruction.

Likely Causes

Region described but never enabled; enable attempted before configuration completed; a ledger that reports installed rather than addressable capacity, hiding the gap.

Debug Sequence

Read the region state machine per region. CONFIG means described but not addressable; LIVE is the only addressable state. Then check whether the platform's reported capacity comes from the live set or the configured set.

Root Cause

Installed and addressable are different quantities. The gap is a configuration or enablement failure, and a ledger that sums configured regions cannot show it.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
counted = live[r];                                  // not configured[r]
if (configured[r] && !live[r]) d_sum += size[r];    // and report the gap
Prevention

Report dark capacity as a first-class number. A platform that reports only installed capacity cannot distinguish a device that failed to enable from one that was never fitted.

2

Two regions claim one address and one of them loses silently

REGION-OVERLAP
Symptom

Writes to a range of addresses appear to succeed and read back as something else entirely. The corruption is confined to a contiguous window. Both regions test fine in isolation.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  overlapping regions, addr 260 : overlap flag=1
  two regions claiming one address was caught                : ok
Evidence

Count how many regions match a given address. Exactly one is required; two means the priority logic is silently choosing, and the losing region's range belongs to someone else.

Likely Causes

Base or limit programmed from stale configuration; a region resized without its neighbour being moved; limits computed as base+size where size already included the last address.

Debug Sequence

Sweep every region boundary and record the match count, not just the target. A single-target check passes on an overlap because priority always produces one answer.

Root Cause

Zero targets is loud and two targets is silent. Priority logic converts a configuration error into a plausible-looking access.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Unguarded on purpose: guarding with the check-enable would switch it off
// on the only configuration that can trip it.
if (acc_valid && (nmatch > 1)) overlap_err <= 1'b1;
Prevention

Assert the match count, not the target. Any decode with configurable ranges needs this, and it costs one adder over the match vector.

3

Each device reaches a quarter of its own memory

SELECT-BITS-RETAINED
Symptom

Capacity enumerates correctly and behaves correctly for a fraction of the range. Beyond that, writes alias onto locations already in use. It looks exactly like an application bounds bug.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  addr 16 : way=0 offset=4 | keep-select-bits offset=16
  and its per-device offset advanced to 4                    : ok
  the keep-select-bits variant left the select bits in the offset : ok
Evidence

Check the maximum per-device offset against the device's real size. An offset far above what the device contains means the select bits were never removed and the address space is being used sparsely.

Likely Causes

The per-device offset taken as a straight slice of the host address; interleave added later without reworking the offset; a granularity change that moved the select bits without moving the reconstruction.

Debug Sequence

Take two addresses differing only in the select bits. They must map to the same offset on different devices. If their offsets differ, the bits are still in the offset.

Root Cause

The decode is not a bijection onto device memory. Each device only ever sees offsets whose select bits match its index, so it reaches 1/ways of its capacity while the host believes all of it is addressable.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign dev_offset = {acc_addr[15:GRAN_LOG2+2], acc_addr[GRAN_LOG2-1:0]};
Prevention

Sweep the full advertised range writing distinct values before reading any back. A spot check inside the working fraction passes.

4

Expanded memory looks as fast as local memory

RECLASSIFY-AT-DONE
Symptom

Telemetry shows expanded and local latency almost identical, which contradicts every architectural expectation. The team concludes the link is faster than modelled and sizes the next generation accordingly.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  issued expanded, map changed, then retired : correct exp done=1 local done=0
  reclassify-at-done variant : exp done=0 local done=1 stale flag=1
Evidence

Check where the latency class comes from. If it is read at completion rather than captured at issue, any configuration change in flight moves the sample to the wrong bucket.

Likely Causes

Class recomputed from the address map at retire; class stored per region rather than per request; the map legitimately changing under live traffic.

Debug Sequence

Issue an expanded access, change the map while it is outstanding, and retire it. The completion must be charged to expanded.

Root Cause

The class is a property of the request, not of the current configuration. Reclassifying at completion inflates the local average with a request that never touched local memory and deflates the expanded average by its absence — both in the flattering direction.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (issue) cls_q[issue_id] <= issue_class;   // capture at issue
used_cls = cls_q[retire_id];                 // use it at retire
Prevention

Reconfigure the map deliberately while requests are outstanding. A static-map test cannot distinguish the two designs.

5

Expanded bandwidth stuck at a third of local

POOL-SIZED-FOR-LOCAL
Symptom

Expanded memory delivers roughly a third of local bandwidth on the same access pattern. Media is idle, link utilisation is low, and no error counter moves.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  6 issues, 8 slots  : inflight=6 peak=6 throttled=0
  same 6 into a 2-slot pool : issued=2 throttled=4 ready=0
Evidence

Compare peak in-flight against the pool size, and throttle count against offers. A pool pinned at its limit with a non-zero throttle count is the bottleneck.

Likely Causes

Outstanding pool sized from local round-trip time; one pool shared by both latency classes; ready never deasserted, so the limit is invisible upstream.

Debug Sequence

Compute required concurrency as bandwidth × round-trip ÷ transaction size for the expanded path. Compare with the pool size. A 3× latency ratio needs a 3× pool for the same bandwidth.

Root Cause

Concurrency, not speed, sets throughput. The pool sized for an 80 ns round trip throttles a 250 ns one, silently and correctly.

Fix

Size the pool from the longest round trip the address map can produce, and deassert ready when full so the limit is observable.

Prevention

Track peak in-flight and throttle count in silicon. A pool that never reaches its peak is oversized; one pinned at peak with throttles is undersized.

6

Accesses in flight to memory that no longer answers

INSTANT-DISABLE
Symptom

Removing a memory region under load produces timeouts and unrecoverable errors on unrelated transactions. Removing it on an idle system works perfectly, so the sequence is declared correct.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  disable with 3 outstanding : state=3 addressable=0 | instant variant state=0 vanish flag=1
  the instant-disable variant made capacity vanish under traffic : ok
Evidence

Check the outstanding count at the instant the region leaves its live state. It must be zero.

Likely Causes

Disable treated as a register write; no drain state; the drain condition checked against the wrong counter.

Debug Sequence

Issue accesses to the region, request disable, and watch the state. It must enter DRAIN, refuse new accesses, and only reach OFF when outstanding hits zero.

Root Cause

Capacity vanished underneath live accesses. Those accesses were issued against memory that no longer answers and have no correct completion.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
R_DRAIN: if (outstanding == 4'd0) st_n = R_OFF;
if ((st_q == R_DRAIN) && (st_n == R_OFF) && (outstanding != 4'd0))
  vanished_err <= 1'b1;
Prevention

Always test disable with work deliberately outstanding. An idle-system disable passes on both designs.

7

A decode that spreads evenly and still halves bandwidth

WRONG-SELECT-BITS
Symptom

Per-device counters are perfectly balanced. Sequential bandwidth is about half of expected. Everything looks correctly interleaved and the imbalance metric reads zero.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  16 consecutive granules : ways=4/4/4/4
  addr 0 selects way 0 / addr 4 selects way 1 / addr 8 selects way 2 : ok
Evidence

Check the exact way for specific consecutive addresses. Addresses 0, 4 and 8 must select ways 0, 1 and 2. A distribution check cannot detect this — the wrong decode is also perfectly even.

Likely Causes

Select bits shifted by one position; granularity changed without moving the select field; interleave configured for a different way count than instantiated.

Debug Sequence

Present known addresses one at a time and record the selected device. Compare against the intended mapping, not against a histogram.

Root Cause

The select field is one bit too high, so consecutive granules pair up on the same device before advancing. The long-run distribution is even; the short-range spreading that interleaving exists for is halved.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
wire [1:0] low_sel = acc_addr[GRAN_LOG2+1 -: 2];
Prevention

Assert the decode as a function, not as a statistic. Evenness is satisfied by a family of wrong decodes; the exact way for a known address is satisfied by one.

8

Acceptance rate reported as 100% on a stalling path

COUNT-OFFERED
Symptom

Traffic counters show every request accepted. Measured bandwidth is far below what that implies. The counters are self-consistent and survive review.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  offered=24 accepted local=8 expanded=8 stalled=8
  count-offered abuse : offered=3 counted=3 flag=0
  and reports 100% acceptance on a path that accepted nothing : ok
Evidence

Check what event the counter advances on. If it is valid rather than valid && ready, it is counting intentions, not transfers.

Likely Causes

Counter placed on the request output rather than the handshake; ready not routed to the counter; a counter copied from a path that had no backpressure.

Debug Sequence

Add a stalled-offer counter alongside. If offers exceed acceptances, the acceptance counter is on the wrong event — note that the broken counter's own conservation law still holds, so consistency proves nothing here.

Root Cause

An event happened when valid && ready. Counting offers hides stalls, and after expansion the local and expanded paths stall at different rates — exactly the difference the counter was added to measure.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign accepted = valid && ready;
assign counted  = accepted;
Prevention

A self-consistent metric can still measure the wrong event. Check the event definition, not only the conservation law.

20. Design Review

  1. How many regions does the decode support, and where does the compare tree sit in the timing path?
  2. What happens to an address that matches no region?
  3. What proves no two regions can claim the same address?
  4. Is enable part of the decode or a filter after it?
  5. Which address bits select the device, and are they removed from the offset?
  6. What is the maximum per-device offset observed, and does it match the device's size?
  7. Where is the latency class captured, and what happens if the map changes in flight?
  8. What is the outstanding pool sized from — local or expanded round-trip time?
  9. Does ready deassert when the pool is full?
  10. What must drain before a region can be disabled, and what proves it did?
  11. Does the platform report installed or addressable capacity — and is dark capacity visible?

21. How This Appears in Real Engineering

Architect. Owns the region layout and the interleave granularity, and the trade between few large regions (shallow decode, coarse control) and many small ones (deep decode in the critical path).

RTL designer. Owns the decode's timing and the pool's priority encoder. Both sit in the request path and both grow with the flexibility the architect asked for.

DV engineer. Owns boundaries, overlaps, disabled regions, mid-flight reconfiguration and simultaneous issue/response. Every mutation that survived this chapter's first run died to one of those.

Firmware engineer. Owns the configure-then-enable sequence and the honest capacity report. Dark capacity is almost always visible here first.

OS/runtime engineer. Owns placement, which sets p in the mixed-latency mean — the one term in the equation software controls.

Performance engineer. Owns the concurrency argument, and must resist "expanded memory is slow" when the measurement shows a pool sized for local latency.

Silicon validation. Owns the full-range sweep that catches retained select bits and over-advertised regions — cheap, and almost never run to completion.

What each needs from the others: the pool size depends on the architect's worst-case round trip; the decode depth depends on the region count firmware wants; the placement policy depends on the latency split RTL is able to report. Get any one wrong and expanded capacity underperforms with every component reporting healthy.

22. Common Misconceptions

BeliefCorrection
Installed capacity is available capacityOnly live regions are addressable; the rest is dark
A single-target decode check is sufficientIt passes on an overlap, because priority always answers
Filtering disabled regions after decode is equivalentThe disabled region has already consumed a priority slot
Even per-device distribution proves the interleaveSeveral wrong decodes are also perfectly even
The offset is just the low address bitsSelect bits must be removed or capacity is unreachable
Latency class can be read at completionIt belongs to the request, not to the current map
Expanded memory is simply slowerOften the pool is sized for local round-trip time
Disabling a region is a register writeIt is a drain barrier with a register write at the end

23. Interview Reasoning

24. Exercises

  1. Calculation. A system has 4 local channels of 64 GB and two expanded devices of 512 GB each. One expanded device is configured but not enabled. Compute installed, addressable and dark capacity. Then compute the mixed-latency mean at 80 ns local and 250 ns expanded when 25% of accesses fall in expanded memory.

  2. Analysis. Per-device counters are perfectly balanced, the imbalance metric is zero, and sequential bandwidth is half of expected. Explain why the balanced counters do not exonerate the decode, and state the single directed measurement that settles it.

  3. RTL task. Extend region_decoder to report all matching regions rather than the first, and to refuse the access entirely when more than one matches. State the timing consequence of the refusal being combinational, and what changes if the decode is registered.

  4. Assertion task. Write the property proving the per-device offset is a bijection onto device memory. Explain why a per-address check is insufficient alone, and what additional structure the testbench needs to prove the mapping is one-to-one.

  5. Testbench design. Design the stimulus that distinguishes a latency-class-at-issue design from a latency-class-at-completion one. Explain why a static address map cannot distinguish them, and what the stimulus must do instead.

  6. Performance calculation. A device sustains 32 GB/s with 64-byte accesses. Local round trip is 80 ns; expanded is 300 ns. Compute the outstanding depth required for each. Then compute the achieved expanded bandwidth if the pool holds 48 entries, and state which counter would have revealed this in silicon.

  7. Debug task. Writes above a certain address alias onto lower addresses on a 4-way interleaved system. Give your investigation order, the two defects in this chapter that both produce that symptom, and the single measurement that distinguishes them.

  8. Design review. A colleague proposes supporting 64 configurable regions instead of 4, arguing it costs only comparators. Give the strongest version of that argument, then state what it does to the request path, what it does to the verification surface, and what you would propose instead.

25. Summary

Addressable capacity is the sum of live regions, and every address must resolve to exactly one.

  • Installed is not addressable. The measured ledger showed 1280 GB installed, 768 addressable and 512 GB dark — configured, described, and unreachable by any instruction.
  • Exactly one target. Zero matches is loud; two matches is silent, because priority always produces an answer. Assert the match count.
  • Enable belongs inside the decode, not after it — a filtered match has already consumed a priority slot.
  • Interleave decides bandwidth and reachable capacity. High-order select put 16 of 16 accesses on one device; retained select bits leave each device reaching a quarter of its own DRAM while the host believes otherwise.
  • An even distribution proves nothing about a decode. Only the exact way and offset for known addresses do.
  • Latency class belongs to the request, captured at issue. Reclassifying at completion moves samples between buckets in the flattering direction.
  • A longer round trip needs proportionally more requests in flight — roughly 3× here — and a pool sized for local latency throttles silently.
  • Capacity may not vanish under traffic. Disable is a drain barrier, the third appearance of that shape in this curriculum.
  • Verification: 26 of 26 mutations killed, 52 assertions. All four first-run escapes were stimulus gaps or unreachable checkers; the baseline separately caught a testbench sampling a combinational output after deassert, and an expectation written as a bound where an exact value existed.

Next: 11.3 — Memory Resource Sharing, which removes the assumption that a region has one owner, and asks what changes when a small number of hosts share a single expanded device.

Continue learning

Related tutorials

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.