Skip to content
VLSI Mentor

CXL · Module 14

Ownership-Transfer Flows

13.3 established that a handover must overlap rather than abut. This is the message sequence that produces the overlap: five phases, a directory that must not publish early, and a third agent that asks for the line while it is in mid-air.

13.3 established what ownership is — a debt, not a right — and stated the rule for moving it: the responsibilities must overlap, never abut.

This is the message sequence that produces that overlap, and the three things it has to survive: a directory that wants to publish the new owner early, a third agent that asks for the line while it is in mid-air, and a transfer that fails half-way.

1. The Engineering Problem — The Line Is Always Somewhere

A transfer moves data from one cache to another. That part is easy. What makes it hard is that the line has to remain answerable for every cycle of the move.

The old owner must stay liable longer than feels natural. The instinct is to hand responsibility over when the data is sent. That is one message too early: the acknowledgement has not arrived, so the new owner may not have the line, and for the duration of that latency nobody can answer. Section 5 measures the gap this produces.

The directory must not publish the new owner until it can answer. A directory that names the destination when the data leaves points every subsequent lookup at a cache that is still empty. The lookup does not fail — it is answered by an agent with nothing, which is worse.

A third agent is entitled to ask at the worst possible moment. An agent that is neither the source nor the destination reads the line mid-transfer. It can always be served, because the line is always somewhere, and stalling it is a choice rather than a necessity.

And the transfer can fail. A failure must leave exactly one owner, and it must be the old one — the new owner may hold a partial copy and cannot be trusted to answer from it.

2. The One-Sentence Model

Ownership moves by overlapping, not by handing off. Both agents are liable for the interval between the data landing and the old owner releasing, the directory names whoever can actually answer, and a failed transfer leaves the line exactly where it started.

Call it overlap, then release. Every defect in this chapter is a release that happened before the take, or a name published before the thing it names could answer.

3. What This Chapter Owns

GroundOwner
Ownership as a duty; the overlap rule stated13.3
The state space and the legal-edge graph13.4
The read flow and three-party forwarding14.1
The write flow and the upgrade transaction14.2
The transfer as an end-to-end message sequencethis chapter

Deferred:

Deferred groundOwner
Host and device cache interaction14.4
Which transitions fire, in what order, during a flow14.5
Directory scaling and snoop filtersModules 15 and 16
Latency anatomy and bandwidth modellingModule 18

13.3 owns the rule; this chapter owns the sequence. Where that chapter proved a three-phase handover keeps the line answerable, this one builds the five-phase message flow, the directory's publication timing, and the third-party and failure cases it did not reach.

4. Teaching-Model Boundary

Every model below is a teaching model, compiled and simulated with Icarus Verilog 13.0, checked by a testbench whose oracle is structurally different from the design.

What these models are not: a coherency controller. There is no link layer, no credit scheme, no address decode, and no migration policy — the decision to move ownership is somebody else's, and this chapter only executes it.

The conventions from Module 13 and 14.1 carry over. Every checker tests cond !== 1'b1. Unreachable monitors get an extra build of the same source. Comparisons are one source under a parameter, instantiated twice, driven from one stimulus stream. Every displayed value is a captured signal, and every combinational sample is preceded by a settle.

This chapter uses nine parameterised twin buildsRELEASE_ON_SEND, PUBLISH_EARLY, DEFER_ALWAYS, ALWAYS_NEW, ACCEPT_ANY, DROP_THEN_TAKE, ABORT_TO_NEW, QUEUE_AS_ISSUED, TWO_OF_THREE and NO_OVERLAP.

5. RTL 1 — Five Phases, And Who Answers In Each

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The old owner answers until the ACK, not until the send. RELEASE_ON_SEND
  // hands responsibility over one message too early, which opens a gap the
  // length of the acknowledgement latency.
  assign old_still_liable = (ph_q == ASKED) || (ph_q == PREP)
                            || (ph_q == FLIGHT)
                            || ((ph_q == SETTLE) && (RELEASE_ON_SEND == 0));
  assign responder_valid  = old_still_liable || resp_v_q;
  assign gap_err          = read_arrives && !responder_valid;

Reads were driven in every phase, on both builds, from one stimulus stream. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  handover : gaps correct=1 early-release=2 | old liable at settle=1 early=0
             held in prepare=2 in flight=3
             held settling=4

One gap in the correct build, and it was the deliberate one — a read issued before any agent had ever owned the line. The early-release build had more, and the extra ones are the point: the very read that arrives during the settling phase is answered by the correct build and unanswerable in the other.

The three hold checks are worth naming. The flow stayed in preparation until the old owner had stopped writing, in flight until the data was actually sent, and in settling until the acknowledgement arrived. Each of those is an event, and a phase machine that advances on elapsed time passes every test in which the event is prompt.

The oracle is a single "who can answer right now" integer, advanced by events, with no notion of a phase at all — so a bug in the phase encoding cannot appear in the reference.

A sequence diagram with four lifelines: the old owner, the directory, the new owner, and a third agent. The directory asks the old owner to hand the line over. The old owner stops writing and reports that it is prepared. A third agent reads the line during this period and is answered by the old owner. The old owner sends the data directly to the new owner. Another third-party read arrives while the data is in flight and is again answered by the old owner. The new owner acknowledges receipt to the directory. Only then does the directory publish the new owner, and only then does the old owner release its responsibility.The old owner answers until the acknowledgementold ownerdirectorynew ownerthird agenthand it overstopped writingread -- answered bythe OLD ownerthe data, directlyread -- still theold ownerreceived ityou are now theowner
Figure 1 — An ownership transfer. The old owner answers reads through four of the five phases, including the whole time the data is in flight and settling. All message names are descriptive, not specification names.

