Skip to content

PCIe · Module 17

Equalization — Undoing a Channel That Remembers

At multi-GT/s a channel spreads one symbol's energy across its neighbours. TX shaping, CTLE and DFE attack that memory from three different places — and the RTL around them is control, not signal processing.

Chapter 17.2 assumed a receiver could sample a bit and get the right answer. Chapter 17.3 noted that the negotiated speed is often below what both ends support, and pointed here for the reason.

This is the reason. At multi-gigatransfer rates a transmitted symbol does not arrive as a clean isolated pulse. It arrives spread out in time, overlapping its neighbours — so the voltage at the sampling instant is not one symbol's contribution but a sum of several.

The channel has memory, and equalization is the set of mechanisms that undo enough of it to make a reliable decision possible.

Why does the channel distort symbols, what is each equalization mechanism actually cancelling, and what does the digital logic around a process whose core is not synchronous logic actually own?

1. The Verified Structure

2. Why a Channel Remembers

Start with the pulse, not with the acronyms.

Send one isolated symbol into an ideal channel and the receiver sees one clean pulse in one time slot:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
transmitted:   0    0    1    0    0
received:      0    0    █    0    0        ideal

A real channel is frequency-dependent. PCB trace, vias, connectors and package attenuate high-frequency content more than low — and a sharp edge is high-frequency content. Remove it and the pulse stops being sharp:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
transmitted:   0    0    1    0    0
received:      ·    ▁    █    ▄    ▂        real, lossy
                   pre   main post post

3. A Worked Example

Illustrative arithmetic, not PCIe constants. Take a channel whose pulse response, sampled at symbol instants, is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
h = [ 0.10,  1.00,  0.30,  0.10 ]
      pre    main   post1  post2

Send the pattern 1 0 0 1 as bipolar symbols (+1 / −1) and compute the received sample for the third symbol:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
r[2] = 1.00·x[2] + 0.30·x[1] + 0.10·x[0] + 0.10·x[3]
     = 1.00·(−1) + 0.30·(−1) + 0.10·(+1) + 0.10·(+1)
     = −1.00 − 0.30 + 0.10 + 0.10
     = −1.10

That one is fine — it should be negative and it is, comfortably.

Now the pattern that hurts, 0 1 1 0 — an isolated transition after a run:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
r[2] = 1.00·(+1) + 0.30·(+1) + 0.10·(−1) + 0.10·(−1)
     = +1.00 + 0.30 − 0.10 − 0.10  =  +1.10
r[3] = 1.00·(−1) + 0.30·(+1) + 0.10·(+1) + 0.10·(−1)
     = −1.00 + 0.30 + 0.10 − 0.10  =  −0.70

The margin collapsed from 1.10 to 0.70 on the symbol that follows a run — a 36% loss of vertical margin caused purely by the data pattern. Add noise and jitter to that reduced margin and the decision becomes unreliable.

This is the whole problem, and it is worth noticing what it is not. Nothing failed. No bit was corrupted by interference from outside. The data interfered with itself, and the worst case depends on the pattern — which is why compliance testing uses specific stressful patterns rather than random data.

4. Three Mechanisms, Three Kinds of Knowledge

The comparison that makes the acronyms inevitable.

TX FIR / emphasisCTLEDFE
Wheretransmitter, before the serializerreceiver analog front endreceiver, adjacent to the CDR
Operates onthe symbols themselvesthe analog waveformalready-decided symbols
Can use future symbols?yesn/a — it is a filterno
Cancelspre- and post-cursorbroadband high-frequency losspost-cursor only
Amplifies noise?no — it is transmit shapingyesno
Can propagate errors?nonoyes (§8)

5. The Boundary

6. Transmitter Shaping

The transmitter deliberately distorts what it sends, so that the channel's distortion produces something clean.

A three-tap transmit filter computes each output from the current symbol and its neighbours:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
y[n] = c_pre · x[n+1] + c_main · x[n] + c_post · x[n-1]

With c_pre and c_post negative and c_main positive, the transmitter subtracts a fraction of the neighbouring symbols — pre-distorting in the opposite direction to the channel's tails.

The practical effect is usually described as emphasis. §1's source exposes it as de-emphasis"0: 6 dB de-emphasis at 5 Gbps" — meaning symbols in a run are transmitted at reduced amplitude while transitions keep full amplitude. De-emphasising the steady state and emphasising the transition are the same operation described from two ends.

7. CTLE, and Why More Gain Is Not Better

A continuous-time linear equalizer boosts the frequencies the channel attenuated. §1's source: "Continuous time linear equalizer (CTLE) with optional auto-calibration to improve received signal integrity", implemented as "two transparent stages".

Its advantage is that it needs no knowledge of the data. It works on pre-cursor and post-cursor alike, because it is reshaping the waveform rather than subtracting known symbols.

8. DFE, and the Error That Feeds Itself

A decision-feedback equalizer subtracts the known tails of symbols it has already decided.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
corrected[n] = sample[n] − Σ tap[k] · decision[n−k]

§1's source implements five taps: "5-tap decision feedback equalizer (DFE) with auto-calibration to compensate for high-frequency losses." Five taps means the tails of the previous five symbols are subtracted — which, from §2's pulse picture, is the post-cursor span it can reach.

