Skip to content

UCIe · Module 21

Throughput Issues

Diagnosing a real bandwidth shortfall on a link whose accounting is already correct — every transfer opportunity classified into exactly one mutually exclusive cause so the classes sum to the total, the denominator discipline that makes a percentage mean something, the credit window that produces a sawtooth without a leak, and the four bottlenecks that are not inside UCIe at all.

Chapter 21.4 closed every credit boundary. The link is correct, stable and delivering less bandwidth than expected — and adding credits will not help.

1. The One-Sentence Model

Throughput loss is lost transfer opportunity. Every opportunity the link offered was either useful work or a named loss — and if the loss classes are mutually exclusive, they sum to the total. The dominant class is the bottleneck; every other conversation about bandwidth is speculation until that sum exists.

This is the performance analogue of a conservation law, and it is exactly as powerful: 21.4 localised a broken credit equation to a boundary, and this chapter localises a bandwidth shortfall to a cycle class.

2. What This Chapter Owns

QuestionWhere it is answered
Bandwidth as a quantity — lanes, rate, the efficiency stack15.1 — Bandwidth
Latency components and where they accumulate15.2 — Latency
Scaling behaviour across modules and packages15.3 · 15.4
Throughput analysis — how to model and predict it15.5 — Throughput Analysis
Congestion, backpressure propagation, buffer sizing13.2 · 13.3 · 13.4
Throughput optimisation — what to change13.5 — Throughput Optimisation
Credit conservation, boundary localisation21.3 · 21.4
Proving a trace is a protocol violation21.6 — Protocol Violations (planned)
Lab instrumentation on real silicon21.7 — UCIe Silicon Debug (planned)

15.5 predicts throughput. 13.5 improves it. This chapter diagnoses a specific shortfall that has already been measured — which is a different activity, and it starts from four questions that a model never asks:

Is the measurement's denominator the right one? (§40–§42) A benchmark that divides by the requested configuration on a link running in a degraded one reports a 50% shortfall on hardware that is at 100%.

Was work ever offered? (§22–§24) A source-limited generator produces a throughput number that describes the generator.

Is the wire busy with useful bytes? (§17–§21) Retransmissions occupy the wire and deliver nothing new, so wire utilisation and delivered payload can move in opposite directions.

And is the bottleneck inside UCIe at all? (§48–§51) Four common shortfalls are consumer-side, and optimising the link cannot touch them.

3. Sourcing

4. Lost Opportunity

Every transfer opportunity the link offered is exactly one of nine things.

ClassMeaningAvoidable?
PERF_USEFULa useful payload transfer happened
PERF_NO_WORKnothing was offerednot by the link — §22
PERF_NO_CREDITwork was ready, no capacity at the far endyes — window or latency, §26
PERF_BACKPRESSUREthe downstream stage was not readyyes — §31
PERF_ARB_LOSSanother requester was grantedmaybe — §32
PERF_REPLAYthe opportunity carried a retransmissionindirectly — §37
PERF_RECOVERYthe link was not carrying trafficindirectly — §38
PERF_PIPE_BUBBLEall conditions met and no transferyes — always a defect, §34
PERF_CFG_LIMITthe active configuration cannot offer moreno — §40's denominator

Three properties, and the third is the whole method.

PERF_NO_WORK and PERF_CFG_LIMIT are the two classes that are not the link's fault — and between them they account for most reported "throughput bugs" that turn out not to be bugs (§24, §41).

PERF_PIPE_BUBBLE is the only class that is always a defect. Every admission condition was satisfied and no transfer occurred; there is no legitimate reason unless the architecture documents one (§35).

And the classes must be mutually exclusive — exactly one per opportunity — or they do not sum to the total and every percentage derived from them is meaningless (§8).

5. Peak Is Not Measured

A second model, because it governs every denominator in the chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE decomposition. Each factor is measured or read, never assumed.
 
  physical opportunity      what the hardware could offer at all
    x  active configuration what the link NEGOTIATED and is running (§40)
    x  protocol efficiency  the fraction of the wire carrying payload (§19)
    x  utilisation          the fraction of opportunities used (§4)
    x  useful fraction      of those, the fraction that was NEW data (§20)
  =  delivered payload throughput

Three readings.

A shortfall lives in exactly one factor, and they are diagnosed by different instruments. Configuration is read from a register; efficiency is computed from bytes; utilisation is counted in cycles; the useful fraction needs identity. An investigation that does not know which factor is low is not an investigation.

Multiplying them means a modest loss in each is a large loss overall — four factors at 90% is 66% — so "everything looks roughly fine" is compatible with a third of the bandwidth being gone.

And no factor's value is stated anywhere in this chapter (§3). The decomposition is a method; the numbers come from the design's own specification and the measured link.

The decomposition above says how much of the peak survives. A second, complementary model says who is holding the link back — and it is a minimum, not a product:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE analytical model, NOT a normative UCIe relationship (§3).
 
  useful_throughput
    ==  min(
          source_supply_rate,        // was work offered?            §20
          admission_capacity,        // could the link take it?      §24
          credit_window_capacity,    // window / round-trip          §26
          adapter_service_rate,      // internal service             §24
          link_service_capacity,     // active configuration         §40
          receiver_drain_rate        // the consumer                 §51
        )
      x  useful_efficiency           // payload / wire, unique/total §14
 
  The MINIMUM term is the bottleneck. Every other term is slack.

Four readings, and the difference between the two models is the point.

The product model (above) attributes a shortfall; the minimum model identifies a binding constraint. A link at 66% because four factors each sit at 90% has no single owner — four modest inefficiencies compound. A link at 45% because the consumer drains at 0.45 has exactly one owner, and improving anything else changes nothing.

Which model applies is itself diagnostic. If lifting the smallest term raises throughput by that amount, the system is minimum-limited and the investigation is over. If it does not, the loss is distributed and the product model is the right frame — and §17's class histogram will show several classes of comparable size rather than one dominant one.

Every term is measurable, and §55's counter set exists to measure them — supply from offered, admission from accepted/offered, window from the outstanding histogram (§26), service rates per stage from §24, link capacity from the counted denominator (§40), and drain rate from the consumer's own completion counter.

And useful_efficiency multiplies rather than competes, which is why §53's trace is so damaging: a link can be at 100% of its binding constraint and still deliver a third of expectation, because efficiency is outside the minimum entirely.

6. Where Opportunities Are Lost

A block diagram of the transfer path from traffic source through protocol queue, adapter admission, flow-control admission, the scheduler, the physical layer and link, the receive buffer, and finally the consumer. Each stage is annotated with the instrument that localises a shortfall there: offered bytes at the source, occupancy at the protocol queue, accepted bytes at adapter admission, no-credit cycles at flow-control admission, grant and transfer counts at the scheduler, useful and replay cycles at the physical layer, occupancy and high-water at the receive buffer, and service rate at the consumer. The consumer at the end sits outside the UCIe link, and the muted edge from it back to flow control closes the credit loop.Traffic sourceoffered bytes (§20)Protocol queueoccupancy (§24)Adapter admitaccepted bytesPHY / linkuseful vs replay (§38)Schedulergrants vs xfers (§29)Flow controlno-credit cyclesRX bufferoccupancy, high-waterConsumerservice rate (§51)12
The transfer path with the instrument that localises a shortfall at each boundary. Each stage carries the instrument that localises a shortfall there, so the first stage whose service rate saturates is the bottleneck — and the consumer at the end sits outside the UCIe link, which is where the shortfall of section fifty-one turns out to live.

Four things to read.

The muted return edge closes the loop, and it is why the consumer's service rate can appear as PERF_NO_CREDIT five stages upstream (§27) — a shortfall observed at flow control whose cause is at the far right.

Each stage's annotation is the instrument, not the symptom. The diagram is a map of where to place counters, and §55 is the compact set it implies.

The consumer at the end sits outside the UCIe link. 15.5's boundary, and §50–§51 are where the shortfall turns out to live there — where optimising anything to its left is wasted work.

And the path is not symmetric. The return direction has its own copy of every stage, which is why §45's cross-direction coupling bug exists and why single-direction benchmarks miss it.

7. The Cycle Classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Exactly ONE class per transfer opportunity, by priority. The
// priority order IS the causal model and must be documented with the design
// (§9) — this is a policy choice, not a natural fact.
typedef enum logic [3:0] {
  PERF_USEFUL      = 4'd0,
  PERF_REPLAY      = 4'd1,
  PERF_CFG_LIMIT   = 4'd2,
  PERF_RECOVERY    = 4'd3,
  PERF_NO_WORK     = 4'd4,
  PERF_NO_CREDIT   = 4'd5,
  PERF_BACKPRESSURE= 4'd6,
  PERF_ARB_LOSS    = 4'd7,
  PERF_PIPE_BUBBLE = 4'd8
} perf_class_e;
 
perf_class_e opp_class;
 
always_comb begin
  // The opportunity does not exist at all if the link is not carrying traffic
  // or the active configuration does not offer this slot.
  if      (!link_operational)      opp_class = PERF_RECOVERY;
  else if (!slot_exists_in_cfg)    opp_class = PERF_CFG_LIMIT;
  // A transfer HAPPENED — but was it new data or a retransmission (§20)?
  else if (xfer_fire && is_replay) opp_class = PERF_REPLAY;
  else if (xfer_fire)              opp_class = PERF_USEFUL;
  // No transfer. Walk the admission conditions from the SOURCE outward, so the
  // FIRST unmet condition is named rather than the last.
  else if (!work_pending)          opp_class = PERF_NO_WORK;
  else if (!credit_available)      opp_class = PERF_NO_CREDIT;
  else if (!downstream_ready)      opp_class = PERF_BACKPRESSURE;
  else if (!granted)               opp_class = PERF_ARB_LOSS;
  // Every admission condition was met and nothing moved. There is no
  // legitimate remaining reason (§34).
  else                             opp_class = PERF_PIPE_BUBBLE;
end

Architecture. A priority encoder over the admission conditions, producing exactly one class per opportunity. 21.1 §17's waiting-reason encoder, applied to performance instead of bring-up.

State. None — combinational. The state is in the counters it drives (§10).

Event behaviour. Evaluated once per transfer opportunity. PERF_PIPE_BUBBLE is the else of a chain that has eliminated every other explanation, which is why it is always a defect.

Contract. The priority order is a documented policy, not a discovered truth (§9). Two designs can legitimately order NO_CREDIT and BACKPRESSURE differently, and a percentage from one is not comparable with a percentage from the other unless the order is stated with it.

Failure. Three. Overlapping counters instead of one class (§8) — the classic. PERF_USEFUL counting a replay (§20) — the wire is busy and nothing new is delivered. And PERF_NO_WORK swallowing a case where work existed upstream but had not reached this point, which reports a link-side queueing problem as a source problem.

Debug/DV. The nine counters sum to the total (§11), so the dominant class is a fact rather than an impression — and the whole chapter's method is one histogram read.

