Skip to content

UCIe · Module 8

Link Discovery

How a UCIe endpoint establishes that a peer exists and the sideband control path works before mainband training — SBINIT, the out-of-reset exchange, bilateral completion, request-outstanding state, bounded timeout and retry, stale-response rejection, and discovery debug.

Chapter 8.1 ended with a die in a known local state: clocks qualified, resets released per domain, state machines at their initial values. Everything it established was local. Nothing in it proved that another die exists, that it is powered, that it is out of reset, or that anything sent toward it will be received.

That gap is larger than it sounds. Two dies bonded into the same package are physically adjacent and mutually irrelevant until they have exchanged something. And the exchange has a bootstrapping problem: the wide, fast mainband — the thing you actually want — cannot be used to arrange its own bring-up, because it is not trained yet and will not be until both ends agree on how to train it.

Discovery solves that problem, and it is more interesting than "send a message and wait". This chapter is about what has to be true before an endpoint may declare it has a link partner.

1. The One-Sentence Model

Discovery turns physical adjacency into a mutually acknowledged relationship.

Four things have to become true, and none of them follows from any other:

  • the local control path is alive and can transmit;
  • the remote endpoint is present and responding;
  • both ends are referring to the same bring-up attempt;
  • enough common information exists for the next phase to begin.

The word doing the most work is mutually. An endpoint that has sent a message and received a reply knows a great deal about its own state and very little about its partner's. §9 is entirely about that asymmetry, and UCIe's own exit condition resolves it explicitly.

2. Why Not the Mainband

The bootstrapping insight, stated once because everything else depends on it:

The path used to bring up the data path must not itself require the data path to be up.

The mainband cannot discover its own peer. Before training, no endpoint knows the lane mapping (Chapter 7.3), the achievable width (7.4), whether the forwarded clock relationship is usable (7.5), or whether any lane carries data at all. Every one of those is something training establishes, and training requires two ends that have agreed to train.

So UCIe provides a sideband: a separate, narrower, lower-rate path with its own initialisation, deliberately simple enough to work before anything else does. Chapter 7.3 counted it among a module's physical resources — four lanes for sideband signalling alongside the data lanes, the Valid lane, the Track lane, and the forwarded clock. Chapter 8.1 noted that UCIe's RESET-exit conditions call out a running sideband clock as a separate prerequisite from the mainband and adapter clocks, for exactly this reason: the sideband domain has to be alive first, because it is what sequences everything else.

The design principle generalises well beyond UCIe. Any system that configures itself needs a configuration channel that requires no configuration. JTAG exists for this reason. So does a bootloader. So does the sideband.

3. Where Discovery Sits

Chapter 8.1 stopped at the point where a die is ready to begin bring-up. UCIe's link state machine names the phase that follows: SBINIT, the sideband-initialisation state, in which the sideband is detected, repaired where applicable, and initialised, and an out-of-reset message is transmitted.

The published description of its exit is worth quoting almost exactly, because it is the chapter's central point in one sentence: when a UCIe Module has sent and received {SBINIT done resp}, it exits to MBINIT — mainband initialisation, which Chapter 8.3 covers.

Read the four verbs in that sequence. Detected — is there anything there? Repaired — on an advanced package with lane redundancy, available sideband lanes can be found and used, so a broken sideband lane need not be fatal. Initialised — the control path is made usable. And then transmitted and received — the bilateral part.

Note what is not yet established when SBINIT completes: no mainband lane has been qualified, no lane mapping exists, no width has been negotiated, and no data has crossed the mainband. Discovery proves you have a partner to negotiate with. It does not negotiate.

Sideband initialisation between two dies. Each side leaves reset, detects and initialises its sideband, exchanges out-of-reset messages, then exchanges SBINIT done requests and responses in both directions before either exits to mainband initialisation.SBINIT — sideband initialisation and the out-of-reset exchangeDie ASideband ASideband BDie Breset exitreset exitdetect + repairout-of-resetout-of-resetdone reqdone respdone reqdone respexit to MBINITexit to MBINIT
Figure 1 — a simplified view of the sideband exchange, showing why completion is bilateral rather than the full specification message sequence. Each side detects and initialises its sideband, transmits an out-of-reset message, and responds to its partner's. The exit condition is the part worth studying: a module leaves for mainband initialisation only when it has both sent and received the done response. Sending alone proves nothing about the peer, and receiving alone proves nothing about what the peer knows.

