Skip to content

UCIe · Module 27

Debug Review Checklist

The pre-tapeout observability gate — why first-failure capture and last-error-wins differ by an entire debug campaign, the trace ring that records before the trigger rather than after, which counter discriminates which hypothesis, and why 'we'll use firmware logs' is a FAIL rather than a plan.

27.5 asked whether the pieces work together. This gate asks what you will know when they do not — and it is held before tapeout, because that is the last moment the answer can change.

1. The One-Sentence Model

Debug capability is an architectural feature. If you wait for the bug to decide what you wish you could observe, tapeout has already answered no.

That is the whole gate. Every other review in Module 27 examines something the design does; this one examines what the design reveals — and unlike a functional bug, an observability gap cannot be patched, worked around in firmware, or fixed in the next drop. It is a respin.

And the gate has a specific technical centre (§6): the first recorded error is almost never the first divergence. A credit return goes missing; credits drift down; a timeout eventually fires. The register says TIMEOUT. The cause was thousands of cycles earlier and is unrecorded. Everything in §7–§15 exists to close that gap.

2. What This Gate Owns

GateAsksNot this chapter
27.1 — Architecturewhat are we building?area H commissioned there
27.2 — RTLdid we implement it safely?code review of the capture logic
27.3 — Verificationcan we prove it correct?pre-silicon closure
27.4 — Performancecan it meet the workload?the counters' performance use
27.5 — Integrationcan the pieces work together?cross-die contracts
27.6 — Debug (this chapter)can we find the first divergence without guessing?

Three boundaries.

This is not a debug tutorial. 21.7 and 25.9 teach how to debug; this gate reviews whether the silicon will let you. The distinction is temporal: those chapters run after tapeout, this one runs before it, and its findings are the only ones that can still be acted on.

It shares counters with 27.4 and reviews them for a different property. 27.4 asks do these counters attribute lost cycles? This gate asks does each counter discriminate between two hypotheses? — and a counter that measures something real but distinguishes nothing is a 27.4 asset and a 27.6 finding (§13).

And 27.1 §13 commissioned area H months ago. If that commissioning did not happen, this gate cannot repair it — the review can only record how much of the design is unobservable and what that costs. §21 is what that conversation looks like.

3. Gate Decisions for Observability

DecisionIn this gate
PASSfirst-divergence evidence exists for every known risk · capture survives the resets that occur around it · the mechanisms have themselves been tested (§18) · reachable in the lab (§20)
CONDITIONAL PASSa low-likelihood risk has partial observability, with a named mitigation — a bench procedure, an emulation-based fallback — and no downstream gate assumes full visibility
FAILa known risk with no first-divergence evidence · last-error-wins on a critical status · trace records only after the trigger · capture cleared by a reset that occurs in the failure path · debug mechanisms never validated · unreachable through the assembled package

Three readings.

"For every known risk" is what makes this gate tractable. You cannot make everything observable. You can enumerate the risks the earlier gates surfaced27.1 §19's three unanswered questions, 27.5 §8's retention disagreement, 27.4 §17's window ceiling — and require evidence for each. A risk that a previous gate named and this gate cannot observe is a FAIL, not a judgement call.

The CONDITIONAL PASS is narrower here than elsewhere, because there is no closure date that helps: after tapeout the mechanism either exists or it does not. The only legitimate conditional is a mitigation — a bench technique, an emulation fallback, an FPGA prototype that carries the missing instrument — and it must be named and costed now.

And the last FAIL row catches a real and expensive mistake. Debug registers that exist in RTL and cannot be reached through the assembled package are not observable (27.5 §17 H7). That is discovered in the lab, and the RTL was correct all along.

4. Statement Versus Evidence

Review statement (worthless)Evidence (accepted)
"we have error registers"first-fault sticky with class, state, semantic ID, generation, epochs and timestamp — and the overflow count (§7)
"we'll use firmware logs"nothing for a lost hardware event (§21)
"there's a trace buffer"the pre-trigger depth, the post-trigger budget, the wrap flag and the freeze (§10)
"we have counters"the counter→hypothesis map: which two explanations does each separate? (§13)
"software can read the state"the atomic snapshot mechanism and its documented read protocol (§14)
"we tested the debug logic"the injection results: second fault, trace wrap, trigger near wrap, reset during trace, saturation (§18)
"we can dump registers"the sequence proving evidence is captured before any reset (§16)

Three readings.

Row 2 is this chapter's flagship (§21), and the reason it fails is structural rather than a matter of log quality. A log records what software was told. A lost credit return was never reported to software — no interrupt, no status change, no event. You cannot log an event that did not happen, so the log's first entry is the timeout, which is the symptom.

Row 4 is the item that separates useful instrumentation from telemetry. Every counter should answer "which two hypotheses does this discriminate?" A counter that measures something real and separates nothing is cost without capability.

And row 6 is the item almost nobody has. The debug mechanisms are logic, and untested logic does not work. A trace ring whose trigger lands one entry before the wrap point, a first-fault register hit by two faults in consecutive cycles — these are the conditions that occur in the failure you built them for.

5. Sourcing and Scope

6. First Divergence Is Not First Error

The distinction that organises the whole gate.

First divergenceFirst recorded error
what it isthe first moment observed behaviour left the contractthe first condition the design was built to detect
whenearlyoften much later
examplea credit return is not generateda timeout fires
what it tells youthe subsystem and the mechanismthat something is wrong
typically recorded?no, unless designed foryes

Worked example — the gap made concrete.

CycleWhat happensRecorded by a typical design
1 000a credit return is not generated for one entrynothing
1 001–40 000traffic continues; credits drift down by onenothing
40 000credits reach zero; the link stallsnothing — a stall is not an error
40 000–44 000no progressnothing
44 000a transaction times outERR_TIMEOUT
44 001+dependent timeouts cascadeERR_FATAL overwrites it (§9)