Why it cannot touch pre-cursor ISI: the interfering symbol is the next one, and it has not been decided yet. You cannot subtract a decision you have not made.

9. Not Deskew, Not CDR

Three different problems that all live in the receive path, and conflating them wastes days.

ProblemFixed byFailure looks like
Lanes arrive at different timesdeskew (17.2 §7)x1 works, x4 corrupts
Sampling at the wrong instantCDR (17.2 §2)errors that vary with conditions
Sampling a distorted valueequalizationrate-dependent errors, pattern-dependent

A x8 receiver can have perfect lane alignment and a closed eye on every lane. It can also have excellent per-lane signal quality and completely wrong deskew. Neither symptom implies the other, and the tests that distinguish them are different (§18).

And equalization interacts with CDR without being it. Better equalization gives the CDR a cleaner edge to lock to, and better CDR gives the equalizer a better sampling instant to work at — they help each other and they solve different problems. Putting them in one "signal recovery" box loses the ability to say which one is failing.

10. What Training Does With All This

Both ends participate, and this chapter says why without saying how.

The transmitter's setting affects the far end's received signal, not its own. So the port that can measure the quality is not the port that can change it — which makes equalization inherently a two-party negotiation rather than a local optimization.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Port A transmitter setting  →  channel  →  Port B receiver quality
Port B evaluates                          Port B requests a change
Port A applies                            ← the request travels back

That loop is what the Link's equalization procedure carries, and it runs during training and recovery at the rates that need it.

The exact phase progression, the request and response encodings, and the state transitions that drive them belong to Chapter 18.5 — which owns Recovery, where speed change and its equalization live.

What this chapter owns is the control plane on one port: §12's atomic setting register, §13's request holding stage, and §14's accept-or-fail controller. Those are the blocks an engineer writes, whatever the negotiation above them looks like.

11. Generation Scope

Higher rates need stronger mechanisms, and the mechanisms are not the same across generations.

Gen1 and Gen2 operate at rates where transmitter de-emphasis of the kind §1's source exposes — "6 dB de-emphasis at 5 Gbps" — is the principal shaping mechanism.

Gen3 onward (Chapter 5.3) run fast enough that a formal Link equalization process, with receiver-driven requests to the far transmitter, becomes part of bringing the Link to that rate.

Gen6 (Chapter 5.6) changes the signalling itself. PAM4 carries two bits per symbol using four voltage levels, so for a comparable total swing the vertical spacing between adjacent levels is substantially smaller than NRZ's single decision threshold — which raises the equalization burden and is part of why the generation's error-handling architecture differs.

12. RTL — Transmitter Setting Register

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Transmitter equalization setting ownership.
// THAT A TRANSMITTER HAS A SELECTABLE SHAPING SETTING is vendor-verified
// (section 1: TXDEEMPH, and emphasis controlled via driver segments). The
// PRESET/COEFFICIENT SPLIT is the architectural shape; the numeric preset
// values are NOT published here (section 6). Validation policy and the
// same-cycle priority are ILLUSTRATIVE.
package tx_eq_pkg;
 
  localparam int COEF_W   = 8;      // signed tap, Q1.7-style
  localparam int PRESET_W = 4;      // opaque preset identifier
 
  typedef struct packed {
    logic signed [COEF_W-1:0] pre;
    logic signed [COEF_W-1:0] main;
    logic signed [COEF_W-1:0] post;
  } tx_taps_t;
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import tx_eq_pkg::*;
 
