Skip to content

UCIe · Module 17

Future Memory Architectures

Which architectural invariants survive as memory becomes tiered, composable, pooled and disaggregated — why migration is a state transfer rather than a pointer change, why a memory copy misses the newest value when a line is cached dirty, why one completion definition cannot serve a volatile and a durable tier, why a mapping decision in a shared pool is also an access-control decision, and why capacity isolation is not bandwidth isolation.

Module 17 has moved from a single memory chiplet, through remote capacity joining one map, through compute moving toward the data, to a many-channel subsystem behind a boundary. This chapter asks what stays true when memory keeps moving.

1. The One-Sentence Model

Future memory architectures are new placements of old obligations. Changing where state lives changes latency, bandwidth, persistence and failure domain — it never removes the need to know who owns that state, when ownership changes, and which path may observe it.

2. What This Chapter Is, and What It Refuses to Be

This is not a predictions article. It contains no forecast about which technology will win, no year, no market claim, and no assertion that any particular architecture is inevitable.

QuestionWhere it is answered
The memory chiplet as a stateful endpoint17.1
Remote capacity in one memory map; region tables, remap guards, tiers17.2
Compute placed beside the controller17.3
Many-channel HBM behind a boundary17.4
Who holds the newest value across dies16.1 · 16.2

What is new here, and what makes the chapter durable rather than dated:

Migration as a state transfer (§11–§17). 17.2 established that a remap must be guarded and that an accepted request must never be rerouted. This chapter asks the harder question that follows: what does it take to actually move the data, and the answer involves five phases, a dirty-data problem that a memory copy cannot solve, and a commit that must come last.

Completion is not one event (§18–§21). A volatile tier and a durable tier can legitimately define the point of completion differently, and a single done bit cannot model both.

Mapping is access control (§25–§27). The moment memory is shared between clients, deciding where an address goes is inseparable from deciding who may go there — and a design that separates those two decisions has an isolation hole that routes perfectly.

And capacity isolation is not bandwidth isolation (§28–§31). Pooling solves a capacity problem and can create a performance one.

3. Sourcing, and the Evidence Ladder

4. The Invariants That Do Not Move

Every architecture below changes placement. None of them changes this list.

InvariantWhat it means, wherever the memory lives
Address ownershipexactly one target owns an address at a time (17.2 §10)
Latest-value ownershipthe newest value has exactly one holder, which may not be the memory (16.2 §25)
Identitya request's identity is the requester's and survives the round trip (17.1 §19)
Orderingwhatever dependencies the memory model requires are preserved (16.3 §17)
Configuration lifetimea mapping is committed atomically and guarded against live work (17.2 §14)
Completiona defined event, later than transmission, that discharges the obligation
Recoverya transport event rebuilds transport state and changes nothing semantic (14.2 §4)
Observabilityevery stall is attributable to one cause (15.5 §10)

A new memory architecture is a proposal about placement. It is not a proposal about any row of that table — and a design that quietly weakens one of them in exchange for a placement benefit has traded correctness for topology.

5. The Tier Picture

A stack of five conceptual memory tiers. At the top, registers and caches, with the lowest latency, the smallest capacity, and a failure domain limited to the local die. Below that, local high bandwidth memory, still on package and still a local failure domain. Below that, local capacity memory, larger and slower. Below that, package expansion memory reached across a die to die link, whose failure domain now includes the link and a remote controller. At the bottom, pooled or disaggregated memory shared with other clients, whose failure domain additionally includes a fabric, an allocation authority and other tenants. The point of the drawing is that latency and capacity both increase downward while the failure domain grows, and that address ownership and latest value ownership are unchanged at every level.Memory tiers — latency, capacity, and failure domainRegisters and cachesLowest latency, smallest capacity. Failure domain: the local dieLowest latency, smallest capacity. Failure domain: the local dieLocal high-bandwidth memoryOn package. Failure domain: local controller and media (17.4)On package. Failure domain: local controller and media (17.4)Local capacity memoryLarger, slower. Same local failure domainLarger, slower. Same local failure domainPackage expansion memoryAcross a link. Adds link, transport and remote controller (17.2)Across a link. Adds link, transport and remote controller (17.2)Pooled / disaggregated memoryShared. Adds fabric, allocation authority and other clients (§22)Shared. Adds fabric, allocation authority and other clients (§22)
A conceptual latency and capacity ordering, not a claim about which technologies occupy which tier. What changes down the stack is latency, capacity and the size of the failure domain; what does not change is who owns an address and who holds its newest value.

Read the descriptions, not the order. No claim is made that any real technology occupies any particular level, and a given system may have fewer levels, more, or a different arrangement. What the picture teaches is the correlation: as you move down, latency and capacity both grow, and so does the number of independent things that can fail.

6. Five Dimensions, Not a Roadmap

DimensionThe architectural question it raisesWhere this chapter treats it
Capacity tieringwhich data belongs in which service class?§7–§10
Composabilitymemory attached, detached and reconfigured at runtime§11–§17
Poolingseveral clients sharing one memory resource§25–§31
Disaggregationmemory further from local compute§22–§24
Persistencea tier where completion may mean something stronger§18–§21
Near-memory computecompute moved to the data17.3 — not repeated

These are dimensions, not stages. A system can be tiered without being pooled, pooled without being persistent, composable without being disaggregated. Treating them as a sequence — "first we tier, then we pool, then we disaggregate" — is the shape of a roadmap article and it is not how systems are actually built.

7. The Tier Descriptor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE memory-tier descriptor. These are ARCHITECTURAL PROPERTIES of a
// tier as this design chooses to model them — not fields of any standard, and
// not a claim about any technology (Section 3).
typedef struct packed {
  logic                 valid;
  logic [TIER_W-1:0]    tier_id;
  logic [LAT_W-1:0]     latency_class;     // service-class ordinal, not a value
  logic [BW_W-1:0]      bandwidth_class;
  logic [CAP_W-1:0]     capacity_class;
  logic                 coherent;          // does hardware coherence cover it?
  logic                 durable;           // does it survive power loss?
  logic [POLICY_W-1:0]  completion_policy; // Section 19
} mem_tier_desc_t;
 
mem_tier_desc_t tier_q [NUM_TIERS];

Architecture. A small table describing service classes rather than devices. The classes are ordinals, not measurementslatency_class says "slower than tier 1, faster than tier 3", which is what a scheduler or a placement policy actually needs and is all a design can honestly assert.

State. NUM_TIERS entries of flops, written at configuration time and read by placement, scheduling and completion logic.

Cycle behaviour. Read combinationally, alongside the region lookup (§8). Written only at a configuration commit, never incrementally.

Contract. Three consumers depend on it: placement policy reads the classes, the completion logic reads completion_policy (§19), and any coherence decision reads coherent. Each must see a consistent descriptor, which is why it commits with the region table rather than separately.

Failure. Encoding a measured latency value rather than a class — which is then wrong the moment the device is migrated, degraded, or reached over a narrower link. §9 is that failure.

DV. Cover every tier as selected; assert that completion_policy is consistent with durable (§21).

8. The Region-to-Tier Map

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. 17.2's region table with the tier made explicit. The commit
// discipline is 17.2 Section 12's and is not restated here.
typedef struct packed {
  logic                 valid;
  logic [ADDR_W-1:0]    base;      // inclusive
  logic [ADDR_W-1:0]    limit;     // inclusive — one convention
  logic [TIER_W-1:0]    tier;
  logic [TARGET_W-1:0]  target;    // WHICH device, within that tier
  logic [EPOCH_W-1:0]   epoch;
} tier_region_t;
 
tier_region_t active_region_q    [NUM_REGIONS];
tier_region_t requested_region_q [NUM_REGIONS];