The data goes from the old owner to the new one directly. The directory is told about the transfer and never carries the line — which is why section 9 has to check that the new owner accepts data only from the agent it was told to expect.

6. RTL 2 — Publishing The New Owner

The directory must name whoever can actually answer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The invariant: whoever the directory names must be able to answer.
  assign points_at_empty_err = lookup && pv_q && (pub_q == new_owner)
                               && !newhas_q;

with the publication gated on the acknowledgement rather than the send:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
      if (data_sent && moving_q) begin
        // PUBLISH_EARLY names the new owner when the data leaves, not when it
        // arrives -- a window in which the directory points at an empty cache.

Lookups were driven before, during and after the transfer. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  publish  : at send correct names 1 early build names 2 | misdirected correct=0 early=1
             lookups=3

At the moment the data leaves, the correct directory still names agent 1 and the early-publish directory already names agent 2 — which has nothing. A lookup in that window is not a miss; it is answered by an empty cache, which is a failure that presents as data corruption rather than as a lookup failure.

Both publication paths are gated on moving_q, a transfer this directory actually started. Without that guard a data message belonging to some other transfer moves the published owner — a defect the testbench found before any mutation ran, because two models shared one data_acked stimulus and the directory reacted to a transfer it had never been told about.

7. RTL 3 — The Third Agent

An agent that is neither source nor destination reads the line mid-transfer:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The line is always somewhere. During a transfer it is with the old owner
  // until the data lands, and with the new one afterwards -- so a third-party
  // read can always be served, and deferring it is a choice rather than a
  // necessity.
  assign server    = (ALWAYS_NEW != 0) ? new_owner
                   : (data_with_new ? new_owner : old_owner);

Measured across three builds:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  third pty: served by agent 1 mid-transfer | defer build deferred=1 starved=1 released=1
             always-new build served from an empty agent=1

The third-party read was served immediately, by the old owner, which still had the data. The DEFER_ALWAYS build stalled it instead — legal, and it produced a request that aged past the starvation threshold before the transfer completed. Deferring is a real design choice with a real cost, and the model measures the cost rather than asserting it.

The ALWAYS_NEW build is the third one, and it exists solely to reach served_by_empty_err. Both other builds name whoever holds the data, so the monitor is unreachable in each — the same pattern 14.1 and 14.2 each needed once.

8. Waveform — Three Cycles With No Answerer

Transcribed from the printed cycle trace of the assembled flow in section 15.

A transfer, against a build that hands over at the send

10 cycles
A transfer, against a build that hands over at the senddata sentdata sentno-overlap build: nobodyno-overlap build: nobodyack: now it is theirsack: now it is theirsclkpreparedsentackedstageidleidleprepprepflightflightsettlesettlesettledonewho0011111111answerblno_ovlpt0t1t2t3t4t5t6t7t8t9
Figure 2 — Transcribed from the printed trace. The correct build keeps the old owner answering through cycles 6, 7 and 8 — the whole settling phase. The no-overlap build releases at the send, and for those three cycles the line has no answerer at all.

Three cycles with nobody able to answer. That is the acknowledgement latency, and on a CXL link it is a round trip rather than three cycles. Over the full trace the correct build recorded 1 unanswerable probe — the deliberate pre-ownership one — against 5 for the no-overlap build.

9. RTL 4 — Where The Data Comes From

The new owner receives a line from an agent it never contacted, so it has to check the sender:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign accept = data_valid && armed_q
                  && ((ACCEPT_ANY != 0) || (data_sender == src_q));
  // Routing the line through the directory doubles the transfer latency for
  // no correctness benefit. It is not an error, it is a cost -- counted.
  assign via_home_err = data_valid && home_carries;

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  data path: wrong_sender=1 accept-any took it=1 via-home=1 accepted=2

The sender check is the same reasoning as 14.1's forwarded read: a cache that held the line a moment ago and has a data message already in flight will deliver a well-formed stale copy, and the only evidence is that it came from an agent nobody named.

via_home_err is deliberately not an error. Routing the line through the directory is correct and slow — it doubles the transfer latency for no correctness benefit — so the model counts it rather than flagging it. Building it as a counter rather than an alarm is the honest choice, and a mutation that turns it into an unconditional flag is killed by a transfer that legitimately took the direct path.

10. RTL 5 — The Duty Moves By Overlapping

13.3 said the responsibilities must overlap. This measures what happens when they do not:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // A dirty line with nobody owing it is 13.3's data-loss condition. During a
  // transfer the safe order is TAKE then DROP; DROP_THEN_TAKE inverts it.
  assign nobody_owes_err = live_q && !old_q && !new_q;

with the faulty build releasing at the send:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
        // DROP_THEN_TAKE releases the old owner when the data is SENT. The new
        // owner does not owe anything until it LANDS, so the interval between
        // the two is a window in which nobody owes the data at all.
        if (data_sent && (DROP_THEN_TAKE != 0)) old_q <= 1'b0;

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  duty     : overlap=1 latched=3 | nobody-owes correct=0 drop-then-take=1
             drop-then-take gap cycles=2

Both agents owed the data during the overlap, and that is safe. Two agents owing one line is not a two-owner failure — it is a period in which either can answer, and 13.3's registry invariant is about who may claim the duty, not about how many agents are discharging it during a handover.

The faulty build produced two cycles in which nobody owed the data, which is 13.3's orphaned-dirty-line condition arriving as a transient rather than as a permanent state. The peak overlap is latched at 3 cycles: a shrinking overlap is what precedes a gap, so the widest one is the safety margin worth watching.

11. RTL 6 — A Transfer That Fails

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // ABORT_TO_NEW leaves the new owner named after a failure, so the line's
  // only recorded holder is the agent that received an incomplete copy.
  assign partial_owner_err = !live_q && ov_q && (own_q == new_owner)
                             && aborted_q;