The register says ERR_FATAL. The divergence was at cycle 1 000, 43 000 cycles earlier, at a specific boundary, and nothing recorded it.

Three readings.

The gap between divergence and error is the amount of debugging you are signing up for, and it is set at design time. Closing it is not about recording more — it is about recording invariant violations rather than consequences. A credit-conservation check (25.6 §10) fires at cycle 1 000; a timeout fires at 44 000. Same bug, two orders of magnitude apart in usefulness.

Which yields the design rule this gate applies everywhere: capture invariant violations, not symptoms. A timeout is a symptom of a dozen different root causes; a conservation violation is one cause, and it names the boundary.

And it explains why a trace ring must record before the trigger (§12). At cycle 44 000 the interesting history is 43 000 cycles old. A buffer that starts recording at the trigger captures only the cascade.

7. Area A — First-Failure Capture

#Must be trueEvidence that countsWeak evidenceFAIL if
A1capture is FIRST, not lastthe !valid guard in RTL"we log errors"last-error-wins (§9)
A2an error class is recorded, not a single bitthe class encodingone error flagcannot triage
A3semantic ID and generation are capturedthe fieldscannot correlate (27.5 §21)
A4config epoch and route epoch are capturedthe fieldsstale-config cases undiagnosable
A5a timestamp is capturedthe counter valuecannot order across dies
A6an overflow/second-fault count existsthe countercannot tell one fault from a storm
A7a compact resource snapshot accompanies itcredits, occupancy, state (§15)context lost
A8clearing is explicit and separate from functional resetthe clear controlcannot re-arm live (27.5 §18 I4)
A9it survives every reset in the failure paththe retention statement + testthe reset destroys the cause

Three readings.

A6 is the field people omit and then desperately need. First-fault capture alone cannot distinguish "one thing went wrong" from "nine thousand things went wrong and this was the first." A count of subsequent faults turns an ambiguous register into a story — and it costs one counter.

A9 is where first-fault capture is most often defeated in practice. The design captures correctly; then the link resets as part of recovery, and the capture is cleared by the same reset. The failure path itself erases the evidence, and the field reports only failures that did not trigger a reset.

And A4 is what makes the modern failure classes findable at all. A fault whose cause is a stale configuration epoch or a route change (26.5 §19) is undiagnosable without those two fields — the error class will say something plausible and wrong.

8. Illustrative RTL — the First-Fault Register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE. First-fault capture. The single guard `!ff_valid_q` is what
// makes this FIRST rather than LAST, and it is the whole difference between
// §9's useless register and a usable one.
typedef enum logic [3:0] {
  FC_NONE, FC_CREDIT_INVARIANT, FC_ORPHAN_CMPL, FC_STALE_GEN,
  FC_ID_REUSE_LIVE, FC_EPOCH_STALE, FC_ROUTE_MISMATCH, FC_TIMEOUT,
  FC_FSM_ILLEGAL, FC_PARITY, FC_OVERFLOW, FC_UNDERFLOW
} fault_class_e;
 
logic              ff_valid_q;
fault_class_e      ff_class_q;
logic [SEM_W-1:0]  ff_sem_id_q;
logic [GEN_W-1:0]  ff_gen_q;
logic [EPOCH_W-1:0] ff_cfg_epoch_q, ff_route_epoch_q;
logic [2:0]        ff_lnk_state_q;
logic [31:0]       ff_timestamp_q;
logic [15:0]       ff_credits_q, ff_outstanding_q;
logic [15:0]       ff_subsequent_q;      // A6 — one fault, or a storm?
logic [31:0]       cycle_q;
 