Architecture. A region binds an address range to both a tier and a target. Those answer different questions: the tier says what kind of memory this is, and the target says which instance. A migration can change the target within a tier, or move a range between tiers, and the two cases have different consequences (§9, §11).

State. Two copies, committed atomically (17.2 §12).

Cycle behaviour. Compared against the incoming address; the resulting {tier, target, epoch} is captured with the request (17.2 §16) and never recomputed.

Contract. Everything from the completion policy to the quota accounting reads tier. A design that captures target and not tier cannot apply a tier-specific completion rule to a request in flight, which is §20.

Failure. §9.

DV. 17.2 §14's stability properties apply unchanged, plus a check that a captured tier matches the tier of its captured target.

9. Wrong Architecture — a Latency Class Derived From the Address, Forever

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the performance expectation is baked into the address decode and
// never revisited.
localparam int FAST_REGION_TOP = 32'h4000_0000;
 
assign expected_latency_class = (addr < FAST_REGION_TOP) ? LAT_FAST : LAT_SLOW;

Three ways this becomes false without the address changing:

EventWhat actually changedWhat the constant still says
the range is migrated to another target (§11)the device, and its service classfast
the link to that target recovers narrower (14.4)latency and bandwidthfast
the target enters a degraded mode (§23)service ratefast

Three consequences.

A scheduler misallocates work. Anything that prioritises, prefetches, or chooses placement based on expected_latency_class is now optimising against a fiction. The result is a large performance regression with correct data, which is the hardest kind to attribute.

And timeouts derived from it manufacture faults. 16.5 §27's failure, arriving through a different door: a bound sized for the fast class applied to a range that is no longer fast.

The fix is that a performance expectation is configuration state with a lifetime, exactly like a route. It is read from the tier descriptor of the captured tier, and it is re-derived when — and only when — the configuration commits:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. The expectation comes from the tier the request was accepted
// under, so it moves when the configuration moves and not before.
assign expected_latency_class = tier_q[txn_q[id].tier].latency_class;

10. Performance Expectations Have Lifetimes Too

A generalisation worth stating on its own, because it recurs.

Derived quantityMust be re-derived whenIf it is not
timeout boundthe tier, target, link width or rate changeshealthy accesses declared failed (16.5 §27)
required outstanding depthround-trip latency changesthroughput silently collapses (17.2 §22)
prefetch distancelatency class changesprefetches arrive too late or too early
placement policy inputa range's tier changeshot data lands in the slow tier
queue and credit sizingsustained rate changesdegradation becomes loss (17.4 §16)

Every one of those is a number computed from a configuration. When the configuration commits, they are stale — and a design that recomputes the route on a commit but not the bounds has fixed half the problem.

11. Migration Is a State Transfer

17.2 §31 established that migration is a cutover problem and declined to build one. This chapter builds the phase structure, because that is what makes the hazards visible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. QUIESCE   — stop accepting new work for the range; let outstanding work drain
2. COPY      — transfer the authoritative contents from the old target to the new
3. DRAIN     — reconcile anything that changed during the copy
4. COMMIT    — atomically switch the map; advance the epoch
5. RETIRE    — release the old target's copy

Three properties of that ordering, and each is a section below.

COMMIT is fourth, not first. Everything before it must complete while the old target is still authoritative. §13 is the failure of committing early.

COPY is not sufficient by itself. If the data is cached and dirty somewhere, the old target's memory does not hold the newest value — so copying it copies a stale value. §15 is that problem.

And RETIRE is last for a reason. Releasing the old copy before the commit is durable leaves the system with no authoritative source if anything fails between the two. The old copy is the fallback until the new one is proven.

12. The Migration State Machine

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE migration control. Not a protocol state machine, and no
// specification defines these states (Section 3).
typedef enum logic [2:0] {
  MIG_IDLE    = 3'd0,
  MIG_QUIESCE = 3'd1,
  MIG_COPY    = 3'd2,
  MIG_DRAIN   = 3'd3,
  MIG_COMMIT  = 3'd4,
  MIG_RETIRE  = 3'd5,
  MIG_ABORT   = 3'd6
} migration_state_e;
 
typedef struct packed {
  logic                 active;
  migration_state_e     state;
  logic [REG_W-1:0]     region;
  logic [TARGET_W-1:0]  old_target;
  logic [TARGET_W-1:0]  new_target;
  logic [ADDR_W-1:0]    copy_cursor;      // how far the copy has progressed
  logic [EPOCH_W-1:0]   start_epoch;
} migration_txn_t;
 
migration_txn_t mig_q;
 
always_comb begin
  mig_nxt = mig_q.state;
  unique case (mig_q.state)
    MIG_IDLE:    if (mig_start)                      mig_nxt = MIG_QUIESCE;
    MIG_QUIESCE: if (mig_abort)                      mig_nxt = MIG_ABORT;
                 else if (region_outstanding == '0)  mig_nxt = MIG_COPY;
    MIG_COPY:    if (mig_abort)                      mig_nxt = MIG_ABORT;
                 else if (copy_complete)             mig_nxt = MIG_DRAIN;
    MIG_DRAIN:   if (mig_abort)                      mig_nxt = MIG_ABORT;
                 else if (reconcile_complete)        mig_nxt = MIG_COMMIT;
    MIG_COMMIT:  if (commit_fire)                    mig_nxt = MIG_RETIRE;
    MIG_RETIRE:  if (old_copy_released)              mig_nxt = MIG_IDLE;
    MIG_ABORT:   if (abort_complete)                 mig_nxt = MIG_IDLE;
    default:                                         mig_nxt = MIG_ABORT;
  endcase
end
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) mig_q.state <= MIG_IDLE;
  else        mig_q.state <= mig_nxt;

Architecture. One migration in flight per controller in this illustration, with an explicit abort path. MIG_ABORT is reachable from every phase before MIG_COMMIT and from none after — which is the state machine encoding §11's rule that the commit is the point of no return.

State. One record: the phase, the region, both targets, a copy cursor and the epoch the migration started under. The cursor is what makes the copy restartable across a link recovery (§37).

Cycle behaviour. One next-state owner, unique case, and mig_abort checked before progress in every abortable state — so an abort cannot be overtaken by a phase completing in the same cycle.

Contract. The region table's commit logic waits for MIG_COMMIT; the old target's release waits for MIG_RETIRE. Neither may act early, and §13 and §14 are those two rules asserted.

Failure. Allowing MIG_COMMIT → MIG_ABORT. After the commit, new work is already going to the new target, so "aborting" would leave writes on the new target and readers pointed at the old one — split ownership, which is the one thing §4's first invariant forbids.

DV. Cover every state and every transition including abort from each abortable phase; assert no transition out of MIG_COMMIT other than to MIG_RETIRE.

13. Wrong Migration — the Map Commits Before the Copy Completes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the map is switched as soon as the migration starts, "so new traffic
// lands in the right place while we copy".
always_ff @(posedge clk)
  if (mig_start)
    active_region_q[mig_region].target <= new_target;   // ← before COPY

The reasoning sounds efficient and it produces split ownership.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. The map now sends all reads and writes of the range to target B.
2. The copy from A to B is still in progress, at cursor X.
3. A read of an address ABOVE the cursor goes to B, which does not have it yet.
   -> it returns whatever B holds: uninitialised, or another tenant's data.
4. A write to an address BELOW the cursor lands on B.
   -> correct, and then the copy overwrites it with A's stale value.
5. A write ABOVE the cursor lands on B and is later overwritten by the copy.
AccessAddress relative to cursorOutcome
readbelowcorrect — already copied
readabovereturns whatever B holds — not the data
writebelowcorrect, then destroyed by the copy
writeabovecorrect, then destroyed by the copy

Three properties.

