Skip to content

UCIe · Module 15

Latency Anatomy

Where a transaction's nanoseconds actually go — one-way against round-trip against semantic latency, residence time as queue wait plus service, timestamp instrumentation and why counters from different clock domains cannot be subtracted, the retirement point that flatters the metric, serialisation and width, credit and arbitration and head-of-line stalls, remote service time that dominates, replay and recovery tail latency, timestamp wrap, and a per-stage residence scoreboard.

Chapter 15.1 answered how much work the link can carry. This chapter answers a question that has almost nothing to do with it.

1. The One-Sentence Model

Latency is the sum of residence times at ownership boundaries. The useful question is never "how fast is the link?" — it is "where did the transaction reside, and for how long?"

A transaction does not travel; it is held, repeatedly, by a series of owners. It waits in a queue, is transformed by a stage, crosses a clock boundary, is serialised onto lanes, traverses the package, is executed remotely, and returns through the mirror image of all of it. Every one of those is a residence, and only some of them are improvable.

2. What This Chapter Owns

ChapterQuestion
13.5 — Throughput Optimisationwhat is limiting my rate?
15.1 — Bandwidthhow many bits per second, and how do I measure it?
15.2 — this chapterfor one transaction, where did the time go — and which part can I change?

Specifically new here: the three latencies that get conflated; residence as queue wait plus service time, which is the distinction that makes a decomposition actionable; timestamp instrumentation and the cross-clock-domain trap that makes naive subtraction meaningless; the outstanding-transaction timestamp table and the retirement point that makes metrics look better than the user's experience; timestamp wrap; serialisation and what width degradation does to latency rather than bandwidth; stall attribution by first blocking reason applied to time rather than to cycles; remote service time, which usually dominates and is not the link's fault; replay and recovery tail latency; histograms rather than averages; and a residence scoreboard whose per-stage sum must equal the end-to-end figure.

3. Sourcing

4. Three Different Latencies

These get conflated constantly, and they differ by an order of magnitude or more.

LatencyFromToTypical dominant term
One-way transportthe local stack's ingress boundarythe remote stack's egress boundaryserialisation and package traversal
Round-trip transportrequest leavesresponse returns, excluding remote executiontwice the one-way, plus turnaround
Semantic transactionthe request is accepted from the protocol layerthe response is consumed by the requesterremote service time (§27)

Three consequences.

The semantic figure is the one users experience and the one that is almost never quoted, because it includes work the interconnect does not control.

The difference between round-trip transport and semantic latency is usually large. §34's worked example has 10 ns of transport inside a 90 ns transaction — the transport is 11% of the number the application sees. An optimisation that halves the transport improves the transaction by 5.5%.

And the start and stop events must be stated, not assumed. "Latency" with unstated endpoints is unmeasurable and uncomparable. Every figure in this chapter names its start event and its stop event, and §16 is what happens when the stop event is chosen for convenience rather than for meaning.

5. The Decomposition

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
T_semantic =
    T_protocol_queue      // waiting to be accepted at all
  + T_map                 // transaction → transport object
  + T_tx_queue            // waiting for resources and arbitration
  + T_adapter_tx          // framing, header, CRC generation
  + T_serialise_tx        // object bits ÷ lane bits per cycle  (Section 20)
  + T_package             // physical traversal
  + T_phy_rx + T_adapter_rx + T_check   // deserialise, framing, integrity (14.1)
  + T_remote_decode
  + T_remote_service      // ← usually dominant (Section 27)
  + T_response_path       // the mirror image of the outbound path
  + T_response_queue      // waiting to be consumed locally  (Section 16)

Two properties of the list.

Not every transaction uses every term identically. A posted write has no response path. A read's response path is a full mirror. A coherence transaction may add probe round trips to third parties (§27). The decomposition is a template to instantiate per transaction class, not a fixed formula.

And the terms must be defined so they sum without overlap, which is the same discipline 15.1 §18 applied to bandwidth factors. §37's scoreboard checks the sum against the measured end-to-end figure, and a mismatch means a stage is double-counted or a residence is unaccounted for — an instrumentation bug rather than a finding.

6. One Transaction, Annotated

A protocol layer offers a read request and waits in a local queue before being accepted. The local stack maps it to a transport object, frames it and serialises it onto the lanes. The object traverses the package to the remote stack, which deserialises it, checks integrity and decodes it. The remote media access dominates the total time. Data returns to the remote stack, traverses the package back, is checked and deserialised by the local stack, and finally waits in a response queue before the protocol layer consumes it. The annotations name each residence, and the media access is marked as dominant.A memory read, decomposed by residenceProtocolLocal stackRemote stackMediaoffer: queue waitmap, frame,serialisetraverse packagecheck, decodeissue accessmedia access:DOMINANTdata readytraverse backdeliver: responsequeue
Figure 1 — one memory read, annotated by residence rather than by message. The dominant term is the remote media access, and the transport contribution is a small fraction of what the requester experiences.

Read the figure for proportion rather than for sequence. The two package traversals and the four stack transformations are all real and all small. The single self-message on the media lifeline is larger than everything else combined, and §34 puts numbers on it.

And note the two warning-coloured messages at each end. Those are queue waits — time the transaction spends being owned by nobody's logic, just waiting. They are frequently the largest improvable term, and they are the ones a transport-focused measurement misses entirely.

7. Cycles and Nanoseconds

Latency measured in cycles is meaningless without a frequency, and different stages run at different frequencies.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
time_ns = cycles / frequency_GHz
 
  10 cycles at 2.0 GHz = 10 / 2.0 = 5.0 ns
  10 cycles at 1.6 GHz = 10 / 1.6 = 6.25 ns
  10 cycles at 0.8 GHz = 10 / 0.8 = 12.5 ns

The same cycle count is three different durations. So a decomposition expressed in cycles cannot be summed across stages in different domains — it must be converted to time first, and that requires knowing each stage's frequency.

This is why the verified 2 ns figure carries "based on 16G frequency and a 2G internal clock" (§3). The internal clock is part of the answer. A design with a 1 GHz internal clock does not get 2 ns from the same cycle count, and quoting the figure without the clock assumption transfers an assumption the reader cannot see.