module tx_eq_config #(
  // Bitmap of preset IDs this macro supports, supplied by the integrator
  // from the device datasheet -- not published here (section 6).
  parameter logic [(1<<PRESET_W)-1:0] SUPPORTED_PRESETS = '1,
  parameter int signed COEF_MIN = -64,
  parameter int signed COEF_MAX =  127
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic                  preset_valid,
  input  logic [PRESET_W-1:0]   preset_id,
  input  logic                  coeff_valid,
  input  tx_taps_t              coeff_req,
 
  // ---- To the transmitter macro -------------------------------------------
  output logic                  cfg_valid,
  output tx_taps_t              cfg_taps,
  output logic [PRESET_W-1:0]   cfg_preset,
  output logic                  cfg_is_preset,
 
  output logic                  reject_unsupported,
  output logic                  reject_range
);
 
  // RANGE CHECK IN SIGNED ARITHMETIC. An unsigned comparison here accepts
  // every negative coefficient as a huge positive one -- and negative taps
  // are the useful ones (section 18, mutation 2).
  function automatic bit coef_ok(input logic signed [COEF_W-1:0] c);
    return (c >= COEF_W'(COEF_MIN)) && (c <= COEF_W'(COEF_MAX));
  endfunction
 
  wire taps_in_range = coef_ok(coeff_req.pre)
                    && coef_ok(coeff_req.main)
                    && coef_ok(coeff_req.post);
  wire preset_supported = SUPPORTED_PRESETS[preset_id];
 
  // ==================================================================
  // SAME-CYCLE PRIORITY, DECLARED.
  // A preset and a coefficient request together is a caller error, not a
  // design case. COEFFICIENTS WIN, being the more specific request, and
  // the conflict is reported rather than silently resolved. Leaving this
  // to `if` ordering is how two blocks come to disagree about which
  // request applied.
  // ==================================================================
  wire take_coeff  = coeff_valid && taps_in_range;
  wire take_preset = preset_valid && !coeff_valid && preset_supported;
 
  tx_taps_t            taps_q;
  logic [PRESET_W-1:0] preset_q;
  logic                is_preset_q, v_q, rej_uns_q, rej_rng_q;
 
  assign cfg_valid          = v_q;
  assign cfg_taps           = taps_q;
  assign cfg_preset         = preset_q;
  assign cfg_is_preset      = is_preset_q;
  assign reject_unsupported = rej_uns_q;
  assign reject_range       = rej_rng_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      // A DEFINED SAFE STATE, not a stale one. cfg_valid low means the
      // macro runs its own default, which is the only honest thing to say
      // before anything has been applied.
      taps_q <= '0; preset_q <= '0; is_preset_q <= 1'b0; v_q <= 1'b0;
      rej_uns_q <= 1'b0; rej_rng_q <= 1'b0;
    end else begin
      // ==============================================================
      // ATOMIC COMMIT: ALL THREE TAPS IN ONE NONBLOCKING ASSIGNMENT.
      //
      // The wrong architecture writes pre, then main, then post under
      // separate conditions. For at least one cycle the transmitter then
      // drives a MIXED old/new tap set -- a shape nobody requested, onto
      // a live lane. Holding them in one packed struct makes that
      // unconstructable rather than merely unlikely.
      // ==============================================================
      if (take_coeff) begin
        taps_q      <= coeff_req;
        is_preset_q <= 1'b0;
        v_q         <= 1'b1;
      end else if (take_preset) begin
        preset_q    <= preset_id;
        is_preset_q <= 1'b1;
        v_q         <= 1'b1;
      end
 
      // Rejections are STICKY. A request the macro cannot honour must
      // never be indistinguishable from one it did.
      if (preset_valid && !coeff_valid && !preset_supported) rej_uns_q <= 1'b1;
      if (coeff_valid && !taps_in_range)                     rej_rng_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. One packed struct, committed in one nonblocking assignment. The atomicity is structural — there is no arm in which one tap moves without the others, because to the assignment they are not separate registers.

Cycle behaviour.

RequestResult
coefficients, all in rangeall three taps commit together
coefficients, any out of rangerefused and reported; config unchanged
preset, supportedpreset committed
preset, unsupportedrefused and reported
both in the same cyclecoefficients win — declared, not accidental
resetdefined safe state; cfg_valid low

Failure — five. Separate per-tap registers expose a mixed configuration for at least one cycle. Unsigned range comparison accepts every negative coefficient as a large positive one. Accepting an unsupported preset hands the macro an index it will interpret as something else. Silent rejection makes a request that did nothing look like one that worked. And leaving the same-cycle case to if ordering makes the answer depend on how the code was written.

13. RTL — Request Holding Stage

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import tx_eq_pkg::*;
 
// SYNTHESIZABLE. Hold an equalization request while the macro control
// path is busy. The need follows from the macro interface being
// backpressurable while the requesting logic is not. Depth and the
// lane-stability contract are ILLUSTRATIVE.
module eq_request_hold #(
  parameter int LANES  = 16,
  // GUARDED. $clog2(1) is zero; LANES = 1 is a legal configuration.
  parameter int LANE_W = (LANES <= 1) ? 1 : $clog2(LANES)
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic                in_valid,
  output logic                in_ready,
  input  logic [LANE_W-1:0]   in_lane,
  input  logic                in_is_preset,
  input  logic [PRESET_W-1:0] in_preset,
  input  tx_taps_t            in_taps,
 
  output logic                out_valid,
  input  logic                out_ready,
  output logic [LANE_W-1:0]   out_lane,
  output logic                out_is_preset,
  output logic [PRESET_W-1:0] out_preset,
  output tx_taps_t            out_taps,
 
  output logic                lane_range_error
);
 
  generate
    if (LANES  < 1) $error("LANES must be at least 1");
    if (LANE_W < 1) $error("LANE_W must be at least 1");
    if ((LANES > 1) && ((1 << LANE_W) < LANES))
      $error("LANE_W too narrow to index LANES");
  endgenerate
 
  // RANGE SAFETY on an externally-supplied lane id. Never rely on "the
  // controller will not send that" -- a request naming a lane that does
  // not exist must be refused, not applied to whatever the index aliases.
  localparam int CHK = LANE_W + 1;
  wire lane_legal = (CHK'(in_lane) < CHK'(LANES));
 
  logic                v_q, isp_q, err_q;
  logic [LANE_W-1:0]   lane_q;
  logic [PRESET_W-1:0] pre_q;
  tx_taps_t            taps_q;
 
  assign out_valid        = v_q;
  assign out_lane         = lane_q;
  assign out_is_preset    = isp_q;
  assign out_preset       = pre_q;
  assign out_taps         = taps_q;
  assign lane_range_error = err_q;
 
  wire fire     = v_q && out_ready;
  wire can_take = !v_q || out_ready;
  assign in_ready = can_take;
  wire accept = in_valid && can_take && lane_legal;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      v_q <= 1'b0; lane_q <= '0; isp_q <= 1'b0; pre_q <= '0;
      taps_q <= '0; err_q <= 1'b0;
    end else begin
      // Capture has priority over drain, so a same-cycle accept-and-consume
      // holds the new request rather than dropping it.
      if (accept) begin
        v_q <= 1'b1;
        // THE WHOLE DESCRIPTOR IS CAPTURED TOGETHER. Nothing downstream
        // re-reads the controller's live outputs: the lane a request names
        // must not change while the request waits (P6).
        lane_q <= in_lane;
        isp_q  <= in_is_preset;
        pre_q  <= in_preset;
        taps_q <= in_taps;
      end else if (fire) begin
        v_q <= 1'b0;
      end
 
      if (in_valid && !lane_legal) err_q <= 1'b1;
    end
  end
 