4. A Discovery State Machine

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative UCIe bring-up RTL — not normative UCIe signal naming or
// state encoding. The mechanism is what transfers; the encodings are not.
typedef enum logic [2:0] {
  DISC_IDLE      = 3'd0,  // sideband not yet usable; nothing attempted
  DISC_SEND_REQ  = 3'd1,  // emit a request, mark it outstanding
  DISC_WAIT_RESP = 3'd2,  // bounded wait for a correlated response
  DISC_VALIDATE  = 3'd3,  // response arrived — is it acceptable?
  DISC_DONE      = 3'd4,  // peer confirmed; may advance
  DISC_RETRY     = 3'd5,  // clear per-attempt state, try again
  DISC_FAIL      = 3'd6   // attempts exhausted; report and stop
} discovery_state_t;

Each state earns its place by owning a decision the others cannot make.

DISC_IDLE exists because discovery has a prerequisite of its own — the sideband must be initialised, which needs its clock, which needs its reset released (Chapter 8.1 §15). Starting from IDLE rather than immediately from SEND makes that prerequisite explicit and gives a debugger a place to see it unmet.

DISC_SEND_REQ is separate from WAIT because emitting and awaiting are different obligations. Emitting creates the outstanding-request state of §6; conflating the two makes it impossible to tell "never sent" from "sent, no reply".

DISC_WAIT_RESP owns the timer. A wait without a bound is a hang, and a hang is the least diagnosable failure there is.

DISC_VALIDATE exists because a response arriving is not a response being acceptable. It may belong to a previous attempt (§8), or it may report something incompatible (§10). Merging validation into WAIT means the design cannot distinguish "no reply" from "wrong reply" — two failures with entirely different causes.

DISC_RETRY is a state rather than a transition because cleanup takes a defined moment. Per-attempt state has to be cleared before the next attempt begins; doing it on a transition edge scattered across several conditions is how attempts leak state into each other.

DISC_FAIL is terminal and observable. Retrying forever converts a diagnosable failure into a livelock.

