Skip to content

PCIe · Module 5

PCIe Gen4 — When the Channel Becomes Part of the Architecture

Gen4 doubles to 16 GT/s with Gen3's encoding unchanged, so capacity doubles cleanly. What does not scale is margin: the unit interval halves while physical impairments do not, which is where signal integrity stops being someone else's problem.

Chapter 5.3 was a two-variable change — rate and encoding both moved. Gen4 returns to one variable, which makes the arithmetic simple and lets the chapter spend its length on what actually got harder.

If Gen4 keeps the efficient Gen3 encoding, what becomes difficult when the signalling rate doubles to 16 GT/s?

This is the chapter where a protocol tutorial has to acknowledge physics. Not because the protocol changed — it barely did — but because the conditions under which the protocol must work changed substantially.

1. The Arithmetic, Briefly

One input changed, so the derivation from Chapter 5.3 applies unmodified.

Step 1 — signalling rate. 16 × 10⁹ transfers/second, per lane, per direction.

Step 2 — apply encoding. Still 128b/130b, still ≈98.4615% efficient. 16 GT/s × (128/130) = 15.753846153... Gb/s≈ 15.7538 Gb/s

Step 3 — convert to bytes. ÷ 8 = 1.969230769... GB/s

Step 4 — express conventionally. ≈ 1969.231 MB/s

Gen3Gen4Ratio
Signalling rate8 GT/s16 GT/s2.000×
Encoding efficiency≈ 0.984615≈ 0.9846151.000×
Encoding-adjusted bit rate≈ 7.8769 Gb/s≈ 15.7538 Gb/s2.000×
Encoding-adjusted byte rate≈ 984.6 MB/s≈ 1969.2 MB/s2.000×

All per lane, per direction, theoretical, before higher-layer overhead.

Because the efficiency ratio is exactly 1.000, the capacity ratio is exactly the rate ratio. The entire capacity increase is attributable to signalling rate — the same clean attribution Chapter 5.2 had for Gen1→Gen2, and for the same reason.

That is the easy half. Note what it conceals: the physical layer was asked to do something twice as demanding, and nothing in the arithmetic reflects that.

2. The Unit Interval

The most useful quantitative concept in this chapter, and the one that makes "harder" concrete rather than hand-waved.

A unit interval (UI) is the nominal time occupied by one transfer on the wire:

UI = 1 / signalling rate

Working it through:

GenerationSignalling rateUI
Gen38 GT/s1 / 8×10⁹ = 125 ps
Gen416 GT/s1 / 16×10⁹ = 62.5 ps

Doubling the rate halves the unit interval. That is arithmetic, not engineering. The engineering is what happens next.

3. What Gets Harder

With the UI framing in place, the specific pressures can be named. All of these are physical-layer concerns whose mechanisms belong to Module 17 — the point here is why rate scaling makes them matter more.

Channel loss rises with frequency content. Faster signalling puts more energy at higher frequencies, and interconnect attenuates high frequencies more than low ones. The same board and connectors that presented an acceptable channel at one rate present a more attenuating one at double the rate — without any physical change.

Inter-symbol interference grows. When a transition has not fully settled before the next one begins, energy from one transfer influences the next. Halving the interval gives the channel less time to settle, so residual energy from previous transfers is proportionally larger.

Reflections have less time to decay. Discontinuities — connectors, vias, package transitions — reflect energy, and that energy returns after a delay set by physical distance. As the interval shortens, reflections arriving from a fixed physical distance land in a proportionally later transfer, and there is less time for them to attenuate below significance.

Jitter consumes more of the interval. The §2 argument, generalised: timing uncertainty from any source occupies a larger fraction of a shorter interval.

Crosstalk matters more. Coupling between neighbouring conductors scales with edge rates and frequency content, so faster signalling generally increases it while the budget to absorb it shrinks.

Equalisation becomes more important. Compensating for frequency-dependent channel behaviour matters more as that behaviour becomes more pronounced. Chapter 5.5 takes this up properly, and Module 17.4 owns the mechanisms.