always_ff @(posedge clk or negedge rst_n) begin
  // NOTE the reset expression: functional/link resets are DELIBERATELY absent.
  // Only a full reset or an explicit debug clear may erase evidence (A8, A9).
  if (!rst_n || ff_clear) begin
    ff_valid_q      <= 1'b0;
    ff_class_q      <= FC_NONE;
    ff_subsequent_q <= '0;
    cycle_q         <= '0;
  end else begin
    cycle_q <= cycle_q + 32'd1;
 
    if (fault_detected) begin
      if (!ff_valid_q) begin
        // FIRST fault: capture everything needed to locate the divergence.
        ff_valid_q       <= 1'b1;
        ff_class_q       <= fault_class;
        ff_sem_id_q      <= fault_sem_id;
        ff_gen_q         <= fault_gen;
        ff_cfg_epoch_q   <= cfg_epoch;        // A4
        ff_route_epoch_q <= route_epoch;      // A4
        ff_lnk_state_q   <= link_state;
        ff_timestamp_q   <= cycle_q;          // A5
        ff_credits_q     <= credits_avail;    // A7 — context at the moment
        ff_outstanding_q <= outstanding_cnt;  // A7
      end else if (ff_subsequent_q != 16'hFFFF) begin
        // Later faults: COUNT them, never overwrite. Saturate rather than wrap.
        ff_subsequent_q <= ff_subsequent_q + 16'd1;
      end
    end
  end
end
 
// MANDATORY. English: once first-fault capture is valid, its class never
// changes until an explicit clear. This is A1 expressed as a property, and it
// catches anyone "improving" the register into last-error-wins (§9).
a_first_fault_stable: assert property (
  @(posedge clk) disable iff (!rst_n)
    (ff_valid_q && !ff_clear) |=> (ff_valid_q && $stable(ff_class_q))
);
 
// MANDATORY. English: a fault always leaves a record — either it captures, or
// it increments the subsequent count. Catches a fault path that reports
// nothing at all, which is worse than reporting the wrong thing.
a_fault_always_recorded: assert property (
  @(posedge clk) disable iff (!rst_n)
    fault_detected |=> (ff_valid_q &&
      ($changed(ff_class_q) || $changed(ff_subsequent_q) ||
       (ff_subsequent_q == 16'hFFFF)))
);

Architecture. One sticky record plus a count of everything after it. Ten fields, chosen so that a single register dump answers what, to which transaction, under which configuration, when, and in what context.

State. ff_valid_q is the guard; the rest is captured once. ff_subsequent_q is what distinguishes a single event from a storm (A6), and it saturates rather than wrapping so a huge count stays huge.

Event. Capture on the first fault_detected. Every subsequent fault increments only.

Contract. The reset expression is a contract, not a style choice — link resets and functional resets are deliberately absent (A9), so a recovery in the failure path cannot erase the cause. ff_clear must be a debug-only control that does not disturb functional state (A8), or you cannot re-arm on a live system.

Failure. Three realistic mistakes. Omitting ff_subsequent_q makes one fault and ten thousand indistinguishable. Including link_rst_n in the reset erases evidence in exactly the failure path you care about. And capturing only the class — without ID, generation and epochs — produces a register that names a symptom and locates nothing.

DV/debug. a_first_fault_stable catches the regression back to last-error-wins, which is a natural-looking "improvement". And §18 requires this logic to be validated by injection: two faults in consecutive cycles, and a fault during a link reset.

9. Wrong RTL — Last Error Wins

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. Every error overwrites the status register. Written by
// someone who reasoned that the most recent error is the most relevant — which
// is true for a stateless peripheral and false for a cascading link failure.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    err_code_q  <= FC_NONE;
    err_valid_q <= 1'b0;
  end else if (fault_detected) begin
    err_code_q  <= fault_class;      // BUG 1: overwrites unconditionally
    err_valid_q <= 1'b1;             // BUG 2: no count of prior faults
  end
  // BUG 3: no ID, generation, epoch or timestamp — nothing to correlate.
end

Architecture. One register, one valid bit. Simple, tidy, and it destroys the only information that mattered.

State. A single class, continuously overwritten.

Event. Every fault.

Contract. A debug engineer reads this register expecting the reason the link failed. It contains the last consequence before someone looked.

Failure — the timeline. §6's scenario, with this register.

CycleEventerr_code_qWhat is now lost
1 000credit-conservation violationFC_CREDIT_INVARIANT
40 000credits exhausted, link stallsFC_CREDIT_INVARIANT
44 000transaction timeoutFC_TIMEOUTthe root cause, overwritten
44 010second timeoutFC_TIMEOUT
44 100dependent unit reports orphanFC_ORPHAN_CMPL
45 000fatal escalationFC_TIMEOUT (last one)
lab, next dayengineer reads the registerFC_TIMEOUTeverything

Root cause. The design recorded the most recent event when the first one was the information. In a cascading failure — which is what link failures are — the last error is almost always a generic escalation, and generic escalations are consistent with dozens of root causes.

And the cost is measurable in weeks, not hours. FC_TIMEOUT sends the team to look at latency, arbitration, the far end, the workload — every hypothesis except credit accounting, because nothing points there. The correct first-fault register would have said FC_CREDIT_INVARIANT at cycle 1 000, with the semantic ID and the credit count at that moment.

The review question that catches it is one line: "is this register first-write-wins or last-write-wins?" — and the answer is visible in the RTL in five seconds. §8 is the correction, and §3 makes this a FAIL on a critical status register rather than an improvement request.

10. Area B — Trace Buffer

#Must be trueEvidence that countsFAIL if
B1the ring records continuously, BEFORE any triggerthe always-writing structurearms on the trigger (§12)
B2post-trigger capture is bounded and smallthe post-trigger countrecords only the cascade
B3it freezes and stays frozenthe freeze conditionoverwritten while being read
B4a wrap indicator existsthe sticky wrapped flagcannot tell 100 entries from 100 000
B5a valid entry count existsthe countcannot tell full from partial
B6entries carry a timestampthe fieldcannot order or correlate
B7entries carry identity and epochsthe fieldscannot follow a transaction
B8the event set is chosen to show divergence, not volumethe event lista firehose of noise

Three readings.

B1 is the item, and §12 is why. At the moment a trigger fires, the interesting history is already in the past. A buffer that begins recording at the trigger captures the consequences of a divergence that happened long before.

B4 is small and decisive. A dump of 1 024 entries with no wrap flag could be 1 024 events total or the last 1 024 of a million. Those two readings support opposite conclusions, and the flag costs one bit.

And B8 is where trace design goes wrong in the other direction. A ring that records every transaction covers microseconds and wraps away the divergence. A ring that records state transitions, credit events, error conditions, epoch changes and recovery entry/exit covers far longer and contains the events that matter. The review item is the event list, and its justification.

11. Illustrative RTL — the Trace Ring

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE (§8). A circular pre-trigger history with a bounded post-trigger
// tail and a hard freeze. The three review-critical properties are: it writes
// ALWAYS, the wrap flag is sticky, and after freeze nothing mutates.
localparam int TR_DEPTH = 1024;
localparam int TR_POST  = 64;              // post-trigger entries, deliberately small
 
typedef struct packed {
  logic [31:0]        ts;
  logic [3:0]         evt;                 // state change / credit / error / epoch...
  logic [SEM_W-1:0]   sem_id;
  logic [GEN_W-1:0]   gen;
  logic [EPOCH_W-1:0] cfg_epoch;
  logic [15:0]        aux;                 // credits, occupancy, sub-state
} tr_entry_t;
 
tr_entry_t             tr_mem   [TR_DEPTH];
logic [$clog2(TR_DEPTH)-1:0] wr_ptr_q;
logic                  wrapped_q;          // STICKY (B4)
logic                  frozen_q;
logic [$clog2(TR_POST):0] post_cnt_q;
logic [$clog2(TR_DEPTH):0] valid_cnt_q;    // B5
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n || tr_arm) begin
    wr_ptr_q <= '0; wrapped_q <= 1'b0; frozen_q <= 1'b0;
    post_cnt_q <= '0; valid_cnt_q <= '0;
    // NOTE: the memory is deliberately NOT cleared. valid_cnt_q and wrapped_q
    // describe what is meaningful, so clearing 1024 entries buys nothing and
    // costs a long, awkward reset sequence.
  end else if (!frozen_q) begin
    // ALWAYS recording. This is B1, and it is the difference from §12.
    if (tr_event) begin
      tr_mem[wr_ptr_q] <= '{ ts:        cycle_q,
                             evt:       tr_evt_code,
                             sem_id:    tr_sem_id,
                             gen:       tr_gen,
                             cfg_epoch: cfg_epoch,
                             aux:       tr_aux };
      if (wr_ptr_q == TR_DEPTH-1) begin
        wr_ptr_q  <= '0;
        wrapped_q <= 1'b1;                 // sticky: never cleared except on arm
      end else begin
        wr_ptr_q <= wr_ptr_q + 1'b1;
      end
      if (valid_cnt_q != TR_DEPTH) valid_cnt_q <= valid_cnt_q + 1'b1;
    end
 
    // After the trigger, keep a BOUNDED tail so the cascade's start is visible,
    // then freeze. Counting ENTRIES, not cycles: a quiet link would otherwise
    // freeze having recorded nothing after the trigger.
    if (tr_triggered) begin
      if (post_cnt_q == TR_POST) frozen_q <= 1'b1;
      else if (tr_event)         post_cnt_q <= post_cnt_q + 1'b1;
    end
  end
  // else: FROZEN. No branch may write tr_mem, wr_ptr_q or wrapped_q (B3).
end
 
// MANDATORY. English: once frozen, the write pointer never moves. Catches a
// later edit that adds a write path outside the !frozen_q guard — which would
// overwrite the evidence while software is reading it.
a_trace_frozen_stable: assert property (
  @(posedge clk) disable iff (!rst_n)
    frozen_q |=> ($stable(wr_ptr_q) && $stable(wrapped_q) && frozen_q)
);
 
// MANDATORY. English: the wrapped flag is sticky until an explicit re-arm.
// Catches the flag being cleared as a side effect, which would make a full
// buffer look like a partial one.
a_wrapped_sticky: assert property (
  @(posedge clk) disable iff (!rst_n || tr_arm)
    wrapped_q |=> wrapped_q
);

Architecture. A continuously-written ring, a sticky wrap flag, a bounded entry-counted post-trigger tail, and a hard freeze. Depth TR_DEPTH of history and only TR_POST of aftermath — the asymmetry is deliberate and is the design.

State. Pointer, wrap flag, freeze, post count, valid count. valid_cnt_q and wrapped_q together tell software exactly what the dump means, which is why the memory need not be cleared on arm.

Event. Written on tr_event — the selected event set (B8), not every transaction.

Contract. The post-trigger tail counts entries, not cycles. A cycle-counted tail on a link that stalls after the trigger freezes having recorded nothing, which is the common case: the trigger often is the moment traffic stops.

Failure. Four realistic mistakes. A non-sticky wrap flag makes a wrapped buffer look partial. A cycle-counted post-trigger tail captures nothing on a stalled link. A write path outside the freeze guard overwrites evidence during the dump. And a too-large TR_POST wraps the pre-trigger history away with cascade entries — which converts a correct ring into §12's broken one.

DV/debug. §18 requires this to be injected: trigger at entry 0, trigger one entry before wrap, trigger after several wraps, and a reset during capture. The last is where wrapped_q and valid_cnt_q are most likely to disagree.

12. Wrong Trace — Recording Starts at the Trigger

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG. ILLUSTRATIVE. Recording begins when the error is detected. This looks
// efficient — why record when nothing is wrong? — and captures only the
// aftermath of a divergence that already happened.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    rec_en_q <= 1'b0; wr_ptr_q <= '0;
  end else begin
    if (error_detected) rec_en_q <= 1'b1;          // BUG: arms ON the error
    if (rec_en_q && tr_event) begin
      tr_mem[wr_ptr_q] <= tr_entry;
      wr_ptr_q <= wr_ptr_q + 1'b1;                 // BUG 2: no freeze; wraps forever
    end
  end
end

Architecture. A buffer that is idle until something goes wrong.

State. An enable and a pointer. No history exists at the moment history becomes valuable.

Event. Arms on error_detected — which by §6 is typically thousands of cycles after the divergence.

Contract. A debug engineer expects the trace to contain the events leading to the failure. It contains the events following it.

Failure — the timeline.

CycleEventTrace contains
1 000credit-conservation violation — the divergencenothing; not recording
1 001–43 999credits drift; the link degradesnothing
44 000timeout → error_detectedrecording beginsempty, now arming
44 001+dependent timeouts, orphans, escalationthe cascade
dump1 024 entries, all consequences

Every recorded entry is derivative. The root cause occurred 43 000 cycles before the first entry, and no amount of analysis of the cascade recovers it.

And there is a second, subtler defect: no freeze (B3). The buffer keeps writing while software reads it, so a slow dump returns a mixture of entries from different wrap generations — which is worse than an empty buffer, because it looks like data.

Root cause. The trigger was treated as the start of the interesting period when it is the end of it (§6). The fix is architectural, not a tuning change: record always, keep a bounded tail, freeze.

DV/debug. The review question is one line: "does the ring record before the trigger?" — visible in the RTL immediately. §3 makes this a FAIL, because a post-trigger-only trace cannot produce first-divergence evidence for any risk.

13. Area C — Counters That Discriminate

A counter earns its area by separating two hypotheses. The review artefact is the counter→hypothesis map.

CounterSeparatesFrom
offered vs acceptedthe source is not offeringdownstream is refusing
accepted vs transmittedadmission is the limitthe wire is
transmitted vs retrieduseful workretransmission (27.4 §11)
delivered vs retiredtransport completedsemantic completion
no-credit cyclesflow controlanything else
stalled-on-no-free-IDthe outstanding window (27.4 §17)congestion
credit-return generated vs appliedour side failed to returnthe peer failed to apply
recovery enter / exit countsone long recoverymany short ones
obligations outstanding at recoveryretaineddiscarded (27.5 §8)
first-fault subsequent countone faulta storm (A6)

Three readings.

Row 7 is the counter that would have made §6's example a five-minute diagnosis. Credit returns generated on one side versus applied on the other localises a missing return to a specific direction and boundary (25.9 §12) — and it is exactly the event that firmware logs cannot see (§21).

Row 9 is 27.5 §8's discriminator, and it must exist on both sides or it discriminates nothing.

And the review test for every proposed counter is a single question: "which two explanations does this separate?" If the answer is vague, the counter is telemetry — it consumes area, appears in dashboards, and does not shorten any investigation.

14. Area D — Atomic Snapshot

#Must be trueEvidence that countsFAIL if
D1state is captured in one cyclethe single-cycle copysequential live reads
D2capture is triggered by the fault, not only by softwarethe trigger paththe state has moved on by the time software arrives
D3an epoch/valid indicates a complete capturethe fieldstorn reads unnoticed
D4the snapshot spans the whole diagnostic setthe field listpartial context
D5saturation is reportedthe sticky bita pinned counter reads as a plateau
D6the read protocol is documentedthe register-map notesoftware trusts a torn read

Two readings.

D2 is the item unique to debug snapshots, as opposed to 27.4 §12's performance snapshots. A performance snapshot can be taken on demand. A debug snapshot must be taken at the moment of the fault — by the time software notices and reads, credits have recovered, occupancy has drained and the link has retrained. The state that explained the fault is gone.

And D1's failure mode produces impossible states rather than wrong ones, if anyone checks. A sequential read across a live system can yield more delivered than accepted — visibly absurd. The dangerous case is a small skew that yields a plausible, wrong picture and sends the investigation somewhere reasonable and incorrect.

15. Illustrative RTL — the Fault-Triggered Snapshot

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ILLUSTRATIVE (§8). Captured BY THE FAULT (D2), in ONE cycle (D1). This is
// the context that makes §8's first-fault class actionable: not just WHAT went
// wrong, but what the machine looked like at that instant.
typedef struct packed {
  logic [2:0]         lnk_state;
  logic [EPOCH_W-1:0] cfg_epoch, route_epoch;
  logic [15:0]        credits_avail, credits_outstanding;
  logic [15:0]        occupancy, occ_high_water;
  logic [31:0]        no_credit_cyc, no_free_id_cyc;
  logic [31:0]        cr_ret_generated, cr_ret_applied;   // §13 row 7
  logic [15:0]        obligations_outstanding;            // §13 row 9
  logic [31:0]        timestamp;
} dbg_snap_t;
 
dbg_snap_t   snap_q;
logic        snap_valid_q;
logic [15:0] snap_epoch_q;
logic        snap_saturated_q;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n || snap_clear) begin
    snap_valid_q <= 1'b0; snap_epoch_q <= '0; snap_saturated_q <= 1'b0;
  end else begin
    // Capture on the FIRST fault, or on an explicit software request. Every
    // field is sampled in the SAME cycle — a multi-cycle copy would reintroduce
    // exactly the tear this exists to prevent (D1).
    if ((fault_detected && !snap_valid_q) || snap_req) begin
      snap_q <= '{ lnk_state:               link_state,
                   cfg_epoch:               cfg_epoch,
                   route_epoch:             route_epoch,
                   credits_avail:           credits_avail,
                   credits_outstanding:     credits_outstanding,
                   occupancy:               occupancy_q,
                   occ_high_water:          occ_high_water_q,
                   no_credit_cyc:           no_credit_q[31:0],
                   no_free_id_cyc:          no_free_id_q[31:0],
                   cr_ret_generated:        cr_gen_q[31:0],
                   cr_ret_applied:          cr_app_q[31:0],
                   obligations_outstanding: obligations_q,
                   timestamp:               cycle_q };
      snap_valid_q <= 1'b1;
      snap_epoch_q <= snap_epoch_q + 16'd1;
      if (ctr_saturated) snap_saturated_q <= 1'b1;     // D5
    end
  end