5. The Controller

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative UCIe bring-up RTL — not normative naming or encoding.
module discovery_ctrl #(
  parameter int TIMEOUT_W = 20,   // illustrative width, not a UCIe constant
  parameter int RETRY_W   = 3,
  parameter int MAX_RETRY = 3,
  parameter int ATTEMPT_W = 2
) (
  input  logic             clk,
  input  logic             rst_n,
  input  logic             sideband_ready,     // from SBINIT-equivalent logic
  input  logic             resp_valid,         // a response was received
  input  logic [ATTEMPT_W-1:0] resp_attempt,   // its correlation tag (§8)
  input  logic             resp_acceptable,    // validation result (§10)
  input  logic             peer_restart_seen,  // §12
  output logic             send_request,
  output discovery_state_t disc_state_q,
  output logic             discovery_complete
);
 
  discovery_state_t          disc_state_d;
  logic                      request_pending_q;
  logic [TIMEOUT_W-1:0]      wait_timer_q;
  logic [RETRY_W-1:0]        retry_count_q;
  logic [ATTEMPT_W-1:0]      active_attempt_q;
  logic                      timeout;
  logic                      resp_matches;
 
  // Saturating compare, never a wrap — see §7.
  assign timeout      = (&wait_timer_q);
  assign resp_matches = resp_valid && request_pending_q &&
                        (resp_attempt == active_attempt_q);
 
  always_comb begin
    disc_state_d = disc_state_q;                    // explicit default: hold
    unique case (disc_state_q)
      DISC_IDLE      : if (sideband_ready)  disc_state_d = DISC_SEND_REQ;
      DISC_SEND_REQ  : disc_state_d = DISC_WAIT_RESP;
      DISC_WAIT_RESP : if      (resp_matches) disc_state_d = DISC_VALIDATE;
                       else if (timeout)      disc_state_d = DISC_RETRY;
      DISC_VALIDATE  : disc_state_d = resp_acceptable ? DISC_DONE : DISC_RETRY;
      DISC_DONE      : if (peer_restart_seen) disc_state_d = DISC_RETRY;
      DISC_RETRY     : disc_state_d = (retry_count_q >= RETRY_W'(MAX_RETRY))
                                        ? DISC_FAIL : DISC_SEND_REQ;
      DISC_FAIL      : ;                            // terminal until reset
      default        : disc_state_d = DISC_IDLE;    // illegal encoding recovers
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      disc_state_q      <= DISC_IDLE;
      request_pending_q <= 1'b0;
      wait_timer_q      <= '0;
      retry_count_q     <= '0;
      active_attempt_q  <= '0;
    end else begin
      disc_state_q <= disc_state_d;
 
      // Outstanding-request state: set on emit, cleared on resolution.
      if (disc_state_q == DISC_SEND_REQ)                    request_pending_q <= 1'b1;
      else if (resp_matches || (disc_state_q == DISC_RETRY)) request_pending_q <= 1'b0;
 
      // Phase-local timer: runs only while waiting, cleared on entry.
      if (disc_state_q != DISC_WAIT_RESP)  wait_timer_q <= '0;
      else if (!timeout)                   wait_timer_q <= wait_timer_q + 1'b1;
 
      // Per-attempt bookkeeping, advanced once per retry.
      if (disc_state_q == DISC_RETRY) begin
        active_attempt_q <= active_attempt_q + 1'b1;
        if (!(&retry_count_q)) retry_count_q <= retry_count_q + 1'b1;
      end
    end
  end
 
  assign send_request       = (disc_state_q == DISC_SEND_REQ);
  assign discovery_complete = (disc_state_q == DISC_DONE);
 
endmodule

Architecture. Discovery is a bounded, correlated, retryable request-response exchange over a path that may be dead. Every piece of state above exists to make one of those four adjectives true.

State. A seven-value FSM, an outstanding-request bit, a phase-local timer, a retry counter, and an attempt tag. Note the deliberate asymmetry: active_attempt_q wraps freely (it only needs to distinguish adjacent attempts), while retry_count_q saturates, because it is a policy input and must not roll back to zero and grant infinite retries.

Cycle behaviour. send_request is a single-cycle pulse from a state occupied for exactly one cycle, which is why request_pending_q is set from that state rather than from a level. The timer is cleared in every state other than WAIT, so it reinitialises on entry with no separate clear signal — the same phase-local discipline Chapter 8.1 §14 used.

Contract. The peer must be able to correlate and reply. The next phase — mainband initialisation — must not begin before discovery_complete, and §11 asserts exactly that.

Failure. Without request_pending_q, an unsolicited or stale response is accepted (§6, §8). Without the phase-local timer clear, a later attempt inherits a partly-elapsed timer and fails early for reasons that depend on the previous attempt's duration.

DV. Every transition taken; timeout forced from WAIT; a response delivered while no request is pending and confirmed rejected; retry driven to exhaustion and FAIL confirmed terminal.

6. Outstanding-Request State

The smallest piece of state in the chapter and one of the most important.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
logic request_pending_q;    // a request has been emitted and not yet resolved

Set when a request is emitted. Cleared on a correlated response, on retry cleanup, or on reset. It answers a question nothing else can: am I entitled to act on a response right now?

The wrong version is the one everybody writes first:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — local transmission treated as evidence about the peer.
if (request_sent)
  discovery_complete_q <= 1'b1;

Sending proves your transmitter works. It proves nothing about whether a peer exists, is powered, is out of reset, or received anything. This is the discovery-phase form of Chapter 8.1 §7's link_ready = rst_n and Chapter 7.1 §9's phy_ready = rst_n, and the family resemblance is the lesson:

Readiness is never a local fact. Every milestone in bring-up that involves a peer requires evidence from the peer, and code that concludes otherwise always has the same shape — a local action assigned directly to a global conclusion.

The failure is quiet and expensive. The endpoint advances to mainband initialisation and begins training against a die that is powered down. Training fails, and every symptom points at the mainband — lanes, channel, clock — while the actual defect is one line in the discovery FSM.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — completion requires evidence from the peer.
property p_discovery_complete_requires_peer;
  @(posedge clk) disable iff (!rst_n)
    $rose(discovery_complete) |-> $past(resp_matches);
endproperty
 
a_discovery_complete_requires_peer :
  assert property (p_discovery_complete_requires_peer)
  else $error("Discovery completed without a correlated peer response.");

7. Bounded Waiting, and the Wrapping-Timer Bug

A peer may be absent, unpowered, held in reset, misconfigured, or connected through a broken sideband. Discovery must therefore have a bound, and the bound must actually bind:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — a wrapping watchdog can never expire.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)                       wait_timer_q <= '0;
  else if (state_q == DISC_WAIT_RESP) wait_timer_q <= wait_timer_q + 1'b1;