Practical rule. Instrument in cycles locally — it is cheap and exact — and convert to time at the point of comparison or summation, in the testbench or in software, where a real division is free (15.1 §30's rule: hardware counts, software divides).

8. What the 2 ns Figure Excludes

Worth its own section, because the misquotation is so common.

The verified statement is "2ns for round-trip from FDI to bump" — an estimate, at 16G, with a 2 GHz internal clock (§3).

TermIncluded in the 2 ns?
Adapter transmit path from FDIyes
PHY transmit path to the bumpyes
Return path from bump back to FDIyes
Package traversalno — the figure stops at the bump
The far die's PHY and Adapterno
The far die's protocol layerno
Remote execution — memory, coherence lookup, snoopno
Local queueing before acceptanceno
Response queueing before consumptionno
Retry, replay or recoveryno

The figure describes the local stack's contribution, twice, at one rate, as a design target. It is a genuinely useful number for exactly that — and it is not a transaction latency.

Why this matters beyond pedantry. In §34's worked memory read the entire transport contribution is around 10 ns and the transaction is 90 ns. A 2 ns figure quoted as the transaction latency is wrong by a factor of 45, and a system architect who budgets on it will size timeouts, buffers and concurrency pools against a number that cannot occur.

And the useful way to use it. It tells you the local stack is engineered to be small relative to the semantic work at either end — which is a real architectural statement, and it is the justification for §27's conclusion that the interconnect is usually not the latency problem.

9. Residence Is Queue Wait Plus Service

The single most important decomposition in the chapter, because it separates the improvable from the structural.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
residence_at_stage = queue_wait + service_time
Queue waitService time
The transaction iswaiting, owned by nobody's logicbeing worked on
Caused bycontention, resource unavailability, arbitrationthe stage's own pipeline depth
Scales withloadnot load
Improved bymore resources, better scheduling, less offered loadfewer stages, faster logic, wider datapath
At zero load≈ 0unchanged

A slow stage and a congested stage produce the same residence and need opposite fixes. Measuring only residence cannot tell them apart; measuring both terms can.

The discriminating experiment is trivial once the terms are separated: run the same transaction at very low offered load. Service time is unchanged; queue wait collapses toward zero. Whatever remains is structural.

And this is why §14's enqueue and dequeue timestamps are worth the registers. A single timestamp per stage gives residence. Two give the split, and the split is what tells you whether to add a resource or to shorten a pipeline.

10. Timestamp Instrumentation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE latency trace. VERIFICATION AND INSTRUMENTATION STATE — this
// is not protocol state and no UCIe timestamp facility is claimed (Section 3).
//
// One record per tracked transaction, capturing entry to each stage.
typedef struct packed {
  logic                valid;
  logic [ID_W-1:0]     ref_id;       // TEST/REFERENCE id — not a protocol id (Sec 15)
  logic [TIME_W-1:0]   t_accept;     // accepted from the protocol layer
  logic [TIME_W-1:0]   t_tx_queue;   // entered the transmit queue
  logic [TIME_W-1:0]   t_adapter;    // entered the Adapter
  logic [TIME_W-1:0]   t_phy;        // first beat onto the lanes
  logic [TIME_W-1:0]   t_remote;     // remote acceptance observed (if visible)
  logic [TIME_W-1:0]   t_resp_rx;    // response arrived locally
  logic [TIME_W-1:0]   t_complete;   // response CONSUMED by the requester (Sec 16)
} latency_trace_t;

Architecture. One record per transaction, one timestamp per ownership boundary. The boundaries are chosen to match §5's decomposition terms, so each difference is exactly one residence and the differences sum to the total.

State. Per transaction, allocated at acceptance and released at completion (§15). This is instrumentation and it may legitimately be a synthesis-time option — but note that if it is compiled out, none of this chapter's measurements exist in silicon, which is a decision to make deliberately rather than by default.

Cycle behaviour. Each field is written once, on the transition it names. A field written more than once has lost the boundary it was recording.

Contract, and it has two halves. All timestamps must come from one coherent timebase (§11) or the differences are meaningless. And t_complete must mean consumed, not arrived — §16 is the whole of why.

Failure. §12 (mixed timebases), §16 (wrong stop event), §18 (wrap).

DV. Drive a transaction with known injected delays at each stage and confirm each difference matches. That is the only way to establish the instrumentation itself is right, and it must be done before any measurement from it is believed.

11. The Timebase Problem

Two counters in different clock domains produce numbers that cannot be subtracted. Not "approximately" — the difference has no physical meaning at all.

Why, concretely. A counter in a 2 GHz domain advances 2 × 10⁹ times per second. One in a 1.6 GHz domain advances 1.6 × 10⁹ times per second. They measure in different units, and they started at different, unrelated instants. Subtracting them is subtracting metres from seconds.

And the error is not small or bounded. It scales with the elapsed time and with the frequency ratio, so a long transaction produces a wildly wrong figure — and the sign can be negative, which is at least a visible symptom (§40).

Four workable approaches:

ApproachHowCost
One common free-running timestampa single timebase distributed to every instrumentation pointdistribution and synchronisation
Convert locally to timeeach domain converts its own cycles to nanoseconds before reportinga multiply per domain; needs each frequency
Measure within one domain and model the resttimestamp only in the domain that owns the boundary; add modelled terms for the othersthe modelled terms are estimates
Simulation time (verification only)use the simulator's global timenot available in silicon

The fourth is what most verification environments actually do, and it is fine — provided nobody assumes the resulting measurement can be reproduced in silicon with the same instrumentation. A latency measurement that exists only in simulation is a design-verification artefact, not a post-silicon capability, and that gap should be a conscious decision.

Do not build a clock-synchronisation subsystem for instrumentation alone. If a common timebase is not already available, the third approach — measure what you can in one domain, model the rest, and label which is which — is honest and sufficient.

12. Wrong Design — Subtracting Independent Clock Counters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — tx_count is in a 2 GHz domain, rx_count in a 1.6 GHz domain.
// The subtraction has no physical meaning.
assign latency_cycles = rx_count_q - tx_count_q;

What the numbers do, with a 100 ns transaction:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2.0 GHz domain advances 200 counts in 100 ns
1.6 GHz domain advances 160 counts in 100 ns
 
If both counters happened to read 1000 at t = 0:
  after 100 ns:  tx_count = 1200,  rx_count = 1160
  latency_cycles = 1160 − 1200 = −40        ← negative
 
If the counters started at unrelated values, the result is unrelated to
the elapsed time in any way.

Three properties of this bug.

A negative or absurd value is the good outcome, because it is visible. §40 lists it as a signature. The dangerous case is a plausible positive number, which happens whenever the counters' arbitrary initial offset masks the drift — and then the measurement is quietly wrong for the life of the project.

The error grows with elapsed time. Short transactions look nearly right; long ones look wildly wrong. So the bug appears to be "latency measurement is inaccurate for slow transactions", which sends the investigation to the slow path rather than to the timebase.

And it survives review because the code is obviously correct-looking. A subtraction of two timestamps is the right shape. The defect is in a property of the signals that the code does not express — which is the argument for putting the domain in the identifier, exactly as 15.1 §17 argued for units.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The naming discipline that makes the hazard visible at the point of use.
logic [TIME_W-1:0] t_accept_clk2g_q;      // 2 GHz domain
logic [TIME_W-1:0] t_resp_clk1p6g_q;      // 1.6 GHz domain
// A reviewer now sees the mismatch in the subtraction without knowing the design.

13. Queue Wait, Measured

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE queue instrumentation. Two timestamps per entry give the
// SPLIT of Section 9, which one timestamp cannot.
logic [TIME_W-1:0] enq_time_q [QDEPTH];
 
always_ff @(posedge clk)
  if (push) enq_time_q[push_idx] <= now;          // one coherent timebase (Sec 11)
 
// On dequeue, the wait is a MODULAR difference (Section 18).
wire [TIME_W-1:0] queue_wait = now - enq_time_q[pop_idx];
 
// And the age of the OLDEST entry, which is the head-of-line indicator (Sec 26).
wire [TIME_W-1:0] head_age  = now - enq_time_q[head_idx];

Architecture. A timestamp per queue entry, giving the wait for each object and the age of the head. The head age is the cheaper and more useful of the two for live monitoring, because it is a single value that indicates whether anything is stuck.

State. Per queue entry, valid only while occupied.

Cycle behaviour. Written on push, read on pop. The subtraction is modular (§18).

Contract. queue_wait is the queue wait term of §9, and it must not include the stage's service time — so the timestamp is taken at dequeue, not at completion of the stage's work. Getting that boundary wrong merges the two terms and destroys the discriminating experiment.

Failure. Taking the second timestamp at the end of the stage's processing rather than at dequeue, which reports wait + service as wait and makes a slow stage look like a congested one.

DV. Push with the queue empty and a ready downstream and confirm the wait is the minimum. Then hold the downstream and confirm the wait grows by exactly the stall length.

14. The Outstanding-Transaction Timestamp Table

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE latency table. Allocated on SEMANTIC REQUEST, retired on
// SEMANTIC COMPLETION — and Section 16 is why that second word matters.
typedef struct packed {
  logic              valid;
  logic [TIME_W-1:0] start_time;
  logic [STAGE_W-1:0] stage;          // where it is now, for live diagnosis
} latency_entry_t;
 
latency_entry_t latency_q [MAX_IDS];
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    for (int i = 0; i < MAX_IDS; i++) latency_q[i].valid <= 1'b0;
  end else begin
    // Allocate when the SEMANTIC request is accepted — the same event that
    // allocates the transaction tracking entry (Ch 12.4).
    if (semantic_request_accepted) begin
      latency_q[req_id].valid      <= 1'b1;
      latency_q[req_id].start_time <= now;
      latency_q[req_id].stage      <= STAGE_ACCEPTED;
    end
 
    // Retire when the response is CONSUMED, not when it arrives (Section 16).
    if (semantic_response_consumed) begin
      latency_q[resp_id].valid <= 1'b0;
    end
  end
end
 
// The measurement, taken at the consumption event.
wire [TIME_W-1:0] txn_latency = now - latency_q[resp_id].start_time;

Architecture. One entry per outstanding transaction, keyed by the same identity space Module 12 already manages — so the latency table's lifetime is by construction the transaction's lifetime, which is what makes §17's property expressible.

State. Per transaction. Note it is a parallel structure to 12.4's tracking table rather than a field inside it: the tracking table is functional state and the timestamp is instrumentation, and keeping them separate lets the instrumentation be compiled out without touching the datapath.

Cycle behaviour. Allocated on acceptance, retired on consumption. Both events are the semantic ones, matching 12.4's definitions rather than transport-level proxies.

Contract. MAX_IDS must cover the identity space, and the allocation must be driven by the same event that allocates the transaction entry — otherwise the two tables can disagree about whether a transaction exists, and a response can arrive for an identity the latency table never allocated.

Failure. §16, and separately: allocating on transport acceptance rather than semantic acceptance, which excludes the protocol-layer queue wait — the term §6's figure marks as frequently the largest improvable one.

DV. Assert the table's population matches the transaction table's at every cycle (§17). Then verify a known transaction's measured latency against the injected delays.

15. Wrong RTL — Retiring on Response Arrival

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — the entry is retired when the response ARRIVES, not when the
// requester CONSUMES it.
if (response_arrived) latency_q[resp_id].valid <= 1'b0;

What this excludes. Under local backpressure, a response can arrive and then wait — in a response queue, behind other responses, or for the requester to be ready. That wait is part of the latency the requester experiences and is invisible to this measurement.

Worked, illustrative. A transaction whose response arrives at 90 ns and is consumed at 110 ns:

Measured with…Reported latencyRelationship to reality
retirement on arrival90 nsunderstates by 18%
retirement on consumption110 nscorrect

Four properties make this the best instrumentation bug in the chapter.

The metric improves under congestion, which is exactly backwards. More local backpressure means more time between arrival and consumption, all of which is excluded — so the measurement gets better as the user's experience gets worse.

It is undetectable from the metric itself. 90 ns is a plausible latency. Nothing is negative, nothing exceeds a bound, no assertion fires. The only way to notice is to compare against an independent end-to-end measurement, which is §37's scoreboard.

It systematically flatters the interconnect. The excluded term is entirely local queueing, so the measurement attributes none of it to anything — it simply disappears. A team optimising against this metric will never see the response-queue term and will never fix it.

And it is the natural thing to write. response_arrived is a clean, local, unambiguous signal. semantic_response_consumed requires reaching to the requester's interface. The wrong one is easier, which is why it needs an assertion rather than a convention.

The stop event must be the event the user experiences. Anything earlier measures a subset of the latency and reports it as the whole.

16. SVA — Latency Entry Lifetime Matches Transaction Lifetime

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The property that catches Section 15, by tying the instrumentation's
// lifetime to the functional transaction's lifetime (Ch 12.4).
property p_latency_entry_matches_txn;
  @(posedge clk) disable iff (!rst_n)
    (latency_q[ID_UT].valid == txn_table_q[ID_UT].valid);
endproperty
a_latency_entry_matches_txn: assert property (p_latency_entry_matches_txn);
 
// Retirement is driven by CONSUMPTION, never by arrival.
property p_retire_on_consumption;
  @(posedge clk) disable iff (!rst_n)
    $fell(latency_q[ID_UT].valid) |-> $past(semantic_response_consumed);
endproperty
a_retire_on_consumption: assert property (p_retire_on_consumption);
 
// The start timestamp is written once and never disturbed.
property p_start_time_stable;
  @(posedge clk) disable iff (!rst_n)
    (latency_q[ID_UT].valid && !$rose(latency_q[ID_UT].valid))
      |=> $stable(latency_q[ID_UT].start_time);
endproperty
a_start_time_stable: assert property (p_start_time_stable);
 
// And the measured latency is never zero or negative in modular terms —
// a cheap sanity bound that catches Section 12's timebase bug.
property p_latency_positive;
  @(posedge clk) disable iff (!rst_n)
    semantic_response_consumed |-> (txn_latency != '0)
                                && (txn_latency < TIME_W'(MAX_PLAUSIBLE_LATENCY));
endproperty
a_latency_positive: assert property (p_latency_positive);

Architecture. Four properties: the lifetimes match, retirement is on the right event, the start stamp is immutable, and the result is plausible.

Why the first is the strongest. Tying the instrumentation table's population to the functional table's population means the instrumentation cannot be measuring a different set of transactions than the design is tracking. That single equality catches §15, catches allocation on the wrong event, and catches a leaked entry — three bugs with one property.

Why the fourth earns its place despite being crude. MAX_PLAUSIBLE_LATENCY is a loose bound, and that is fine — its job is to catch a timebase error (§12) or a wrap error (§18), both of which produce values that are absurd rather than merely wrong. A loose sanity bound that catches catastrophic errors is worth more than a tight bound that fires on legitimate tail latency.

DV. All four need transactions that actually complete, and the second specifically needs a case where arrival and consumption are separated by backpressure — otherwise the two events coincide and §15's bug is indistinguishable from correct behaviour.

17. Timestamp Wrap

A finite timestamp counter wraps, and the subtraction must be modular.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// CORRECT — modular subtraction. Valid for any true elapsed time under
// 2**TIME_W, which is the constraint below.
wire [TIME_W-1:0] elapsed = now - start_time;      // wraps naturally

The constraint this imposes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
max_transaction_duration  <  2**TIME_W   (in timebase ticks)

Worked, with a 2 GHz timebase:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
32-bit timestamp: 2**32 ticks / 2e9 ticks/s = 2.147 s
  → any transaction under ~2 seconds is measurable      comfortable
 
16-bit timestamp: 2**16 ticks / 2e9 ticks/s = 32.8 µs
  → a transaction taking 40 µs measures WRONG            too narrow

Why 32 µs is not obviously enough. A recovery event can take far longer than a normal transaction (§29), and a transaction can remain alive across it (14.2 §20). So the bound must cover the worst case including a recovery, not the typical case — and that is the sizing argument for TIME_W.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The relationship, checked at elaboration rather than assumed.
initial assert (MAX_TXN_DURATION_TICKS < (1 << TIME_W))
  else $fatal(1, "timestamp width too narrow for the worst-case transaction duration");

The same principle as 14.3 §25's sequence numbers — a finite identity space, a modular comparison, and a bound on how much can be outstanding. Three chapters, three different counters, one rule.

18. Wrong Comparison Across a Wrap

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a magnitude comparison across a wrap.
assign elapsed_wrong = (now > start_time) ? (now - start_time) : 0;

The failure. A transaction starts at 0xFFFF_FF00 and completes at 0x0000_0100 — a true elapsed time of 512 ticks.

Value
start_time0xFFFF_FF00
now0x0000_0100
now > start_time?false
elapsed_wrong0
Correct modular result0x0000_0100 − 0xFFFF_FF00 = 512

The guard reports zero latency for a transaction that took 512 ticks.

And the variant without the guard is worse. A plain now - start_time interpreted as signed gives a large negative number; interpreted as unsigned in a wider variable it gives a huge positive one. Either way the value is absurd, which at least makes it visible — whereas the guarded version above produces a clean, plausible zero that silently pulls the mean down.

The rate at which it happens is the diagnostic. With a 32-bit timestamp at 2 GHz the wrap occurs every 2.1 seconds, so roughly one transaction per 2.1 seconds is mismeasured — a rare, periodic, load-independent anomaly. §40 lists the signature.

19. Serialisation Latency

An object larger than the datapath takes multiple cycles to place on the lanes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
serialisation_cycles = ceil(object_bits / (active_lanes × bits_per_transfer))

Worked, illustrative object size with verified lane and rate figures (15.1 §3):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
object = 68 B = 544 bits                     [verified flit size]
active data lanes = 16                       [verified cluster size]
bits per transfer = 1                        [verified for UCIe-S @ 32 GT/s]
 
serialisation_transfers = ceil(544 / (16 × 1)) = ceil(34) = 34 transfers
 
at 32 GT/s, one transfer per lane takes 1/32e9 s:
  serialisation_time = 34 / 32e9 = 1.0625e-9 s ≈ 1.06 ns

This is a latency term that scales with object size and inversely with width — and it is the mechanism connecting 12.3's beat structure to a time in nanoseconds.

Note what it is not. It is not a bandwidth loss — the lanes are fully busy for all 34 transfers. It is the reason a large object cannot arrive instantaneously, and it is why §20's width argument affects latency and not only throughput.

20. Width Degradation Hurts Latency, Not Just Bandwidth

A commonly missed consequence. 15.1 §34 quantified what width degradation does to bandwidth. It also does this:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
same 68 B object, degraded from 16 to 8 active lanes:
 
  x16: ceil(544 / 16) = 34 transfers → 34 / 32e9 = 1.06 ns
  x8:  ceil(544 /  8) = 68 transfers → 68 / 32e9 = 2.13 ns
 
  serialisation latency DOUBLES

So width degradation is not purely a throughput event. Every object takes twice as long to place on the lanes, which:

  • adds directly to one-way latency for every transaction;
  • lengthens the credit round trip in time (13.1 §4), because the object occupies the link longer;
  • lengthens the acknowledgement round trip (14.3 §26), which is what the replay window is sized against;
  • and increases head-of-line blocking duration (§26), because a blocked head occupies the datapath for longer.

Width degradation halves the ceiling and doubles the serialisation time. The second effect is the one that surprises people, and it propagates into every rate × latency pool in Module 13.

21. Rate Reduction

Lower transfer rate means each transfer takes longer, so serialisation time rises proportionally:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
same 68 B object, x16, at three verified rates:
 
  64 GT/s: 34 / 64e9 = 0.53 ns
  48 GT/s: 34 / 48e9 = 0.71 ns
  32 GT/s: 34 / 32e9 = 1.06 ns

And the trade is the one 14.4 §24 established. Lower rate costs latency and bandwidth and buys margin — the verified BER targets are 10⁻¹⁵ at 48 GT/s against 10⁻¹² at 64 GT/s (§3), three orders of magnitude better at the lower rate.

Which matters for latency in a non-obvious way. A lower error rate means fewer retries, and a retry costs far more latency than the rate reduction does (§28). So for a marginal channel, dropping the rate can reduce tail latency while increasing mean serialisation time — the two move in opposite directions, and which one the system cares about determines the right choice.

22. Pipeline Latency Against Throughput

Reinforcing 13.5 §9 because it is the most misapplied idea in performance work.

Adding a pipeline stage adds one cycle of latency and may reduce the cycle time. Whether latency in nanoseconds gets better or worse depends entirely on how evenly the path splits.

Single-cycleFour-stage, balancedFour-stage, unbalanced (70% in one)
Latency in cycles144
Cycle timeTT/40.7T
Latency in timeTT — unchanged2.8T — much worse
Throughput1 per T4 per T1.43 per T

So "more stages always hurts latency" is false, and "more stages never hurts latency" is also false. The quality of the split is the whole answer, and a design review should ask where the timing actually is rather than counting registers.

23. Credit Stall Latency

Time spent waiting for permission is a resource wait, not serialisation or service — and it must be attributed separately or it is invisible.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE per-reason stall time, accumulated per transaction.
// EXACTLY ONE bucket accrues per stalled cycle — the same partition discipline
// as Chapter 13.5 Section 34, applied to time rather than to cycles.
logic [TIME_W-1:0] stall_credit_ticks_q  [MAX_IDS];
logic [TIME_W-1:0] stall_replay_ticks_q  [MAX_IDS];
logic [TIME_W-1:0] stall_arb_ticks_q     [MAX_IDS];
logic [TIME_W-1:0] stall_queue_ticks_q   [MAX_IDS];
 
always_ff @(posedge clk) begin
  if (latency_q[ID_UT].valid && !advancing[ID_UT]) begin
    unique case (1'b1)
      !credit_available : stall_credit_ticks_q[ID_UT] <= stall_credit_ticks_q[ID_UT] + 1;
      !replay_space     : stall_replay_ticks_q[ID_UT] <= stall_replay_ticks_q[ID_UT] + 1;
      lost_arbitration  : stall_arb_ticks_q[ID_UT]    <= stall_arb_ticks_q[ID_UT]    + 1;
      default           : stall_queue_ticks_q[ID_UT]  <= stall_queue_ticks_q[ID_UT]  + 1;
    endcase
  end
end

Architecture. Per-transaction, per-reason time accumulation with a priority-encoded single-bucket rule.

Contract, and it is the section's point. unique case (1'b1) takes the first true condition, so exactly one bucket accrues per stalled cycle and the buckets partition the transaction's stall time. §31 is the argument for the ordering. Counting every true condition would make the buckets sum to more than the residence, and §37's sum check would fail — correctly.

Failure. Overlapping accumulation (above), or accumulating while the transaction is advancing, which merges service time into stall time and destroys §9's split.

DV. Stall a transaction on exactly one resource and confirm only that bucket accrues, then confirm the sum of buckets plus advancing cycles equals the residence.

24. Arbitration Latency

Time between being eligible and being granted is scheduler delay, and it is distinct from queue occupancy.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
arbitration_wait = t_granted − t_became_head_and_eligible

Why the two timestamps must be different events. A transaction at the head of a queue with all its resources available is eligible. If it is not granted, the delay is the arbiter's. If it is not at the head, the delay is queue occupancy — a completely different problem with a completely different fix.

Measuring the head's eligible-to-grant time separates them, and 13.4 §17's rotating-priority arbiter is what bounds it: with N classes and one grant per cycle, an eligible head is granted within N grant opportunities.

And official material provides one verified arbitration-style latency bound worth citing, from a different mechanism. UCIe 3.0 bounds sideband priority insertion at "48 UI (8UI to get to the boundary + 8UI SB clock low to switch to high priority + 32UI of transfer) at 800MHz or 60ns" (§3). Check the arithmetic:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
48 UI at 800 MHz:  48 / 800e6 = 60e-9 s = 60 ns          ✓ matches the stated figure

The design lesson is the form of the statement rather than the number. A preemption or arbitration mechanism is valuable in proportion to how tightly its worst case can be stated — and UCIe states its worst case as a decomposed sum whose arithmetic a reader can verify. That is the standard a latency bound should meet.

25. Head-of-Line Latency

A blocked older entry delays unrelated younger ones, and the delay belongs to neither the blocked object nor the scheduler.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE head-of-line blocking time. Accrues when the head cannot
// advance AND something behind it could have.
logic [TIME_W-1:0] hol_block_ticks_q;
 
always_ff @(posedge clk)
  if (head_blocked && any_non_head_ready)
    hol_block_ticks_q <= hol_block_ticks_q + 1;

Architecture. One counter with a two-term condition. Both terms are necessary: a blocked head with nothing behind it is not head-of-line blocking, it is simply a stall.

Contract. This is a queue structure metric, not a scheduling one. 13.2 §15 established that the fix is separate queues rather than a cleverer scheduler — so a high value here points at partitioning, and no amount of arbitration tuning will help.

Why it belongs in a latency chapter. Head-of-line blocking shows up as queue wait on transactions that had no resource problem at all. Without this counter, that time is attributed to the queue generically, and the conclusion is "make the queue deeper" — which makes it worse (13.4 §28's bufferbloat).

DV. Block the head while a younger entry is ready and confirm the counter accrues; block the head with an empty queue behind it and confirm it does not.

26. Remote Service Latency

The term that usually dominates, and the one the interconnect does not control.

Worked, illustrative service times with the verified transport anchor. Using §8's observation that the local stack is engineered to be small:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE decomposition of one memory read:
 
  local stack out (map, frame, serialise)      ≈  3 ns   [ILLUSTRATIVE]
  package traversal                            ≈  1 ns   [ILLUSTRATIVE]
  remote stack in (deserialise, check, decode) ≈  3 ns   [ILLUSTRATIVE]
  ─────────────────────────────────────────────────────
  transport, outbound                          ≈  7 ns
 
  MEDIA ACCESS                                 ≈ 80 ns   [ILLUSTRATIVE]
 
  transport, return (mirror of outbound)        ≈  7 ns   [ILLUSTRATIVE]
  ─────────────────────────────────────────────────────
  TOTAL                                        ≈ 94 ns
 
  transport fraction = 14 / 94 = 15%
  media fraction     = 80 / 94 = 85%

Three conclusions from the proportion, and they hold for any plausible substitution of these illustrative numbers.

Halving the transport improves the transaction by 7%. Eliminating it entirely improves it by 15%. A latency budget that treats the interconnect as the problem is optimising the smaller term — and that is why §8 insists on not misquoting the 2 ns figure as a transaction latency.

The media term is not the interconnect's fault and must not be attributed to it. A system-level latency complaint routed to the interconnect team when 85% of the time is in the media wastes everyone's effort. The decomposition is what routes it correctly.

And coherence can be worse. A CXL.cache transaction may add an ownership lookup, probes to third parties, and their responses (11.3) — each of which is its own round trip. Coherence latency can exceed link latency by a large multiple, and the transport contribution shrinks further. This chapter does not re-teach that flow; it notes that the decomposition template of §5 must be instantiated per transaction class, and a coherence class has more terms.

27. Replay Tail Latency

A retry is rare and expensive, which is the definition of a tail-latency source.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE, using the transport figures of Section 26:
 
  normal transaction                            ≈ 94 ns
 
  with one retry:
    + detection at the receiver                 ≈  2 ns   [ILLUSTRATIVE]
    + acknowledgement / retry indication return  ≈  7 ns   [ILLUSTRATIVE]
    + replay scheduling                          ≈  2 ns   [ILLUSTRATIVE]
    + retransmission (a full outbound transport) ≈  7 ns   [ILLUSTRATIVE]
  ─────────────────────────────────────────────────────
  transaction with one retry                    ≈ 112 ns   (+19%)

Now the frequency, from verified figures. At the UCIe 3.0 target BER of 10⁻¹² at 64 GT/s (§3), and following 15.1 §23's calculation, a 16-lane cluster sees roughly 1.02 bit errors per second.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if transactions arrive at 10e6 /s and ~1 per second is retried:
  fraction retried ≈ 1 / 10e6 = 1e-7
 
effect on the MEAN:
  mean ≈ 94 + (1e-7 × 18) ≈ 94.0000018 ns        → utterly negligible
 
effect on the TAIL:
  the 1-in-10-million transaction takes 112 ns   → +19% for that transaction

The mean moves by two parts in a hundred million. The affected transaction is 19% slower. Averages cannot see this, and it is exactly what a tail-latency requirement is about.

Two consequences.

Timeout sizing must accommodate the tail, not the mean. 12.4's timeout must exceed the worst plausible latency including retries and possibly a recovery (§29) — a timeout sized on mean latency will fire on healthy traffic.

And a channel not meeting its BER changes the arithmetic completely. If the retried fraction rises to 1%, the mean moves by 0.18 ns and the P99 becomes the retry case. The tail is where a degrading channel shows up first, long before the mean or the bandwidth notices.

28. Recovery Latency

Recovery is orders of magnitude larger than a retry, and a transaction can survive it.

EventIllustrative scaleChapter
one retrytens of ns14.3
a link recoveryquiesce + retrain + validate + commit — potentially µs or more14.2

Official material gives one relevant verified anchor: UCIe 1.0 describes "very fast entry and exit times (sub-ns vs multiple micro-seconds for SERDES based designs)" for its power-bandwidth curve (§3). That is about power-state transitions rather than about error recovery, and it is quoted here only to indicate that UCIe's fast-transition claims are in the sub-nanosecond regime for that mechanism — no recovery duration is published, and none is invented.

The two measurements to keep separate:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
recovery_duration          — how long the link was not carrying traffic
transaction_extension      — how much longer a SPECIFIC transaction took

They are not the same number, and conflating them is a real error. A transaction that was already waiting on remote service when a recovery began may be extended by less than the recovery duration; one that was mid-transport may be extended by more, because it also pays the replay. 14.2 §20's five in-flight states each have a different extension, and only per-transaction instrumentation can distinguish them.

And this is the strongest argument for §17's timestamp width. A transaction alive across a recovery can have a duration in microseconds. A 16-bit timestamp at 2 GHz wraps in 32.8 µs — narrow enough that a recovery-spanning transaction may be mismeasured, which is precisely the case a debugger most needs to be correct.

29. Histograms, Not Averages

An average latency hides everything that matters.

DistributionMeanWhat the mean hides
every transaction 94 ns94nothing — but this never happens
99% at 90 ns, 1% at 490 ns94a 1% population 5.4× slower
uniform 50–140 ns94wide variance, unpredictable

All three have the same mean and describe completely different systems.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY latency histogram. Buckets, not statistics — the analysis
// belongs in the testbench or in software.
int unsigned lat_hist[NUM_BUCKETS];
 
function automatic int bucket_of(int unsigned ticks);
  // Logarithmic-ish bucketing keeps the tail visible without many buckets.
  if (ticks <   64) return 0;
  if (ticks <  128) return 1;
  if (ticks <  256) return 2;
  if (ticks <  512) return 3;
  if (ticks < 1024) return 4;
  return 5;                       // the tail bucket — the one that matters
endfunction
 
always @(posedge clk)
  if (semantic_response_consumed) lat_hist[bucket_of(txn_latency)]++;

Architecture. Buckets rather than a mean, with coarser buckets toward the tail so that a small tail population remains visible rather than being averaged away.

Contract. The final bucket must be open-ended and must be reported even when it contains one entry. A histogram whose top bucket is empty in every run is either well-provisioned or has a threshold set too high to be useful — and only reporting the count distinguishes them.

Why this is verification-only. It needs a divide-free bucketing function and an array of counters; that is cheap. What is not cheap and not necessary is computing percentiles in hardware. Hardware buckets; software computes P99.

30. Tail Latency, and What It Is For

P99 and P99.9 matter for four concrete engineering decisions:

DecisionWhy the tail, not the mean
Timeout sizing (12.4)a timeout must exceed the worst plausible latency or it fires on healthy traffic
QoS commitmentsa latency guarantee is a statement about the tail by definition
Coherence stallsa core blocked on a coherence transaction stalls for the actual latency, not the mean
Buffer and window sizingrate × latency pools must cover the latency they will actually see (13.5 §14)

The third row is the one with the largest system consequence. A processor waiting on a remote line is stalled for that transaction's latency. A P99.9 of 5× the mean means one access in a thousand stalls five times as long — and at millions of accesses per second, that is thousands of long stalls per second, which is visible as jitter in the application.

Do not over-elaborate the statistics. The four decisions above need the tail's magnitude and frequency, which a histogram provides directly. Percentile estimation beyond that is analysis, not instrumentation, and building it in hardware is the kind of complexity §29's rule exists to prevent.

31. Stall Attribution — First Blocking Reason

When several resources are unavailable simultaneously, one bucket must own the cycle (§23), and the priority ordering is a design decision.

The ordering used in §23, and why:

PriorityBucketReasoning
1creditremote resource, longest feedback loop, most expensive to fix — surface it first
2replay spacelocal but tied to a remote confirmation (14.3 §26)
3arbitrationcontention, meaningful only when resources exist
4queuelocal capacity, cheapest to change

Two properties, the same as 13.5 §35 established for cycles.

It is ordered by remoteness and cost of remedy, not by frequency. When credit and queue space are both unavailable, "credit-limited" is the more actionable report because the credit loop is longer and its fix takes longer to validate.

And the attribution must be re-measured after every change, because fixing the top-priority stall reveals the next. A transaction that is 40% credit-stalled does not become 40% faster when credits are added — it becomes replay- or queue-stalled at some new fraction. That is what "optimisation moves the bottleneck" means in the latency domain.

When overlap measurement is genuinely wanted — "how often were credits and the queue both blocking?" — that is a separate, deliberately overlapping counter set with a different name. Mixing the two purposes in one set destroys both, because the partitioning set's sum check (§37) would no longer hold.

32. Worked Memory-Read Decomposition

One fully specified illustrative case. Clock frequencies and stage delays are illustrative; the rate, lane count and flit size are verified (15.1 §3).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
CONFIGURATION
  transfer rate      = 32 GT/s per lane          [VERIFIED]
  active data lanes  = 16                        [VERIFIED cluster size]
  bits per transfer  = 1                         [VERIFIED for UCIe-S @ 32 GT/s]
  object size        = 68 B = 544 bits           [VERIFIED flit size]
  local clock        = 2.0 GHz                   [ILLUSTRATIVE — cf. the 2G clock in Section 3]
  remote clock       = 1.6 GHz                   [ILLUSTRATIVE]
 
STAGE-BY-STAGE  (cycles → ns, converted per Section 7)
 
  1. protocol queue wait      4 cy @ 2.0 GHz  =  2.00 ns   [ILLUSTRATIVE]
  2. map to transport object  2 cy @ 2.0 GHz  =  1.00 ns   [ILLUSTRATIVE]
  3. tx queue + arbitration   3 cy @ 2.0 GHz  =  1.50 ns   [ILLUSTRATIVE]
  4. adapter: frame + CRC     2 cy @ 2.0 GHz  =  1.00 ns   [ILLUSTRATIVE]
  5. serialise 544 b / 16 ln  34 transfers @ 32 GT/s = 1.06 ns   [DERIVED, Section 19]
  6. package traversal                          =  0.50 ns   [ILLUSTRATIVE]
  7. remote deserialise+check 3 cy @ 1.6 GHz  =  1.88 ns   [ILLUSTRATIVE]
  8. remote decode + issue    2 cy @ 1.6 GHz  =  1.25 ns   [ILLUSTRATIVE]
  9. MEDIA ACCESS                               = 80.00 ns  [ILLUSTRATIVE]
 10. remote frame response    2 cy @ 1.6 GHz  =  1.25 ns   [ILLUSTRATIVE]
 11. serialise response (64 B data + framing ≈ 68 B) = 1.06 ns  [DERIVED]
 12. package traversal back                     =  0.50 ns   [ILLUSTRATIVE]
 13. local deserialise+check  3 cy @ 2.0 GHz  =  1.50 ns   [ILLUSTRATIVE]
 14. response queue wait      2 cy @ 2.0 GHz  =  1.00 ns   [ILLUSTRATIVE]
 
TOTAL
  2.00 + 1.00 + 1.50 + 1.00 + 1.06 + 0.50 + 1.88 + 1.25
       + 80.00 + 1.25 + 1.06 + 0.50 + 1.50 + 1.00
  = 96.50 ns
 
DECOMPOSITION BY CATEGORY
  queue wait (1, 3, 14)        =  4.50 ns   →  4.7%
  local stack (2, 4, 13)       =  3.50 ns   →  3.6%
  serialisation (5, 11)        =  2.12 ns   →  2.2%
  package (6, 12)              =  1.00 ns   →  1.0%
  remote stack (7, 8, 10)      =  4.38 ns   →  4.5%
  MEDIA (9)                    = 80.00 ns   → 82.9%
  ─────────────────────────────────────────
  TOTAL                        = 96.50 ns   → 100.0%   ✓ sums correctly

Four readings, and the check at the end is not decoration.

The categories sum to the total, which is the evidence the decomposition has no gaps and no double-counting. §37's scoreboard automates exactly this check, and a failure means instrumentation rather than performance.

The media term is 83%. Everything the interconnect controls — local stack, serialisation, package, remote stack — is 11.3% combined. Halving all of it improves the transaction by 5.7%.

Queue wait is 4.7% and is the largest thing the interconnect can actually reduce. More than the local stack, more than serialisation, more than the package. And it is the term that scales with load, so under congestion it grows while everything else stays fixed.

And note that the two clock domains are converted before summing (§7). Stages 7, 8 and 10 are at 1.6 GHz; the rest are at 2.0 GHz. Adding the raw cycle counts would give 22 "cycles" and no valid time at all — which is §12's bug arriving through arithmetic rather than through hardware.

33. Flagship Trace

Illustrative. 2 GHz local clock, so one cycle is 0.5 ns. A read with a 2-cycle credit stall, an 8-cycle remote service and a 1-cycle response-queue stall.

CycStageAdvancing?Stall reasonAccrued toNote
0acceptedt_accept latched; entry allocated
1mapyeslocal stack
2tx queuenono creditcredit13.1
3tx queuenono creditcredit2 cycles = 1.0 ns
4tx queueyesqueuecredit returned; granted
5adapteryeslocal stackframe + CRC
6serialiseyesserialisationfirst beat onto lanes
7serialiseyesserialisation
8in flightyespackage
9remote rxyesremote stackdeserialise + check
10remote decodeyesremote stack
11–18mediayesremote service8 cycles = 4.0 ns — dominant
19remote frameyesremote stack
20in flightyespackage
21local rxyeslocal stackcheck passes (14.1 §19)
22response queuenorequester not readyqueuearrival ≠ completion (§15)
23consumedt_complete latched; entry retired

The arithmetic, in one place:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
end-to-end = 23 − 0 = 23 cycles @ 2 GHz = 11.5 ns
 
by category:
  credit stall      2 cy = 1.0 ns   →  8.7%
  queue             2 cy = 1.0 ns   →  8.7%   (cycles 4, 22)
  local stack       4 cy = 2.0 ns   → 17.4%   (cycles 1, 5, 21, and 23's edge)
  serialisation     2 cy = 1.0 ns   →  8.7%
  package           2 cy = 1.0 ns   →  8.7%
  remote stack      3 cy = 1.5 ns   → 13.0%
  remote service    8 cy = 4.0 ns   → 34.8%
  ────────────────────────────────────────
  sum              23 cy = 11.5 ns  → 100%    ✓ matches end-to-end

Five readings.

Cycles 2–3 are a credit stall, attributed to credit and not to the queue. Without §23's per-reason attribution these two cycles would be "queue wait", and the conclusion would be "make the queue deeper" — which would not help, because the transaction was not waiting for space.

Cycle 22 is the whole of §15. The response arrived at cycle 21 and was consumed at 23. A design retiring on arrival reports 21 cycles instead of 23 — an 8.7% understatement, and it is precisely the term that grows under local congestion.

The remote service is 34.8% here rather than 83% as in §32, because this trace uses an 8-cycle service time rather than an 80 ns media access. The proportion depends entirely on what is at the far end, which is why §26 insists the decomposition be instantiated per transaction class.

The sum equals the end-to-end figure, which is the instrumentation self-check.

And every stall cycle went to exactly one bucket. Cycles 2 and 3 to credit, cycles 4 and 22 to queue. If cycle 2 had also been counted as a queue stall, the sum would be 24 and the check would fail — which is what §37 is for.

34. Retry-Tail Trace

The same transaction, with one CRC failure and a replay.

PhaseCyclesCumulativeNote
accept → first transmission complete0–88as before
integrity check fails at the receiver9–101014.1 — object not delivered
retry indication returns11–1414reverse traversal + scheduling (14.3)
replay: retransmission15–2020the same object, same identity (14.3 §24)
remote receive, check, decode21–2323passes this time
media24–31318 cycles as before
response path + consumption32–3636
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
without retry:  23 cycles = 11.5 ns
with one retry: 36 cycles = 18.0 ns          → +56.5% for THIS transaction
 
effect on the mean, at the verified BER (Section 27's calculation):
  retried fraction ≈ 1e-7
  mean ≈ 11.5 + (1e-7 × 6.5) ≈ 11.5000007 ns  → invisible
 
effect on the tail:
  the affected transaction is 56.5% slower

The mean is unchanged to seven decimal places. One transaction in ten million takes 56% longer. This is what a tail-latency requirement is about, and an averaging measurement cannot see it at all.

Two design consequences.

The timeout must cover this, and the recovery case beyond it (§28). A timeout sized on the mean fires on healthy traffic, and a timeout sized on one retry fires when two occur.

And the histogram's tail bucket is the only instrument that shows it. A mean, a median, and even a P90 all report 11.5 ns. P99.9999 reports 18 ns, and at ten million transactions per second that population is ten transactions per second — small, real, and invisible to every summary statistic short of the tail.

35. The Latency Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Verification-only reference model. Not synthesisable.
class latency_scoreboard;
 
  typedef struct {
    longint accepted_time;
    longint completed_time;
    longint stage_entry[NUM_STAGES];
    longint stage_exit[NUM_STAGES];
    longint stall_ticks[NUM_REASONS];
    bit     completed;
  } txn_t;
 
  txn_t txns[int];
  int   hist[NUM_BUCKETS];
 
  // ---- Check 1: the per-stage residences sum to the end-to-end latency.
  //      THE integrity check for the whole decomposition (Section 32).
  function void check_sum(int id);
    longint e2e = txns[id].completed_time - txns[id].accepted_time;
    longint sum = 0;
    for (int s = 0; s < NUM_STAGES; s++)
      sum += txns[id].stage_exit[s] - txns[id].stage_entry[s];
    if (sum != e2e)
      $error("DECOMPOSITION BROKEN: txn %0d stages sum to %0d, end-to-end is %0d%0d unaccounted",
             id, sum, e2e, e2e - sum);
  endfunction
 
  // ---- Check 2: stall buckets partition the stalled time (Section 23).
  function void check_stall_partition(int id, longint advancing_ticks);
    longint e2e = txns[id].completed_time - txns[id].accepted_time;
    longint stalls = 0;
    for (int r = 0; r < NUM_REASONS; r++) stalls += txns[id].stall_ticks[r];
    if (stalls + advancing_ticks != e2e)
      $error("STALL PARTITION BROKEN: txn %0d stalls %0d + advancing %0d != %0d",
             id, stalls, advancing_ticks, e2e);
  endfunction
 
  // ---- Check 3: the DUT's measured latency matches the independent one.
  //      Catches Section 15 — retirement on arrival rather than consumption.
  function void check_measurement(int id, longint dut_measured);
    longint e2e = txns[id].completed_time - txns[id].accepted_time;
    if (dut_measured != e2e)
      $error("MEASUREMENT WRONG: txn %0d dut %0d, actual %0d — check the stop event",
             id, dut_measured, e2e);
  endfunction
 
  // ---- Check 4: monotonic stage ordering — no stage exited before it entered,
  //      which catches a timebase or wrap error (Sections 12, 18).
  function void check_ordering(int id);
    for (int s = 0; s < NUM_STAGES; s++)
      if (txns[id].stage_exit[s] < txns[id].stage_entry[s])
        $error("NEGATIVE RESIDENCE: txn %0d stage %0d exit %0d < entry %0d — timebase or wrap",
               id, s, txns[id].stage_exit[s], txns[id].stage_entry[s]);
  endfunction
 
  // ---- Report: the distribution, not the mean alone.
  function void report();
    longint total = 0; int n = 0; longint worst = 0;
    foreach (txns[id]) if (txns[id].completed) begin
      longint e2e = txns[id].completed_time - txns[id].accepted_time;
      total += e2e; n++;
      if (e2e > worst) worst = e2e;
      hist[bucket_of(e2e)]++;
    end
    $display("n=%0d  mean=%0d  worst=%0d", n, (n ? total/n : 0), worst);
    foreach (hist[b]) $display("  bucket %0d: %0d", b, hist[b]);
  endfunction
 
endclass

Architecture. Four checks and a distribution report, in three categories: decomposition integrity (1, 2), measurement correctness (3), and arithmetic sanity (4).

Check 1 is the chapter's central integrity claim. If the per-stage residences do not sum to the end-to-end latency, the decomposition has a gap or an overlap — and its error message reports the unaccounted amount, which is usually enough to identify the missing stage.

Check 3 is what catches §15, and it can only be done with an independent end-to-end measurement. The design cannot self-check its own stop event, which is precisely why the bug survives without a scoreboard.

Check 4 is crude and valuable. A negative residence is impossible physically, so it catches §12's mixed timebases and §18's wrap error immediately, regardless of how plausible the totals look.

And the report deliberately prints the worst case and the full histogram alongside the mean, because §29 and §30 established that the mean is the least informative of the three.

36. Performance Assertions, Conditionally

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// PERFORMANCE properties, and they MUST be conditional. An unconditional
// latency bound will fire on legitimate congestion, get disabled, and then
// protect nothing.
//
// Assumptions, stated:
//   A1: no congestion — the transaction is never resource-stalled
//   A2: no retry
//   A3: the link is ACTIVE throughout (no recovery)
//   A4: the remote responds within its own stated bound
 
property p_uncongested_latency_bound;
  @(posedge clk) disable iff (!rst_n)
    (semantic_request_accepted && no_congestion && no_retry
      && link_active && remote_within_bound)
        |-> ##[1:UNCONGESTED_LATENCY_BOUND] semantic_response_consumed;
endproperty
a_uncongested_latency_bound: assert property (p_uncongested_latency_bound);
 
// A safety companion that needs no assumptions: the measurement is sane.
property p_residence_non_negative;
  @(posedge clk) disable iff (!rst_n)
    stage_exit_event |-> (stage_exit_time >= stage_entry_time);
endproperty
a_residence_non_negative: assert property (p_residence_non_negative);

Architecture. One conditional performance property with four explicit assumptions, plus one unconditional sanity property.

Why the conditions are mandatory rather than defensive. A transaction under congestion legitimately takes arbitrarily long — 13.3 established that a fully backed-up pipeline is a correct steady state. An unconditional latency bound therefore fails on correct behaviour, and a property that fails on correct behaviour is disabled by the next engineer to see it fail. Then it protects nothing at all.

And this is a performance property, which is a different category from safety and liveness. 13.5 §2 made the distinction: a design can satisfy every safety and liveness property and still be slow. A performance property makes a claim about rate under stated conditions, and stating the conditions is what makes it checkable rather than aspirational.

The unconditional companion is the one to keep always enabled. It makes no performance claim — it asserts the measurement is arithmetically possible, which catches §12 and §18 with no assumptions at all.

37. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
covergroup cg_latency @(posedge clk);
  option.per_instance = 1;
 
  // --- Which stage dominated. Every stage must dominate at least once.
  cp_dominant_stage : coverpoint dominant_stage_id {
    bins protocol_queue = {ST_PROTO_Q};
    bins tx_queue       = {ST_TX_Q};
    bins adapter        = {ST_ADAPTER};
    bins serialise      = {ST_SERIAL};
    bins remote         = {ST_REMOTE};
    bins response_queue = {ST_RESP_Q};      // Section 15's term
  }
 
  // --- Stall reasons (Section 23). Each must be the first blocker once.
  cp_stall_reason : coverpoint first_stall_reason {
    bins none   = {SR_NONE};                // the uncongested case
    bins credit = {SR_CREDIT};
    bins replay = {SR_REPLAY};
    bins arb    = {SR_ARB};
    bins queue  = {SR_QUEUE};
  }
 
  // --- Latency distribution, including the tail.
  cp_latency_bucket : coverpoint latency_bucket {
    bins best   = {0};                      // the uncongested minimum
    bins typical= {[1:3]};
    bins tail   = {[4:$]};                  // MUST be non-zero (Sections 29, 34)
  }
 
  // --- Exceptional paths that create the tail.
  cp_retry_in_txn    : coverpoint txn_experienced_retry;
  cp_recovery_in_txn : coverpoint txn_spanned_recovery;    // Section 28
  cp_hol_blocked     : coverpoint txn_was_hol_blocked;     // Section 25
 
  // --- Configuration effects on latency (Sections 20, 21).
  cp_width : coverpoint active_lanes { bins full = {16}; bins degraded = {[1:15]}; }
  cp_rate  : coverpoint rate_sel { bins r32 = {R_32}; bins r48 = {R_48}; bins r64 = {R_64}; }
 
  // --- Instrumentation mechanics.
  cp_timestamp_wrap : coverpoint timestamp_wrapped_during_txn;   // Section 18
  cp_concurrency    : coverpoint outstanding_count {
    bins one = {1}; bins few = {[2:8]}; bins many = {[9:$]};
  }
 
  // --- Crosses.
  x_retry_tail    : cross cp_retry_in_txn, cp_latency_bucket;   // retry ⇒ tail
  x_width_serial  : cross cp_width, cp_dominant_stage;          // Section 20
  x_stall_bucket  : cross cp_stall_reason, cp_latency_bucket;
  x_wrap_tail     : cross cp_timestamp_wrap, cp_latency_bucket;
endcovergroup

Six bins whose value is being non-zero:

cp_latency_bucket.tail and x_retry_tail. Without a populated tail, §29's histogram and §30's tail argument have no data, and the design's worst case has never been observed.

cp_dominant_stage.response_queue. A transaction whose largest residence is the response queue — which is the case §15's bug makes invisible. If this bin is never hit, the stop-event bug cannot be detected.

cp_stall_reason — every bin including none. The none case establishes the uncongested baseline that §36's property needs; the others establish that each attribution path works.

cp_recovery_in_txn. A transaction alive across a recovery, which is where §28's timestamp-width argument bites and which never occurs spontaneously.

cp_timestamp_wrap. §18's wrap, which at 32 bits and 2 GHz occurs once every 2.1 seconds of simulated time — effectively never in a normal regression, so it needs a directed test starting the timestamp near its maximum.

And cp_width.degraded crossed with the dominant stage, because §20's doubled serialisation should shift which stage dominates — a measurable, checkable consequence of degradation.

38. Debug Taxonomy

SignatureMost likely causeFirst instrument
End-to-end high, transport normalremote service or upper protocol (§26) — not the linkthe per-stage decomposition
Queue residence high, service normalcongestion or under-sized resources (§9)per-reason stall attribution
Serialisation term highwidth degraded (§20) or a large objectactive lane count; object size
Only the tail is highreplay (§27), recovery (§28), or head-of-line (§25)the histogram's top bucket; retry counters
Negative or absurd latency values§12 mixed timebases, or §18 wrapare the two timestamps in the same clock domain?
Roughly one bad value per 2 seconds§18 — timestamp wrap at 32 bits / 2 GHzthe wrap interval against the observed period
Measured latency lower than software observes§15 — retirement on arrival, not consumptionis the stop event the consumption event?
Stage residences do not sum to end-to-enda stage unaccounted, or double-counted (§35 check 1)the reported unaccounted amount
Latency improves under congestion§15 — the excluded term grows with loadcompare against an independent end-to-end measure
Mean fine, application jitterytail latency (§30)P99.9, not the mean
Latency rises after a "successful" repair§20 — degraded width doubled serialisationactive width before and after
Every latency identical and suspiciously roundthe instrumentation is measuring a fixed pipeline and missing the queuesis the start event semantic acceptance?

39. Debug Checklist

  1. What latency is being measured? One-way, round-trip transport, or semantic (§4)?
  2. What is the start event, exactly? Semantic acceptance, or a transport proxy that excludes the protocol queue (§14)?
  3. What is the stop event, exactly? Consumption, or arrival (§15)?
  4. Are all timestamps in one timebase? If not, the differences mean nothing (§11).
  5. Which stage has the largest residence? The decomposition, not a guess.
  6. Is that residence queue wait or service time? Run at low load — service is unchanged, queue wait collapses (§9).
  7. Was there a credit stall, and how long? Attributed separately (§23).
  8. Was there an arbitration stall? Eligible-to-grant, not queue occupancy (§24).
  9. Was the transaction head-of-line blocked? A structural problem, not a scheduling one (§25).
  10. Was there a retry? Rare and expensive — check the tail, not the mean (§27).
  11. Did the transaction span a recovery? And is the timestamp wide enough to have measured it (§28)?
  12. Has width or rate degraded? Serialisation scales inversely with width and rate (§20, §21).
  13. How long did the remote take? Usually the dominant term, and not the link's fault (§26).
  14. Did the timestamp wrap? Roughly one transaction per wrap interval is mismeasured (§18).
  15. Do the per-stage residences sum to the end-to-end figure? If not, stop — the instrumentation is wrong (§35).
  16. Are you looking at the mean or the tail? They answer different questions and the mean hides the one that matters (§29, §30).

40. Common Misconceptions

"UCIe has one latency number." Latency is a sum of residences at ownership boundaries, and the decomposition differs per transaction class. There is no single number, and the useful question is where the time went (§1, §5).

"2 ns means end-to-end memory latency." The verified figure is "2ns for round-trip from FDI to bump" — a target, for one segment of the local stack, at 16G, assuming a 2 GHz internal clock. It excludes the package, the far die entirely, and all remote execution. In the worked example the whole transport is ~11% of a 96.5 ns transaction (§8, §32).

"More pipeline stages always reduce performance." Adding a stage adds a cycle and may shorten the cycle time. Whether latency in nanoseconds improves depends entirely on how evenly the path splits — a balanced four-way split leaves latency unchanged and quadruples throughput (§22).

"Queue latency and service latency are the same." A slow stage and a congested stage give the same residence and need opposite fixes. Service time is load-independent; queue wait collapses at low load, which is the experiment that separates them (§9).

"Average latency is sufficient." Three distributions with the same 94 ns mean describe completely different systems, one of which has a 1% population 5.4× slower. Timeouts, QoS, coherence stalls and pool sizing all depend on the tail (§29, §30).

"Width degradation affects bandwidth only." It also doubles serialisation time for every object, which adds to one-way latency, lengthens the credit and acknowledgement round trips in time, and extends head-of-line blocking (§20).

"Replay is negligible because the BER is low." Its effect on the mean is negligible — parts in a hundred million. Its effect on the affected transaction is tens of percent, and that transaction is what a tail requirement and a timeout are about (§27, §34).

"Timestamps from different clocks can be subtracted." They measure in different units and started at unrelated instants. The error grows with elapsed time, and a plausible positive result is more dangerous than a negative one because it is invisible (§12).

"Response arrival is the end of transaction latency." It is the end of the transport. The requester experiences the time until consumption, and the excluded interval grows under local congestion — so a metric using arrival improves as the user's experience worsens (§15).

"If link latency is low, application latency must be low." In the worked case 83% of the transaction is media access. The link can be optimised to zero and the application improves by 12% (§26, §32).

"A latency assertion should just bound the transaction time." Under congestion a transaction legitimately takes arbitrarily long, so an unconditional bound fails on correct behaviour and gets disabled. Performance properties need their assumptions stated (§36).

41. Understanding Check

42. Summary and What Comes Next

Latency is the sum of residence times at ownership boundaries. A transaction does not travel; it is held, repeatedly, and the useful question is where it resided and for how long.

Three latencies get conflated — one-way transport, round-trip transport, and semantic transaction — and they differ by an order of magnitude. Always name the start event and the stop event, because a figure with unstated endpoints is unmeasurable and uncomparable.

The verified 2 ns figure is a target, for one segment of the local stack, at 16G, assuming a 2 GHz internal clock. It excludes the package, the entire far die, and all remote execution. In the worked case the whole transport is ~11% of a 96.5 ns transaction, and the media access is 83%.

Residence splits into queue wait and service time, and the split is what makes a decomposition actionable — a slow stage and a congested stage look identical and need opposite fixes. The experiment is to run at low load: service is unchanged, queue wait collapses.

Timestamps from different clock domains cannot be subtracted. They measure in different units from unrelated origins, the error grows with elapsed time, and a plausible positive result is more dangerous than a negative one. Put the domain in the identifier.

The stop event must be consumption, not arrival. A metric retiring on arrival excludes local response queueing — a term that grows under congestion, so the measurement improves as the user's experience worsens, and only an independent end-to-end comparison catches it.

Serialisation scales inversely with width and rate, so width degradation doubles it — and that propagates into the credit round trip, the acknowledgement round trip and head-of-line duration. Width degradation is a latency event as well as a bandwidth event.

Attribute stalls to the first blocking resource in a fixed priority order, so the buckets partition the residence and the per-stage sum equals the end-to-end figure. A sum that does not match is an instrumentation bug, not a finding.

And report the distribution, not the mean. A retry costs tens of percent on the affected transaction and parts per hundred million on the mean; a recovery costs far more. Timeouts, QoS, coherence stalls and every rate × latency pool depend on the tail — and the tail is also where a degrading channel appears first, long before the mean or the bandwidth notices.

One link can now be quantified in both bandwidth and latency. The next chapter asks what happens when the package contains many such links and many dies competing for the same physical and architectural resources.

Browse the full path on the UCIe tutorials index.