A transfer is under way. If it completes, the destination becomes the owner and the directory publishes it. If it fails, the correct outcome is rolling back to the source, which is safe because the source still holds the whole line and never gave it up. The wrong outcome is naming the destination, which may hold only part of the line and will answer reads from it. Worse still is clearing both records, which leaves the line with no owner at all.yesnocorrectwrongor worsetransfer underwaydid itcomplete?destinationowns itroll back to thesourcename thedestination:partial copysource stillholds the wholelineclear both:nobody owns it
Figure 3 — What a failed transfer must leave behind. Rolling back to the source is safe because the source never relinquished anything; naming the destination names an agent that may hold half a line. All message names are descriptive, not specification names.

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  abort    : owner after abort correct=1 abort-to-new=2 partial reported=1 orphan=0
             orphaned before any transfer=1

A failed transfer leaves the OLD owner holding the line. The new owner may have received part of it, and a directory that names it is naming an agent that will answer reads from an incomplete copy. Rolling back to the source is the only safe outcome, and it is safe precisely because the source never gave anything up.

no_owner_err is reachable at exactly one moment — before any transfer has ever run, when the line genuinely has no recorded owner. That is a real condition rather than an artefact, and the bench checks it explicitly.

One redundancy was found and removed here. The completion path cleared an aborted_q flag that the start path already cleared, and no stimulus could ever make the second clear load-bearing. It was a provably equivalent mutation pointing at a redundant assignment; the assignment was removed and the mutation replaced with one on the clear that actually matters.

12. RTL 7 — Two Transfers Of One Line

The second transfer's source is whatever the first leaves behind:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
          to_q   <= qto_q;
          // The retarget: the queued transfer starts from wherever the line
          // actually is now, not from where the requester thought it was.
          from_q <= (QUEUE_AS_ISSUED != 0) ? qfrom_q : to_q;

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  serialise: retarget source=1 (queue-as-issued keeps 0) overlap detected=1 stale source=1

The queued transfer was retargeted to source from agent 1 — where the line actually is — while the queue-as-issued build still sources it from agent 0, which let go. That is 14.2's reclassification result in a different guise: a queued request encodes an assumption about a state that the transfer ahead of it has already changed.

stale_source_err had to be rewritten during verification. As first written it tested q_q && !busy_q, a combination the design can never produce — the queue clears in the same cycle the next transfer starts. That is a dead monitor rather than an unreachable one, and the fix was in the condition: checking while the transfer is still queued, which is both reachable and the moment the information is actually useful.