end
 
// MANDATORY. English: a captured snapshot never changes until explicitly
// cleared. Catches a later "refresh on every fault" change, which would
// replace the first fault's context with the cascade's (§9's error, again).
a_snapshot_stable: assert property (
  @(posedge clk) disable iff (!rst_n)
    (snap_valid_q && !snap_clear && !snap_req) |=> $stable(snap_q)
);
 
// MANDATORY. English: the first fault always produces a snapshot. Catches a
// fault path that records a class with no context, which names a symptom and
// locates nothing.
a_fault_captures_context: assert property (
  @(posedge clk) disable iff (!rst_n)
    (fault_detected && !$past(snap_valid_q)) |=> snap_valid_q
);

Architecture. One structure, captured in one cycle, by the fault. Thirteen fields chosen so that the four most common root-cause families — credit accounting, the outstanding window, configuration staleness and retention — are each distinguishable from the dump alone.

State. One snapshot plus an epoch and a valid bit. snap_epoch_q lets software detect a re-capture between reads (D3, 27.4 §13's protocol).

Event. First fault, or explicit request. Note the !snap_valid_q guard: like §8, this is first-wins — a refresh-on-every-fault design replaces the first fault's context with the cascade's, which is §9's error in a different register.

Contract. Every field is sampled in the same cycle from the same clock domain. A field pulled from another die's domain must be a synchronised, stable value (27.5 §13 E3) — capturing a raw cross-domain bus here produces a value that was never real.

Failure. The realistic mistakes are capturing only on software request (D2 — the state has moved on), capturing over multiple cycles (D1 — a torn picture), and refreshing on every fault (the cascade overwrites the cause).

DV/debug. The pair cr_ret_generated / cr_ret_applied in the snapshot is what turns §6's 43 000-cycle mystery into a one-read answer: a difference of exactly one, at the moment of the first fault, names the boundary and the mechanism.

16. Area E — Reset Must Not Destroy Evidence

#Must be trueEvidence that countsFAIL if
E1first-fault survives link reset and transport recoverythe reset expression + a testthe failure path erases the cause
E2the trace survives the resets in the failure paththe retention statementhistory lost
E3the snapshot survives themthe reset expressioncontext lost
E4software captures evidence BEFORE any resetthe documented recovery sequenceautomatic reset first
E5diagnostic clear is separate from functional resetthe two controlscannot re-arm live (27.5 §18 I4)
E6what survives which reset is written downthe retention table (26.5 §8)assumed

Three readings.

E4 is a software review item in a hardware gate, and it belongs here because nobody else asks it. A recovery routine that resets the link before dumping evidence destroys the record every single time — and the hardware was correct. The required sequence is: capture → dump trace → read snapshot → then reset.

E1 is checked by reading one line: the reset expression on the capture registers. if (!rst_n || !link_rst_n) on a first-fault register is the failure, visible instantly (26.5 §9 is the same defect on transaction state).

And E6 exists because retention is an agreement. Debug hardware, link logic and firmware are frequently owned by three different groups. A retention table nobody wrote is three groups' three assumptions, which is 27.5 §8's pattern applied to evidence.

17. Area F — Cross-Die Correlation

Inherited from 27.5 §21, owned here.

#Must be trueFAIL if
F1a common time base or shared milestone countertwo dies' traces cannot be interleaved
F2semantic ID and generation in both sides' recordscannot follow one transaction
F3config epoch and route epoch in bothstale-config cases undiagnosable
F4obligations_outstanding_at_recovery on both27.5 §8 undiagnosable
F5credit-return generated/applied on both§13 row 7 — direction unresolvable
F6first-fault on each die, independently clearableone side's cause lost
F7the contract version readable from siliconwhich contract is present is unknown

And F1 is a hard prerequisite, not a nice-to-have. Without a correlated time base, two dies' traces are two unrelated lists — and every cross-die failure is a correlation problem. This is a design-time decision that cannot be added when the first silicon bug appears, which is precisely the shape §1 describes.

18. Area G — Validating the Debug Mechanisms

The debug logic is logic. Untested logic does not work — and it will be exercised for the first time during the failure it exists for.

InjectionMust produce
a single faultcorrect class, ID, epochs, timestamp, context
two faults in consecutive cyclesfirst captured; subsequent count = 1
a fault stormfirst captured; count saturates and reports saturation
trigger at trace entry 0usable dump; valid_cnt correct
trigger one entry before wrapusable dump; wrapped correct
trigger after several wrapswrapped set; the most recent history present
a link reset during capturefirst-fault and trace SURVIVE (§16)
a fault during trace freezefreeze holds; no mutation
counter saturationsticky saturation reported, not a silent plateau
a stale-epoch requestcaptured with the epoch fields populated
a fault on the far dieboth sides' records correlate (§17)

Three readings.

Rows 5 and 7 are where debug hardware most often fails, and both are corner cases of the mechanism rather than of the design. A trigger one entry before wrap exercises the pointer, the wrap flag and the valid count simultaneously. A link reset during capture is not a corner case at all — it is what happens in most real failures, because faults trigger recovery.

The evidence is the injection report, in the same form 27.3 §22 requires for mutation testing: eleven rows, eleven expected outcomes, eleven observed. "We tested the debug logic" is a review statement (§4).

And this belongs to this gate rather than to 27.3 for a specific reason. A DV plan naturally verifies functional requirements; debug registers are frequently outside the functional requirement set entirely, so nobody's plan covers them — and a gap here is invisible until silicon.

19. Area H — Observability Cost

#Must be trueEvidence that countsFAIL if
H1the area cost is estimatedflops/SRAM count for the debug setunbudgeted
H2the trace depth is justifieddepth vs the event rate it must spanarbitrary depth
H3the power cost is consideredthe always-on portionignored
H4capture paths are not on critical timing pathsthe timing plan's treatmentdebug logic constrains the design
H5what was cut is recorded, with its riskthe descope listsilent removal
H6the cost is compared against the debug riskthe argumentcost with no benefit stated

Three readings.

H2 turns trace depth from a guess into a derivation. Depth × event rate = time span, and the span must cover the gap between divergence and error for the risks §3 enumerated. A 1 024-entry ring at one event per hundred cycles spans ~100 000 cycles — enough for §6's example. At one event per cycle it spans 1 024 cycles and would have wrapped the divergence away. That arithmetic is the justification, and it also drives B8's event selection.

H5 is the row that protects the next review. Observability cut for area is a decision with a named risk, and recording it means the eventual field failure is a known accepted risk rather than a surprise. Silent removal is how a gate that passed produces an undebuggable part.

And H4 is the boundary with physical design (§5). This gate requires that debug logic is not on critical paths and that its cost is budgeted; it does not do timing closure or congestion analysis. Those belong to the PD flow and its owners.

20. Area I — Lab Reproducibility and Access

#Must be trueFAIL if
I1every debug register is reachable through the assembled packageunreachable in the lab (27.5 §17 H7)
I2the access path works when the link is downdebug requires the thing that is broken
I3the register map documents the read protocoltorn reads trusted
I4a documented evidence-collection sequence existsad-hoc dumps, evidence lost (E4)
I5failing conditions are reproducible, or the evidence is sufficient without reproduction"it happened once"
I6the same instruments exist in emulation/FPGApre-silicon debug cannot rehearse

Two readings.

I2 is the item that turns a good design into an unusable one. If the only path to the debug registers is over the link that has failed, the registers do not exist for the failure that matters. A sideband or independent management path is the standard answer, and the review question is simply "how do I read this with the link down?"

And I6 is cheap and disproportionately valuable. If the same first-fault, trace and snapshot mechanisms exist in emulation, the debug procedure is rehearsed before silicon — and §18's injection campaign runs there naturally. A team that has never dumped its own trace ring will do it for the first time under pressure.

21. Flagship — "We'll Use Firmware Logs"

The pre-tapeout review failure that defines this gate.

What the team presents. Comprehensive firmware logging: every command, every completion, every error interrupt, every state change firmware observes, with timestamps. Megabytes of it, well structured, searchable. The proposal: logs are the debug strategy; dedicated hardware capture is not needed.

What the reviewer asks. "A credit return goes missing at cycle 1 000 and the link times out at cycle 44 000. What in your logs tells you which credit-return boundary failed?"

The answer: nothing.

QuestionFirmware logHardware capture
was a credit return generated?not visible — no software eventcr_ret_generated (§13)
was it applied at the peer?not visiblecr_ret_applied
when did credits start drifting?not visibletrace ring (§11)
what was the first invariant violation?not visiblefirst-fault (§8)
what was the state at that moment?not visiblesnapshot (§15)
what did software see?the timeout, at cycle 44 000

Gate decision: FAIL.

Four readings, and the reasoning generalises well beyond credits.

The limitation is structural, not a matter of log quality. A log records what software was told. A credit return that was never generated produced no interrupt, no status change, no event — there was nothing to tell. You cannot log the absence of something that was never reported, so the first log entry is necessarily the timeout: the symptom, 43 000 cycles late (§6).

Better logging cannot fix it, and that is the part teams resist. More verbose logs, higher-frequency polling, timestamps at finer resolution — none of them observe an event that hardware never surfaced. The only fix is hardware that notices the invariant violation when it happens.

And it is a FAIL rather than a CONDITIONAL PASS (§3) because no mitigation exists after tapeout. There is no bench technique, no firmware change and no software workaround that recovers an unrecorded hardware event. The conditional-pass option existed at 27.1 when the counters could still be added; here the only honest outcome is FAIL with a named cost.

The constructive form of the finding is a short list, not a rejection. Credit-return generated/applied counters on both sides; a credit-conservation check feeding first-fault capture; a trace ring covering the drift window; and a fault-triggered snapshot. That is a modest amount of logic (§19), and it converts §6's 43 000-cycle gap into a single register read.

22. The Debug Gate Checklist

Forty-four items.

First-fault capture

#QuestionFAIL if
1Is capture first-write-wins?last-error-wins (§9)
2Is an error class recorded, not one bit?cannot triage
3Are semantic ID and generation captured?cannot correlate
4Are config epoch and route epoch captured?stale-config undiagnosable
5Is a timestamp captured?cannot order across dies
6Is there a subsequent-fault count?one fault vs a storm indistinguishable
7Is a resource context snapshot captured with it?class without context
8Is clearing explicit and separate from functional reset?cannot re-arm live
9Read the reset expression — does capture survive link reset?failure path erases the cause

Trace

#QuestionFAIL if
10Does the ring record before the trigger?arms on trigger (§12)
11Is the post-trigger tail bounded and small?pre-trigger history wrapped away
12Is the post-trigger tail counted in entries, not cycles?records nothing on a stalled link
13Does it freeze, and stay frozen?overwritten during the dump
14Is there a sticky wrap flag?1 024 entries vs a million indistinguishable
15Is there a valid entry count?full vs partial unknown
16Do entries carry timestamp, identity and epochs?cannot follow a transaction
17Is the event set justified against the span it must cover?a firehose that wraps in microseconds

Counters

#QuestionFAIL if
18For each counter: which two hypotheses does it separate?vague → telemetry
19Are offered/accepted/transmitted/retried/delivered/retired all present?stage divergence unlocatable
20Are credit-return generated and applied counted separately?§21 — direction unresolvable
21Is stalled-on-no-free-ID counted?27.4 §17 invisible
22Are recovery enter and exit counted separately?one long vs many short
23Is obligations-outstanding-at-recovery counted on both dies?27.5 §8 undiagnosable
24Are counters saturating, with saturation reported?a pinned counter reads as a plateau

Snapshot

#QuestionFAIL if
25Is the snapshot captured in one cycle?torn state
26Is it triggered by the fault, not only by software?state has moved on
27Is it first-wins, not refreshed by the cascade?cause overwritten
28Is there an epoch/valid so software detects re-capture?torn reads unnoticed
29Are all fields from the same clock domain, or synchronised?a value that was never real

Reset and evidence

#QuestionFAIL if
30Does the trace survive the resets in the failure path?history lost
31Does the snapshot survive them?context lost
32Does the software recovery sequence capture before resetting?evidence destroyed every time
33Is what-survives-which-reset written down?three groups, three assumptions

Cross-die

#QuestionFAIL if
34Is there a common time base across dies?traces cannot be interleaved
35Do both sides record identity, generation and epochs?cannot follow a transaction
36Is the contract version readable from silicon?which contract is present is unknown

Validation, cost, access

#QuestionFAIL if
37Has two faults in consecutive cycles been injected?untested mechanism
38Has trigger one entry before wrap been injected?pointer/flag interaction untested
39Has a link reset during capture been injected?the common real case untested
40Is there an injection report with expected vs observed?"we tested it"
41Is the area/power cost estimated and budgeted?unbudgeted
42Is trace depth justified by depth × event rate vs the required span?arbitrary
43Is every debug register reachable through the assembled package?unreachable in the lab
44Does the access path work when the link is down?debug needs the broken thing

Run items 1, 9, 10, 18, 20, 32 and 44 first. Each is a single question with a visible answer, and each can fail the gate on its own — first-vs-last capture, the reset expression, pre-vs-post trigger, counter discrimination, credit-return direction, the software sequence, and access with the link down.

23. How the Debug Review Itself Fails

Failure modeLooks likeWhy it is fatal
counting mechanisms instead of risks"we have 40 debug registers"§3 — the question is coverage of known risks
accepting logs as observabilitya comprehensive logging plan§21 — logs cannot record unreported events
not reading the reset expressionscapture existsthe failure path erases it (§16)
never validating the debug logicit is in the RTL§18 — first exercise is the real failure
deferring to "we'll add it if needed"flexibleafter tapeout there is no "if needed"
reviewing RTL without package accessregisters existunreachable in the lab (§20 I1)

Three readings.

Row 1 is the most common shape. A count of registers says nothing about whether the risks the earlier gates named are observable. §3's PASS criterion is explicitly per-risk, which makes the review a mapping exercise: 27.1 §19's three questions, 27.5 §8's retention disagreement, 27.4 §17's window — what evidence exists for each?

Row 5 is the one that sounds most reasonable in the room. "We'll add instrumentation if we find we need it" is a perfectly good policy for firmware and has no meaning for silicon. The moment you know you need it is the moment you cannot have it — which is §1, stated as a schedule decision.

And row 3 is a ten-second check that reviewers skip because it feels like 27.2's job. It is not: the scope of a reset over evidence registers is an observability decision, and its consequence is that the field reports only failures that did not trigger a reset.

24. Red Flags

Heard in reviewUsually meansAsk
"we'll use firmware logs"§21 — unreported events are invisible"what tells you which credit boundary failed?"
"we have an error register"possibly last-error-wins"first-write or last-write?"
"there's a trace buffer"possibly post-trigger only"does it record before the trigger?"
"we can dump 1 024 entries"wrap flag unknown"how do I know it wrapped?"
"we have plenty of counters"telemetry, not discrimination"which two hypotheses does each separate?"
"software reads the status"possibly torn, possibly too late"captured by the fault, or by the read?"
"reset clears the errors"§16 — the failure path erases evidence"which resets clear the capture?"
"the driver resets and retries"evidence destroyed before dump"what is captured before the reset?"
"the debug logic is simple"§18 — never validated"what happens on a trigger one entry before wrap?"
"we'll add it if we need it"there is no later"which risk are we accepting?"
"JTAG can read everything"maybe not with the link down"how do I read this when the link is dead?"

And the single most useful question in the room is the first one, generalised: "walk me through the failure we most fear, cycle by cycle, and tell me what we would read." Any step where the answer is "nothing" is a finding.

25. Common Misconceptions

"The error register tells you why the link failed." §9: with last-error-wins it tells you the final escalation — usually TIMEOUT, which is consistent with dozens of root causes.

"The first error is the root cause." §6: the first recorded error is often 43 000 cycles after the first divergence. Capture invariant violations, not symptoms.

"Firmware logs are enough." §21: a log records what software was told. A credit return that was never generated produced no event to log.

"More verbose logging would have caught it." §21: verbosity does not create an observation of something hardware never surfaced.

"The trace buffer captures the failure." §12: if it arms on the trigger, every entry is a consequence.

"A 1 024-entry dump is 1 024 events." §10 B4: without a wrap flag it could be the last 1 024 of a million.

"More counters means better observability." §13: a counter that separates no hypotheses is telemetry. The test is which two explanations it distinguishes.

"Software can read the state after the fault." §14 D2: by then credits have recovered and the link has retrained. Capture must be triggered by the fault.

"Reset clears errors — that's what reset is for." §16 E1: if it clears first-fault capture, the recovery erases the cause of the failure that triggered it.

"The debug logic is too simple to need testing." §18: a trigger one entry before wrap, and a link reset during capture, are the conditions that occur in the failure you built it for.

"We'll add instrumentation if we need it." §23: for silicon, the moment you know you need it is the moment you cannot have it.

26. Understanding Check

27. Summary

Seven things.

Debug capability is architectural (§1). The moment you know you need an instrument is the moment you can no longer add one — which is why this gate runs before tapeout and why "we'll add it if needed" has no meaning here.

The first recorded error is not the first divergence (§6). Credit return missing at cycle 1 000, TIMEOUT at 44 000. Capture invariant violations, not symptoms.

First-wins, not last-wins (§7–§9). With a class, semantic ID, generation, both epochs, a timestamp, a subsequent-fault count, and a context snapshot — and a reset expression that excludes the resets in the failure path.

The ring records before the trigger (§10–§12), with a bounded entry-counted tail, a hard freeze, a sticky wrap flag and a valid count. A post-trigger-only trace records the cascade.

Counters must discriminate (§13). Which two hypotheses does this separate? — and credit-return generated versus applied, plus obligations-outstanding-at-recovery on both dies, are the two that close this module's recurring failures.

Validate the debug logic (§18). Trigger one entry before wrap; a link reset during capture; two faults in consecutive cycles. Eleven injections, expected versus observed — because the mechanism's first real exercise is the failure it exists for.

And "we'll use firmware logs" is a FAIL (§21). A log records what software was told, and the events that matter most were never reported. No mitigation exists after tapeout.