endmodule

Classification: synthesizable.

Architecture. A one-entry holding stage with the whole descriptor captured together — lane, kind, preset and taps.

The lane field matters most. A request naming lane 3 must still name lane 3 when it is applied. Re-reading the controller's live output at apply time attaches the setting to whatever lane the controller has since moved to — and the symptom is that one lane's equalization mysteriously changes while another's request appears ignored.

Failure — four. A pulse instead of a holding stage loses the request whenever the macro path is busy. Re-reading live inputs at apply time applies a setting to the wrong lane. Indexing before the range check applies a request to an aliased lane. And drain priority over capture drops a request arriving on a draining cycle.

14. RTL — Adaptation Controller

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Illustrative adaptation control around a PHY macro.
// The QUALITY METRIC is an abstraction: real margin measurement is done
// by dedicated circuitry (section 1's eye monitor), and this block only
// consumes a normalized scalar. Thresholds and the attempt limit are
// ILLUSTRATIVE POLICY -- no PCIe constant appears here.
module eq_adaptation #(
  parameter int Q_W       = 8,
  parameter int MAX_TRIES = 4,
  parameter int TRY_W     = (MAX_TRIES <= 1) ? 1 : $clog2(MAX_TRIES+1)
) (
  input  logic clk,
  input  logic rst_n,
 
  input  logic             eq_start,
 
  output logic             apply_valid,
  input  logic             apply_ready,
  input  logic             apply_done,     // macro reports setting applied
 
  input  logic             quality_valid,
  input  logic [Q_W-1:0]   quality,
  input  logic [Q_W-1:0]   quality_target,
 
  output logic             eq_success,
  output logic             eq_fail,
  output logic [TRY_W-1:0] attempts
);
 
  typedef enum logic [2:0] {
    S_IDLE, S_APPLY, S_SETTLE, S_MEASURE, S_ACCEPT, S_FAIL
  } st_e;
 
  st_e              st_q;
  logic [TRY_W-1:0] try_q;
  logic [Q_W-1:0]   best_q;
 
  assign apply_valid = (st_q == S_APPLY);
  // SUCCESS AND FAIL ARE STATES, NOT FLAGS -- they cannot both assert.
  assign eq_success  = (st_q == S_ACCEPT);
  assign eq_fail     = (st_q == S_FAIL);
  assign attempts    = try_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      st_q <= S_IDLE; try_q <= '0; best_q <= '0;
    end else begin
      unique case (st_q)
        S_IDLE :
          if (eq_start) begin st_q <= S_APPLY; try_q <= '0; best_q <= '0; end
 
        S_APPLY :
          if (apply_ready) st_q <= S_SETTLE;
 
        // ==============================================================
        // A MEASUREMENT MUST BELONG TO THE SETTING THAT PRODUCED IT.
        //
        // The macro needs time to apply a setting and the channel needs
        // time to settle. Consuming a quality result before apply_done
        // attributes the OLD setting's measurement to the NEW setting --
        // so the loop optimises a SHIFTED function, converges, and
        // reports success on a setting it never measured (section 18).
        // ==============================================================
        S_SETTLE :
          if (apply_done) st_q <= S_MEASURE;
 
        S_MEASURE :
          if (quality_valid) begin
            if (quality >= quality_target) begin
              st_q <= S_ACCEPT; best_q <= quality;
            end else if (try_q == TRY_W'(MAX_TRIES-1)) begin
              // BOUNDED. Retrying forever would hold the Link in
              // adaptation on a channel that cannot meet the target --
              // which must be reported, not hidden in a loop.
              st_q <= S_FAIL;
            end else begin
              try_q <= try_q + TRY_W'(1);
              if (quality > best_q) best_q <= quality;
              st_q  <= S_APPLY;
            end
          end
 
        S_ACCEPT, S_FAIL :
          if (eq_start) begin st_q <= S_APPLY; try_q <= '0; best_q <= '0; end
 
        default : st_q <= S_IDLE;
      endcase
    end
  end
 
endmodule

Classification: synthesizable (illustrative controller).

Architecture. Six states, with a settling state between applying and measuring — the state a naive design omits.

S_SETTLE is the whole lesson. Without it, quality_valid from the previous setting is consumed as though it described the new one. The loop then optimises a measurement that lags by one attempt — and it converges, on the wrong setting, with every signal looking healthy.

Failure — four. No settle state attributes measurements to the wrong setting. An unbounded retry loop hides an unachievable channel. Success and fail as independent flags allows both. And restarting without clearing try_q makes the second run fail early.

15. Verification Model — A Channel With Memory

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// VERIFICATION-ONLY. A three-tap channel and a three-tap transmit filter,
// in explicit signed fixed point.
// FORMAT: Q1.7 -- one sign bit, seven fractional bits, so 1.0 is 8'sd128
// and 0.30 is 8'sd38. Coefficients and samples share the format.
package eq_model_pkg;
 
  localparam int FRAC = 7;
  localparam int ONE  = 1 << FRAC;            // 128 == 1.0
 
  // ==================================================================
  // THE FORMAT MUST HOLD THE VALUES. This is not a formality, and it is
  // the first thing to get wrong.
  //
  // A "Q1.7" 8-bit signed value spans -1.0 to +0.992: it CANNOT REPRESENT
  // +1.0. A main tap of 1.0 and bipolar symbols of +/-1.0 both overflow
  // it, and the result saturates silently -- every number comes out
  // slightly small and nothing reports a problem (section 18, mutation 9).
  //
  // So coefficients get integer headroom, and samples get more again,
  // because a four-tap sum of unit symbols can reach +/-1.5 here.
  // ==================================================================
  localparam int COEF_W = 10;                 // Q3.7 : -8.00 .. +7.99
  localparam int SAMP_W = 12;                 // Q5.7 : -16.0 .. +15.99
  localparam int ACC_W  = COEF_W + SAMP_W + 2;
 
  typedef logic signed [SAMP_W-1:0] samp_t;
  typedef logic signed [COEF_W-1:0] coef_t;
  typedef logic signed [ACC_W-1:0]  acc_t;
 
  // ONE rounding point, at the end, with explicit saturation. Note the
  // ARITHMETIC shift: >> would shift zeros into the sign position and
  // turn every negative result into a large positive one.
  function automatic samp_t to_samp(input acc_t a);
    acc_t r;
    r = (a + acc_t'(1 << (FRAC-1))) >>> FRAC;
    if (r >  acc_t'( (1<<(SAMP_W-1)) - 1 )) return samp_t'( (1<<(SAMP_W-1)) - 1 );
    if (r <  acc_t'(-(1<<(SAMP_W-1))     )) return samp_t'(-(1<<(SAMP_W-1)));
    return samp_t'(r);
  endfunction
 
  // rx[n] = h0*x[n+1] + h1*x[n] + h2*x[n-1] + h3*x[n-2]
  //          pre-cursor  main     post 1      post 2
  function automatic samp_t channel(
      input samp_t x_next, x_cur, x_p1, x_p2,
      input coef_t h0, h1, h2, h3);
    acc_t acc;
    // EVERY PRODUCT ACCUMULATED AT FULL WIDTH. No intermediate narrowing
    // anywhere before the single rounding step.
    acc = acc_t'(x_next) * acc_t'(h0)
        + acc_t'(x_cur ) * acc_t'(h1)
        + acc_t'(x_p1  ) * acc_t'(h2)
        + acc_t'(x_p2  ) * acc_t'(h3);
    return to_samp(acc);
  endfunction
 
  // y[n] = c_pre*x[n+1] + c_main*x[n] + c_post*x[n-1]
  // THE TRANSMITTER MAY USE x[n+1] BECAUSE IT ALREADY HAS IT (section 4).
  function automatic samp_t tx_fir(
      input samp_t x_next, x_cur, x_prev,
      input coef_t c_pre, c_main, c_post);
    acc_t acc;
    acc = acc_t'(x_next) * acc_t'(c_pre )
        + acc_t'(x_cur ) * acc_t'(c_main)
        + acc_t'(x_prev) * acc_t'(c_post);
    return to_samp(acc);
  endfunction
 
endpackage

Classification: verification-only.

The signed discipline is not decoration. Every operand is logic signed, every product is cast to the accumulator width before multiplying, and the single rounding step uses an arithmetic shift. Omit any one and negative taps — the useful ones — become large positives, and the model silently computes the opposite of equalization.

Worked numbers, computed by running this model over a twenty-symbol stressful sequence with h = [0.10, 1.00, 0.30, 0.10] and c = [−0.10, 1.00, −0.30]:

unequalizedwith TX FIR
best-case margin1.5001.039
worst-case margin0.5000.680

Read both rows together, because the trade is the point. The best case got worse — the transmitter is deliberately reducing the amplitude of symbols in a run. The worst case improved by 36%, and the worst case is what decides whether the link works.

That is what equalization buys and what it costs: peak amplitude traded for a smaller spread between best and worst. A design that measured only the best case would conclude equalization had made things worse.

16. Verification Model — DFE

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import eq_model_pkg::*;
 
// VERIFICATION-ONLY. Decision-feedback equalization over already-decided
// symbols, with error propagation deliberately representable.
// Five taps matches the vendor implementation quoted in section 1; the
// arithmetic here is this model's, not a claim about that device.
module dfe_model #(
  parameter int TAPS = 5
) (
  input  samp_t sample,                   // post-CTLE sample
  input  coef_t tap      [TAPS],          // signed, Q3.7
  // PREVIOUS decisions, decision[0] most recent. NOT the current one: a
  // DFE using its own current decision would subtract a quantity derived
  // from the value it is computing.
  input  samp_t decision [TAPS],
 
  output samp_t corrected,
  output logic  bit_out
);
 
  acc_t acc;
  always_comb begin
    acc = acc_t'(sample) <<< FRAC;        // bring sample to accumulator scale
    for (int k = 0; k < TAPS; k++)
      acc -= acc_t'(tap[k]) * acc_t'(decision[k]);
    corrected = to_samp(acc);
  end
 
  // The slicer. In real hardware this is an analog comparator with offset
  // and noise; here it is a sign test -- which is exactly why this model
  // says nothing about BER.
  assign bit_out = (corrected >= 0);
 
endmodule

Classification: verification-only.

decision[0] is the most recent previous symbol, never the current one. A DFE feeding back its own current decision would be a combinational loop in hardware and an infinite regress in the model.

Error propagation is representable on purpose. Drive a wrong value into decision[0] and the correction is displaced by 2 × tap[0] in the wrong direction — §18 measures exactly that, so §8's claim can be seen rather than believed.

17. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over tx_eq_config, eq_request_hold and eq_adaptation. These assert
// the CONTROL-PLANE contract. They assert NOTHING about analog
// convergence, eye margin, BER, or that equalization succeeds -- all of
// which depend on a physical channel this design does not control.
 
// ---- ENVIRONMENT ------------------------------------------------------
// A1: a request is stable while offered and not accepted.
assume property (@(posedge clk) disable iff (!rst_n)
  (in_valid && !in_ready) |=> ($stable(in_lane) && $stable(in_taps)));
// A2: quality_valid pulses only for a completed measurement.
assume property (@(posedge clk) disable iff (!rst_n)
  quality_valid |-> !$isunknown(quality));
 
// ---- CONFIGURATION ATOMICITY ------------------------------------------
 
// P1: THE CHAPTER'S CENTRAL RTL PROPERTY. Taps change ONLY on an accepted
// commit -- so the transmitter never sees a partially-updated setting.
property p_taps_change_only_on_commit;
  @(posedge clk) disable iff (!rst_n)
  (!$stable(cfg_taps)) |-> $past(take_coeff);
endproperty
a_commit : assert property (p_taps_change_only_on_commit);
 
// P1b: and when they change, the WHOLE struct came from one request.
property p_taps_match_request;
  @(posedge clk) disable iff (!rst_n)
  $past(take_coeff) |-> (cfg_taps == $past(coeff_req));
endproperty
a_atomic : assert property (p_taps_match_request);
 
// P2: an UNSUPPORTED preset never reaches the macro as valid config.
property p_unsupported_never_applied;
  @(posedge clk) disable iff (!rst_n)
  (preset_valid && !SUPPORTED_PRESETS[preset_id] && !coeff_valid)
    |=> ($stable(cfg_preset) && reject_unsupported);
endproperty
a_preset : assert property (p_unsupported_never_applied);
 
// P3: OUT-OF-RANGE COEFFICIENTS never applied. Checked in SIGNED
// arithmetic -- an unsigned test accepts every negative tap.
property p_range_enforced;
  @(posedge clk) disable iff (!rst_n)
  (coeff_valid && !taps_in_range) |=> ($stable(cfg_taps) && reject_range);
endproperty
a_range : assert property (p_range_enforced);
 
// P4: the APPLIED state is always within the legal signed range -- which
// additionally catches a corrupted register, not only a bad request.
property p_applied_taps_legal;
  @(posedge clk) disable iff (!rst_n)
  cfg_valid |-> (($signed(cfg_taps.pre)  >= COEF_MIN)
              && ($signed(cfg_taps.pre)  <= COEF_MAX)
              && ($signed(cfg_taps.main) >= COEF_MIN)
              && ($signed(cfg_taps.main) <= COEF_MAX)
              && ($signed(cfg_taps.post) >= COEF_MIN)
              && ($signed(cfg_taps.post) <= COEF_MAX));
endproperty
a_legal : assert property (p_applied_taps_legal);
 
// P5: the declared same-cycle priority holds -- coefficients win.
property p_coeff_beats_preset;
  @(posedge clk) disable iff (!rst_n)
  (preset_valid && coeff_valid && taps_in_range) |=> !cfg_is_preset;
endproperty
a_priority : assert property (p_coeff_beats_preset);
 
// ---- REQUEST OWNERSHIP ------------------------------------------------
 
// P6: THE LANE A REQUEST NAMES DOES NOT CHANGE WHILE IT WAITS.
property p_lane_stable;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_ready) |=> (out_valid && $stable(out_lane)
                                           && $stable(out_taps));
endproperty
a_lane : assert property (p_lane_stable);
 
// P7: a held request is not replaced before it is taken.
property p_not_overwritten;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_ready) |=> $stable(out_preset);
endproperty
a_once : assert property (p_not_overwritten);
 
// P8: an out-of-range lane id is refused and reported, never applied.
property p_lane_range;
  @(posedge clk) disable iff (!rst_n)
  (in_valid && !lane_legal) |=> lane_range_error;
endproperty
a_lane_range : assert property (p_lane_range);
 
// ---- ADAPTATION -------------------------------------------------------
 
// P9: A MEASUREMENT BELONGS TO THE SETTING THAT PRODUCED IT. The
// controller reaches MEASURE only through SETTLE, which requires
// apply_done -- section 14's settle state, asserted.
property p_measure_only_after_settle;
  @(posedge clk) disable iff (!rst_n)
  $rose(st_q == S_MEASURE) |-> ($past(st_q) == S_SETTLE) && $past(apply_done);
endproperty
a_settle : assert property (p_measure_only_after_settle);
 
// P10: success and fail are MUTUALLY EXCLUSIVE.
property p_success_xor_fail;
  @(posedge clk) disable iff (!rst_n)
  !(eq_success && eq_fail);
endproperty
a_excl : assert property (p_success_xor_fail);
 
// P11: success is never declared before at least one represented
// measurement met the target. A controller reporting success on entry
// would look perfect and change nothing.
property p_no_success_without_measurement;
  @(posedge clk) disable iff (!rst_n)
  $rose(eq_success) |-> $past(quality_valid && (quality >= quality_target));
endproperty
a_measured : assert property (p_no_success_without_measurement);
 
// P12: BOUNDED. The attempt count never exceeds the configured limit -- a
// deterministic local bound, not a fairness assumption.
property p_attempts_bounded;
  @(posedge clk) disable iff (!rst_n)
  attempts <= TRY_W'(MAX_TRIES-1);
endproperty
a_bounded : assert property (p_attempts_bounded);
 
// P13: reset returns a defined safe state.
property p_reset;
  @(posedge clk)
  !rst_n |=> (!cfg_valid && !out_valid && !eq_success && !eq_fail);
endproperty
a_reset : assert property (p_reset);
 
// ---- SCOPE ------------------------------------------------------------
 
// P14: this controller is not an LTSSM and does not drive link state
// (section 14). An integration property, so a refactor that wires them
// together is caught rather than assumed correct.
property p_not_link_control;
  @(posedge clk) disable iff (!rst_n)
  (eq_success || eq_fail) |-> $stable(dut_ltssm.state);
endproperty
a_scope : assert property (p_not_link_control);

P1 and P1b are the atomicity pair. P1 says the taps move only on an accepted commit; P1b says that when they move, the whole struct came from one request. A design with three separately-written registers fails P1 on the cycles they disagree, and a design that mixes two requests fails P1b.

P3 and P4 both concern range, at different points. P3 forbids applying an out-of-range request; P4 asserts the applied state is always legal, which catches a corrupted register as well as a bad request.

P9 is the settle property, and it catches the highest-value bug in the chapter: a measurement attributed to the wrong setting, in a loop that then converges confidently on something it never measured.

No convergence property exists, deliberately. "Equalization eventually succeeds" depends on a physical channel — asserting it would require assuming away exactly the failures worth catching.

18. Verification and Fault Injection

The scoreboard maintains its own configuration model and its own channel arithmetic, and never calls channel(), tx_fir() or the DUT's range check.

Configuration

  • Preset apply, supported and unsupported.
  • Coefficients at the minimum and maximum legal values, and one step outside each — including negative taps, the case an unsigned comparison fails.
  • Preset and coefficients in the same cycle — verify the declared priority (P5).
  • Back-to-back applies; reset while a request is pending.

Request ownership

  • Macro path stalled across several requests — verify holding, no loss, lane stability (P6, P7).
  • A request naming an out-of-range lane — verify refusal and report (P8). Required.
  • LANES = 1, 2, 4 — including the $clog2(1) corner.

Adaptation

  • Quality above target on the first attempt — success after exactly one measurement.
  • Quality below target for MAX_TRIES — verify fail, bounded (P12).
  • quality_valid arriving before apply_done — verify the controller waits (P9). Required, and the mutation-6 test.
  • eq_start while in ACCEPT — verify a clean restart with try_q cleared.

Numerical model

  • Clean channel (h = [0, 1, 0, 0]) — verify no ISI, and verify a unit symbol comes back as exactly ±1.0. That second check is the format test: a width too narrow to hold 1.0 saturates and returns 0.992, which looks like rounding and is not.
  • Post-cursor dominant and pre-cursor dominant channels, and §3's pattern.
  • TX FIR applied — verify worst-case margin improves, not just best-case.
  • DFE with correct taps — verify post-cursor removed.
  • DFE with a wrong decision injected — verify the correction is displaced by 2 × tap[0] (§8, measured rather than asserted).
  • Coefficients forced positive where they should be negative — verify margin gets worse, which is the sign-error signature.

Mutations

#MutationCaught byLab symptom
1pre and post coefficients swappednumerical model — margin worsenstrains at low rate only; EQ "completes"
2unsigned coefficient range checkP3 with a negative tapnegative taps become huge positives; eye closes
3one tap register updates a cycle lateP1, P1bone cycle of wrong shape per change, on a live lane
4unsupported preset acceptedP2macro applies an unintended shape
5request dropped when the macro stallsP6some lanes never receive their setting
6measurement taken before apply_doneP9adaptation converges on the wrong setting
7success declared on entryP11EQ always "passes"; Link still fails at rate
8DFE uses the current decisionmodel — combinational loopnonsense corrections
9coefficient or sample width too narrow to hold 1.0model — every value saturates slightly lownumbers come out plausible and consistently small; nothing reports it
9beach product rounded before the summodel — error grows with tap countsmall, tap-count-dependent inaccuracy
10lane id re-read at apply timeP6setting lands on the wrong lane
11reset leaves stale configurationP13post-reset behaviour depends on history
12preset and coefficient partially combinedP1b, P5a shape neither side requested
13retry loop unboundedP12Link sits in adaptation forever on a bad channel
14>> instead of >>> when roundingmodel — negatives invertequalization inverts on negative samples

19. Debugging

Symptom → likely layer → signals and instruments → distinguishing experiment.

Trains at Gen3, fails at Gen5

Do not start at the Transaction Layer. Chapter 17.3 §6: the negotiated speed is bounded by capability and channel, and equalization is what stands between them.

Inspect: per-lane equalization result and applied settings; CDR lock; the eye monitor if the device exposes one (§1's source does); and the negotiated speed in Link Status.

The distinguishing experiment: change the channel — shorter cable, different slot, different riser. If Gen5 comes up, the logic is fine and the channel was marginal. If it fails identically on every channel, suspect the control plane — a setting that never reaches the macro (mutation 5) looks exactly like a bad channel.

One lane fails equalization; the others pass

Lane-specific, which is diagnostic in itself — Link-wide protocol logic cannot fail on one lane.

Inspect: that lane's applied setting versus its neighbours', and whether the request naming that lane was ever accepted (mutation 10 puts the setting on the wrong lane and presents exactly this way).

The distinguishing experiment: move the lane's physical path if the board allows — a different slot position or a reversed connection. If the failure follows the physical lane it is channel or connector; if it stays with the logical lane it is the control plane.

The request is visible but the waveform never changes

The control-to-macro path, not the adaptation logic.

Inspect: cfg_valid, the applied taps, and the rejection flags. reject_unsupported or reject_range asserted is the answer — the request was refused and reported, exactly as designed. If neither is set and the registers hold the new value, the break is between register and macro.

Quality gets worse after adaptation

Three candidates, and they separate cleanly.

Sign error (mutation 2 or 14): taps applied with the wrong polarity, so the transmitter adds to the channel's distortion. Check whether applied taps are negative where they should be.

Tap order swapped (mutation 1): pre-compensation applied to the post-cursor and vice versa.

Attribution error (mutation 6): the loop is optimising a shifted function.

The distinguishing experiment: apply a known-good static setting with adaptation disabled. If quality is good, the settings work and the loop is wrong. If it is still bad, the settings themselves are wrong — and the sign test then separates the remaining two.

Errors arrive in short bursts rather than singly

Suspect DFE error propagation (§8) before the protocol layers.

Isolated random errors point at noise crossing a marginal threshold. Bursts of two to five — within the tap span — point at a decision feeding back wrongly.

The distinguishing experiment: if the device allows it, reduce DFE tap weights and re-measure. If burst length shortens while total error count rises, the DFE was propagating. If nothing changes, it was not.

20. Common Misconceptions

  • "Equalization means increasing amplitude." It means reshaping in time to cancel ISI. Amplitude alone does not help — the interference scales with it (§2).
  • "CTLE and DFE are the same." One is a continuous-time filter with no knowledge of the data; the other subtracts already-decided symbols. One amplifies noise, the other does not (§4).
  • "Deskew and equalization are the same." Lane timing versus signal quality within a lane (§9).
  • "CDR and equalization are interchangeable." Sampling instant versus sampled value (§9).
  • "RTL can model the real eye." It cannot. §§15–16 are numerical illustrations and say so (§5).
  • "More CTLE gain is always better." It amplifies noise identically. There is an optimum, not a maximum (§7).
  • "A DFE cannot make things worse." A wrong decision produces a wrong subtraction, displaced by twice the tap weight (§8).
  • "Every generation uses the same equalization procedure." A fixed de-emphasis selection and a negotiated procedure are different mechanisms (§11).
  • "Equalization happens once at power-up and never matters again." Settings are established as part of reaching a rate, and re-established on retraining (Chapter 18.5).
  • "One setting should work on every lane." Every lane has its own channel — length, vias, connector position (§19).
  • "A preset number is a voltage." It is an index naming a predefined response (§6).
  • "The negotiated speed is purely a digital capability decision." It is bounded by capability and determined by the channel (Chapter 17.3 §6).

21. Understanding Check

22. What's Next

The channel remembers, and equalization is three mechanisms attacking that memory from three places — distinguished by what each one knows. The transmitter knows the future; a CTLE knows only the waveform and pays for its gain in noise; a DFE knows only what it has already decided and can propagate its own mistakes.

The RTL around all of it is control: an atomically-committed setting, a request that keeps its lane identity while it waits, and a loop that must prove its measurement belongs to its stimulus.

Chapter 17.5 — Electrical Concepts goes underneath. This chapter has been treating "the channel" as a filter with a pulse response — what is actually travelling on the wire, why differential signalling helps, where the clock information lives, and which physical effects close the eye are the foundations it has been standing on.

Module 18 then takes the state machine that coordinates all of it, with Chapter 18.5 owning the Recovery process in which equalization is actually negotiated.

The idea to carry forward: interference caused by your own data is a computation, not a hazard.