4. Where Digital RTL Ends

Gen4 sharpens a question Chapter 3.3 introduced: which parts of "the PHY" does a digital RTL engineer actually own?

A representative digital-to-analog partition: digital RTL owns configuration, status and monitoring, and datapath control; a boundary separates these from hardened blocks comprising the high-speed transmitter, receiver front-end, timing recovery, and analog equalisation; beyond those lies the physical channel.Configuration +statusdigital RTLMonitoring +telemetrydigital RTLDatapath controldigital RTLImplementationboundaryposition varies bydesignHigh-speedtransmittercommonly hardenedReceiver front-endcommonly hardenedTiming recovery +EQcommonly hardenedPhysical channelboard, connector,package12
Figure 1 — a representative partition of responsibility. Digital RTL typically owns configuration, status, monitoring, and the datapath control adjacent to the physical layer. The high-speed transmitter, receiver front-end, timing recovery, and analog equalisation elements are commonly hardened. The boundary's exact position varies by implementation and vendor — what is stable is that it exists, and that RTL simulation cannot verify what sits on the far side.

Two consequences matter more at Gen4 than they did at lower rates.

Your simulation does not model the channel. RTL simulation operates on logical values. It does not represent attenuation, reflections, jitter, or eye closure, and it cannot — those require different tools and different models. A design can pass every RTL test and fail on real hardware for reasons the simulation had no way to represent.

Observability becomes the digital side's contribution. If you cannot simulate the channel, the next best thing is to instrument the design so the hardware can report on it. That is squarely digital RTL work, and §5 develops it.

5. Compile-Time Timing Comparison

Before instrumentation, a small piece of arithmetic worth having available at elaboration.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Compile-time only (elaboration constants and pure functions).
// Computes nominal unit intervals in FEMTOSECONDS so that rates through the
// generations covered here yield exact integers with no `real` arithmetic.
//
//   UI = 1 / rate.  With rate in MT/s (mega-transfers/s):
//   UI[fs] = 1e15 / (mtps * 1e6) = 1e9 / mtps
//
// Gen3:  1e9 / 8000  = 125000 fs = 125.000 ps
// Gen4:  1e9 / 16000 =  62500 fs =  62.500 ps
package pcie_ui_pkg;
 
  localparam longint unsigned GEN3_MTPS = 8000;
  localparam longint unsigned GEN4_MTPS = 16000;
 
  // Nominal unit interval in femtoseconds. NOT a sampling margin.
  function automatic longint unsigned ui_femtoseconds(input longint unsigned mtps);
    if (mtps == 0) return 0;
    return 1_000_000_000 / mtps;      // 1e9 fs·MT/s
  endfunction
 
  localparam longint unsigned GEN3_UI_FS = ui_femtoseconds(GEN3_MTPS);   // 125000
  localparam longint unsigned GEN4_UI_FS = ui_femtoseconds(GEN4_MTPS);   //  62500
 
  // A hypothetical fixed timing uncertainty, expressed as a fraction of the
  // interval in hundredths of a percent. HYPOTHETICAL — not a PCIe budget.
  //   Gen3: 10000 fs / 125000 fs =  8.00%  -> 800
  //   Gen4: 10000 fs /  62500 fs = 16.00%  -> 1600
  localparam longint unsigned HYPO_UNCERTAINTY_FS = 10_000;   // 10 ps
 
  function automatic longint unsigned uncertainty_pct_x100(
    input longint unsigned unc_fs,
    input longint unsigned ui_fs
  );
    if (ui_fs == 0) return 0;
    return (unc_fs * 10_000) / ui_fs;
  endfunction
 
  localparam longint unsigned GEN3_UNC_X100 =
      uncertainty_pct_x100(HYPO_UNCERTAINTY_FS, GEN3_UI_FS);   // 800  =  8.00%
  localparam longint unsigned GEN4_UNC_X100 =
      uncertainty_pct_x100(HYPO_UNCERTAINTY_FS, GEN4_UI_FS);   // 1600 = 16.00%
 