end
assign timeout = (wait_timer_q == TIMEOUT_LIMIT);

Two independent bugs, and the second is the interesting one.

The timer never clears between attempts — it is only reset by rst_n — so a second attempt starts from wherever the first left off.

The comparison is against a single value on a wrapping counter. If TIMEOUT_LIMIT is ever missed — because the counter was at LIMIT − 1 when it left the wait state, or because a wrap put it past the value on a cycle the state was not WAIT — the counter rolls over and the design waits an entire counter period again. Worse, the wrap makes a dead peer look periodically young: the timer keeps sweeping back through low values, so any logic that reasons about "how long have we been waiting" gets a fresh-looking answer forever.

The correct forms are saturation or a >= compare:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — saturating, phase-local, cannot wrap past its own threshold.
assign timeout = (&wait_timer_q);
 
if (disc_state_q != DISC_WAIT_RESP) wait_timer_q <= '0;
else if (!timeout)                  wait_timer_q <= wait_timer_q + 1'b1;

Why saturation rather than >=. Both work, and saturation composes better: once the counter is at its maximum it stays there, so any consumer sampling it late still sees the timeout condition. A >= compare on a free-running counter is correct only if the counter is guaranteed to be sampled every cycle it is above the threshold.

TIMEOUT_W is illustrative. The real bound must be long enough to cover the peer's legitimate worst-case bring-up latency — which, per Chapter 8.1 §3, includes a PLL-stabilisation floor on the order of milliseconds — and short enough that a genuinely dead peer is reported in useful time. Take it from your specification revision and your platform's power sequencing, not from a tutorial.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — a timeout must produce a state change, not a longer wait.
property p_timeout_leaves_wait;
  @(posedge clk) disable iff (!rst_n)
    ((disc_state_q == DISC_WAIT_RESP) && timeout) |=> (disc_state_q != DISC_WAIT_RESP);
endproperty

That is a bounded-liveness property expressed as safety — it does not claim discovery eventually succeeds, which would be unprovable against an absent peer, but it does claim the machine cannot sit in WAIT after its bound expired. Chapter 7.1 §18 introduced the pattern; here it is the difference between "no peer" being reported and being silent.

8. Stale Responses

The subtle failure in this chapter, and one that survives most testbenches.

Consider the timeline:

TimeLocal endpointIn flight
tattempt 0: request emittedrequest 0 →
ttimer expires; attempt 0 abandoned← response 0 (slow, still travelling)
tretry cleanup; attempt 1 beginsrequest 1 →
tresponse 0 arrives

If the design accepts any response while a request is pending, it accepts response 0 as though it answered request 1. Discovery completes. The endpoint advances — on evidence that was generated before the current attempt existed, possibly by a peer that has since reset.

The general remedy is correlation: every response must be attributable to the request it answers.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative correlation — an attempt tag, used where the protocol does not
// otherwise let a response be attributed to a specific request.
logic [ATTEMPT_W-1:0] active_attempt_q;   // increments on every retry
 
assign resp_matches = resp_valid
                   && request_pending_q                       // one outstanding
                   && (resp_attempt == active_attempt_q);     // and it is THIS one

Architecture. A retry creates a second request while the first may still be answered. Without a way to tell the answers apart, the design can complete on stale evidence.

State. A small counter, advanced once per retry. It may wrap freely — it only has to distinguish an attempt from its immediate predecessors, so two bits is usually ample.

Cycle behaviour. Incremented in the RETRY state, compared combinationally against each arriving response.

Contract. The peer must echo something that identifies the request. If the protocol already provides a correlation mechanism — a sequence field, a transaction identifier, a state qualifier — use it rather than inventing one, because an invented tag only works if both ends implement it, which for an interoperable interface means it does not work at all. Present the tag here as what it is: the technique to reach for when the protocol does not give you one, and a way to reason about the hazard even when it does.