8. Wrong — Overlapping Reason Counters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — three independent counters, incremented whenever their condition
// holds. The conditions are NOT mutually exclusive.
always_ff @(posedge clk) begin
  if (!credit_available)  no_credit_q  <= no_credit_q  + 1;   // fires
  if (!downstream_ready)  backpres_q   <= backpres_q   + 1;   // ALSO fires
  if (!work_pending)      no_work_q    <= no_work_q    + 1;   // ALSO fires
end

Worked, over 1,000 cycles. On 400 of them, all three conditions held simultaneously — the queue was empty, so no work was pending; with nothing outstanding there were plenty of credits, but the credit signal was sampled in a state that read low; and the downstream stage was idle-not-ready.

CounterValueAs a percentage of 1,000
no_credit_q62062%
backpres_q48048%
no_work_q70070%
sum1,800180%

Four properties.

The sum exceeds the total, so no value is a fraction of anything. The percentages cannot be compared, ranked, or added.

The largest counter is not the bottleneck. no_work is largest here, and the link genuinely was starved — but no_credit at 62% invites a credit investigation that has nothing to find, because on most of those cycles there was no work to send anyway.

The failure is silent. Nothing errors; the counters just do not mean what their names suggest. The only symptom is the sum, which nobody computes because there is no reason to expect it to be 1,000.

And the fix is not to remove the counters. Raw condition counters are genuinely useful — "how often was credit low?" is a real question. Keep both sets, and never mix them: the classified set for the accounting equation, the raw set for condition prevalence, and label which is which.

9. Priority Is a Documented Policy

Which condition to blame when several hold at once is a choice, and the two defensible policies disagree.

PolicyBlamesArgument
first unmet, source outwardthe earliest missing conditionif there is no work, nothing else matters
furthest downstream blockerthe last blocking conditionremoving it is what would unblock the flow

Three notes.

§7 uses the first. When no work is pending, calling it PERF_ARB_LOSS because the arbiter also happened not to grant is misleading — there was nothing to grant.

But the second is defensible for a saturated link, where work is always pending and the question genuinely is "which resource is short". On a saturated link the two policies largely agree, because the upstream conditions are all satisfied.

And the practical rule is to state the policy next to every histogram. 15.5 §7's reproducibility argument: a percentage without its classification policy is not a measurement, it is a number.

10. Reason Counters

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. One counter per class, plus the total. Passive — nothing here
// gates a transfer or sits in a timing path (14.5 §5).
logic [63:0] perf_cnt_q [NUM_PERF_CLASSES];
logic [63:0] perf_total_q;
logic [63:0] perf_useful_bytes_q;      // BYTES, not cycles — §17
logic [63:0] perf_wire_bytes_q;        // including replay and control
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    for (int c = 0; c < NUM_PERF_CLASSES; c++) perf_cnt_q[c] <= '0;
    perf_total_q        <= '0;
    perf_useful_bytes_q <= '0;
    perf_wire_bytes_q   <= '0;
  end else if (measure_window_active) begin      // §43's window gate
    // EXACTLY ONE class increments. The total increments unconditionally, so
    // the conservation check of §11 is structural rather than hopeful.
    perf_cnt_q[opp_class] <= perf_cnt_q[opp_class] + 64'd1;
    perf_total_q          <= perf_total_q + 64'd1;
 
    if (xfer_fire) begin
      perf_wire_bytes_q <= perf_wire_bytes_q + 64'(xfer_bytes);
      // Only NEW payload counts as useful (§20). A replay occupies the wire
      // and delivers nothing the far end did not already have.
      if (!is_replay)
        perf_useful_bytes_q <= perf_useful_bytes_q + 64'(xfer_payload_bytes);
    end
  end
end

Architecture. Nine class counters, a total, and two byte counters — cycles and bytes both, because neither answers the other's question (§17).

State. Eleven 64-bit counters. Wide, not saturating (21.3 §46) so a long benchmark's ratios stay meaningful.

Event behaviour. One class increment and one total increment per opportunity, gated by the measurement window (§43) so bring-up is not folded into a steady-state figure.

Contract. opp_class must be a single value (§7). And the window gate must be the same for the class counters and the byte counters — a byte counter that runs during warm-up and a cycle counter that does not produces a bytes-per-cycle figure that is wrong in a way no individual counter reveals.

Failure. Incrementing the total outside the if — or from a free-running clock counter — breaks §11's identity and hides every classification bug, because the sum no longer has to match anything.

Debug/DV. perf_wire_bytes against perf_useful_bytes is §21's ratio, and the two diverging while the cycle counters look healthy is the retry signature (§53).

11. The Performance Conservation Equation

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. Performance's version of a conservation law — and it is checkable
// continuously rather than only at the end of a run.
function automatic logic [63:0] class_sum();
  logic [63:0] s = '0;
  for (int c = 0; c < NUM_PERF_CLASSES; c++) s += perf_cnt_q[c];
  return s;
endfunction
 
a_classes_sum_to_total: assert property (
  @(posedge clk) disable iff (!por_n)
    (class_sum() == perf_total_q)
);
 
// And useful bytes can never exceed wire bytes (§21).
a_useful_le_wire: assert property (
  @(posedge clk) disable iff (!por_n)
    (perf_useful_bytes_q <= perf_wire_bytes_q)
);

Architecture. One identity that makes every derived percentage trustworthy.

State. None beyond the counters.

Event behaviour. Evaluated every cycle; fires at the cycle the classifier double-counts or misses, rather than at the end of a run when the sum is finally computed.

Contract. NUM_PERF_CLASSES must cover the enumeration exhaustively. A class added to the enum and not to the counter array silently drops opportunities, and the identity catches exactly that.

Failure. Without this property, §8's overlapping-counter failure is undetectable until somebody sums the histogram by hand — which is a step nobody takes when the individual numbers look plausible.

Debug/DV. This is the single most valuable property in the chapter. It converts the histogram from an impression into an accounting, and everything downstream — the dominant class, the percentages, the decision tree of §56 — depends on it holding.

Three further invariants, each guarding a measurement that would otherwise be quietly wrong:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// (1) ACCEPTED NEVER EXCEEDS OFFERED. English: the link cannot take more work
// than was presented to it. Verification-only. Fires when `offered` has been
// mis-instrumented as `valid && ready` — which makes the ratio 1.0 by
// construction and destroys §21's entire source-limited diagnosis.
a_accepted_le_offered: assert property (
  @(posedge clk) disable iff (!por_n)
    (accepted_objects_q <= offered_objects_q)
);
 