Three of four rows are silent corruption. No error is reported anywhere; the transport is perfect; both devices behave correctly.

The failure is a function of a moving cursor, so it is timing-dependent and irreproducible. The same workload run twice corrupts different addresses.

And it looks like it should work if you only think about reads below the cursor. The mental model "copy from the front, serve from the front" is coherent for reads and completely wrong for writes — because the copy is a writer too, and it is writing older data.

The correct sequence keeps the old target authoritative until the commit (§11), which means new traffic goes to A throughout the copy, and the copy must reconcile anything A accepted while it ran (§14's MIG_DRAIN).

14. SVA — the Target Changes Only at the Commit

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. The active target for a migrating region changes only in MIG_COMMIT.
property p_target_changes_only_at_commit;
  @(posedge clk) disable iff (!rst_n)
    $changed(active_region_q[REG_UT].target)
      |-> ($past(mig_q.state) == MIG_COMMIT) && $past(commit_fire);
endproperty
a_target_changes_only_at_commit:
  assert property (p_target_changes_only_at_commit);
 
// The commit requires the copy and the reconciliation to have completed.
property p_commit_requires_copy_and_drain;
  @(posedge clk) disable iff (!rst_n)
    commit_fire |-> (copy_complete && reconcile_complete);
endproperty
a_commit_requires_copy_and_drain:
  assert property (p_commit_requires_copy_and_drain);
 
// The old copy is not released before the commit.
property p_old_copy_held_until_commit;
  @(posedge clk) disable iff (!rst_n)
    old_copy_released |-> (mig_q.state == MIG_RETIRE);
endproperty
a_old_copy_held_until_commit: assert property (p_old_copy_held_until_commit);
 
// No abort after the commit — the commit is the point of no return (Section 12).
property p_no_abort_after_commit;
  @(posedge clk) disable iff (!rst_n)
    ($past(mig_q.state) == MIG_COMMIT) |-> (mig_q.state != MIG_ABORT);
endproperty
a_no_abort_after_commit: assert property (p_no_abort_after_commit);

Architecture. Four properties enforcing §11's ordering: commit only at the commit, commit only when the work is done, the fallback held until then, and no retreat afterwards.

Why the third matters as much as the first. A design can commit correctly and still release the old copy too early — and then a failure between the commit and the retirement leaves no authoritative source. The first three properties together are what make the migration recoverable at every point.

DV. All four always-on. The fourth needs an abort injected in each phase, and the test that matters is the one that tries to abort after the commit and must be refused.

15. The Dirty-Data Complication

The reason migration is not a memcpy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. Address X lives on target A. Its range is being migrated to B.
2. A COMPUTE CHIPLET holds X in its cache, DIRTY.
   -> A's memory holds a STALE value. The newest value is in a cache.
3. The migration copies A's memory contents to B.
   -> B now holds the stale value.
4. The map commits. Reads of X go to B and return the stale value.
5. Later the cache evicts its dirty line — to A, which is no longer the target.
   -> the newest value is written to a device nobody reads.

The newest value has been lost, and not one component did anything wrong.

Where the newest value wasWhat the copy captured
in A's memory✓ the correct value
in a cache, dirty✗ A's stale value
in flight in a write that A has accepted but not applied✗ depends on timing

A memory copy captures what the memory holds. Migration must capture what the system holds (16.1 §18's memory-value against latest-value distinction, with the highest possible stakes).

Two consequences.

Migration of a coherent range requires coherence participation. The dirty holders must be made to write back, or the migration must obtain ownership, before or during the copy. 16.2 is where those mechanisms live, and this chapter does not invent one — it states the requirement.

And the eviction in step 5 must be handled. A dirty line evicted after the commit is addressed to a target that is no longer authoritative. Either the caches are drained before the commit, or the old target must forward late writebacks — and choosing neither is how the failure above happens.

16. Wrong Migration — Copy the Physical Memory Contents

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG for a coherent range — the copy engine reads the old target's memory
// and writes the new one, with no coherence participation.
always_ff @(posedge clk)
  if (copy_active && copy_beat_fire) begin
    write_target(new_target, copy_cursor, read_target(old_target, copy_cursor));
    copy_cursor <= copy_cursor + BEAT_BYTES;
  end

Three properties.

It is correct for a non-coherent range — one that no cache holds, such as a device-private buffer. That is why it gets written: it works, in a test where nothing caches the range.

It silently loses every dirty line. The number of lost values equals the number of cached-dirty lines at copy time, which depends entirely on the workload. A migration performed on an idle system loses nothing; the same migration under load loses data.

And the loss is invisible until read. The migration reports success. The corruption surfaces later as a stale read, with no connection to the migration in any log.

The correct form makes the coherence requirement explicit in the code:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. A coherent range must obtain the authoritative value, not the
// memory's copy. The mechanism belongs to the coherence protocol (16.2).
assign copy_read_ok = tier_q[region_tier].coherent
                    ? coherent_read_authoritative(old_target, copy_cursor)
                    : plain_read(old_target, copy_cursor);

17. SVA — Migration Completeness

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Every address in the range was copied before the commit.
property p_copy_covers_range;
  @(posedge clk) disable iff (!rst_n)
    commit_fire |-> (mig_q.copy_cursor > active_region_q[mig_q.region].limit);
endproperty
a_copy_covers_range: assert property (p_copy_covers_range);
 
// The cursor is monotonic — a restart after a recovery resumes, never rewinds
// past what is already reconciled (Section 37).
property p_copy_cursor_monotonic;
  @(posedge clk) disable iff (!rst_n)
    (mig_q.state == MIG_COPY) |-> (mig_q.copy_cursor >= $past(mig_q.copy_cursor));
endproperty
a_copy_cursor_monotonic: assert property (p_copy_cursor_monotonic);
 
// For a coherent range, the commit requires the coherence precondition to hold.
// The precondition itself is the coherence protocol's (16.2) — this asserts the
// migration WAITED for it, not what it is.
property p_coherent_range_reconciled;
  @(posedge clk) disable iff (!rst_n)
    (commit_fire && tier_q[region_tier].coherent) |-> no_dirty_holders_for_range;
endproperty
a_coherent_range_reconciled: assert property (p_coherent_range_reconciled);
 
// No request is ever accepted with a target that a migration is retiring.
property p_no_accept_to_retiring_target;
  @(posedge clk) disable iff (!rst_n)
    (req_accept_fire && (sel_region == mig_q.region))
      |-> (sel_target != mig_q.old_target) || (mig_q.state < MIG_COMMIT);
endproperty
a_no_accept_to_retiring_target:
  assert property (p_no_accept_to_retiring_target);

Architecture. Four properties: coverage, monotonic progress, the coherence precondition, and no acceptance into a retiring target.

Why the third is written as "waited for", not "achieved". This chapter does not define what makes a range free of dirty holders — that is the coherence protocol's (16.2). What it can and must assert is that the migration did not commit until that condition was signalled, which is the boundary this chapter owns.

DV. The third needs a migration performed with a dirty line outstanding, which is §36's trace and never occurs spontaneously.

18. Completion Is Four Events, Not One

A distinction that becomes load-bearing the moment a tier is durable.

EventWhat is true afterwardsEnough for a volatile tier?Enough for a durable tier?
transport deliveredthe request crossed the linknono
controller acceptedthe memory side owns the obligationpossibly — architecture-definedno
volatile media updateda read would now see the valueyesno
durability achievedthe value survives power lossn/athis is the one

Two consequences.

The gap between events can be large. Controller acceptance and durability may be separated by a substantial interval, and a design that treats them as one has either reported durability it does not have or waited for durability it did not need — a correctness bug in one direction and a performance bug in the other.

And the gap differs per tier. Which is exactly why §20's single done bit cannot work.

19. Tier-Specific Completion Policy

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE completion policy. NOT a standard-defined semantic (Section 3).
// The POLICY is per tier; the EVENTS are per request.
typedef enum logic [POLICY_W-1:0] {
  CPL_ON_CONTROLLER = 'd0,   // controller acceptance discharges the obligation
  CPL_ON_MEDIA      = 'd1,   // a subsequent read must see it
  CPL_ON_DURABLE    = 'd2    // it must survive power loss
} completion_policy_e;
 
function automatic logic semantic_complete(
  input completion_policy_e policy,
  input logic               controller_done,
  input logic               media_done,
  input logic               durable_done
);
  unique case (policy)
    CPL_ON_CONTROLLER: semantic_complete = controller_done;
    CPL_ON_MEDIA:      semantic_complete = media_done;
    CPL_ON_DURABLE:    semantic_complete = durable_done;
    default:           semantic_complete = 1'b0;   // fail closed
  endcase
endfunction

Architecture. One function, three inputs, one policy selector taken from the captured tier of the request (§8). The policy is configuration; the events are per-request; and the function is the only place the two meet.

State. None — a pure function. The policy lives in the tier descriptor and the events come from the memory-side status path.

Cycle behaviour. Combinational, evaluated where the obligation would be retired. default returns false, so an unrecognised policy stalls rather than completing — fail-closed, because the failure mode of completing too early is unbounded and the failure mode of stalling is a visible hang.

Contract. The transaction table retires on this function's output and on nothing else. A design with any other retirement path has a second, unpoliced definition of completion.

Failure. §20. Also deriving the policy from the live tier table rather than the request's captured tier — which changes a request's completion rule underneath it if the configuration commits mid-flight, and is 17.2 §15's recompute bug applied to semantics rather than routing.

DV. Cover all three policies; assert the fail-closed default is unreachable in a correct configuration (§21).

20. Wrong RTL — One Completion Bit Across Tiers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one definition, applied everywhere.
assign txn_done = controller_accepted;      // ← true for one tier, unsafe for another
TierWhat this reportsWhat is actually true
a tier whose architecture completes at controller acceptancecorrectcorrect
a tier requiring the media to be updatedcompletea subsequent read may not see it
a durable tiercompletethe value would not survive power loss

Three properties.

It is correct for exactly one tier and reported identically for all of them. So a system that starts with one tier and later adds another inherits a silent correctness bug from a line that was right when it was written.

The failure is not observable at the interface. The requester is told the operation completed; it acts accordingly; and nothing detects the difference until a power event or a racing read.

And the reverse mistake costs performance, not correctness. A design that hardcodes the strongest policy for every tier is safe and slow — which is a legitimate conservative choice, and should be a stated decision rather than an accident (§19's function makes it one).

21. SVA — Completion Respects the Captured Policy

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. A transaction retires only when its OWN tier's policy is satisfied.
property p_retire_respects_policy;
  @(posedge clk) disable iff (!rst_n)
    txn_retire_fire |-> semantic_complete(
        tier_q[txn_q[retire_id].tier].completion_policy,
        txn_q[retire_id].controller_done,
        txn_q[retire_id].media_done,
        txn_q[retire_id].durable_done);
endproperty
a_retire_respects_policy: assert property (p_retire_respects_policy);
 
// The policy a request uses is the one captured at acceptance, not the live one.
property p_policy_captured_not_live;
  @(posedge clk) disable iff (!rst_n)
    txn_q[IDX].valid |-> $stable(txn_q[IDX].tier);
endproperty
a_policy_captured_not_live: assert property (p_policy_captured_not_live);
 
// A durable tier's descriptor and policy must agree — checked at commit.
property p_durable_tier_has_durable_policy;
  @(posedge clk) disable iff (!rst_n)
    tier_commit_fire |=> (tier_q[TIER_UT].durable
                          |-> (tier_q[TIER_UT].completion_policy == CPL_ON_DURABLE));
endproperty
a_durable_tier_has_durable_policy:
  assert property (p_durable_tier_has_durable_policy);

Architecture. Three properties: retirement respects the policy, the policy is captured, and the configuration is internally consistent.

Why the third fires at the commit. A tier marked durable with a weaker completion policy is a configuration error, and catching it when the configuration is written names the mistake. Catching it via the first property names only its first victim, thousands of cycles later (17.2 §10's argument).

DV. Cover a configuration with tiers of differing policies active simultaneously — which is the only condition under which §20's bug is distinguishable from correct behaviour.

22. Failure Domains Grow With Distance

Memory locationIndependent things that can fail
local mediamedia, local controller
on-package, across a link+ link, transport, remote controller (17.2 §4)
pooled or disaggregated+ fabric, allocation authority, configuration service, other clients

Three consequences.

The failure rate of the whole is the composition, not the best component. A highly reliable memory device reached through a less reliable path is a less reliable memory. Availability is a property of the path, not of the device.

"Other clients" is a failure source, and it is the one people omit. A client that saturates a shared fabric degrades every other client (§28); a client that misconfigures shared state can affect others (§25). Neither is a hardware failure and both are availability events.

And more composability means more distributed configuration state, every piece of which has a lifetime and a commit discipline. The chapters that taught those — 14.4, 17.2, 17.4 — become more relevant as the architecture gets more flexible, not less.

23. Availability Is a State Machine, Not a Bit

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE per-target availability. Five states because five different
// responses are required. Not a normative model (Section 3).
typedef enum logic [2:0] {
  TGT_AVAILABLE   = 3'd0,   // serving normally
  TGT_DEGRADED    = 3'd1,   // serving, at a reduced service class  <- Section 9
  TGT_MIGRATING   = 3'd2,   // serving, but its range is being moved
  TGT_UNAVAILABLE = 3'd3,   // temporarily not serving — WAIT
  TGT_LOST        = 3'd4    // permanently unreachable — explicit failure
} target_avail_e;
 
target_avail_e   avail_q      [NUM_TARGETS];
logic [AGE_W-1:0] unavail_age_q [NUM_TARGETS];   // saturating
StateNew requestsOutstanding requestsExpectations
AVAILABLEacceptedcomplete normallyas configured
DEGRADEDacceptedcomplete, slowermust be re-derived (§9, §10)
MIGRATINGaccepted at the old target until commitcomplete at the old targetunchanged
UNAVAILABLEheldremain live (17.2 §29)unchanged
LOSTfailed explicitlyfailed explicitly, with a reportn/a

Architecture. Five states because there are five distinct correct responses. A design with fewer states must give two situations the same response, and every such merge is a bug in one of the two.

State. One three-bit register and one saturating age per target.

Cycle behaviour. DEGRADED and MIGRATING are serving states — the target still works. UNAVAILABLE is entered on recovery, maintenance or throttle and left on resumption, with the age promoting it to LOST past a bound.

Contract. Admission, the failure policy, the expectation derivation and the migration controller all read this. Four consumers, five states, and each consumer needs a different subset — which is precisely why one bit cannot serve them.

Failure. §24.

DV. Cover all five; cover the promotion from UNAVAILABLE to LOST; confirm outstanding transactions survive UNAVAILABLE and DEGRADED and are explicitly failed on LOST.

24. Wrong RTL — Availability as One Bit

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — one bit for five situations.
assign target_ok = !target_error;

What the design must then choose, once, for all five:

If target_ok means "accept and wait"If target_ok means "fail fast"
a permanently lost target hangs every request forevera target in an ordinary maintenance window fails healthy requests
a degraded target is used with stale expectations (§9)a migrating target's traffic is failed for no reason

Neither column is acceptable, and the design must pick one. That is the whole argument for the state machine: the bit is not imprecise, it is insufficient — it cannot express a distinction the system genuinely has.

25. In a Shared Pool, Mapping Is Access Control

The invariant this section exists for:

Deciding which target an address routes to and deciding whether this client may reach that target are two questions. In a shared memory system they must both be answered, and answering only the first produces a route that is perfectly correct and completely unauthorised.

QuestionAnswered byIf it is skipped
where does this address live?the region map (§8)the request goes nowhere, or to the wrong device
may this client access that region?a permission check (§26)isolation violation with a correct route

Two properties of shared memory that make this unavoidable.

A stale mapping is an access-control hazard, not just a correctness one. If a region is reassigned from client A to client B and A retains a mapping, A's perfectly-formed requests reach B's data. The map is stale; the routing is correct; the isolation is gone.

And the check must be at the authoritative side, not only at the requester. A requester-side check is an optimisation. A requester that has a stale or malicious mapping is exactly the case the check exists for, so the decision has to be made where the resource lives.

26. Wrong RTL — a Valid Mapping Implies Permission

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — routing is treated as authorisation.
assign access_allowed = region_match;      // ← "we found a target, so go"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1. Region 4 is reassigned from client A to client B. The pool's authority
   updates its allocation and B begins using the range.
2. Client A's local mapping is stale — it still names region 4.
3. A issues a read. region_match is TRUE: the region exists and names a target.
4. The read is routed correctly, transported correctly, and served correctly.
5. -> A reads B's data. Nothing failed.

Three properties.

Every mechanism worked. The map matched, the route was right, the transport was clean, the memory returned exactly what it holds. There is no error to report anywhere in the path.

The requester cannot detect it, because from A's point of view the read succeeded and returned data. Only the resource's owner can detect it, and only if it checks.

And the fix is a second, independent decision:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Two decisions, both required. The permission state is owned by
// whatever authority allocates the resource — NOT by the requester's map.
assign access_allowed =
      region_match                                  // WHERE  — Section 8
   && region_perm_q[sel_region][req_domain]         // WHETHER — owned elsewhere
   && (req_perm_epoch == region_perm_epoch_q[sel_region]);  // and NOT stale

The epoch term is what closes the stale-mapping case. A permission granted under one allocation epoch is not a permission under the next. Without it, revocation is not enforceable — it only becomes effective when the client happens to refresh, which is not an enforcement mechanism.

27. SVA — Permission Is an Independent Decision

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Access requires permission, and permission is not derived from the map.
property p_access_requires_permission;
  @(posedge clk) disable iff (!rst_n)
    req_accept_fire |-> region_perm_q[sel_region][req_domain];
endproperty
a_access_requires_permission: assert property (p_access_requires_permission);
 
// A stale permission epoch is refused.
property p_stale_permission_refused;
  @(posedge clk) disable iff (!rst_n)
    (req_offered && (req_perm_epoch != region_perm_epoch_q[sel_region]))
      |-> !req_accept_fire;
endproperty
a_stale_permission_refused: assert property (p_stale_permission_refused);
 
// Revocation takes effect: after a permission epoch advances, no request from a
// revoked domain is accepted.
property p_revocation_effective;
  @(posedge clk) disable iff (!rst_n)
    (perm_commit_fire && !requested_perm[REG_UT][DOM_UT])
      |=> always (!(req_accept_fire && (sel_region == REG_UT) && (req_domain == DOM_UT)));
endproperty
a_revocation_effective: assert property (p_revocation_effective);
 
// A permission check is never satisfied by the routing result alone.
property p_permission_not_derived_from_route;
  @(posedge clk) disable iff (!rst_n)
    (region_match && !region_perm_q[sel_region][req_domain]) |-> !req_accept_fire;
endproperty
a_permission_not_derived_from_route:
  assert property (p_permission_not_derived_from_route);

Architecture. Four properties: permission required, staleness refused, revocation effective, and the route explicitly insufficient.

Why the fourth is not a duplicate of the first. The first says permission was present when accepted. The fourth says the specific bad combination — route matched, permission absent — cannot be accepted, which is the exact shape of §26's bug and is what a reviewer looks for.

Why revocation needs its own property. Granting is easy to get right and revoking is not. A design that checks permission but never advances the epoch has an access-control system that can only add rights, and that is a common and serious hole.

DV. Inject a revocation with a client still holding a stale mapping — §34's coverage bin, and it must be constructed.

28. Capacity Isolation Is Not Bandwidth Isolation

Pooling solves a capacity problem. It can create a performance one.

Isolation kindWhat it guaranteesProvided by
capacityclient A cannot consume client B's allocated spacethe allocation authority
bandwidthclient A cannot consume client B's service ratea scheduling mechanism — and only if one exists
latencyclient A cannot inflate client B's latencya stronger scheduling mechanism

Allocating capacity says nothing about who gets served. A client with a small allocation can saturate a shared fabric and degrade every other client's latency — using only memory it legitimately owns.

Two consequences.

A pool without bandwidth isolation has a noisy-neighbour problem by construction, and no amount of capacity accounting detects it. The symptom appears at the victim, whose own metrics all look normal except that everything is slow.

And the isolation mechanism must be per client, in the shared resource. Per-client counting at the requester is advisory. Enforcement has to happen where the contention is — which is §29.

29. A Per-Client Bandwidth Quota

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE token-bucket quota. Generic mechanism; NO standard is claimed to
// mandate it or any parameterisation of it (Section 3).
logic [TOK_W-1:0] tokens_q  [NUM_CLIENTS];
logic [WIN_W-1:0] refill_q;
 
localparam int TOK_MAX     = 64;   // burst allowance, in service units
localparam int REFILL_RATE = 4;    // tokens added per refill tick, per client
 
always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) begin
    for (int c = 0; c < NUM_CLIENTS; c++) tokens_q[c] <= TOK_MAX[TOK_W-1:0];
    refill_q <= '0;
  end else begin
    refill_q <= (refill_q == REFILL_PERIOD - 1) ? '0 : refill_q + 1'b1;
 
    for (int c = 0; c < NUM_CLIENTS; c++) begin
      // Refill and spend are INDEPENDENT events and can coincide.
      unique case ({(refill_q == REFILL_PERIOD - 1),
                    (grant_fire && (grant_client == c[CLI_W-1:0]))})
        2'b10: tokens_q[c] <= (tokens_q[c] + REFILL_RATE > TOK_MAX)
                              ? TOK_MAX[TOK_W-1:0]
                              : tokens_q[c] + REFILL_RATE[TOK_W-1:0];
        2'b01: tokens_q[c] <= tokens_q[c] - 1'b1;
        2'b11: tokens_q[c] <= (tokens_q[c] + REFILL_RATE - 1 > TOK_MAX)
                              ? TOK_MAX[TOK_W-1:0]
                              : tokens_q[c] + REFILL_RATE[TOK_W-1:0] - 1'b1;
        default: ;
      endcase
    end
  end
 
assign client_eligible = (tokens_q[req_client] != '0);

Architecture. A bucket per client that fills at a rate and drains on service. The bucket size is the burst allowance and the refill rate is the sustained share — two independent knobs, which is why a token bucket is used rather than a simple rate counter.

State. NUM_CLIENTS counters plus one shared refill timer. Per client, in the shared resource (§28) — a bucket at the requester enforces nothing.

Cycle behaviour. Refill and spend are independent events that can occur in the same cycle, so the 2'b11 arm is written explicitly. Two separate if statements lose an update on that cycle, and the bucket drifts — slowly, in one direction, until a client is either permanently throttled or unthrottled.

Contract. The arbiter treats client_eligible as a gate. The guarantee delivered to each client is REFILL_RATE / REFILL_PERIOD of the resource, sustained, with a burst of TOK_MAX — and stating it in those terms is what makes it reviewable.

Failure. §30. Also allowing the bucket to saturate at a value large enough to permit an unbounded burst, which defeats the sustained guarantee for everyone else.

DV. §31's bounds and conservation. Drive one client at maximum and confirm the others still achieve their configured share.

30. Wrong RTL — One Quota for Everyone

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a single shared bucket. It limits TOTAL traffic and isolates nobody.
logic [TOK_W-1:0] tokens_q;
assign client_eligible = (tokens_q != '0);
ClientOffered loadTokens consumedService received
A (aggressive)continuousnearly allnearly all
B (modest)occasionalalmost nonealmost none — it arrives to an empty bucket
C (modest)occasionalalmost nonealmost none

Three properties.

The total is correctly limited, which is what makes it look like it works. Aggregate bandwidth is exactly the configured rate.

And the distribution is the opposite of isolation. The client that asks most often gets most, which is precisely the behaviour a quota is supposed to prevent. A shared bucket is a rate limiter, not an isolation mechanism, and the two are frequently conflated.

The victim has no local evidence. B's requests are simply not granted. Nothing in B's own counters distinguishes "the memory is slow" from "A took my share" — only a per-client view at the shared resource does (§32).

31. SVA — Quota Bounded and Conserved

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Buckets are bounded on both sides.
property p_tokens_bounded(int c);
  @(posedge clk) disable iff (!rst_n)
    (tokens_q[c] <= TOK_MAX);
endproperty
a_tokens_bounded: assert property (p_tokens_bounded(CLI_UT));
 
// Service requires a token — the gate is real.
property p_grant_requires_token;
  @(posedge clk) disable iff (!rst_n)
    grant_fire |-> (tokens_q[grant_client] != '0);
endproperty
a_grant_requires_token: assert property (p_grant_requires_token);
 
// Simultaneous refill and spend nets to the right value (Section 29).
property p_refill_and_spend_consistent(int c);
  @(posedge clk) disable iff (!rst_n)
    (refill_tick && grant_fire && (grant_client == c))
      |=> (tokens_q[c] == min(TOK_MAX, $past(tokens_q[c]) + REFILL_RATE - 1));
endproperty
a_refill_and_spend_consistent:
  assert property (p_refill_and_spend_consistent(CLI_UT));
 
// Isolation, as a bounded liveness claim with assumptions:
//   A1: the shared resource eventually serves a granted request
//   A2: a client with tokens keeps requesting until served
property p_client_served_within_bound;
  @(posedge clk) disable iff (!rst_n)
    (client_req[CLI_UT] && (tokens_q[CLI_UT] != '0))
      |-> ##[1:CLIENT_SERVICE_BOUND] (grant_fire && (grant_client == CLI_UT));
endproperty
a_client_served_within_bound: assert property (p_client_served_within_bound);

Architecture. Bounds, gate enforcement, the simultaneous-event check, and the isolation guarantee as bounded liveness.

The fourth property is the one that means "isolation". Bounds and gates prove the mechanism exists; only the liveness bound proves that a well-behaved client gets served regardless of what other clients do — which is the entire point (§30).

DV. Prove the fourth with one client offering maximum load. Then replace the per-client buckets with a shared one and confirm it fails, which demonstrates that the property is testing what it claims.

32. Observability Across Tiers

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Diagnostic only. Per TIER, per TARGET and per CLIENT — because the three
// questions are different and an aggregate answers none of them.
logic [63:0] tier_requests_q    [NUM_TIERS];
logic [63:0] tier_bytes_q       [NUM_TIERS];
logic [63:0] tier_latency_sum_q [NUM_TIERS];   // /requests -> mean, per tier
logic [63:0] target_unavail_q   [NUM_TARGETS];
logic [63:0] target_degraded_q  [NUM_TARGETS]; // Section 23
logic [63:0] client_granted_q   [NUM_CLIENTS];
logic [63:0] client_throttled_q [NUM_CLIENTS]; // token-starved cycles
logic [63:0] perm_denied_q      [NUM_CLIENTS]; // Section 27
logic [63:0] migration_cycles_q;
logic [63:0] migration_stall_q;                // work held for a quiesce
CounterAnswersWithout it
tier_latency_sum_q per tieris a tier meeting its service class? (§9)tiers are averaged together and describe no real access
client_throttled_qis this client being throttled, or is the memory slow?§30's victim has no evidence
perm_denied_qis something misconfigured, or is something probing?denials are invisible
target_degraded_qhow long has a target been below class?expectations are never re-derived
migration_stall_qwhat does reconfiguration cost?quiesce cost is blamed on the memory

Two properties.

Three different keys, because three different questions. Per tier answers is this service class working; per target answers is this device working; per client answers am I getting my share. An aggregate counter answers none of them, and this is 15.3 §14's per-link argument generalised.

And client_throttled_q is the highest-value counter in the list. It converts §28's invisible failure into a measurement, at the cost of one comparator per client.

33. The Future-Memory Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Verification-only. FIVE models, because this architecture has five kinds of
// state that can independently be wrong.
class future_memory_scoreboard;
 
  // ---- Layer 1: MAPPING model — address to tier/target, per epoch.
  typedef struct {
    int tier;
    int target;
    int epoch;
  } map_model_t;
  map_model_t maps [int][bit [ADDR_W-1:0]];   // [epoch][address] — all epochs kept
 
  // ---- Layer 2: AUTHORITATIVE VALUE model. WHERE the newest value lives,
  //      which is NOT the same as what memory holds (Section 15).
  typedef struct {
    bit [DATA_W-1:0] value;
    int              holder;      // -1 = memory; otherwise a cache agent id
    bit              dirty;
  } authoritative_t;
  authoritative_t authoritative [bit [ADDR_W-1:0]];
 
  // ---- Layer 3: MIGRATION model.
  typedef struct {
    int  region;
    int  old_target;
    int  new_target;
    int  state;
    bit [ADDR_W-1:0] cursor;
    bit  committed;
  } migration_model_t;
  migration_model_t mig;
 
  // ---- Layer 4: PERMISSION model.
  bit perms [int][int];        // [region][domain]
  int perm_epoch [int];        // [region]
 
  // ---- Layer 5: COMPLETION model.
  typedef struct {
    int  policy;
    bit  controller_done;
    bit  media_done;
    bit  durable_done;
    bit  retired;
  } completion_model_t;
  completion_model_t cpl [int];   // keyed by request identity
 
  // ---- Catches Section 13 — split ownership during a migration.
  function void check_no_split_ownership(bit [ADDR_W-1:0] a, int served_by);
    int expect = mig.committed ? mig.new_target : mig.old_target;
    if ((maps[mig_epoch][a].target == mig.region) && (served_by != expect))
      $error("SPLIT OWNERSHIP addr %0h served by %0d, authoritative %0d (mig state %0d)",
             a, served_by, expect, mig.state);
  endfunction
 
  // ---- Catches Section 16 — the copy captured a stale value.
  function void check_migration_captured_authoritative(bit [ADDR_W-1:0] a,
                                                       bit [DATA_W-1:0] copied);
    if (copied !== authoritative[a].value)
      $error("MIGRATION COPIED STALE addr %0h: copied %0h, authoritative %0h held by %0d",
             a, copied, authoritative[a].value, authoritative[a].holder);
  endfunction
 
  // ---- Catches Section 26 — a correct route with no permission.
  function void check_access(int region, int domain, int epoch);
    if (!perms[region][domain] || (epoch != perm_epoch[region]))
      $error("UNAUTHORISED ACCESS region %0d domain %0d epoch %0d (current %0d)",
             region, domain, epoch, perm_epoch[region]);
  endfunction
 
  // ---- Catches Section 20 — retired before the tier's policy was satisfied.
  function void check_completion(int id);
    bit ok;
    case (cpl[id].policy)
      0: ok = cpl[id].controller_done;
      1: ok = cpl[id].media_done;
      2: ok = cpl[id].durable_done;
      default: ok = 1'b0;
    endcase
    if (cpl[id].retired && !ok)
      $error("PREMATURE RETIREMENT id %0d policy %0d (ctrl %0b media %0b durable %0b)",
             id, cpl[id].policy, cpl[id].controller_done,
             cpl[id].media_done, cpl[id].durable_done);
  endfunction
 
endclass

Architecture. Five models, because five kinds of state can independently be wrong: the map, the authoritative value, the migration, the permissions, and the completion.

Layer 2 is the one that makes this chapter's hardest bug detectable. It tracks where the newest value lives, not what memory contains. A model that only tracks memory contents cannot detect §16, because from memory's point of view the copy was faithful — it copied exactly what was there, and what was there was stale.

And every map epoch is retained, for the same reason 17.2 §35 keeps them: a request accepted under one configuration must be checked against that configuration.

34. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_future_memory @(posedge clk);
  option.per_instance = 1;
 
  // --- Tiering (Sections 7-10).
  cp_tier : coverpoint sel_tier { bins each[] = {[0:NUM_TIERS-1]}; }
  cp_tier_mix : coverpoint num_tiers_active {
    bins one = {1}; bins two = {2}; bins many = {[3:$]};   // Section 21 needs >1
  }
  cp_expectation_rederived : coverpoint expectation_rederived_after_commit;
 
  // --- Migration (Sections 11-17).
  cp_mig_state : coverpoint mig_q_state { bins each[] = {[0:6]}; }
  cp_mig_abort_phase : coverpoint abort_injected_in_phase {
    bins quiesce = {1}; bins copy = {2}; bins drain = {3};
    bins after_commit = {4};                 // MUST be refused
  }
  cp_mig_with_dirty : coverpoint dirty_line_present_during_copy;   // Section 15
  cp_mig_access : coverpoint access_during_migration {
    bins none = {0};
    bins read_below_cursor  = {1};
    bins read_above_cursor  = {2};           // Section 13's corruption
    bins write_below_cursor = {3};
    bins write_above_cursor = {4};
  }
  cp_recovery_during_mig : coverpoint link_recovery_during_migration;
 
  // --- Completion (Sections 18-21).
  cp_policy : coverpoint active_completion_policy {
    bins on_controller = {0}; bins on_media = {1}; bins on_durable = {2};
  }
  cp_policy_gap : coverpoint controller_to_durable_gap_class {
    bins zero = {0}; bins short = {[1:16]}; bins long = {[17:$]};
  }
 
  // --- Availability (Sections 22-24).
  cp_avail : coverpoint avail_q_ut { bins each[] = {[0:4]}; }
  cp_avail_promotion : coverpoint unavailable_promoted_to_lost;
 
  // --- Isolation (Sections 25-31).
  cp_perm : coverpoint permission_outcome {
    bins granted = {0}; bins denied = {1}; bins stale_epoch = {2};
  }
  cp_revocation : coverpoint revocation_with_stale_client_map;   // Section 27
  cp_clients : coverpoint num_clients_requesting {
    bins one = {1}; bins some = {[2:3]}; bins many = {[4:$]};
  }
  cp_throttled : coverpoint tokens_q_ut {
    bins empty = {0};                        // the client is being throttled
    bins some  = {[1:TOK_MAX-1]};
    bins full  = {TOK_MAX};
  }
 
  // --- Crosses that carry the information.
  x_mig_access   : cross cp_mig_state, cp_mig_access;      // Section 13
  x_mig_dirty    : cross cp_mig_with_dirty, cp_mig_state;  // Section 15
  x_policy_tier  : cross cp_policy, cp_tier;               // Section 20
  x_clients_thr  : cross cp_clients, cp_throttled;         // Section 30
  x_avail_expect : cross cp_avail, cp_expectation_rederived;
endcovergroup

Six bins worth calling out:

cp_mig_access.read_above_cursor and .write_below_cursor. §13's exact corruption cases. They require an access issued during a copy at a known cursor position, which no random test constructs.

cp_mig_with_dirty. §15 and §16. A migration on an idle system loses nothing, so this bin is the difference between a test that passes and a test that means something.

cp_mig_abort_phase.after_commit. Must be refused, not achieved — a detector bin (§12).

cp_tier_mix above one, crossed with cp_policy. §20's bug is invisible with a single tier, because one done bit is correct when there is one policy.

cp_revocation. §27's third property, requiring a client holding a stale mapping at the moment of revocation.

And cp_throttled.empty crossed with cp_clients.many. §30 — a client at zero tokens while several compete, which is the state in which a shared bucket and per-client buckets behave differently.

35. Flagship Trace 1 — a Correct Migration

Illustrative. Region 4 migrates from target A to target B. Cycle numbers illustrative.

CycMigration stateRegion outstandingMap targetCopy cursorNew reads go toNote
0IDLE3AAsteady
1QUIESCE3Aheldno new accepts for region 4
8QUIESCE1Ahelddraining
14COPY0AbaseAA is still authoritative
20COPY0Abase + 4KAcopy progressing
60COPY0Abase + 64KA
96DRAIN0A> limitAreconciling
104COMMIT0AAcommit conditions checked
105RETIRE0BBatomic, epoch advanced
112IDLE0BBA's copy released

Five readings.

Cycles 14 to 96: the map still says A. Every access during the copy is served by A, which holds the authoritative data throughout. §13's design flips the map at cycle 1 and corrupts everything above the cursor.

The quiesce costs 13 cycles here (1 to 14), waiting for three outstanding requests. That cost is measurable (migration_stall_q, §32) and is routinely mistaken for memory slowness.

The copy runs with zero outstanding requests to the region — which is what MIG_QUIESCE bought, and it is what makes the reconciliation in MIG_DRAIN small.

Cycle 105 is the only cycle in the trace where the target changes. §14's first property is exactly that.

And A's copy is released at cycle 112, seven cycles after the commit. Between 105 and 112 both copies exist. That overlap is deliberate — it is the fallback window (§11).

36. Flagship Trace 2 — a Migration With a Dirty Line

Same migration, with address X cached dirty on a compute chiplet.

CycMigrationX in A's memoryX in a cacheCopy capturedCorrect?
0IDLEV_oldV_new, dirty
14COPYV_oldV_new, dirty
the wrong design (§16)
30COPYV_oldV_new, dirtyV_old✗ stale
105committedV_new, dirtyB holds V_old
140evicted to AB holds V_old✗ newest value written to a dead target
200a read returns V_old✗ silent corruption
the correct design (§16)
20COPYV_oldwrite-back forced
24COPYV_newinvalid
30COPYV_newV_new
96DRAINno dirty holders (§17)
105committedB holds V_new

Four readings.

The wrong design's copy is faithful and wrong. It copied exactly what A's memory held. The failure is in what it chose to read, not in how it read it.

Cycle 140 is the second half of the loss. The cache eventually evicts to A — the old target, now retired — so the newest value is written somewhere nobody reads. Even if someone later noticed B was stale, the correct value is not recoverable from anywhere the system consults.

In the correct design the write-back happens at cycle 20, before the copy reaches X. The coherence mechanism belongs to 16.2; the migration's obligation is to require it and to wait (§17's third property).

And cycle 96 is the check that makes it provable. no_dirty_holders_for_range is a precondition of the commit, so a migration cannot complete with the problem outstanding.

37. Flagship Trace 3 — a Recovery During a Migration

CycMigrationCopy cursorLinkMap targetMust be true
40COPYbase + 32KoperationalA
44COPYbase + 36Kerror detectedA
45COPYbase + 36Krecovery enteredAnothing semantic changes
46COPYbase + 36KquiescingAthe copy pauses; the cursor holds
58COPYbase + 36Krecovered, x8 → x4Acapacity changed only
59COPYbase + 36KoperationalAcopy resumes from the cursor
60COPYbase + 40KoperationalAprogress continues
96DRAIN> limitoperationalA
105RETIREoperationalBone commit, one migration

Five readings.

Cycle 45: the migration does not restart and does not abort. It is a semantic operation; a link event is a transport event (14.2 §4). A design that aborts here has discarded 36K of completed copying for no reason — and worse, has taken an abort path whose correctness depends on nothing having been committed.

The cursor is what makes resumption possible, and §17's monotonicity property is what makes resumption safe: it may not rewind past what has already been reconciled.

Cycle 58: the link returns narrower. The copy will now take longer. The migration's own timeout — if it has one — must be rescaled, or the design aborts a healthy migration on a recovered link (16.5 §27).

The map says A throughout, including across the recovery. The two events are independent and neither may disturb the other.

And there is exactly one commit. A restart-on-recovery design under a flapping link performs the copy repeatedly and may never commit at all — liveness lost to a transport event, which is the failure mode of treating a semantic operation as retryable transport.

38. Debug Taxonomy

SignatureMost likely causeFirst instrument
Data corrupt only during a migration window§13 — the map committed before the copy completedwhen did the target change relative to the cursor?
A migrated range returns stale values, migration reported success§15, §16 — the copy captured memory, not the authoritative valuewere there dirty holders at copy time?
Correct data, large latency regression after a reconfiguration§9, §10 — an expectation not re-derivedwhich bounds derive from the captured tier
A client reads another client's data, everything reports success§26 — routing treated as authorisationis there a permission check, and does it have an epoch?
Revoking access has no effect until the client restarts§27 — the permission epoch never advancesdoes revocation commit an epoch?
One client is slow, its own metrics look normal§28, §30 — a shared quota, or noneclient_throttled_q against client_granted_q
High pooled capacity, poor throughput§28 — capacity isolation without bandwidth isolationper-client grant distribution
Recovery causes a migration to restart from the beginning§37 — a semantic operation treated as retryable transportdoes the copy cursor survive a link event?
A durable tier loses data on power loss despite reported completion§20 — one completion definition across tierswhich policy did the retiring transaction use?
A permanently removed target hangs every request§24 — availability collapsed to one bitis LOST distinguishable from UNAVAILABLE?

Row 2 is the hardest failure in the chapter. A migrated range returns stale values while the migration reported success has no error anywhere in any log, and only a model that tracks where the newest value lives can attribute it (§33's Layer 2).

39. Debug Checklist

  1. Which tier and which target does this address map to, under which epoch? (§8)
  2. Was the tier captured with the request, or read live? (§21)
  3. Which completion policy applies, and which events had fired at retirement? (§19, §21)
  4. Is a migration active for this region, and in which phase? (§12)
  5. Where is the copy cursor relative to this address? (§13)
  6. When did the map target change — and was it at the commit? (§14)
  7. Were there dirty holders for this range during the copy? (§15, §17)
  8. Did any cache evict to the old target after the commit? (§15)
  9. Was the old copy released before or after the commit? (§14)
  10. Did a link retry or recovery occur during the migration? (§37)
  11. Did the copy cursor survive it, or did the migration restart? (§37)
  12. Did the link return degraded, and were bounds rescaled? (§10, §37)
  13. What is the target's availability state, and for how long? (§23)
  14. Was the performance expectation re-derived after the last commit? (§9, §10)
  15. Which client and which domain issued this request? (§25)
  16. Was permission checked independently of the route? (§26, §27)
  17. What permission epoch did the request carry, against the current one? (§27)
  18. How many tokens does this client have, and how long has it been at zero? (§29, §32)
  19. What is the per-client grant distribution at the shared resource? (§30, §32)
  20. Which of the five scoreboard layers diverged first? (§33)

40. Common Misconceptions

"Future memory is mostly about new media technology." Media changes latency, bandwidth, density and persistence. It does not change address ownership, latest-value ownership, identity, ordering, configuration lifetime, completion or recovery — and the engineering that composes a system is almost entirely about those (§4).

"Migration is just remapping an address." It is a five-phase state transfer in which the commit comes fourth, the old target stays authoritative throughout the copy, and the old copy is held as a fallback until the new one is proven. Remapping first corrupts every address above the copy cursor, in three of four access cases, silently (§11, §13).

"A memory copy is enough to migrate a range." A copy captures what memory holds. If a line is cached dirty, memory holds a stale value — so the copy is faithful and wrong, and the newest value is later evicted to a target nobody reads (§15, §16).

"More capacity automatically improves performance." Usable bandwidth is bounded by the path, the outstanding depth and the workload's demand, not by capacity (17.2 §21). Pooling in particular solves a capacity problem and can create a performance one (§28).

"Persistence changes only software." It changes what completion means, and therefore which hardware event may retire an obligation. A single done bit that was correct for a volatile tier silently reports durability it does not have (§18, §20).

"Memory pooling solves bandwidth bottlenecks." It solves capacity allocation. Without a per-client scheduling mechanism in the shared resource, one client can consume the service rate while staying entirely within its capacity allocation — and the victim's own metrics all look normal (§28, §30).

"Transport reliability guarantees migration correctness." Every byte of a migration can cross with clean CRC while the migration copies stale data, commits early, or releases the old copy too soon. Transport correctness is a strictly weaker claim (16.5 §29).

"A shared memory pool is safe if the routing is correct." Routing answers where; authorisation answers whether. A stale mapping produces a perfectly-routed, perfectly-transported, perfectly-served read of another client's data — and only the resource's owner can detect it, and only if it checks (§25, §26).

"One completion definition works for every memory tier." It is correct for exactly one tier and reported identically for all of them, so adding a second tier inherits a silent bug from a line that was right when written (§20).

"Remote memory is just local memory with longer wires." It adds a link, a transport, a remote controller, and — when pooled — a fabric, an allocation authority and other clients. Availability is a property of the path, not of the device (§22).

"Predicting the architecture matters more than knowing the invariants." The architecture will change. The list in §4 is what a design is still judged against afterwards, which is why this chapter is organised around it and names no product.

41. Understanding Check

42. Module 17 Complete — and What Comes Next

Module 17 in five chapters:

17.1The memory chipleta stateful endpoint with scheduled unavailability, not a passive target
17.2Expansionremote capacity in one map, and a stall classifier that never says "memory is slow"
17.3Near-memory computetrading data movement for command movement, and paying for it correctly
17.4HBM integrationmany channels, and the concurrency needed to use them
17.5Future memorynew placements of the same obligations

One sentence holds across all five: memory is not a passive target, and every one of its properties has a lifetime the rest of the system must respect.

Placement changes; obligations do not. Address ownership, latest-value ownership, identity, ordering, configuration lifetime, completion, recovery and observability survive every architecture in this chapter.

Migration is a state transfer with the commit fourth, an old target that stays authoritative throughout, and a copy that must capture what the system holds rather than what memory holds.

Completion is four events, and one bit cannot express a distinction the system genuinely has — which is also why availability needs five states rather than one.

And in a shared pool, mapping is access control and capacity is not bandwidth. Both are places where a perfectly correct route produces a completely wrong outcome.

Module 17 asked what happens when memory becomes the interesting part of the package. Module 18 turns to the other side of the boundary: the compute that consumes all this bandwidth. The next chapter treats an AI accelerator not as a peak-throughput number but as a compute island with local state, local queues, a finite communication surface, and a job whose lifetime the host cannot take back.

Browse the full path on the UCIe tutorials index.