Failure. Without correlation, a slow response from a dead attempt completes discovery. The endpoint advances against a peer that may have restarted, and mainband training then fails for reasons that appear entirely unrelated.

DV.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — a response may only be consumed while a request is outstanding.
property p_response_requires_pending_request;
  @(posedge clk) disable iff (!rst_n)
    resp_matches |-> request_pending_q;
endproperty
 
// Illustrative — and it must belong to the current attempt.
property p_no_stale_response_accepted;
  @(posedge clk) disable iff (!rst_n)
    resp_matches |-> (resp_attempt == active_attempt_q);
endproperty

Then force it in the testbench: time out an attempt, start the next, and deliver the previous attempt's response. A discovery testbench that never does this has not tested the hazard, and the hazard is precisely the one that appears in the lab when a peer is slow.

9. Completion Is Bilateral

The point the specification makes for us, and the one people most often get wrong.

Endpoint A can be in a state where it has sent its request and received a valid response — so from A's local view, discovery is complete. Meanwhile endpoint B may not have received A's message at all, or may have received it and not yet had its own response acknowledged. A is ready; B is not. If A advances alone, it begins mainband initialisation against a partner still sitting in sideband initialisation.

UCIe resolves this explicitly: a module exits SBINIT to MBINIT when it has sent and received {SBINIT done resp}. Both directions. Each side observes evidence that the other side has what it needs.

Bring-up milestones are bilateral, not local. A phase is complete when both ends have observed enough to know that both ends are complete — which requires at least one round trip more than intuition suggests.

The digital consequence is that completion is a conjunction over both directions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — local completion requires evidence in both directions.
assign discovery_complete = sent_done_resp_q && received_done_resp_q;

Failure. With only received_done_resp_q, endpoint A advances while B is still waiting for A's response. A begins transmitting training patterns into an endpoint that is not observing them. B eventually times out, restarts, and now A is mid-training while B is in sideband initialisation — the mutual-restart livelock of §12, which is one of the nastiest bring-up bugs because each side individually looks correct.

10. Peer Present Is Not Peer Compatible

A response arriving proves a peer exists and the control path works. It does not prove the two ends can form a usable link.

Discovery may carry information about the partner, and whatever the specification defines for the revision you implement, the architectural pattern is the same: capture it, mark it valid, and invalidate it when the attempt is abandoned.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — peer information captured on a validated response.
logic [CAP_W-1:0] peer_caps_q;
logic             peer_caps_valid_q;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    peer_caps_q       <= '0;
    peer_caps_valid_q <= 1'b0;
  end else if (resp_matches && resp_acceptable) begin
    peer_caps_q       <= resp_caps;
    peer_caps_valid_q <= 1'b1;
  end else if (disc_state_q == DISC_RETRY) begin
    peer_caps_valid_q <= 1'b0;      // per-attempt state — invalidate on retry
  end
end

Architecture. Information learned about a peer is only meaningful for the attempt that learned it. A retry may reach a different peer state, or a peer that has itself restarted.

State. A captured value plus a validity bit. The value need not be cleared — the validity bit is what makes it unreadable, which is Chapter 8.1 §8's "invalid data need not be known" applied to a register instead of a memory.

Cycle behaviour. Written once per successful validation, invalidated on retry and on reset.

Contract. Downstream configuration reads this only when valid. The rule from Chapter 8.1 §12 applies with force: state that must be re-established should read as invalid, not as stale.

Failure. If validity survives a retry, the next phase configures against information gathered from a previous attempt — plausible-looking values that describe a state the peer is no longer in.

Two conclusions to keep separate, because they have completely different responses:

Peer presentPeer compatible
Established bya response arriving at allvalidating its contents
Failure looks liketimeout, no responseresponse received, validation fails
Likely causepower, reset, sideband, wiringconfiguration, SKU, revision mismatch
Retry helpssometimes — peer may be slowalmost never — the mismatch is stable

That last row is the practically valuable one. A validation failure that retries is a design wasting its retry budget on a condition that will not change. The right response is to fail fast with a recorded cause, not to try three more times.

11. Gating the Next Phase

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative — mainband initialisation may not begin before discovery completes.
property p_no_mbinit_before_discovery;
  @(posedge clk) disable iff (!rst_n)
    $rose(mbinit_start) |-> discovery_complete;