// (2) OCCUPANCY AGREES WITH ITS OWN EVENTS. English: a stage's occupancy is
// the running difference of its enqueues and dequeues — it is DERIVED, so it
// cannot drift (21.3 §11). Fires when a counter is maintained independently of
// the events it claims to summarise, which makes §23's waterfall lie.
generate
  for (genvar s = 0; s < NUM_STAGES; s++) begin : g_occ
    a_occupancy_derived: assert property (
      @(posedge clk) disable iff (!por_n)
        occ_q[s] == ($past(occ_q[s]) + 32'($past(stage_enq_fire[s]))
                                     - 32'($past(stage_deq_fire[s])))
    );
    // And it can never exceed the stage's depth — an impossible occupancy is
    // an instrumentation bug, not a queueing observation.
    a_occupancy_bounded: assert property (
      @(posedge clk) disable iff (!por_n) (occ_q[s] <= 32'(STAGE_DEPTH[s]))
    );
  end
endgenerate
 
// (3) COUNTERS BELONG TO ONE MEASUREMENT EPOCH. English: every counter feeding
// a single reported ratio must have been gated by the same window (§43). Fires
// when a byte counter free-runs while a cycle counter is gated — producing a
// bytes-per-cycle figure that no individual counter reveals as wrong.
a_bytes_gated_with_cycles: assert property (
  @(posedge clk) disable iff (!por_n)
    (!measure_window_active) |=> ($stable(bytes_wire_q) && $stable(perf_total_q))
);

Architecture. A supply bound, an occupancy-consistency pair, and a window-gating check.

State. None beyond the counters.

Sampled timing. Property (2) uses $past on three signals and compares them against the current occupancy — so it is checking that this cycle's occupancy equals last cycle's occupancy adjusted by last cycle's events, which is the correct phase relationship for a non-blocking update. Writing it against this cycle's enq/deq would be off by one and would fire on every transfer.

Contract. Property (2) assumes occupancy is maintained as a derived difference. A design that maintains it independently will fail this, and that failure is the finding — 21.3 §11's argument that a derived quantity cannot drift while an independent one can.

Failure. Without (1), §21's three wasted weeks are undetectable — the ratio reads 1.0 and the link is blamed. Without (2), the waterfall of §23 points at whichever stage has the most wrong counter. Without (3), §44's window error appears in a derived figure while every source counter looks fine.

Debug/DV. These are verification-only instrumentation checks, not architectural claims. Their whole purpose is that a performance report is only as trustworthy as its least-checked counter — and unlike a correctness bug, a measurement bug produces a confident number rather than a failure.

12. Fifty Per Cent, and Where It Went

Half utilisation with every condition met

8 cycles
A waveform of eight cycles showing a link at fifty per cent utilisation. The source valid signal is high throughout, credit is available throughout, and the physical layer ready signal is high throughout. The scheduler grant signal alternates high and low every cycle. The useful transfer signal follows the grant, so transfers occur on cycles zero, two, four and six only. The stall reason signal shows useful on the transfer cycles and arbitration loss on the alternate cycles. Markers highlight the first lost opportunity at cycle one, the fact that no rival requester was granted on the lost cycles, and the resulting fifty per cent utilisation with every admission condition satisfied.first lost opportunityfirst lost opportunityno rival was grantedno rival was granted50% — every condition met50% — every condition metclksrc_validcredit_okphy_readysched_grantuseful_xferstall_reasonUSEARBUSEARBUSEARBUSEARBt0t1t2t3t4t5t6t7
Eight cycles of a link at half utilisation with every admission condition satisfied. The source offers continuously, credit is available and the physical layer is ready, yet a grant appears only on alternate cycles — so half the opportunities classify as arbitration loss with no requester to lose to, which is the signature of a scheduler bubble rather than contention.

Four readings, and the fourth is the diagnosis.

Every admission condition is high on every cycle. Source offering, adapter ready, credit available, physical layer ready — so PERF_NO_WORK, PERF_NO_CREDIT and PERF_BACKPRESSURE are all zero.

The classifier reports PERF_ARB_LOSS on four of eight opportunities, which reads as contention — "another class is taking half the bandwidth."

But the other classes' transfer counters are zero. Nobody won those cycles. PERF_ARB_LOSS with no corresponding grant to any requester is not contention — it is §34's bubble wearing the arbiter's name, and it is a classifier that stops one condition too early.

The correction is to distinguish lost to a rival from granted to nobody (§33), and once that distinction exists this trace classifies as PERF_PIPE_BUBBLEwhich §4 marks as always a defect, and the investigation goes to the scheduler's registration rather than to arbitration weights.

13. Cycles and Bytes Are Different Questions

InstrumentAnswersBlind to
cycle counters"was the wire busy?"what it was busy with
byte counters"how much moved?"whether an opportunity was wasted

Worked, two links, same utilisation.

Link ALink B
useful cycles / total600 / 1,000 = 60%600 / 1,000 = 60%
bytes per transferfull widthone quarter width
delivered payload4× link B

Both links report 60% utilisation. One delivers four times the data.

And the mirror case is worse: a link at 95% utilisation where most transfers are retransmissions delivers less new data than one at 60% that never retries (§21). Utilisation alone cannot distinguish them, and it is the number most often quoted.

14. Useful Bytes

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Three byte counters, because "bandwidth" is three quantities.
logic [63:0] bytes_wire_q;        // everything that occupied the wire
logic [63:0] bytes_payload_q;     // payload, including retransmitted payload
logic [63:0] bytes_unique_q;      // payload delivered for the FIRST time — §20
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    bytes_wire_q <= '0; bytes_payload_q <= '0; bytes_unique_q <= '0;
  end else if (measure_window_active && xfer_fire) begin
    // Everything on the wire, including headers, control and replays.
    bytes_wire_q    <= bytes_wire_q    + 64'(xfer_total_bytes);
    // Payload only — the header and CRC are overhead, not application data.
    bytes_payload_q <= bytes_payload_q + 64'(xfer_payload_bytes);
    // UNIQUE requires the semantic identity model (20.4 §18). A second attempt
    // at the same object contributes ZERO here, however many bytes it moves.
    if (first_attempt_for_object)
      bytes_unique_q <= bytes_unique_q + 64'(xfer_payload_bytes);
  end
end

Architecture. Three nested quantities — wire ⊇ payload ⊇ unique — so the two gaps between them are the two overhead questions.

State. Three 64-bit counters.

Event behaviour. All three update on a transfer. bytes_unique_q requires first_attempt_for_object, which is a model output rather than a design signal21.4 §36's independence argument, because a design that has misclassified a replay will also report it as a first attempt.

Contract. The three must be measured over the same window (§43) or their ratios are meaningless.

Failure. Counting retransmitted payload as unique is §21's failure, and it produces a "bandwidth" metric that rises when the link gets worse.

Debug/DV. Two ratios diagnose two different overheads. payload / wire is protocol and framing overhead. unique / payload is retransmission overhead — and a unique / payload well below 1.0 sends the investigation to signal integrity, not to the scheduler (§37).

15. Three Metrics, Three Questions

MetricDefinitionAnswers
wire utilisationoccupied opportunities / available opportunities"is the link busy?"
delivered payload throughputunique payload bytes / time"how much data arrived?"
semantic operation ratecompleted operations / time"how much work got done?"

Three notes.

They can move in opposite directions. A retry storm raises the first and lowers the second. Small objects raise the third relative to the second. A design delivering many tiny operations can have a high operation rate and low payload throughput, and vice versa.

The application cares about the second and third. 15.5 §5: wire utilisation is a diagnostic, not a goal, and a link at 100% utilisation delivering retransmissions is at its worst, not its best.

And a report that quotes one without naming which is the most common reporting error in performance work — which is §42's denominator discipline applied to the numerator.

16. Wrong — Retransmitted Bytes as Bandwidth

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — every byte on the wire counts as delivered bandwidth.
assign delivered_bytes = bytes_wire_q;

Worked. A marginal channel begins to produce errors. The Adapter's retry machinery does its job: every corrupted transfer is retransmitted and every object is eventually delivered correctly.

Conditionbytes_wirebytes_uniqueReported "bandwidth"Application sees
clean100 units100 units100100
30% retry130 units100 units130 — "improved"100, with worse latency
60% retry160 units85 units160 — "excellent"85 — degraded

Four properties.

The metric rises as the link degrades, which is the worst possible property for a performance indicator — it will be quoted as evidence that a change helped.

The third row is where it becomes dangerous. Wire traffic is at its highest and unique delivery has actually fallen, because retransmissions are displacing new work.

The correctness machinery is working perfectly. 14.2: every object is delivered. The performance symptom is a consequence of correctness doing its job, and the root cause is not in the transfer path at all.

And the discriminating counter is bytes_unique (§14), which requires object identity — so a performance investigation cannot get this right without borrowing the verification model's lifecycle tracking (20.4 §18).

17. Worked Example — Reading a Histogram

Illustrative. A 1,000-opportunity measurement window on one direction.

ClassCountFractionAvoidable?
PERF_USEFUL60060.0%
PERF_NO_CREDIT20020.0%yes — largest avoidable
PERF_ARB_LOSS10010.0%maybe
PERF_BACKPRESSURE505.0%yes
PERF_NO_WORK505.0%not by the link
others00%
total1,000100.0%§11 holds

The arithmetic, stated explicitly.

Utilisation = 600 / 1,000 = 60%. Against the active configuration's opportunities (§42), not the requested one.

Avoidable loss = 350 — credit, arbitration and backpressure. NO_WORK's 50 is not the link's to recover, so the ceiling with a perfect link is 950/1,000, not 1,000.

Dominant avoidable class is credit at 200, which is 57% of the avoidable loss — so it is where the first investigation goes, and §26 is the question it must answer: window, or round-trip latency, or a genuine consumer bottleneck five stages downstream?

And the sum is 1,000 exactly, which is the only reason any of the above is a fact rather than an impression.

18. Worked Example — Same Number, Different Bug

Two runs, identical measured throughput.

ClassRun ARun B
PERF_USEFUL600 (60%)600 (60%)
PERF_NO_CREDIT400 (40%)0
PERF_NO_WORK0400 (40%)
measured throughputidenticalidentical

Four readings.

Run A's link is starved of capacity. Work was always pending; the far end had nowhere to put it. The fix is in the credit window, the round trip, or the consumer (§26–§31).

Run B's link was never asked to do more. It transferred everything offered, the instant it was offered. The link is at 100% of demand and the benchmark is source-limited (§24).

A single throughput number cannot distinguish them, and it is the number that gets reported. Weeks of scheduler work have been spent on run B's shape.

And the discriminating observation is one counterPERF_NO_WORKwhich costs nothing and is the first thing the decision tree of §56 asks.

19. Demand Before Capacity

Before optimising anything, establish that work was continuously offered. If the source was not offering, the link is not the bottleneck and no change to it can raise the number.

And "offered" must be measured at the source, not inferred from the link being busy — because a link that is 100% busy servicing a trickle looks identical in its own counters to one servicing a flood.

20. Offered, Accepted, Delivered

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Three counts at three points, so §21's ratios exist.
logic [63:0] offered_objects_q;    // the source ATTEMPTED to hand these over
logic [63:0] accepted_objects_q;   // the link took them
logic [63:0] delivered_objects_q;  // the far end completed them
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    offered_objects_q <= '0; accepted_objects_q <= '0; delivered_objects_q <= '0;
  end else if (measure_window_active) begin
    // OFFERED counts the source asserting valid, whether or not it was taken.
    // This is the counter that distinguishes §18's two runs.
    if (src_valid)                    offered_objects_q   <= offered_objects_q + 64'd1;
    if (src_valid && src_ready)       accepted_objects_q  <= accepted_objects_q + 64'd1;
    if (far_end_complete_fire)        delivered_objects_q <= delivered_objects_q + 64'd1;
  end
end

Architecture. Three counters at three points, giving two ratios that separate a source problem from a link problem from a completion problem.

State. Three 64-bit counters.

Event behaviour. offered counts a held valid every cycle it is asserted, which is deliberate: it measures offered pressure, and a source holding valid for ten cycles waited ten cycles. A design that instead wants offered objects must count the rising edge, and the two must not be confused (§56's checklist question 6).

Contract. The three must be measured over the same window, and delivered must come from a completion observation at the far end rather than from the local transmit — which is 20.4 §9's independence rule.

Failure. Counting offered as src_valid && src_ready makes it identical to accepted and destroys the entire measurement — the ratio becomes 1.0 by construction, and every benchmark reports the link as the bottleneck.

Debug/DV. accepted / offered near 1.0 means the link accepted everything offered, so any shortfall is upstream. Well below 1.0 means the link was the constraint, and the class histogram (§17) says which resource.

21. Wrong — the Source-Limited Benchmark

Worked. A traffic generator is configured with an inter-packet gap. Measured throughput is 70% of the link's active peak. An engineer spends three weeks on the scheduler and the number does not move.

CounterValueReading
offered / opportunities0.70the source offered 70%
accepted / offered1.00the link took everything
PERF_NO_WORK30%the missing 30%, named
PERF_NO_CREDIT0no capacity shortage
PERF_ARB_LOSS0no contention

Four properties.

The link is at 100% of demand, which is the highest score it can achieve. There is nothing to fix.

Every scheduler change is untestable because the metric cannot move — which is itself the signal, and three weeks is a long time to notice it.

PERF_NO_WORK at 30% is the answer and it was available on day one. The single counter that would have prevented the whole exercise.

And the generator's gap may be deliberate — modelling a realistic source. In which case 70% is the right answer to the right question, and only the interpretation was wrong.

22. Occupancy as Evidence

Upstream queueReadingConfidence
always emptysource-limitedstrong, if offered load confirms
always fulldownstream-limitedstrong, if service rate confirms
oscillatingburst or latency mismatchweak — §26's sawtooth looks like this
occasionally fulltransient, absorbedusually healthy

Occupancy is evidence and not proof, and the reason is §26: a credit-window-limited link produces a full-then-empty oscillation that is indistinguishable from bursty demand without the service counters. Always pair occupancy with rate.

23. The Occupancy Waterfall

21.3 §32 built this for backpressure. The performance reading is different — it names a rate, not a blockage.

Q0Q1Q2Reading
highlowlowthe Q0 → Q1 boundary is the limiter
highhighlowthe Q1 → Q2 boundary
highhighhighthe consumer — outside the link (§48)
lowlowlownothing is limiting — source-limited (§21)

Two notes.

The first non-full stage after a run of full ones is the bottleneck, because it is being fed faster than it drains. That is the same rule as 21.3 §32, read for rate rather than for deadlock.

And the fourth row is the one that matters most here. All queues empty and throughput low is not a queueing problem at all — it is §21, and no amount of waterfall reading will find anything.

24. Service Rate Per Stage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Transfers per boundary over the measurement window. The MINIMUM
// sustained service rate across the chain is the throughput ceiling — and no
// change anywhere else can exceed it.
logic [63:0] svc_transfers_q [NUM_STAGES];
logic [63:0] svc_active_q    [NUM_STAGES];   // cycles the stage COULD have served
logic [31:0] occ_q           [NUM_STAGES];
logic [31:0] occ_high_water_q[NUM_STAGES];
 
always_ff @(posedge clk or negedge por_n) begin
  for (int s = 0; s < NUM_STAGES; s++) begin
    if (!por_n) begin
      svc_transfers_q[s] <= '0; svc_active_q[s] <= '0; occ_high_water_q[s] <= '0;
    end else if (measure_window_active) begin
      if (stage_xfer_fire[s]) svc_transfers_q[s] <= svc_transfers_q[s] + 64'd1;
      // "Could have served" = had something to serve. A stage with an empty
      // input queue was not failing to serve — it had nothing (§23 row 4).
      if (stage_input_nonempty[s]) svc_active_q[s] <= svc_active_q[s] + 64'd1;
      if (occ_q[s] > occ_high_water_q[s]) occ_high_water_q[s] <= occ_q[s];
    end
  end
end

Architecture. Per stage: transfers, serviceable cycles, and an occupancy high-water mark. svc_transfers / svc_active is the stage's efficiency when it had work, which is the number the waterfall needs.

State. Four counters per stage.

Event behaviour. svc_active counts cycles the stage had something to serve — not every cycle, which is the distinction that separates "slow" from "idle".

Contract. The stages must be the actual queueing points, not the logical block diagram. A stage that is really two queues with a shared counter reports an average that describes neither.

Failure. Dividing transfers by total cycles instead of serviceable cycles makes an idle stage look slow, so the waterfall points at whichever stage was least busy — the exact opposite of the correct answer.

Debug/DV. occ_high_water is the cheap silicon instrument (§55): a stage whose high-water never approaches its depth was never the constraint, which eliminates it without any rate computation at all.

25. Wrong Fix — a Deeper FIFO

The condition 19.4 §9 states, in performance terms:

IfThen depth
arrival rate > sustainable service rate, sustainedcannot help — it only delays the backpressure
arrival is bursty, average below service ratehelps — absorbs the burst, producer catches up

Worked, both cases.

Case A — sustained. Arrival 1.0 objects/cycle, service 0.8. Depth 16 delays the stall by 80 cycles; depth 1,024 delays it by 5,120 cycles. Steady-state throughput is 0.8 either way. The deeper FIFO buys latency, and worse latency at that.

Case B — bursty. Arrival averages 0.6 with bursts of 1.0 lasting 40 cycles; service 0.8. A depth-8 FIFO overflows mid-burst and backpressures the source, losing the tail. A depth-32 FIFO absorbs the burst and the producer catches up in the gap. Throughput rises from ~0.65 to 0.60's demand — fully served.

Three properties.

The two cases look identical at the queue — full, backpressuring. The discriminating observation is the average arrival rate against the service rate over a long window, which is §24's counters.

Case A's deeper FIFO makes things worse, not merely neutral: more buffered work means longer latency for everything in it, and more work to drain on a recovery.

And a design that cannot tell which case it is in will keep increasing depth, because each increase moves the symptom and the symptom's movement reads as progress.

26. The Bandwidth-Delay Product

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE generic model. NOT a UCIe formula and NOT a normative
relationship — the units and the architecture's exact definition of
"outstanding" must come from the design's own specification (§3).
 
  required_outstanding  ~=  target_rate  x  resource_round_trip_latency
 
  where:
    target_rate   is in objects (or units) per cycle
    round_trip    is the full loop: consume -> far end -> release ->
                  return generated -> transported -> applied  (21.4 §5)
    outstanding   is what the credit window permits to be in flight

Worked, illustrative.

QuantityValue
target rate1 object / cycle
credit round trip20 cycles
required outstanding~20 objects
available credit window8
achievable sustained rate~8 / 20 = 0.4 objects / cycle

Four properties, and this is approximate.

The link is capped at 40% and every credit is returned correctly. 21.4's entire ledger closes. There is no leak, no misattribution, no lost return.

Two independent fixes exist — enlarge the window, or shorten the round trip — and they have completely different costs. Window costs receiver buffering; round trip costs design effort in the return path.

The model is an approximation, ignoring burst structure, batching granularity (19.5 §36) and any margin the architecture requires. It is a first estimate to be checked against measurement, not a design rule.

And the round trip must be measured, not assumed21.4's per-boundary journey timestamps give it directly, which is one of the two payoffs of building that correlator.

Measuring it needs two instruments — a latency distribution and an outstanding-count distribution — because an average round trip is the wrong statistic for a window calculation:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Round-trip latency in buckets, not an average. A window sized
// for the MEAN round trip stalls on every excursion above it, so the TAIL is
// the quantity that governs (§27's stall pattern).
logic [63:0] rt_bucket_q [4];      // short / nominal / long / extreme
logic [31:0] rt_max_q;
logic [63:0] rt_sum_q, rt_count_q; // mean, for reference only
 
// Each outstanding object carries the cycle it consumed a credit. The timestamp
// comes from the CONSUME event and is retired at the APPLY event (21.4 §5's
// arrows 2 and 9) — the full loop, not one leg of it.
logic [TIME_W-1:0] consume_time [MAX_OUTSTANDING];
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    for (int b = 0; b < 4; b++) rt_bucket_q[b] <= '0;
    rt_max_q <= '0; rt_sum_q <= '0; rt_count_q <= '0;
  end else if (measure_window_active && return_apply_fire) begin
    automatic logic [31:0] rt = 32'(time_q - consume_time[apply_slot]);
    if      (rt <  RT_SHORT)   rt_bucket_q[0] <= rt_bucket_q[0] + 64'd1;
    else if (rt <  RT_NOMINAL) rt_bucket_q[1] <= rt_bucket_q[1] + 64'd1;
    else if (rt <  RT_LONG)    rt_bucket_q[2] <= rt_bucket_q[2] + 64'd1;
    else                       rt_bucket_q[3] <= rt_bucket_q[3] + 64'd1;
    if (rt > rt_max_q) rt_max_q <= rt;
    rt_sum_q   <= rt_sum_q + 64'(rt);
    rt_count_q <= rt_count_q + 64'd1;
  end
end
 
// And the OUTSTANDING distribution — how much work was actually in flight.
// A window of 8 that is never more than 3 deep is not the limiter (§27).
logic [63:0] outstanding_hist_q [MAX_OUTSTANDING+1];
logic [31:0] outstanding_now_q;
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    outstanding_now_q <= '0;
    for (int i = 0; i <= MAX_OUTSTANDING; i++) outstanding_hist_q[i] <= '0;
  end else begin
    // Derived as a running difference so it cannot drift (21.4 §10).
    outstanding_now_q <= outstanding_now_q
                       + 32'(credit_consume_fire) - 32'(return_apply_fire);
    if (measure_window_active)
      outstanding_hist_q[outstanding_now_q] <= outstanding_hist_q[outstanding_now_q] + 64'd1;
  end
end

Architecture. A latency bucket histogram with a maximum, and an occupancy histogram of the outstanding count.

State. Four latency buckets, a max, a sum and count, plus one bin per possible outstanding value.

Event behaviour. The latency is measured from consume to apply — the full loop of 21.4 §5, not one leg — because that is the quantity §26's model requires. The outstanding count is maintained as a running difference, so it cannot disagree with the two counters that define it.

Contract. The bucket thresholds must be derived from the design's own latency budget, not chosen to make the distribution look balanced. This chapter states no latency figure (§3); the boundaries come from the design.

Failure. Reporting only the mean round trip. A window sized for the mean stalls on every excursion above it, so a distribution with a long tail produces intermittent stalls that a mean-based analysis predicts should not exist.

Debug/DV. The two histograms answer §26's question together, and they answer it in a way the mean cannot. An outstanding histogram concentrated at the window maximum means the window is genuinely the binding constraint. Concentrated well below it means the window is not the limiter and the shortfall is elsewhere — which eliminates the most commonly-blamed cause with one read.

27. Wrong Diagnosis — "Credit Leak"

Observed. The sender's credit counter oscillates: 8 → 0, stall, 8 → 0, stall. An engineer opens a credit-leak investigation.

CheckResult
21.4's nine-counter ledgerevery arrow closes
the three exact identitieshold
rejection counterszero
per-domain conservationholds
credit reaching zerorepeatedly, by design

Four properties.

This is not a leak and not an accounting bug of any kind. Every credit issued is returned and applied. The window is simply smaller than the round trip requires (§26).

The two are genuinely easy to confuse, because both produce a sender at zero credit. The discriminating observation is whether credit recovers: a leak's ceiling ratchets downward and never returns to the full window; a window limit returns to exactly the full window every round trip.

21.3 §7's distinction, arrived at from the performance side — and the counter that settles it is the maximum credit observed, which should equal the advertised window.

And the sawtooth's shape carries the round trip. §28.

28. Reading the Sawtooth

Illustrative trace, one direction, 40 cycles.

CyclesBehaviourCredit
0–78 transfers, back to back8 → 0
8–1912 idlePERF_NO_CREDIT0
20–278 transfers8 → 0
28–3912 idle0

Four readings.

Average utilisation is 8/20 = 40%, matching §26's prediction exactly.

The burst length is the window — 8 — and the period is the round trip — 20. Both parameters are readable straight off the trace, which makes this one of the few performance bugs that is fully diagnosable from a waveform alone.

An average hides it completely. 40% average utilisation is compatible with a smooth 40% (a genuinely slower source) and with this sawtooth. They need different fixes, and only the burst shape distinguishes them.

And PERF_NO_CREDIT at 60% is the histogram's answer — which points at credit and is correct, provided the next question is "window or leak?" rather than "where is the leak?".

The shape is measurable rather than eyeballed, and the instrument is small enough for silicon:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Burst accounting — the two numbers that turn "40% average"
// into "8-transfer bursts every 20 cycles" (§28's reading, as registers).
logic [31:0] burst_len_q;        // consecutive transfer cycles, current run
logic [31:0] gap_len_q;          // consecutive non-transfer cycles, current run
logic [31:0] burst_len_max_q, gap_len_max_q;
logic [63:0] burst_count_q;      // number of bursts — period = window / this
logic [63:0] burst_len_sum_q;    // mean burst length = sum / count
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    burst_len_q <= '0; gap_len_q <= '0;
    burst_len_max_q <= '0; gap_len_max_q <= '0;
    burst_count_q <= '0; burst_len_sum_q <= '0;
  end else if (measure_window_active) begin
    if (xfer_fire) begin
      // A burst STARTS when a transfer follows a gap.
      if (gap_len_q != '0) begin
        burst_count_q <= burst_count_q + 64'd1;
        if (gap_len_q > gap_len_max_q) gap_len_max_q <= gap_len_q;
        gap_len_q     <= '0;
      end
      burst_len_q     <= burst_len_q + 32'd1;
      burst_len_sum_q <= burst_len_sum_q + 64'd1;
    end else begin
      // A gap only counts if work was PENDING — otherwise it measures the
      // source, not the link (§21).
      if (burst_len_q != '0) begin
        if (burst_len_q > burst_len_max_q) burst_len_max_q <= burst_len_q;
        burst_len_q <= '0;
      end
      if (work_pending) gap_len_q <= gap_len_q + 32'd1;
    end
  end
end

Architecture. Burst length and gap length, with maxima, a burst count and a sum — six registers that reconstruct the sawtooth's two parameters.

State. Six counters.

Event behaviour. A burst accumulates on transfer cycles and closes on the first non-transfer cycle. A gap accumulates only while work is pending, so an idle source does not manufacture a fictitious sawtooth.

Contract. work_pending must be the same signal §7's classifier uses, or the burst instrument and the class histogram will disagree about what an idle cycle was — and two instruments disagreeing about the same cycle is worse than either alone.

Failure. Without the work_pending qualification, a bursty source produces an identical signature to a credit-window limit — which is the exact confusion §22 warns about, reproduced inside the instrument meant to resolve it.

Debug/DV. Mean burst length ≈ the credit window and burst period ≈ the round trip is §26's model confirmed from two registers, with no waveform. And a burst length that varies while the gap stays fixed is a different shape entirely — a fixed-latency resource with variable demand, not a window limit.

29. Arbitration — Grants Are Not Service

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Per class: what was asked, what was granted, what actually
// MOVED, and how long between services. The gap between grants and transfers
// is §30's failure.
logic [63:0] arb_requests_q [NUM_CLASSES];
logic [63:0] arb_grants_q   [NUM_CLASSES];
logic [63:0] arb_transfers_q[NUM_CLASSES];   // grant AND the transfer completed
logic [63:0] arb_bytes_q    [NUM_CLASSES];
logic [31:0] arb_gap_q      [NUM_CLASSES];   // cycles since last TRANSFER
logic [31:0] arb_gap_max_q  [NUM_CLASSES];
 
always_ff @(posedge clk or negedge por_n) begin
  for (int c = 0; c < NUM_CLASSES; c++) begin
    if (!por_n) begin
      arb_requests_q[c] <= '0; arb_grants_q[c] <= '0;
      arb_transfers_q[c]<= '0; arb_gap_q[c]    <= '0; arb_gap_max_q[c] <= '0;
    end else if (measure_window_active) begin
      if (arb_request[c]) arb_requests_q[c] <= arb_requests_q[c] + 64'd1;
      if (arb_grant[c])   arb_grants_q[c]   <= arb_grants_q[c]   + 64'd1;
      if (arb_grant[c] && xfer_fire) begin
        arb_transfers_q[c] <= arb_transfers_q[c] + 64'd1;
        arb_bytes_q[c]     <= arb_bytes_q[c] + 64'(xfer_payload_bytes);
        arb_gap_q[c]       <= '0;                      // reset on TRANSFER
      end else if (arb_request[c] && (arb_gap_q[c] != '1)) begin
        // Age only while REQUESTING — an idle class is not being starved
        // (21.3 §30's empty-not-serviced rule).
        arb_gap_q[c] <= arb_gap_q[c] + 32'd1;
        if ((arb_gap_q[c] + 32'd1) > arb_gap_max_q[c])
          arb_gap_max_q[c] <= arb_gap_q[c] + 32'd1;
      end
    end
  end
end

Architecture. Four counts and two ages per class, so the arbiter's fairness and its effectiveness are separately visible.

State. Six counters per class.

Event behaviour. arb_gap_q resets on a transfer, not a grant — which is the entire point of §30 — and ages only while the class is requesting, so an idle class does not accumulate a fictitious starvation age.

Contract. arb_grant and xfer_fire must be sampled in the same cycle. If the grant is registered and the transfer happens a cycle later, the && never holds and every class reports zero transfers — a wiring error that produces an alarming and entirely false result.

Failure. Resetting the gap on a grant hides §30. Ageing unconditionally reports every idle class as starved, so the instrument fires constantly and gets disabled.

Debug/DV. arb_transfers / arb_grants is the number to read. 1.0 means every grant became service; well below 1.0 means the arbiter is granting into a blocked path, which is a scheduler-and-backpressure interaction rather than an arbitration policy problem.

30. Wrong Metric — Counting Grants

Worked, two classes, 1,000 opportunities.

Class AClass B
requests1,0001,000
grants500 (50%)500 (50%)
downstream ready when granted500250
actual transfers500 (50%)250 (25%)
effective share67%33%

Four properties.

The arbiter is perfectly fair by its own metric — 50/50 grants — and the effective bandwidth split is 2:1.

Class B's grants are being wasted, half of them landing when its downstream path is not ready. Those cycles are lost to everyone: class A could have used them.

Total utilisation is 750/1,000 = 75%, and 250 opportunities evaporated between grant and transfer. The class histogram would show them as PERF_BACKPRESSURE, correctly — but only the per-class arbiter counters say whose backpressure.

And the fix is a look-ahead rather than a weight change: an arbiter that does not grant to a requester whose path is blocked. Raising class B's weight makes it worse, because it wastes more opportunities.

31. Scheduler Bubbles

If work is pending, credit is available, the downstream stage is ready, and no transfer occurs — an avoidable opportunity was lost, and there is no legitimate reason unless the architecture documents one.

This is the highest-value invariant in the chapter, because unlike every other class it needs no baseline, no comparison run and no target: the conditions themselves prove the loss.

Three common mechanisms.

A registered arbitration decision that cannot issue back-to-back to different requesters, inserting one idle cycle at every ownership change — §12's waveform.

A handshake that de-asserts ready for one cycle after each transfer to recompute something, halving the rate under continuous load and being invisible under light load.

And a credit check that reads a registered credit value, so the cycle after a return arrives still sees zero — a one-cycle bubble per return, which at high return rates is a large fraction.

32. Bubble Detection

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. All admission conditions met, no transfer. Counted, and the
// FIRST one captured with its context (14.5 §8's sticky-first rule).
logic all_conditions_met;
assign all_conditions_met = link_operational && slot_exists_in_cfg
                         && work_pending && credit_available
                         && downstream_ready && granted;
 
logic bubble_fire;
assign bubble_fire = all_conditions_met && !xfer_fire;
 
logic [63:0]        bubble_cnt_q;
logic               bubble_first_valid_q;
logic [TIME_W-1:0]  bubble_first_time_q;
logic [CLASS_W-1:0] bubble_first_class_q;
logic [31:0]        bubble_run_q, bubble_run_max_q;   // consecutive bubbles
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    bubble_cnt_q <= '0; bubble_first_valid_q <= 1'b0;
    bubble_run_q <= '0; bubble_run_max_q <= '0;
  end else if (measure_window_active) begin
    if (bubble_fire) begin
      bubble_cnt_q <= bubble_cnt_q + 64'd1;
      if (!bubble_first_valid_q) begin           // STICKY FIRST
        bubble_first_valid_q <= 1'b1;
        bubble_first_time_q  <= time_q;
        bubble_first_class_q <= pending_class;
      end
      bubble_run_q <= bubble_run_q + 32'd1;
      if ((bubble_run_q + 32'd1) > bubble_run_max_q)
        bubble_run_max_q <= bubble_run_q + 32'd1;
    end else begin
      bubble_run_q <= '0;
    end
  end
end

Architecture. A count, a sticky first occurrence with context, and a maximum consecutive run.

State. Five registers.

Event behaviour. bubble_fire is the conjunction of every admission condition with the absence of a transfer. The run length distinguishes an alternating bubble from a sustained one — §12's waveform gives a max run of 1; a stuck scheduler gives a large one.

Contract. Every condition in all_conditions_met must be sampled in the same cycle and the same clock domain as xfer_fire. A condition sampled a cycle early makes the conjunction false exactly when it matters, and the instrument silently reports zero bubbles forever.

Failure. Omitting a condition from the conjunction — say, a second credit pool — produces false bubbles on every cycle that pool is empty, which discredits the instrument and gets it removed.

Debug/DV. bubble_first_time is the first avoidable lost opportunity (§52), which is this chapter's analogue of a first-divergence timestamp. bubble_run_max of 1 with a large count is an alternating pattern — a registration bubble. A large run is a stall wearing a bubble's name, and the two need different investigations.

33. SVA — No Unexplained Bubble

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// MANDATORY. ILLUSTRATIVE ARCHITECTURAL CONTRACT — the bound comes from the
// design's own pipeline specification, NOT from any UCIe requirement (§3).
property p_no_unexplained_bubble;
  @(posedge clk) disable iff (!por_n)
    all_conditions_met |-> ##[0:XFER_ISSUE_LATENCY] xfer_fire;
endproperty
a_no_unexplained_bubble: assert property (p_no_unexplained_bubble);
 
// MANDATORY. A grant implies a request — an arbiter granting to nobody is
// wasting an opportunity outright (§12's misclassification).
a_grant_implies_request: assert property (
  @(posedge clk) disable iff (!por_n)
    arb_grant[0] |-> arb_request[0]
);
 
// MANDATORY. Counters move only on defined events — a performance counter that
// free-runs makes every ratio wrong (§10).
a_useful_only_on_transfer: assert property (
  @(posedge clk) disable iff (!por_n)
    ($changed(perf_cnt_q[PERF_USEFUL]) |-> $past(xfer_fire && !is_replay))
);
 
// MANDATORY. Unique bytes ignore retransmission attempts (§16).
a_unique_ignores_replay: assert property (
  @(posedge clk) disable iff (!por_n)
    (xfer_fire && is_replay) |=> $stable(bytes_unique_q)
);

Architecture. One property for the bubble, one for arbitration sanity, and two that check the instrumentation itself.

State. None.

Event behaviour. XFER_ISSUE_LATENCY is a bounded window, not zero. A design with a legitimate registration stage takes a cycle; asserting a same-cycle transfer would fire on correct hardware.

Contract. XFER_ISSUE_LATENCY must come from the design's pipeline specification. A bound that is guessed and then raised each time the assertion fires is not a check — it is a record of the design's worst observed behaviour.

Failure. With the bound set too loose, the property never fires and the bubble goes undetected. With no property at all, §12's waveform is discovered by a human staring at a trace.

Debug/DV. The last two properties are the ones most often omitted, and they guard the instrument rather than the design: a PERF_USEFUL counter that increments without a transfer, or a unique-byte counter that counts a replay, produces a performance report that is confidently wrong — and no amount of analysis downstream can recover from it.

34. Initiation Interval, Not Latency

Latency and throughput are independent, and confusing them is the most common performance-reasoning error.

PipelineLatencyInitiation intervalMax rate
A20 cycles11.0 / cycle
B5 cycles20.5 / cycle
C6 cycles11.0 / cycle
D6 cycles30.33 / cycle

Pipeline A has four times the latency of B and twice the throughput.

Three properties.

Latency affects the credit window requirement (§26), so it is not irrelevant — but it does not cap the rate. A 20-cycle pipeline with II=1 and adequate credit runs at full rate.

The initiation interval is what a benchmark actually measures, and it is set by whichever resource in the pipeline cannot accept new work every cycle — a shared multiplier, a single-ported memory, a state machine that takes two cycles per item.

And II is directly observable from the valid-shift pattern (§35), so it never has to be inferred.

35. Instrumenting the Pipeline

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Stage-valid vector, so a bubble's PROPAGATION is visible and
// the initiation interval is read directly rather than inferred.
logic [NUM_STAGES-1:0] stage_valid_q;
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) stage_valid_q <= '0;
  else        stage_valid_q <= {stage_valid_q[NUM_STAGES-2:0], issue_fire};
end
 
// The observed initiation interval: cycles between consecutive issues, in a
// small histogram. II is the MINIMUM observed gap under continuous demand.
logic [31:0] issue_gap_q;
logic [63:0] ii_hist_q [8];        // gap of 1..7, and 8+ in the last bin
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    issue_gap_q <= '0;
    for (int i = 0; i < 8; i++) ii_hist_q[i] <= '0;
  end else if (measure_window_active) begin
    if (issue_fire) begin
      // Only bin the gap when demand was CONTINUOUS — a gap caused by an empty
      // source measures the source, not the pipeline (§21).
      if (work_pending_throughout_gap)
        ii_hist_q[(issue_gap_q > 32'd7) ? 7 : issue_gap_q[2:0]]
          <= ii_hist_q[(issue_gap_q > 32'd7) ? 7 : issue_gap_q[2:0]] + 64'd1;
      issue_gap_q <= 32'd1;
    end else if (issue_gap_q != '1) begin
      issue_gap_q <= issue_gap_q + 32'd1;
    end
  end
end

Architecture. A shift register of stage validity plus a histogram of issue gaps.

State. One vector of NUM_STAGES bits and eight histogram bins.

Event behaviour. The vector shifts every cycle; a zero travelling through it is a bubble propagating, and it is directly readable in a trace table.

Contract. The gap is binned only when demand was continuous throughout it — otherwise a source gap is recorded as a pipeline limit, which is §21's error moved into the instrument.

Failure. Without the work_pending_throughout_gap qualification, a lightly-loaded run produces a histogram dominated by large gaps and the pipeline is blamed for the generator's idle time.

Debug/DV. The lowest occupied bin is the initiation interval. A histogram concentrated at bin 1 means II=1 and the pipeline is not the limiter; concentrated at bin 2 means II=2 and the ceiling is 0.5 regardless of anything else — which is a hardware fact that no scheduler change can move.

36. Payload Efficiency

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE, VERIFICATION/PERFORMANCE ONLY. Object sizes relative to the
// architecture's transfer width. The bins are defined by the DESIGN's width,
// not by any UCIe quantity (§3).
logic [63:0] size_hist_q [4];    // <1/4 width, <1/2, <full, full
 
always_ff @(posedge clk) begin
  if (measure_window_active && xfer_fire) begin
    if      (xfer_payload_bytes * 4 <  XFER_WIDTH_BYTES) size_hist_q[0] <= size_hist_q[0] + 1;
    else if (xfer_payload_bytes * 2 <  XFER_WIDTH_BYTES) size_hist_q[1] <= size_hist_q[1] + 1;
    else if (xfer_payload_bytes     <  XFER_WIDTH_BYTES) size_hist_q[2] <= size_hist_q[2] + 1;
    else                                                 size_hist_q[3] <= size_hist_q[3] + 1;
  end
end

Architecture. Four bins, so a "wire busy, data slow" report has a cause.

State. Four counters.

Event behaviour. Binned per transfer, relative to the architecture's transfer width.

Contract. The bins are relative to the design's own width. A histogram reported without stating the width is uninterpretable — and the width may itself have changed with the active configuration (§40).

Failure. Binning against the requested width on a degraded link reports every full-width transfer as partial.

Debug/DV. A distribution concentrated in bin 0 means the link is occupied and mostly empty: high utilisation, low payload throughput (§13). Whether that is a workload property, a packetisation choice, or a mapping defect is the next question — and this chapter states no UCIe framing efficiency figure to compare against (§3).

37. Wire Traffic Decomposition

CategoryContributes to utilisationContributes to payload
unique payloadyesyes
protocol and framing overheadyesno
retransmitted payloadyesno — already delivered
training and recovery trafficyesno
idlenono

This table is why §13's two links can both read 60%.

And the second row's size is architectural, not a defect — the Adapter's 2-byte header and 2-byte CRC (§3) are the price of the reliability the link provides. This chapter states no percentage for it; the design's own framing specification does.

38. Retry Overhead Is a Symptom of Something Else

Observed. bytes_unique / bytes_payload is 0.72. Twenty-eight per cent of the payload on the wire has already been delivered once.

Four readings.

The transfer path is not the problem. The scheduler, the arbiter, the credit window and the pipeline are all doing their jobs; they are simply being asked to move the same data repeatedly.

The correctness machinery is working. 14.2: every object is delivered correctly. A performance investigation that "fixes" this by weakening retry trades bandwidth for corruption.

The investigation belongs elsewhere — signal integrity, margin, temperature, a specific lane (21.2 §35's per-lane evidence). This is the clearest case in the chapter of a performance symptom whose root cause is in another domain entirely.

And it should be checked early. It is one ratio from two counters, and it eliminates or confirms an entire category before any scheduler analysis begins — which is why it sits near the top of §56's tree.

39. Recovery Overhead

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. Recoveries are not free even when every one succeeds.
logic [63:0] recovery_count_q;
logic [63:0] recovery_cycles_q;
logic [31:0] cycles_since_recovery_q;
logic [31:0] min_time_between_recovery_q;
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    recovery_count_q <= '0; recovery_cycles_q <= '0;
    cycles_since_recovery_q <= '0; min_time_between_recovery_q <= '1;
  end else begin
    if (!link_operational) recovery_cycles_q <= recovery_cycles_q + 64'd1;
    if (recovery_entry_fire) begin
      recovery_count_q <= recovery_count_q + 64'd1;
      if (cycles_since_recovery_q < min_time_between_recovery_q)
        min_time_between_recovery_q <= cycles_since_recovery_q;
      cycles_since_recovery_q <= '0;
    end else if (cycles_since_recovery_q != '1) begin
      cycles_since_recovery_q <= cycles_since_recovery_q + 32'd1;
    end
  end
end

Architecture. Count, total cycles, and the minimum interval between recoveries.

State. Four counters.

Event behaviour. recovery_cycles_q runs whenever the link is not operational — including outside the measurement window, deliberately, because a recovery that straddles the window boundary still cost time.

Contract. recovery_entry_fire must be a pulse, not a level (21.2 §46) — a level makes recovery_count count cycles instead of events.

Failure. Tracking only the count misses the shape: twenty short recoveries spread evenly are a different problem from twenty in a burst, and only the minimum interval distinguishes them.

Debug/DV. Frequent short successful recoveries can destroy average bandwidth while every individual recovery is a success and no error is reported. 21.2 owns why they happen; this chapter's contribution is that recovery_cycles / total_cycles belongs in the histogram — otherwise those cycles vanish from the accounting and the remaining classes' percentages are computed against the wrong total. This chapter states no threshold at which a recovery rate becomes unacceptable (§3); that is the design's own budget.

40. Active Configuration, Not Requested

Read the active configuration from the link, at the time of the measurement. 21.1 §29 and 19.6 §31: the requested configuration is what was asked for. The active one is what the link negotiated, and a recovery may have changed it since.

And the change may have happened during the measurement (§42's stability requirement), which is worse than a wrong denominator — it means there is no single correct denominator for the window.

The denominator is not computed after the fact from a configuration register — it is counted, in hardware, as the measurement runs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. The denominator, COUNTED. An opportunity exists only when the
// ACTIVE configuration offers the slot — so a degraded link counts fewer
// opportunities and its utilisation is automatically correct (§41).
logic [63:0] opportunities_active_q;    // the CORRECT denominator
logic [63:0] opportunities_requested_q; // what the benchmark assumed — §41's trap
logic [63:0] cycles_operational_q;
logic [63:0] cycles_total_q;
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    opportunities_active_q    <= '0;
    opportunities_requested_q <= '0;
    cycles_operational_q      <= '0;
    cycles_total_q            <= '0;
  end else if (measure_window_active) begin
    cycles_total_q <= cycles_total_q + 64'd1;
    if (link_operational) cycles_operational_q <= cycles_operational_q + 64'd1;
    // Derived from the ACTIVE configuration, sampled every cycle — not from a
    // register read taken once at the start (§43's stability requirement).
    opportunities_active_q    <= opportunities_active_q    + 64'(slots_per_cycle_active);
    opportunities_requested_q <= opportunities_requested_q + 64'(slots_per_cycle_requested);
  end
end
 
// MANDATORY. The two denominators being unequal is the §41 finding, surfaced
// as an assertion rather than discovered by hand a month later.
a_active_matches_requested: assert property (
  @(posedge clk) disable iff (!por_n)
    measure_window_active |-> (slots_per_cycle_active == slots_per_cycle_requested)
);

Architecture. Two denominators counted side by side, so the discrepancy is a measured quantity rather than a discovery.

State. Four 64-bit counters.

Event behaviour. Both accumulate per cycle inside the window, from the live configuration rather than from a single register read at the start — which is what makes a mid-window change visible instead of silently averaged.

Contract. slots_per_cycle_active must reflect the configuration the link is actually running, read from the same place the transfer logic reads it. A denominator derived from a different copy of the configuration can disagree with the hardware it is measuring.

Failure. Computing the denominator offline as cycles × assumed_width is §41 exactly: it produces a plausible number that describes a link that does not exist.

Debug/DV. Three ratios, and each answers a different question. useful / opportunities_active is the honest utilisation. opportunities_active / opportunities_requested is the degradation factor — 0.5 in §41's trace, and it is the whole finding. And cycles_operational / cycles_total is how much of the window the link was carrying traffic at all (§39), which is the term most often silently omitted from the denominator.

41. Wrong — Benchmarking Against the Request

Worked. A benchmark computes utilisation against the requested configuration. The link trained, then degraded during a recovery to a narrower active configuration.

QuantityValue
requested opportunities in window2,000
active opportunities in window1,000
useful transfers980
utilisation vs requested49% — "half the expected bandwidth"
utilisation vs active98% — near-perfect

Four properties.

The hardware is performing near-optimally and is reported as a 51% failure.

A performance investigation will find nothing, because there is nothing wrong in the transfer path — and it may run for weeks before anyone reads the configuration register.

The real finding is the degradation, not the throughput — why the link came up narrower than requested, which is 21.2's territory and a completely different investigation.

And the fix is one register read at measurement time. 21.1 §29's discipline: the active configuration is read, recorded with the measurement, and checked for stability across it.

42. Denominator Discipline

Every percentage states its denominator. "Seventy per cent" is not a measurement.

DenominatorThe percentage means
physical maximumwhat the hardware could ever do
active configurationwhat this link, in this state, could do — usually correct
requested configurationwhat was asked for — §41's trap
offered loadwhat was asked of it — §20's ratio
a targetprogress against a goal

And the numerator needs the same discipline (§15): wire utilisation, delivered payload, or operation rate. A complete statement names both"98% of active-configuration transfer opportunities carried unique payload, over a 700-microsecond steady-state window."

43. Measurement Window

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. The window gates every counter in this chapter (§10), and it
// EXCLUDES bring-up, priming and drain unless the metric intends them.
logic measure_window_active;
logic window_cfg_stable;
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    measure_window_active <= 1'b0;
    window_cfg_stable     <= 1'b1;
  end else begin
    measure_window_active <= link_operational
                          && queues_primed
                          && !drain_phase
                          && window_enable;
    // If the ACTIVE configuration changes mid-window, the denominator changed
    // and the whole measurement is invalid (§41). Sticky — the window cannot
    // silently become valid again.
    if (measure_window_active && active_cfg_changed)
      window_cfg_stable <= 1'b0;
  end
end
 
// MANDATORY. The denominator must not move during the measurement.
a_cfg_stable_in_window: assert property (
  @(posedge clk) disable iff (!por_n)
    measure_window_active |-> !active_cfg_changed
);

Architecture. One gate over every counter, plus a sticky flag that invalidates the measurement if the denominator moved.

State. Two bits.

Event behaviour. The window opens once the link is operational and the queues are primed; it does not close for a brief stall, which is deliberate — a stall is a result, not a reason to stop measuring.

Contract. queues_primed must be a real condition, not a fixed delay. A fixed warm-up that is too short includes the fill transient; too long, and it discards the steady state it was meant to isolate.

Failure. §44.

Debug/DV. window_cfg_stable being clear is the only correct response to a mid-window configuration change: report the measurement as invalid rather than as a number. A benchmark that silently averages across a configuration change produces a figure that describes neither configuration.

44. Wrong Window — Bring-Up Inside the Benchmark

Worked, illustrative.

PhaseDurationUseful transfers
bring-up and training300 µs0
steady-state data700 µsat essentially full rate
whole run1,000 µs
reported throughput~70% of active peak

Four properties.

The link saturated during the entire data phase. The steady-state figure is near 100%; the reported figure is 70%, and the missing 30% is bring-up.

The number is not wrong — it is a correct end-to-end figure for a workload that includes bring-up. It is the wrong metric for the question being asked, which was about steady-state bandwidth.

Both metrics are legitimate and they answer different questions. A system that brings the link up once at boot cares about steady state; one that powers the link down between bursts cares very much about the 300 µs, and for that workload the end-to-end figure is the right one.

And the discipline is to report both, labelled — steady-state throughput and end-to-end throughput including startup — rather than one number whose window is undocumented (15.5 §7).

45. Head-of-Line Blocking

A shared queue serialises classes that have no reason to be serialised.

At the queueState
head entrya large bulk object whose path is blocked
entries behindsmall objects whose paths are clear, with credit available
the arbitersees only the head
resultthe link idles while eligible work sits two entries back

Three properties.

Every resource the blocked work needs is available. Credit, downstream readiness, physical capacity — the queue's own structure is the only obstacle.

The classifier reports it as PERF_BACKPRESSURE, correctly and unhelpfully: the head is backpressured. The instrument that names it is the count of eligible entries behind a blocked head (§46).

And it is invisible to occupancy alone — the queue is full, which reads as a downstream limitation (§22), when in fact the downstream path for most of its contents is wide open.

46. Evidence for Head-of-Line

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. The one counter that distinguishes HOL from genuine
// backpressure: work that COULD have moved, sitting behind work that could not.
logic [31:0] hol_eligible_behind;      // combinational scan, sim-friendly
logic [63:0] hol_blocked_cycles_q;
logic [63:0] hol_lost_opportunity_q;   // eligible entries x blocked cycles
 
always_comb begin
  hol_eligible_behind = '0;
  // Simulation and small queues only. Silicon uses per-class queues, or a
  // single sampled bit (§55).
  for (int i = 1; i < QDEPTH; i++)
    if (q_valid[i] && class_path_ready[q_class[i]] && class_credit_ok[q_class[i]])
      hol_eligible_behind = hol_eligible_behind + 32'd1;
end
 
always_ff @(posedge clk or negedge por_n) begin
  if (!por_n) begin
    hol_blocked_cycles_q <= '0; hol_lost_opportunity_q <= '0;
  end else if (measure_window_active
               && q_valid[0] && !class_path_ready[q_class[0]]
               && (hol_eligible_behind != '0)) begin
    hol_blocked_cycles_q   <= hol_blocked_cycles_q + 64'd1;
    hol_lost_opportunity_q <= hol_lost_opportunity_q + 64'(hol_eligible_behind);
  end
end

Architecture. Two counters that fire only when the head is blocked and something behind it could have moved — which is exactly the head-of-line condition and nothing else.

State. Two 64-bit counters.

Event behaviour. The combinational scan is a simulation instrument. In silicon, either the queues are per class — in which case the problem is structurally absent — or a single sampled "eligible work behind a blocked head" bit is enough to establish that the condition occurs at all.

Contract. class_path_ready must be per class. A single shared ready signal makes hol_eligible_behind always zero, so the instrument reports no head-of-line blocking in exactly the architecture most prone to it.

Failure. Counting only hol_blocked_cycles understates the cost: one blocked cycle with eight eligible entries behind is eight lost opportunities, not one — which is why the second counter weights by the count.

Debug/DV. A nonzero hol_lost_opportunity is proof, not inference. And it is a proof that no aggregate utilisation number can produce, which is why head-of-line blocking survives so many performance investigations.

47. Wrong Fix — More Arbiter Weight

The arbiter never sees the blocked work's competitors.

ChangeEffect on head-of-line
raise the blocked class's weightnone — it is already at the head
raise the other class's weightnone — its work is not visible to the arbiter
separate the queues per classfixes it — each class has its own head
a bypass path for eligible entriesfixes it, at the cost of ordering guarantees

Two properties.

Weights operate on requesters, and there is only one requester — the queue, presenting its head. The eligible work behind is not a requester at all, so no weight can favour it.

And separating the queues has an ordering consequence that must be checked against the architecture: two queues can complete out of order relative to one, which may or may not be legal. 21.6's ordering-domain question, arriving from the performance side.

48. Full Duplex

Measure each direction independently, then together.

MeasurementWhat it establishes
transmit alonethe transmit path's ceiling
receive alonethe receive path's ceiling
both simultaneouslywhether they are actually independent

And the third is the one that finds bugs, because a coupling defect is invisible in both single-direction tests by construction.

49. Cross-Direction Coupling

Worked, illustrative.

TestTransmitReceiveCombined
transmit alone100% of active
receive alone100% of active
both together55%55%110% of one direction

Four properties.

Both single-direction benchmarks pass at 100%, so the whole test suite is green.

If the architecture intends the directions to be independent, the combined figure should approach 200% of one direction. 110% means they are sharing something.

The usual mechanisms are a shared ready or grant signal, a shared scheduler state machine, a shared counter, or a shared buffer — and each is a distinct, findable defect once the coupling is known to exist.

And whether the directions are independent is an architectural question that must be answered before the number means anything. 19.1: if the design deliberately shares a resource, 110% may be exactly correct — in which case the finding is that the benchmark's expectation was wrong, which is §42's discipline again.

50. The Bottleneck May Not Be UCIe

Four shortfalls whose cause is outside the link.

SymptomActual cause
PERF_NO_CREDIT dominant, credits conservedthe consumer is draining slowly — §51
every queue full, including the lastthe consumer, again
PERF_NO_WORK dominantthe source — §21
high wire, low uniquesignal integrity — §38

And the rule is 15.5 §5's: the waterfall's last full stage points outward. When every stage including the consumer's input is full, the constraint is downstream of the link and optimising the link changes nothing.

51. Trace — the Consumer Bottleneck

Illustrative. Every UCIe counter is healthy and the bandwidth is low.

InstrumentValueReading
offered / opportunities0.98demand is present
accepted / offered0.45the link is refusing work
PERF_NO_CREDIT54%dominant class
credit conservation (21.4)every arrow closesnot a leak
bubble_cnt0no scheduler bubbles
arb_transfers / arb_grants1.00arbitration effective
unique / payload1.00no retry overhead
RX buffer occupancyat capacity, sustained
consumer service rate0.45 objects / cyclethe ceiling
credit round tripnormalnot §26's window limit

Five readings.

Every UCIe instrument is clean. Conservation closes, no bubbles, effective arbitration, no retries — so nothing in the link is broken.

PERF_NO_CREDIT at 54% is correct and points five stages downstream (§6's muted return edge). The credits are not being returned because the entries are not being released, because the consumer has not read them.

The two discriminators against §26's window limit are the round trip and the occupancy. A window-limited link has a normal consumer and a short window, producing an empty receive buffer between bursts. This link's receive buffer is permanently at capacity, which is the opposite.

The measured ceiling — 0.45 — equals the consumer's service rate exactly, which is §24's minimum-service-rate rule identifying the binding constraint.

And no change to the link can exceed it. More credits mean a fuller buffer and worse latency. A faster scheduler means more time spent at zero credit. The only fixes are outside the link, which is a finding worth reaching in an afternoon rather than a month.

52. First Lost Opportunity

Correctness debugging looks for the first illegal event. Performance debugging looks for the first avoidable one — and they are different searches.

Correctness (21.6)Performance
looking forthe first event that could not occur in a correct designthe first opportunity that could have been used and was not
evidenceone event is enougha pattern — one lost cycle proves nothing
baselinethe specificationa known-good run, or the conditions themselves
toolan assertion firingbubble_first_time (§32), or a divergence from a good run

Two notes.

A single lost opportunity is not a bug. Performance conclusions are statistical; a single illegal event is a bug outright. This asymmetry governs how much evidence each investigation needs.

But §32's bubble is the exception, and that is what makes it valuable: the conditions themselves prove the loss was avoidable, so one occurrence is evidence — no baseline run required. Every other class needs a comparison.

The comparison, when needed, is aligned by opportunity rather than by time:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ILLUSTRATIVE. Known-good and failing runs, aligned by transfer opportunity.
 
  opp:   1     2     3     4     5     6     7     8
  good:  XFER  XFER  XFER  XFER  XFER  XFER  XFER  XFER
  bad:   XFER  ----  XFER  ----  XFER  ----  XFER  ----
                ^
                FIRST DIVERGENCE at opportunity 2.
                Classifier at opportunity 2 says: PERF_ARB_LOSS
                But no class was granted -> §12: it is a BUBBLE.

53. Trace — High Wire, Low Delivery

Illustrative. The metric that improves as the link degrades.

InstrumentValueReading
wire utilisation94%"excellent"
PERF_USEFUL + PERF_REPLAY94%the wire is busy
PERF_REPLAY alone31%a third of it is repeats
bytes_unique / bytes_payload0.67a third delivers nothing new
size histogram62% in bin 0and most of the rest are small
delivered payloadfar below expectation
retry-triggering errorsrising over the runthe cause

Four readings.

Two independent efficiency losses compound. Retries waste a third of the opportunities; small objects waste most of the width of what remains — and 0.69 × a low payload fraction is a large shortfall from a link reporting 94%.

The single number that would be reported is 94%, and it is the most flattering number available (§15).

The two causes need different owners. Retries go to signal integrity (§38); object size goes to the workload or the packetisation, and may not be a defect at all.

And the ordering matters: fix the retries first, because they are unambiguously a defect, and the object-size question may resolve differently once the link is clean — a smaller effective latency changes how the source batches.

54. Reproducibility

A throughput number without this list is not reproducible, and therefore not comparable with any other number.

RecordWhy
active configuration§41 — the denominator
traffic mix and object sizes§36 — payload efficiency
read/write and direction mix§49 — coupling
offered load and its shape§21 — source limiting
peer latency / round trip§26 — the window requirement
error and retry conditions§38 — a marginal channel changes everything
generator seedany randomised mix
measurement window definition§44 — warm-up and drain
classification priority policy§9 — makes the histogram comparable

And the last row is the one most often omitted. Two teams' histograms are not comparable unless they classify the same way — which makes a shared policy more valuable than a slightly better one.

Reproducibility is about one measurement. Coverage is about whether the suite ever produced the condition that fails — and 20.5 owns the model, so what follows is only the performance-specific cross:

ConditionWhich class it exercisesWhy it is easy to miss
offered load at saturationevery avoidable classa gapped generator never saturates (§21)
offered load well below capacityNO_WORKusually skipped as "uninteresting"
credit exhaustedNO_CREDITneeds a round trip longer than the window
transient backpressureBACKPRESSUREabsorbed by depth, so it leaves no trace
persistent backpressureBACKPRESSURE, the waterfallneeds a deliberately slow consumer
both directions simultaneouslycoupling (§49)single-direction suites pass by construction
minimum-size objectspayload efficiency (§36)mixes usually average it away
full-width objectsthe utilisation ceiling
sustained retry loadREPLAY (§38)needs injected errors, not a clean model
a recovery during the windowRECOVERY (§39)usually excluded as "not a performance test"
a degraded active configurationCFG_LIMIT (§41)the trap that costs the most time
multi-class contentionARB_LOSS (§30)single-class benchmarks miss it entirely
a blocked head with eligible work behindhead-of-line (§46)requires a specific mix, never random
a credit-window-limited burstthe sawtooth (§28)needs the round trip to exceed the window
back-to-back issue at maximum ratePIPE_BUBBLE (§32)a bubble is invisible below full rate

Two readings, and they are 20.5 §11's.

An uncovered condition means the behaviour there is unknown, not that it works. A suite that never saturates the link has never observed a bubble, because a bubble cannot be seen below full rate — the opportunity it wastes was not wanted.

And the last four rows require directed stimulus. Random traffic will not reliably produce a blocked head with eligible work behind it, a round trip longer than the window, or a recovery inside a measurement window. Those are written, not generated.

55. What Silicon Actually Needs

RankInstrumentCostBuys
1the nine class counters + total10 × 64b§11's accounting — everything else depends on it
2active configuration registeralready exists§41's denominator
3bytes_wire, bytes_payload, bytes_unique3 × 64b§53's retry signature
4offered / accepted counts2 × 64b§21's source-limited check
5bubble_cnt + first-bubble time3 regs§32 — always a defect
6queue high-water per stagesmall × stages§24's waterfall
7recovery count, cycles, min interval4 regs§39
8per-class grants and transfers2 × classes§30

Three notes.

Ranks 1, 2 and 4 answer "is this a link problem at all?" and together they are about a dozen registers. They should be present on every link, because they eliminate the most common false investigations before any deep instrumentation is needed.

Not in silicon: the head-of-line scan (§46), the size histogram (§36), the initiation-interval histogram (§35) and the per-stage service rates (§24) are simulation instruments — except as a single sampled bit or a high-water mark.

And all of it is passive. Nothing gates a transfer or sits in a timing path (14.5 §5). A performance counter that perturbs performance is not an instrument.

56. The Diagnosis Tree

The chapter's centerpiece. Each branch either eliminates a category or names the owner.

1. Does §11's identity hold — do the classes sum to the total? Nothe instrument is broken (§8). Fix it before reading anything.

2. Was the offered load high? (offered / opportunities, §20) Nosource-limited. The link is at 100% of demand (§21). Stop.

3. Is the active configuration what the benchmark assumed? (§40) Nothe denominator is wrong (§41). Recompute. The real finding is the degradation21.2.

4. Did the configuration change during the window? (§43) Yesthe measurement is invalid. Re-run.

5. Does the window include bring-up or drain? (§43) Yesseparate startup from steady state (§44) and report both.

6. Is bytes_unique / bytes_payload near 1.0? (§14) Noretry overhead. Owner is signal integrity (§38), not the transfer path. Stop.

7. Is the wire near-fully occupied but payload low? (§36) Yespayload efficiency. Object size, packetisation, or the workload. Stop.

8. Is bubble_cnt nonzero? (§32) Yesan avoidable defect, always. Investigate the scheduler registration (§31). Read bubble_run_max to distinguish alternating from sustained.

9. What is the dominant avoidable class? (§17)

DominantNext question
NO_CREDITwindow or consumer? Is credit conserved (21.4)? Is the receive buffer full (consumer, §51) or empty between bursts (window, §26)?
BACKPRESSUREis eligible work sitting behind a blocked head? (§46) If yes, head-of-line, and weights will not fix it (§47).
ARB_LOSSdid any class actually transfer on those cycles? If not, it is a bubble misclassified (§12). If yes, is transfers/grants near 1.0 (§30)?
RECOVERY§39 — count, cycles and minimum interval, then 21.2.
PIPE_BUBBLE§35's initiation-interval histogram — the lowest occupied bin is the ceiling.

10. If a downstream blocker: which stage's service rate is minimum? (§24) The last full stage in the waterfall points at the owner (§23) — and if it is the consumer's input, the bottleneck is outside the link (§50).

11. Are the directions independent? (§48) Test both together. A combined figure near one direction's ceiling means coupling (§49).

57. Debug Checklist

Thirty-six questions.

Establish the measurement (1–9).

  1. Which metric — wire utilisation, delivered payload, or operation rate (§15)?
  2. Which denominator — physical, active, requested, offered, or a target (§42)?
  3. What is the active configuration, read at measurement time (§40)?
  4. What was requested, and do they differ (§41)?
  5. Did the configuration change during the window (§43)?
  6. What is the measurement window, and does it include bring-up or drain (§44)?
  7. Do the classes sum to the total (§11)?
  8. What is the classification priority policy (§9)?
  9. Is offered counted as held-valid cycles or as objects (§20)?

Demand (10–12). 10. What was the offered load (§20)? 11. What is accepted / offered (§21)? 12. Is PERF_NO_WORK dominant (§18)?

Bytes (13–17). 13. Wire bytes, payload bytes, and unique bytes (§14)? 14. Is unique / payload near 1.0 (§38)? 15. Are replay bytes counted as useful (§16)? 16. What is the object-size distribution relative to the transfer width (§36)? 17. How much of the wire is framing overhead (§37)?

Cycles (18–23). 18. Useful cycles, and against which total (§17)? 19. No-credit cycles (§26)? 20. Backpressure cycles (§45)? 21. Arbitration-loss cycles — and did anyone actually transfer on them (§12)? 22. Pipeline-bubble cycles, and bubble_run_max (§32)? 23. Recovery cycles, count, and minimum interval (§39)?

Queues and rates (24–28). 24. Occupancy at each stage, and its high-water (§24)? 25. Which queue fills first (§23)? 26. Which stage's service rate is minimum (§24)? 27. Is the arrival rate above the sustainable service rate, or merely bursty (§25)? 28. Is any queue always empty — the source-limited signature (§23)?

Credit as a performance quantity (29–31). 29. What is the credit round trip, measured (§26)? 30. What is the outstanding window, and does it cover the round trip (§26)? 31. Is a burst-then-idle sawtooth present, and does credit return to the full window (§27)?

Structure (32–36). 32. What is the initiation interval, from the gap histogram (§35)? 33. Is head-of-line blocking occurring — eligible work behind a blocked head (§46)? 34. Do grants become transfers (§30)? 35. Are the directions independent under simultaneous load (§49)? 36. What is the first avoidable lost opportunity, and what class does it belong to (§52)?

58. Common Misconceptions

"Low throughput means the width or rate is wrong." §41: it more often means the denominator is wrong, or the source never offered the load.

"A deeper FIFO always improves throughput." §25: it helps a burst and cannot help a sustained rate deficit — where it only adds latency.

"A large pipeline latency means low throughput." §34: a 20-cycle pipeline with an initiation interval of 1 outruns a 5-cycle pipeline with an interval of 2.

"More credits always fix bandwidth." §51: when the consumer's service rate is the ceiling, more credits mean a fuller buffer and worse latency.

"A zero-credit sawtooth means a leak." §27: a leak's ceiling ratchets down; a window limit returns to the full window every round trip.

"Grant count equals service." §30: a grant into a blocked path is a wasted opportunity, and a perfectly fair arbiter can produce a 2:1 bandwidth split.

"High wire utilisation means high application throughput." §53: 94% utilisation with a third of it retries delivers less than 70% clean.

"Replay bytes are useful bandwidth." §16: the metric then rises as the link degrades — the worst possible property for an indicator.

"The requested configuration is the right denominator." §41: 98% of active reads as 49% of requested, and the investigation goes to the wrong subsystem.

"Average latency explains throughput." §28: the same 40% average is produced by a smooth source and by a sawtooth, and only one is a defect.

"One throughput percentage is enough." §42: without a numerator definition, a denominator, and a window, it is a number rather than a measurement.

"Include bring-up in a steady-state benchmark." §44: 300 µs of training in a 1 ms run turns a saturated link into a 70% result.

"High queue occupancy means the queue is too shallow." §23: a permanently full queue usually means the next stage is the limiter, and depth will not change its rate.

"Increase the arbiter weight to fix head-of-line blocking." §47: the work behind the blocked head is not a requester, so no weight can favour it.

"If UCIe is slow, the bottleneck is inside UCIe." §50: four common shortfalls are the source, the consumer, the channel, or the benchmark.

59. Understanding Check

60. Summary

The accounting, and what each class sends you to do.

ClassRead nextOwner
USEFULthe byte counters — is it unique (§14)?
NO_WORKoffered load (§20)the source
NO_CREDITconservation (21.4), then buffer occupancy (§51)window, round trip, or the consumer
BACKPRESSUREeligible work behind a blocked head (§46)the queue structure, or downstream
ARB_LOSSdid anyone transfer (§12); transfers/grants (§30)the arbiter, or a bubble in disguise
REPLAYunique / payload (§38)signal integrity
RECOVERYcount, cycles, minimum interval (§39)21.2
PIPE_BUBBLEalways a defect (§32); the II histogram (§35)the pipeline
CFG_LIMITthe denominator (§41)the benchmark, or 21.2

Six things that carry beyond UCIe.

Mutually exclusive classes that sum to the total turn a performance impression into an accounting — and overlapping counters that sum to 180% are worse than no counters at all (§8).

A percentage needs a numerator definition, a denominator and a window, and the active configuration is read rather than assumed (§42).

Demand is established before capacity is investigated. A source-limited benchmark measures the generator (§21).

Wire bytes, payload bytes and unique bytes are three quantities, and a metric built on the first improves as the link degrades (§16).

Latency and throughput are independent — the initiation interval sets the ceiling, and it is directly observable (§34).

And the bottleneck may not be in the link at all. The last full stage in the waterfall points outward, and four of the most common shortfalls are the source, the consumer, the channel, or the benchmark (§50).