endpackage

Classification: compile-time only.

What it teaches: that the margin argument is arithmetic you can check, not rhetoric. Femtoseconds are chosen so the divisions are exact for these rates — picoseconds would have made Gen5's 31.25 ps non-integer.

Deliberately simplified: nominal intervals only, with no representation of actual margin, which depends on the transmitter, channel, receiver, and conditions.

What to notice: the constant is named HYPO_UNCERTAINTY_FS, not JITTER_BUDGET_FS. Naming a hypothetical as though it were a specification value is how invented numbers escape into designs, and a name is the cheapest place to prevent it.

6. Instrumenting for Transport Quality

If simulation cannot see the channel, the hardware should be able to report on it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative synthesizable RTL — transport-quality telemetry.
// NOT a PCIe controller and NOT PCIe-defined registers. Counts abstract
// transport-quality events over a measurement epoch, with saturating counters
// and an atomic snapshot so software reads a coherent set.
module transport_telemetry #(
  parameter int unsigned CNT_W = 32
) (
  input  logic             clk,
  input  logic             rst_n,
 
  // Abstract event indications from whatever produces them.
  input  logic             err_event,        // a transport error was detected
  input  logic             recovery_event,   // a recovery action occurred
  input  logic             link_down_event,  // transport became unusable
 
  // Operating rate identifier, latched for the epoch so counts are attributable
  input  logic [3:0]       active_gen,
 
  input  logic             epoch_start,
  input  logic             epoch_stop,
 
  output logic [CNT_W-1:0] snap_errors,
  output logic [CNT_W-1:0] snap_recoveries,
  output logic [CNT_W-1:0] snap_link_downs,
  output logic [CNT_W-1:0] snap_cycles,
  output logic [3:0]       snap_gen,
  output logic             snap_saturated,   // any counter hit its maximum
  output logic             snap_valid,
  output logic             degraded_sticky   // set on first error, software-cleared
);
 
  logic [CNT_W-1:0] c_err, c_rec, c_dn, c_cyc;
  logic [3:0]       gen_q;
  logic             running_q, sat_q;
 
  // Saturating increment: a wrapped counter understates a badly-behaving link
  // and looks plausible, which is worse than an obviously pinned maximum.
  function automatic logic [CNT_W-1:0] sat_inc(input logic [CNT_W-1:0] v);
    return (&v) ? v : (v + 1'b1);
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      running_q <= 1'b0; snap_valid <= 1'b0; sat_q <= 1'b0;
      c_err <= '0; c_rec <= '0; c_dn <= '0; c_cyc <= '0;
      gen_q <= '0; degraded_sticky <= 1'b0;
    end else if (epoch_start) begin
      running_q <= 1'b1; snap_valid <= 1'b0; sat_q <= 1'b0;
      c_err <= '0; c_rec <= '0; c_dn <= '0; c_cyc <= '0;
      // Latch the operating rate so every count in this epoch is attributable
      // to one rate. A rate change mid-epoch would make the data meaningless.
      gen_q <= active_gen;
    end else if (running_q && epoch_stop) begin
      running_q       <= 1'b0;
      snap_errors     <= c_err;
      snap_recoveries <= c_rec;
      snap_link_downs <= c_dn;
      snap_cycles     <= c_cyc;
      snap_gen        <= gen_q;
      snap_saturated  <= sat_q;
      snap_valid      <= 1'b1;
    end else if (running_q) begin
      c_cyc <= sat_inc(c_cyc);
      if (err_event)       begin c_err <= sat_inc(c_err); if (&c_err) sat_q <= 1'b1; end
      if (recovery_event)  begin c_rec <= sat_inc(c_rec); if (&c_rec) sat_q <= 1'b1; end
      if (link_down_event) begin c_dn  <= sat_inc(c_dn);  if (&c_dn)  sat_q <= 1'b1; end
    end
 
    // Sticky degradation survives epoch boundaries: it records that this link
    // has misbehaved at some point, which a per-epoch counter would lose.
    if (rst_n && err_event) degraded_sticky <= 1'b1;
  end
endmodule

Classification: synthesizable.

What it teaches: three design choices that make telemetry trustworthy — saturation instead of wrapping, latching the operating rate so counts are attributable, and an atomic snapshot so derived figures are self-consistent.

Deliberately simplified: abstract event inputs rather than any specific error taxonomy; a single epoch rather than rolling history; no per-lane breakdown; no software access mechanism.

What to notice:

  • degraded_sticky deliberately survives epoch clears. A per-epoch counter that resets loses the fact that a link misbehaved at all — and "it was fine in the last window" is not the same as "it has always been fine."
  • snap_saturated reports that a count is untrustworthy. Without it, a pinned counter is indistinguishable from a genuine maximum, and a reader would take the number at face value.
  • The rate is latched at epoch_start. Comparing error counts across generations requires knowing which generation produced them, and a mid-epoch change would silently mix two populations.

Production implication: a real design would tie these to the normative error-detection mechanisms, report per lane, and expose the counters through a defined software-visible interface.

7. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the illustrative telemetry. Implementation invariants for THIS
// design — not PCIe protocol requirements.
 
// STABILITY — P1: the operating rate does not change during an epoch, which
// would make its counts attributable to two different rates at once.
property p_gen_stable_in_epoch;
  @(posedge clk) disable iff (!rst_n)
  running_q |=> $stable(gen_q);
endproperty
a_gen_stable : assert property (p_gen_stable_in_epoch);
 
// CONSERVATION — P2: a counter increments only when its event occurred.
property p_err_needs_event;
  @(posedge clk) disable iff (!rst_n)
  (c_err != $past(c_err)) |-> $past(err_event && running_q);
endproperty
a_err_needs_event : assert property (p_err_needs_event);
 
// SAFETY — P3: counters saturate rather than wrap. A wrapped counter reports
// a small plausible number for a link that is failing badly.
property p_no_wrap;
  @(posedge clk) disable iff (!rst_n)
  (c_err == '1) |=> (c_err == '1);
endproperty
a_no_wrap : assert property (p_no_wrap);
 
// SAFETY — P4: sticky degradation is not cleared by an epoch boundary.
// Only an explicit reset clears it.
property p_sticky_survives_epoch;
  @(posedge clk) disable iff (!rst_n)
  (degraded_sticky && epoch_start) |=> degraded_sticky;
endproperty
a_sticky_survives : assert property (p_sticky_survives_epoch);
 
// STABILITY — P5: the snapshot is coherent once taken and until a new epoch.
property p_snapshot_stable;
  @(posedge clk) disable iff (!rst_n)
  (snap_valid && !epoch_start) |=> ($stable(snap_errors) && $stable(snap_cycles)
                                    && $stable(snap_gen));
endproperty
a_snapshot_stable : assert property (p_snapshot_stable);

P1 catches the bug that makes generation comparison meaningless — counts attributed to the wrong rate. Simulation rarely produces a mid-epoch rate change, so it needs an assertion rather than a test.

P3 catches wrapping, which is insidious because the resulting number is plausible. A link generating four billion errors reporting "7" looks like a healthy link.

P4 catches sticky state being cleared by an ordinary epoch boundary, which would erase exactly the history the flag exists to preserve.

8. Verification at Three Scopes

Gen4 makes explicit something that was implicit before: one testbench cannot verify all of this.

Digital RTL scope. Configuration correctness, telemetry accounting, snapshot atomicity, datapath control at the boundary. This proves your logic behaves correctly given well-behaved inputs. It is necessary and it is not sufficient.

PHY-model scope. Abstract degradation scenarios — inject error events, recovery events, transitions to unusable — and verify the digital side responds sensibly. This exercises paths that never occur in a clean simulation and is where most integration bugs are found.

Electrical scope. Channel characterisation, margin measurement, compliance. This requires measurement equipment or specialised simulation and is outside ordinary RTL simulation entirely.

Corner cases: rate change attempted mid-epoch; counters driven to saturation; error burst concurrent with an epoch boundary; sticky flag set then epochs cleared repeatedly; every event type asserted simultaneously.

Coverage: each event type observed; each generation value latched; saturation reached for each counter; snapshot taken with and without prior errors.

9. Debugging: Gen3 Stable, Gen4 Unstable

The characteristic Gen4 report, and the one where reflexes send engineers to the wrong layer.

The observation: the same design, the same workload, and the same transaction semantics work reliably at Gen3 and misbehave at Gen4.

What that pattern suggests. Transaction semantics are rate-independent — a malformed operation is malformed at any rate (Chapter 3.4). If the logical behaviour is identical and only the rate changed, the difference is in conditions, not in logic.

The investigation, in order:

  1. Are the transactions actually the same? Confirm the workload and traffic pattern did not change with the rate. If the higher rate also drove more traffic, two variables moved.
  2. Do errors correlate with the rate? Compare telemetry across generations, using the latched rate to attribute counts correctly. Errors appearing only at the higher rate are the central signal.
  3. Is it channel- or path-dependent? Does the problem follow a particular Link, slot, or board path? A fault confined to one physical path while equivalent paths are clean points strongly at that path.
  4. Is it condition-dependent? Sensitivity to temperature or supply variation is characteristic of margin problems and uncharacteristic of logic bugs, which tend to be deterministic.
  5. Does the lower generation stay clean under equivalent load? Running Gen3 at the same traffic volume separates rate sensitivity from load sensitivity — an important distinction, because a load-dependent failure at both rates is a different problem.

10. Common Misconceptions

11. Understanding Check

12. Summary

Gen4 doubles the signalling rate to 16 GT/s while retaining Gen3's 128b/130b encoding at ≈98.4615%. Because the efficiency ratio is exactly 1.000, the capacity ratio is exactly the rate ratio:

16 GT/s × 128/130 ≈ 15.7538 Gb/s÷ 8 ≈ 1.9692 GB/s≈ 1969.2 MB/s

per lane, per direction, theoretical — exactly 2.000× Gen3.

The arithmetic is the easy half. The unit interval halves from 125 ps to 62.5 ps, while jitter, loss, reflections, and crosstalk do not shrink — so each consumes a proportionally larger share of the budget. A hypothetical 10 ps uncertainty goes from 8% of the interval to 16%. And the UI is not the sampling margin; actual margin is smaller and implementation-dependent.

That makes the channel part of the architecture. It also sharpens the digital/hardened partition: RTL typically owns configuration, status, monitoring, and boundary datapath control, while the high-speed transmitter, receiver front-end, timing recovery, and analog equalisation are commonly hardened. Since RTL simulation cannot model the channel, the digital side's contribution to transport quality is largely observability — saturating counters, latched operating rate, atomic snapshots, sticky degradation.

For debugging: if failure severity tracks the signalling rate while transaction semantics stay constant, suspect physical margin before rewriting the Transaction Layer — strong evidence, not proof, confirmed by whether the problem follows the physical path.

Hold the model: capacity doubled cleanly; the margin available to achieve it did not.

13. What Comes Next

Chapter 5.5 — PCIe Gen5 doubles again to 32 GT/s with the same encoding, halving the interval to 31.25 ps. The arithmetic will be familiar; the engineering will not. That chapter takes up equalisation as an escalating burden, why raw error counts across generations must be normalised before comparison, and a full debug flow for separating workload-, topology-, digital-, and margin-limited behaviour.

Revisit PCIe Gen3 for the encoding this generation retains, or Physical Layer for the architectural framing of the boundary. Browse the full path on the PCIe tutorials index.