endproperty
 
// Illustrative — per-attempt state is genuinely cleared on retry.
property p_retry_clears_attempt_state;
  @(posedge clk) disable iff (!rst_n)
    (disc_state_q == DISC_RETRY) |=> (!request_pending_q && (wait_timer_q == '0)
                                      && !peer_caps_valid_q);
endproperty

The second property is the one worth writing even though it feels like restating the code. Per-attempt cleanup is spread across several always blocks, and a refactor that moves one of them is exactly how a retry starts inheriting state. An assertion that names every piece of per-attempt state in one place is documentation the tool checks.

12. When the Peer Restarts Mid-Phase

Both ends run independent state machines with independent timers, and nothing synchronises their notion of which attempt is current. So consider: A is in DISC_WAIT_RESP when B's watchdog fires and B restarts its own discovery.

Three things can now go wrong:

A completes on evidence from B's previous attempt. §8's stale-response hazard, arriving from the peer's side rather than from A's own retry.

A and B alternate. A times out just as B starts; B times out just as A starts; neither ever sees the other in the right state. This mutual-restart livelock can persist indefinitely, and — the reason it is so unpleasant — each endpoint's logs show a perfectly reasonable sequence of attempts and timeouts. Nothing looks broken locally.

A believes it has a partner that no longer agrees. A reached DONE; B restarted afterward. A advances to mainband initialisation alone.

The architectural responses, in the order they should be reached for:

  • Detect it. Losing an expected sequence, or seeing an out-of-reset indication from a peer you believed was past that point, is evidence the peer restarted. That is what peer_restart_seen gates in §5's DISC_DONE transition.
  • Invalidate attempt state and re-enter the appropriate phase, rather than continuing on stale evidence.
  • Do not let both ends restart in lockstep. Where the architecture allows any variation in retry timing, symmetric endpoints with symmetric timers can synchronise their restarts and never overlap.

The exact detection mechanism and the exact transition are specification questions for your revision. What is universal is the hazard: the peer's state can change underneath a phase you are in the middle of, and a design that assumes otherwise is correct only until the peer has a bad day.

13. Failure Classification

No peerSideband failureIncompatible peerStale responseMutual restart
Response receivednevernever, or corruptyesyes, but lateintermittently
Validationn/an/afailspasses — wronglypasses
Timeout firesyes, every attemptyesnoyes on the previous attemptyes, alternately
Retry helpsif the peer is merely slownonomasks the bugno — may sustain it
Deterministicyesyesyesno — timing-dependentno
First movepeer power, reset, clocksideband lanes, repair statecapability and SKU configattempt correlationboth ends' timers and logs

Two columns deserve attention. Incompatible peer is the only column where a response arrives and validation fails — which makes it trivially separable from everything else, provided the design records the distinction. And mutual restart is the only one where both endpoints' individual logs look healthy, which is why the last row says to look at both ends: the failure exists only in the relationship between two timelines.

14. Coverage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative discovery coverage — not UCIe-defined.
covergroup cg_discovery @(posedge clk iff disc_state_change);
 
  cp_attempt : coverpoint retry_count_q {
    bins first   = {0};
    bins retried = {[1 : MAX_RETRY-1]};
    bins last    = {MAX_RETRY};
  }
  cp_outcome : coverpoint disc_state_q {
    bins done    = {DISC_DONE};
    bins retry   = {DISC_RETRY};
    bins failed  = {DISC_FAIL};
  }
  cp_reason : coverpoint disc_fail_reason;    // timeout / validation / restart
  cp_caps   : coverpoint peer_cap_class;      // equivalence classes, not values
 
  // Was success ever reached on something other than the first attempt?
  x_attempt_by_outcome : cross cp_attempt, cp_outcome;
  // Was each failure reason seen, and at which attempt?
  x_reason_by_attempt  : cross cp_reason, cp_attempt;
 
endgroup

Why these crosses. Every regression reaches "first attempt, done" immediately; the interesting points are success after a retry — which exercises the cleanup path of §11 — and each distinct failure reason, since timeout and validation failure take different code and only one of them is commonly tested. cp_caps deliberately bins into equivalence classes rather than enumerating capability values, per the rule that coverage should describe behaviour rather than count numbers.

15. Debug Checklist

In order, cheapest first:

  1. Did reset fully release in the sideband domain? Chapter 8.1 §20 — and remember a stopped clock legitimately holds reset.
  2. Is the sideband clock alive? UCIe names it as a separate RESET-exit prerequisite for a reason.
  3. Did the discovery FSM leave IDLE? If not, sideband_ready is low and the problem is upstream of discovery entirely.
  4. Was a request actually emitted? Check the send pulse, not the intent to send.
  5. Is request_pending_q set? If it is not, no response can ever be accepted.
  6. Did the peer receive it? This needs the far end's visibility — which is why both ends' bring-up state must be readable.
  7. Did the peer respond? Distinguish "no response" from "response rejected".
  8. Was the response correlated to the current attempt? A rejected response looks identical to no response unless the design counts them separately — count them separately.
  9. Did validation reject it? If so, this is capability or configuration, not connectivity, and retrying will not help.
  10. Did the timeout fire, and how many times? The retry counter tells you whether this is one slow attempt or a persistent absence.
  11. Did either endpoint restart while the other was mid-attempt? Compare timelines from both ends, not just one.
  12. Is capability state surviving a retry or reset? Stale-but-valid peer information causes a later phase to fail for reasons that point nowhere near discovery.

Steps 1 to 5 are local and take minutes. Step 6 onward needs both ends, which is the practical argument for making bring-up state observable on both dies — a requirement that, like Chapter 6.5's buried-die test access, must be designed in long before it is needed.

16. Common Misconceptions

"Physical adjacency means discovery succeeded." Two bonded dies are mutually irrelevant until they have exchanged something (§1).

"Sending a discovery request proves the peer is present." It proves your transmitter works. Completion needs evidence from the peer (§6).

"One endpoint completing is enough." UCIe's own exit condition requires having both sent and received the done response. Advancing alone begins training against a partner still in sideband initialisation (§9).

"Timeout is testbench logic." It is the only thing that turns an absent peer into a diagnosable report rather than a silent hang (§7).

"A wrapping timer is harmless." It can miss its threshold entirely and makes a dead peer look periodically young to anything reasoning about elapsed time (§7).

"A late response can be accepted — it is still a valid response." It answered a request that no longer exists, possibly from a peer that has since restarted (§8).

"Peer present and peer compatible are the same." One is established by a response arriving, the other by validating its contents — and retry helps with the first and never with the second (§10).

"The mainband can discover the peer." The mainband is not usable until training, and training requires two ends that have already agreed to train (§2).

"Retry means try again." It means clear every piece of per-attempt state and then try again. State that leaks between attempts makes failures depend on history (§11).

17. Understanding Check

18. Summary and What Comes Next

Discovery turns physical adjacency into a mutually acknowledged relationship. It runs on the sideband, because the path used to bring up the data path must not require the data path — the same structural reason JTAG and bootloaders exist. UCIe names the phase SBINIT: the sideband is detected, repaired where redundancy allows, and initialised, an out-of-reset message is transmitted, and a module exits to MBINIT when it has sent and received {SBINIT done resp}.

That exit condition is the chapter. Completion is bilateral, because each endpoint can only observe its own half of the exchange, and advancing alone begins training against a partner that is not there yet — with mutual-restart livelock as the pathological case, invisible in either endpoint's log alone.

The digital mechanism is a bounded, correlated, retryable request-response: an outstanding-request bit so that only solicited responses are consumed, a phase-local saturating timer so an absent peer is reported rather than waited on forever, a bounded retry count so failure is terminal and diagnosable, and correlation so that a slow response from an abandoned attempt cannot complete the current one. Per-attempt state is cleared on retry; diagnostic state deliberately is not.

And the distinction to carry into every later phase: peer present is not peer compatible. A timeout and a validation failure are different findings with different causes, and retry helps with one and never the other.

Discovery proves a partner exists and that management communication works. It proves nothing about the wide mainband — no lane has been qualified, no identity established, no timing aligned, no width agreed:

  • 8.3 — Link Training — how MBINIT and MBTRAIN turn an untrusted set of mainband wires into a qualified, identified, timing-aligned logical link.

Browse the full path on the UCIe tutorials index.