13. RTL 8 — A Transfer Finishes On Three Things

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign complete = live_q && ((TWO_OF_THREE != 0) ? (cnt >= 2'd2)
                                                   : (d_q && a_q && p_q));
  assign partial_complete_err = complete && !(d_q && a_q && p_q);

The data landed, the new owner acknowledged, and the directory published. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  complete : two-of-three declared early=1 partial=1 tag_mismatch=1

The two-of-three build declared the transfer complete with the directory still naming the old owner. Every agent involved believes the transfer happened; the directory does not, so every subsequent lookup goes to an agent that no longer has the line. That is the worst of the three partial states, because the two participants agree with each other and disagree with the system.

All three parts are tag-matched. The publish half is the one usually left unchecked, and a mutation removing that check survives any test that only mismatches the data or the acknowledgement.

14. RTL 9 — What A Transfer Costs, And Whether It Paid

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The payoff: how many local hits the new owner got out of the transfer. A
  // transfer that yields fewer hits than it cost cycles was not worth making.
  assign hits_per_xfer = (n_xfers == 16'd0) ? 8'd0
                       : ({16'd0, n_hits_after} / {16'd0, n_xfers});

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  cost     : xfers=2 mean=50 cycles | stall mean=12 | hits after=20 = 10 per transfer

Ten local hits per transfer, at 50 cycles per transfer. That is the number that decides whether moving the duty was worth it: a transfer that yields two hits has cost more than it saved, and one that yields fifty has paid for itself many times over. Without it, n_xfers measures activity rather than value, and a line ping-ponging between two agents looks identical to one that migrated once and stayed.

The third-party stall counter is separate because it is a cost borne by an agent that had nothing to do with the decision. At a 12-cycle mean it is small; on a design that defers third-party reads for the whole transfer it is the transfer latency.

15. RTL 10 — The Flow Assembled

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  // The old owner answers until the acknowledgement. NO_OVERLAP hands over at
  // the send, leaving the acknowledgement latency with no answerer at all.
  assign answerable = (st_q == IDLE) ? cur_v_q
                    : ((st_q == SETTLE) ? (NO_OVERLAP == 0) : 1'b1);
  assign unanswerable_err = probe && !answerable;

Probes were driven in every phase. Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assembled: unanswerable correct=1 no-overlap=2 | answerable at settle correct=1 no-overlap=0
  holds    : prep=1 flight=2 settle=3 (each waits for its own event)

One unanswerable probe in the correct build across the whole run, and it was the deliberate one before any agent owned the line. Each of the three phases waited for its own event rather than for elapsed time.

16. Quantitative Reasoning

The gap is the acknowledgement latency. Measured at 3 cycles in the trace; on a CXL link it is a full round trip. At the 13.2 figure of a 4-to-8-cycle round trip, releasing at the send opens a gap of that length on every transfer, during which any read of the line has no correct answer.

The overlap is the safety margin, and it is free. Both agents owing the line costs nothing — no extra message, no extra state beyond one bit. The measured peak was 3 cycles. A design that shortens it is trading a margin for nothing, because the transfer's total latency is set by the data movement and the acknowledgement, not by how long the old owner keeps a flag set.

Publishing early buys nothing and costs a window. The directory's publication is not on any critical path — the new owner does not need it to receive the data, and the old owner does not need it to release. Publishing at the send moves it earlier by exactly the acknowledgement latency and creates a window in which every lookup is misdirected.

The payoff test. At a measured 50-cycle transfer and 10 local hits afterwards, the transfer pays for itself if a remote access costs more than 5 cycles more than a local one. Below that threshold the migration is a net loss, and hits_per_xfer is the counter that says which side of it a workload sits on.

A deferred third-party read costs the remainder of the transfer. Measured at a 12-cycle mean stall in this model; on a design that defers for the whole transfer it is the full 50. Serving from whoever currently holds the line costs nothing and is always possible, because the line is always somewhere.

17. Assertions

Presented as SystemVerilog and executed as procedural checkers — see section 19.

The line is answerable in every phase of a transfer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_always_answerable;
  @(posedge clk) disable iff (!rst_n)  (phase != IDLE) |-> responder_valid;
endproperty

The old owner answers until the acknowledgement.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_old_liable_until_ack;
  @(posedge clk) disable iff (!rst_n)  (phase == SETTLE) |-> old_still_liable;
endproperty

The directory never names an agent that cannot answer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_published_can_answer;
  @(posedge clk) disable iff (!rst_n)
    (lookup && published == new_owner) |-> new_owner_has_data;
endproperty

A third-party read is always servable.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_third_party_servable;
  @(posedge clk) disable iff (!rst_n)  req |-> (serve_now || defer_req);
endproperty

Nobody is ever served by an agent with no data.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_no_empty_server;
  @(posedge clk) disable iff (!rst_n)
    (serve_now && server == new_owner) |-> data_with_new;
endproperty

Data is accepted only from the named sender.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_named_sender;
  @(posedge clk) disable iff (!rst_n)  accept |-> (data_sender == expected_sender);
endproperty

Somebody always owes the data during a transfer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_someone_owes;
  @(posedge clk) disable iff (!rst_n)  xfer_live |-> (old_owes || new_owes);
endproperty

A failed transfer leaves the source holding the line.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_abort_to_source;
  @(posedge clk) disable iff (!rst_n)  aborted |-> (owner_after == old_owner);
endproperty

A queued transfer sources from where the line actually is.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_retargeted;
  @(posedge clk) disable iff (!rst_n)
    (queue_starts) |-> (new_source == previous_destination);
endproperty

A transfer completes on all three parts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_three_parts;
  @(posedge clk) disable iff (!rst_n)
    complete |-> (got_data && got_ack && got_pub);
endproperty

Never two transfers of one line at once.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_serialised;
  @(posedge clk) disable iff (!rst_n)  !(busy && queue_running);
endproperty

A probe is never unanswerable during a transfer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
property p_no_unanswerable;
  @(posedge clk) disable iff (!rst_n)  (probe && stage != IDLE) |-> answerable;
endproperty

18. Mutation Testing

88 mutations were injected into the ten models, one at a time, each a single-line change a competent engineer could plausibly write. Every one must make the testbench print RESULT: FAIL.

ModelMutations killed
xfer_flow11 / 11
owner_publish8 / 8
third_party9 / 9
xfer_data_path8 / 8
duty_handoff9 / 9
xfer_abort9 / 9
xfer_serialise10 / 10
xfer_completion9 / 9
xfer_cost6 / 6
xfer_top9 / 9
Total88 / 88

Representative mutations, all killed:

MutationWhat it models
The old owner releases at the senda gap the length of the acknowledgement latency
The new owner answers during the transferan agent answering from data it has not received
The correct build also publishes at the sendthe directory naming an empty cache
A data message for an unknown transfer moves the ownera directory reacting to somebody else's flow
The server is always the new ownerreads served from nothing
The correct build also defers every requesta third party stalled for no reason
Any sender is accepteda stale copy from an agent that let go
The long path is treated as an erroran alarm on a legal, slow routing choice
The correct build also releases at the sendtwo cycles with nobody owing the data
The widest overlap is sampled rather than latchedthe safety margin is gone before anyone looks
The correct build also leaves the new ownera partial copy named as the owner
A new transfer inherits the previous abort flaga completion reported as a failure
The queued transfer keeps its requested sourcesourcing from an agent that let go
Two of three parts retire the transferthe directory disagreeing with both participants
The publish half is not tag-checkedthe wrong transfer published
The correct build also hands over at the sendthree cycles with no answerer

Fifteen mutations survived the first run. None was patched away.

Eight stimulus gaps. The bench never held the flow in its settling phase, never held the assembled flow in preparation, flight or settling, never checked the lookup count, never checked the deferral count, never issued a second start while a transfer was running, and never drove a queued transfer whose recorded source differed from where the line would be.

Three unobserved outputs. The lookup total, the deferral total, and the peak overlap after the handoff completed — the last of which needed care, because the peak was first sampled during the overlap rather than after it, so a later shorter handoff appeared to reduce it when it had not.

Two unreachable checkers. served_by_empty_err needed a third build of third_party, ALWAYS_NEW, because both existing builds name whoever holds the data. This is the third consecutive chapter in which a monitor needed an extra parameter for exactly this reason, and the pattern is now clear: a monitor that watches for "answering from nothing" is unreachable in any design that computes the answerer correctly.

One dead monitor, fixed in the design. stale_source_err tested q_q && !busy_q — a combination the design can never produce, because the queue clears in the same cycle the next transfer starts. No stimulus and no fault-injection hook could reach it. The fix was in the condition: checking while the transfer is still queued, which is both reachable and the moment the information is actually useful. This is the same class as 13.4's three-bit range check — a monitor watching for something that cannot exist, as distinct from one watching for something a correct design does not do.

One provably equivalent mutation, fixed by removing a redundancy. The completion path cleared an abort flag that the start path already cleared, and no stimulus could make the second clear load-bearing — a transfer can only complete after a begin. The redundant assignment was removed and the mutation replaced with one on the clear that actually matters.

One defect found before any mutation ran. Two models shared a data_acked stimulus, and the directory reacted to a transfer it had never been told about — moving the published owner on somebody else's flow. Both publication paths are now gated on a transfer this directory started. That is a real design fix that the shared-stimulus discipline surfaced.

19. Verification Strategy

The oracle must not be the design. Each testbench models the same behaviour in a structurally different representation.

For xfer_flow the design holds a five-value phase register. The oracle holds a single "who can answer right now" integer, advanced by events, with no notion of a phase at all:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  integer o_answerer;   // -1 nobody, else the agent id
  task o_event(input integer ev);   // 0 start 1 sent 2 acked

Note which events move it: start and acked, and not sent. That is the overlap rule expressed as a reference rather than as an assertion, and a design that hands over at the send cannot agree with it.

For duty_handoff the design has one nobody_owes expression; the oracle holds two independent booleans and an "at least one" test.

For xfer_completion the design has one complete expression over three flags; the oracle holds three independent booleans and an explicit AND.

Every displayed value is a captured signal, latched into a named integer before any later event changes it. This chapter needed that twice: the peak overlap, which grows after it is first sampled, and the served-by count, which continues to rise after its assertion.

Delta-cycle discipline. Every sample of a combinational output is preceded by a settle. One check additionally needed an extra clock, because a released deferral is combinationally true in the cycle the transfer ends and the pending flag clears at the following edge.

Coverage recorded: 171 assertion sites across three testbenches; reads driven in all five handover phases on both builds; lookups driven before, during and after publication; third-party reads driven before and after the data landed, across three builds; data driven from the right sender, the wrong sender and via the directory; the duty driven through send, land and release with the faulty ordering compared on the same stimulus; a transfer aborted and one completed, with a second start attempted during each; two transfers queued with and without a stale source; completion driven with each part alone, all three, and mismatched tags in all three directions; and the assembled flow probed in every phase with each phase held.

20. Synthesis and Implementation Reality

The transfer state machine is per line in flight, not per line. A directory tracking millions of lines has a handful of transfer contexts, and the number of them bounds how many migrations can be in progress. Every structure in this chapter is replicated that many times and no more.

The overlap costs one bit. Both agents owing the line is one extra flag on the old owner, held for the acknowledgement latency. That is the entire cost of the property this whole chapter exists to preserve, and it is the cheapest correctness margin in the module.

The publication is a directory write, and its timing is a design decision rather than a constraint. Nothing on the critical path needs it early. Placing it after the acknowledgement costs the acknowledgement latency in staleness and removes the misdirected-lookup window entirely.

The queue is one entry deep here and deeper in production, and every entry needs retargeting when the one ahead of it completes. That retarget is a write to the source field of every queued entry, which at depth becomes a small broadcast — and a design that skips it queues requests that encode assumptions already invalidated.

unanswerable_err and nobody_owes_err are both a handful of gates and both belong in silicon. Each is unreachable in a correct design, so each needs its fault-injection build in the regression before a zero reading means anything.

Reset must leave no transfer in flight. A transfer context that comes out of reset in the settling phase will wait forever for an acknowledgement to a message that was never sent, and its line has an old owner that no longer believes it is liable.

21. Silicon Observability

CounterQuestion it answers
n_gaphow often a read found no responsible agent
max_overlapthe safety margin on the handover, latched
n_gap_cyclescycles in which nobody owed the data
n_misdirectedlookups sent to an agent that could not answer
n_deferred and the deferred agehow long third parties wait for somebody else's migration
n_retargetshow often a queued transfer's source had to be corrected
mean_latencywhat a transfer actually costs
hits_per_xferwhether the transfer was worth making
n_aborts against n_completeshow often migrations fail
n_via_homehow many transfers took the long path

Five error signals belong in silicon. gap_err, points_at_empty_err, served_by_empty_err, nobody_owes_err and partial_owner_err all detect states from which no correct behaviour is possible, and each is a handful of gates over signals that already exist.

hits_per_xfer is the counter that turns migration from a policy into a measurement. A system moving ownership aggressively and a system moving it well look identical in n_xfers; only the payoff distinguishes them. A falling value while transfers rise is a line ping-ponging, and the fix is in the migration policy rather than anywhere in this chapter.

max_overlap is a shrinking-margin detector. The overlap is what stands between a correct handover and a gap. A design whose peak overlap is falling over time — because acknowledgements are arriving faster relative to the release — is approaching the boundary, and the latched peak is the only thing that shows it before it is crossed.

22. Debug Lab

1

A read finds no agent able to answer

HANDOVER-GAP
Symptom

During ownership migrations, occasional reads time out or return a miss for a line that is definitely cached somewhere. The migrations themselves complete successfully.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  handover : gaps correct=1 early-release=2 | old liable at settle=1 early=0
Evidence

gap_err fires when a read arrives with no responsible agent. Check which phase the gap falls in — if it is the settling phase, the old owner released at the send rather than at the acknowledgement.

Likely Causes

Responsibility handed over when the data is sent; a phase machine that advances on elapsed time; an acknowledgement path with more latency than the design assumed.

Debug Sequence

Drive reads in all five phases. The correct build answers in every one; the early-release build cannot answer during settling. Then hold the flow in each phase with its advancing event absent and confirm it does not advance — a machine driven by time passes the first test and fails the second.

Root Cause

The old owner released one message too early. The acknowledgement had not arrived, so the new owner may not have had the line, and for that interval nobody could answer.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign old_still_liable = (phase != IDLE) && (phase != DONE);   // through settling
Prevention

The overlap costs one flag held for the acknowledgement latency. It is the cheapest correctness margin in the module, and shortening it trades a real margin for nothing measurable.

2

Lookups reach a cache that does not have the line

EARLY-PUBLISH
Symptom

A directory lookup returns an agent; the agent reports a miss. This happens only during migrations and only for a few cycles each time.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  publish  : at send correct names 1 early build names 2 | misdirected correct=0 early=1
Evidence

points_at_empty_err fires when a lookup reaches the published owner and that owner does not yet hold the data. Check when the directory publishes — at the send, or at the acknowledgement.

Likely Causes

Publication on the send message; a directory write pipelined ahead of the acknowledgement; a transfer whose acknowledgement was lost, leaving the publication permanently premature.

Debug Sequence

Drive lookups before, during and after the transfer. The correct directory names the old owner until the acknowledgement; the early-publish one names the destination from the send. Then check the publication is gated on a transfer this directory started — a shared data signal from an unrelated flow will otherwise move the published owner.

Root Cause

The directory named an agent that could not answer. The lookup did not fail; it succeeded and reached an empty cache, which presents as data corruption rather than as a lookup failure.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (data_acked && moving_q) begin
  pub_q <= new_owner;          // publish only when the new owner can answer
end
Prevention

Nothing on any critical path needs the publication early. Placing it after the acknowledgement costs the acknowledgement latency in staleness and removes the window entirely.

3

An unrelated agent stalls for the length of a migration

DEFERRED-THIRD-PARTY
Symptom

An agent with no involvement in a migration sees its reads to that line stall for tens of cycles. The migration is between two other agents entirely.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  third pty: served by agent 1 mid-transfer | defer build deferred=1 starved=1 released=1
Evidence

Check n_deferred and the deferred age. A third-party read can always be served, because the line is always somewhere — with the old owner until the data lands, with the new one afterwards. Deferring is a choice.

Likely Causes

A design that blocks all access to a line under migration; a serialisation point that queues everything behind the transfer; a deferral with no release path when the transfer stalls.

Debug Sequence

Drive a third-party read before and after the data lands. The correct build serves from the old owner then from the new one; the defer-always build stalls both and its request ages past the starvation threshold before the transfer completes.

Root Cause

The line was available throughout. The stall was a policy decision, and its cost is borne by an agent that had nothing to do with the migration.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign server    = data_with_new ? new_owner : old_owner;
assign serve_now = req && !defer_req;
Prevention

If deferral is genuinely required, bound it and alarm on the age. An unbounded deferral behind a stalled transfer is a permanent stall for an uninvolved agent.

4

The new owner installs a stale line

WRONG-SENDER
Symptom

After a migration, the new owner serves a value that predates the last write. The transfer reported success and the data arrived on time.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  data path: wrong_sender=1 accept-any took it=1 via-home=1 accepted=2
Evidence

wrong_sender_err compares the sender against the agent the transfer named. A cache that held the line a moment ago and has a data message already in flight delivers a well-formed stale copy, and the sender is the only evidence.

Likely Causes

A new owner that accepts the first data beat for its transfer; an old copy invalidated with data already in flight; a transfer that did not carry the source identity to the destination.

Debug Sequence

Arm the new owner with an expected sender, then deliver data from a different agent. The correct build rejects and stays armed; the accept-any build completes. Separately, deliver data routed through the directory and confirm it is accepted — the long path is a cost, not an error, and flagging it produces an alarm on legal traffic.

Root Cause

The data moves directly between caches, so the destination receives a line from an agent it never contacted. Only the sender identity distinguishes the intended copy from a stale one.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign accept = data_valid && armed_q && (data_sender == expected_sender);
Prevention

Count the routed-through-directory case rather than flagging it. n_via_home rising is a latency finding worth having; an alarm on it would be switched off.

5

A dirty line briefly has nobody responsible for it

DROP-THEN-TAKE
Symptom

Rare data loss during migrations under load. Both agents behave correctly. Neither reports an error.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  duty     : overlap=1 latched=3 | nobody-owes correct=0 drop-then-take=1
             drop-then-take gap cycles=2
Evidence

nobody_owes_err is true whenever a transfer is live and neither agent owes the data. Check the order: does the old owner release on the send or on the release message, and does the new owner take the duty on the land?

Likely Causes

The old owner releasing when it sends rather than when told to; the two updates in different pipeline stages; an eviction of the old copy racing the transfer.

Debug Sequence

Drive send, land and release as separate events. The correct build has both agents owing the line between land and release — which is safe. The drop-then-take build has neither owing it between send and land, and the monitor counts those cycles.

Root Cause

Two agents owing one line is safe; zero agents owing it is 13.3's data-loss condition arriving as a transient rather than a permanent state.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (data_landed)  new_q <= 1'b1;     // take first
if (old_released) old_q <= 1'b0;     // then drop
Prevention

Latch the peak overlap. A shrinking margin is what precedes a gap, and the latched value is the only thing that shows the trend before the boundary is crossed.

6

A failed migration leaves the line with an incomplete copy

ABORT-TO-DESTINATION
Symptom

After a migration that reported a failure, reads of the line return partially-correct data. The directory names an owner and that owner responds.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  abort    : owner after abort correct=1 abort-to-new=2 partial reported=1 orphan=0
Evidence

partial_owner_err fires when the destination is named as owner after an abort. Check what the failure path leaves behind: the source, which never gave anything up, or the destination, which may hold part of a line.

Likely Causes

A failure path that completes the directory update anyway; an abort that runs after the publication; a rollback that clears the transfer context without restoring the ownership record.

Debug Sequence

Begin a transfer and abort it. The correct build leaves the old owner recorded; the abort-to-new build leaves the destination. Then confirm the line is not orphaned — a rollback that clears both is a different and equally bad failure, detected by no_owner_err.

Root Cause

Rolling back to the source is safe precisely because the source never relinquished anything. The destination may have received part of a line and cannot answer from it.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (abort) begin
  own_q  <= old_owner;      // roll back to the source
  live_q <= 1'b0;
end
Prevention

Check that a completion after an abort is not itself reported as partial. The abort flag must be cleared by the next transfer's start, and a flag that persists turns every subsequent success into a false alarm.

7

A queued migration sources from an agent that let go

STALE-QUEUE-SOURCE
Symptom

Back-to-back migrations of one line: the first succeeds, the second fails or returns stale data. Each request was valid when issued.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  serialise: retarget source=1 (queue-as-issued keeps 0) overlap detected=1 stale source=1
Evidence

stale_source_err compares the queued transfer's recorded source against where the line will be when it starts. Check it while the transfer is queued — once it starts, the information is gone.

Likely Causes

A queue that stores requests as issued; a retarget that runs only on the head entry; two transfers accepted for one line without serialisation.

Debug Sequence

Queue a transfer behind a running one whose destination differs from the queued transfer's source. The correct build retargets to source from the running transfer's destination; the queue-as-issued build sources from the original agent, which by then has let go.

Root Cause

A queued request encodes an assumption about a state that the transfer ahead of it has already changed. This is the same shape as 14.2's losing writer, which needs reclassification rather than a retry.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
from_q <= to_q;      // source from where the line actually is now
Prevention

Note that this monitor was dead as first written — it tested a combination the design cannot produce. Check that every "this cannot happen" monitor is watching a condition that is at least representable, distinct from one a correct design merely avoids.

8

Two agents agree a migration happened and the directory does not

PARTIAL-COMPLETION
Symptom

After a migration, lookups are sent to the old owner while the new owner believes it holds the line. Both agents report the transfer as successful.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  complete : two-of-three declared early=1 partial=1 tag_mismatch=1
Evidence

partial_complete_err fires when a transfer is declared complete without all three parts. The data landing and the acknowledgement involve only the two participants; the publication is what makes the rest of the system agree.

Likely Causes

A completion condition counting parts rather than requiring all of them; a publication response dropped and never noticed; a directory write that failed silently.

Debug Sequence

Deliver the data and the acknowledgement but not the publication. The correct build stays outstanding; the two-of-three build retires. Then deliver a wrong-tagged publication — that half is the one usually left unchecked, and a mutation removing its tag comparison survives any test that only mismatches the other two.

Root Cause

The two participants agree with each other and disagree with the system. That is the worst of the three partial states, because nothing either agent can observe reveals it.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign complete = live_q && got_data && got_ack && got_pub;
Prevention

Tag-match all three parts independently and clear all three on retirement. A fresh transfer that inherits a previous one's flags completes on fewer messages than it should.

23. Design Review

The assembled ownership transfer showing the phase machine, the duty overlap, the directory publication, the third-party path, the abort rollback and the answerability invariantfive phaseseach waits its eventdata pathcache to cacheserialiseretarget the queuethe overlapboth liable, brieflypublishonly on the ackabortroll back to sourceinvariantalways answerabledrivesfeedsordersgatesunwinds viapreserves12
Figure 4 — The transfer assembled. The overlap is what keeps the line answerable; the publication is gated on the acknowledgement; and the abort path rolls back to the source, which never gave anything up.

What was built. Ten models: a five-phase handover with a release-on-send twin, a directory publisher with a publish-early twin, a third-party server in three builds, a data-path acceptor with an accept-any twin, a duty tracker with a drop-then-take twin, an abort path with a roll-forward twin, a serialiser with a queue-as-issued twin, a three-part completion with a two-of-three twin, a cost model with a payoff metric, and an assembled flow with a no-overlap twin.

What was measured. One gap in the correct handover — the deliberate pre-ownership one — against more in the early-release build, with all three phases held. A directory naming agent 1 at the send where the early build named agent 2, and one misdirected lookup against zero. A third-party read served immediately by the old owner, while the deferring build's request aged past starvation. Data rejected from an unnamed sender and accepted from the named one, with the routed-through-directory case counted rather than flagged. A 3-cycle latched overlap against 2 cycles of nobody owing the data in the faulty build. An abort leaving the source holding the line while the roll-forward build named a partial copy. A queued transfer retargeted to source from agent 1 where the queue-as-issued build kept agent 0. A completion requiring all three parts. 50-cycle transfers yielding 10 local hits each. Three cycles with no answerer in the no-overlap build.

What would be different in production. The transfer context count bounds concurrent migrations and every structure is replicated per context. The queue is deeper and the retarget becomes a small broadcast across its entries. The migration policy — the decision to move ownership at all — sits above everything here and is where hits_per_xfer actually gets used.

The strongest argument against this design. Keeping the old owner liable through the settling phase means it cannot free its transfer context until the acknowledgement returns, which on a long link is the dominant term in migration throughput. That argument is correct, and the honest response is that the alternative is not "release early" but "overlap on a cheaper structure": the liability is one flag, and it can outlive the full context. Releasing the context while retaining the flag keeps the margin and recovers most of the throughput.

What would be built differently next time. stale_source_err was written before the design it monitors was finished, and it ended up testing a state combination the design cannot produce. Writing a monitor against an intended behaviour rather than against the implemented one is how dead monitors get created — and this is the second chapter in this module to produce one. The discipline that catches it is asking, for every monitor, which stimulus makes this true, before the mutation suite asks the same question less politely.

24. How This Appears In Real Engineering

In a microarchitecture review, the question that separates a specified transfer from an aspirational one is when the old owner stops answering. "When it sends" and "when the acknowledgement returns" are one message apart in a diagram and a full round trip apart in silicon.

In bring-up, n_gap and n_misdirected are the two counters to watch. Both detect windows in which the line is unreachable or misdirected, both are silent everywhere else, and both are cheap if they were designed in.

In a performance investigation, hits_per_xfer is what distinguishes aggressive migration from good migration. A falling value while transfer counts rise is a line ping-ponging between two agents, and the fix is in the policy rather than in the flow.

In a verification plan review, ask whether the transfer has been probed in every phase. The settling phase is the one that separates a correct handover from an early release, and it is the phase a directed test spends the least time in.

In silicon debug, a lookup that reaches an empty cache presents as data corruption rather than as a lookup failure — the directory answered, the agent answered, and the answer was wrong. points_at_empty_err is the only thing that names it.

In a design review of somebody else's transfer path, ask what a failure leaves behind. If the answer is "the destination", the design is naming an agent that may hold half a line.

25. Common Misconceptions

"A handover is a moment." It is five phases, and the line must be answerable in all of them. Measured: the correct build answered reads in every phase; the early-release build could not answer during settling.

"Two agents owing one line is a two-owner failure." It is the overlap, and it is safe. 13.3's registry invariant is about who may claim the duty, not about how many agents are discharging it during a handover. Zero agents owing it is the failure.

"The directory should be updated as early as possible." Nothing on any critical path needs it early, and publishing at the send names a cache that is still empty. Measured: one misdirected lookup in the window.

"A third-party read has to wait for the migration." The line is always somewhere. Measured: served immediately by the old owner before the data landed and by the new one afterwards. Deferring is a policy choice whose cost falls on an uninvolved agent.

"The data can go via the directory." It can, and it doubles the transfer latency for no correctness benefit. That is why the model counts it rather than flagging it — it is a cost, not an error.

"A failed transfer should complete anyway." It must roll back to the source, which never relinquished anything. Naming the destination names an agent that may hold a partial copy.

"A queued transfer can be replayed as issued." Its source is whatever the transfer ahead of it leaves behind. Measured: retargeted to agent 1 where the naive build kept agent 0, which had let go.

"Data landing plus an acknowledgement means the transfer is done." The directory has to agree. Measured: the two-of-three build retired with the directory still naming the old owner — the two participants agreeing with each other and disagreeing with the system.

26. Interview Reasoning

27. Exercises

  1. Calculation. A link has an 8-cycle acknowledgement latency and a system performs 500 migrations. Compute the total exposure a release-on-send design creates, and express it as a fraction of the total migration time at a 50-cycle mean transfer.

  2. Analysis. A system reports 200 transfers with a hits_per_xfer of 3 and a mean transfer latency of 50 cycles. State whether the migrations are paying for themselves, what remote-access penalty would be required to break even, and what the counter would look like for a line that is ping-ponging.

  3. RTL task. Extend xfer_flow to support cancelling a transfer during the flight phase, after the data has been sent but before it is acknowledged. State what the old owner must do, what the new owner must do with data that may already have arrived, and which monitor must change.

  4. Assertion task. Write the property proving the line is answerable in every phase of a transfer. Then explain why it passes trivially on a design that derives answerable from the phase register alone, and what independent source of the answerer is required to make it meaningful.

  5. Design task. Add a second transfer context so two lines can migrate concurrently. State what must be true about the mapping from line to context, and the failure that becomes possible if one line can reach both.

  6. Testbench design. Design the stimulus that distinguishes a handover that overlaps from one that hands off at the send. Explain why any test with a zero-latency acknowledgement passes on both, and state the minimum stimulus that separates them.

  7. Debug task. A directory lookup returns an agent that reports a miss, and the directory's publication logic looks correct in isolation. Give your investigation order, and name the guard whose absence lets an unrelated flow move the ownership record.

  8. Design review. A colleague proposes routing transfer data through the directory so the destination need not validate the sender. Give the strongest version of that argument, then the latency cost, and the reason the sender check is cheaper than the routing.

28. Summary

Ownership moves by overlapping, not by handing off.

  • The old owner answers until the acknowledgement, through four of the five phases. Measured: one gap in the correct build — the deliberate pre-ownership one — against more in the release-on-send build, with three cycles of no answerer on the trace.
  • Two agents owing one line is safe; zero is the failure. The faulty ordering produced 2 cycles in which nobody owed the data, and the peak overlap was latched at 3.
  • The directory publishes on the acknowledgement. At the send the correct directory still named agent 1 while the early build named agent 2 — which had nothing, producing one misdirected lookup.
  • A third-party read is always servable, because the line is always somewhere. Served by the old owner before the data landed and the new one afterwards; the deferring build's request aged past starvation.
  • The data goes cache to cache, so the destination must check the sender. Rejected from an unnamed agent, accepted from the named one, and the routed-through-directory case counted rather than flagged — it is a cost, not an error.
  • A failed transfer rolls back to the source, which never relinquished anything. The roll-forward build named a destination holding a partial copy.
  • A queued transfer is retargeted, not replayed. Sourced from agent 1 where the naive build kept agent 0, which had let go.
  • A transfer completes on all three parts. The two-of-three build retired with the directory still naming the old owner — the participants agreeing with each other and disagreeing with the system.
  • The payoff decides whether it was worth it. 50-cycle transfers yielding 10 local hits each; n_xfers alone cannot distinguish good migration from a line ping-ponging.
  • Verification: 171 assertion sites, 88 of 88 mutations killed, zero surviving. Fifteen first-run escapes were eight stimulus gaps, three unobserved outputs, two unreachable checkers — one needing a third build — one dead monitor fixed in the design, and one equivalent mutation that exposed a redundant assignment. Plus one design defect found before any mutation ran, by the shared-stimulus discipline.

Next: 14.4 Cache-Interaction Flows, which puts a host cache and a device cache on opposite sides of a link and asks what each may do without asking the other.

Continue learning

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.