Skip to content

PCIe · Module 5

PCIe Gen6 — The Generation That Changed More Than the Rate

Gen6 reaches 64 GT/s by changing three things at once: PAM4 signalling carrying two bits per symbol, the removal of block line encoding, and FLIT-based transport carrying its own error protection. Why the Gen3-to-Gen5 arithmetic stops working here, and what corrected-error telemetry actually tells you.

Five chapters have built one habit: take the signalling rate, apply an encoding efficiency, divide by eight. It worked for Gen1 through Gen5 because only two variables ever moved, and only one of them moved more than once.

Gen6 breaks the habit. Not because the arithmetic gets harder, but because the model underneath it changes.

Why is PCIe Gen6 an architectural transition rather than another signalling-rate doubling?

1. Why Another Clean Doubling Was Not the Obvious Path

Chapter 5.5 ended with a specific engineering picture: at 32 GT/s the unit interval is 31.25 ps, the channel is a first-class design constraint, and equalisation is doing substantial work within real limits.

Now extend that. A conventional doubling to 64 GT/s using binary signalling would mean an interval of 15.625 ps, and — the part that matters more — roughly twice the frequency content for the channel to carry.

No single factor forced the change. Several pressures compounded, and the response addressed several of them at once:

The interval shrinks again. Every fixed impairment discussed in Chapter 5.4 — jitter, timing uncertainty, residual reflections — occupies double the fraction of the budget it did at Gen5.

Channel loss rises with frequency content. This is the pressure that dominates the discussion. Attenuation in practical interconnect generally increases with frequency, and often steeply. Doubling the frequency content typically costs considerably more than double in loss at the frequencies that carry the signal's energy.

Receiver complexity grows. Recovering a signal from a more attenuated, more distorted channel demands more capable equalisation and timing recovery, and those cost silicon area and power.

Package and board constraints tighten. Transitions between package, board, and connector — approximately transparent at low rates, significant at Gen5 — become harder to make adequate as frequency content rises.

Implementation and power cost climb. Running more circuitry faster is not free.

Three dimensions changed by PCIe Gen6: signalling moves from NRZ with two levels to PAM4 with four levels carrying two bits per symbol; line encoding moves from 128b/130b block encoding to unencoded 1b/1b at 64 giga-transfers per second; and transport framing moves from variable-length packet framing to a fixed 256-byte FLIT carrying its own CRC and forward error correction.1Signalling: NRZ → PAM42 levels → 4 levels; 1 bit → 2 bits per symbol2Line encoding: 128b/130b → 1b/1bblock encoding removed at 64 GT/s3Transport: variable framing → FLITfixed 256-byte unit carrying CRC and FEC
Figure 1 — the three dimensions Gen6 changed. Earlier generations moved at most one of these at a time, which is why a single arithmetic formula covered Gen1 through Gen5. Gen6 moves all three, so the signalling model, the encoding model, and the transport-framing model must each be reconsidered rather than carried forward.

2. PAM4 — Where "Two Bits Per Symbol" Comes From

This deserves to be derived rather than asserted, because the shorthand version is the source of most confusion about Gen6.

Binary signalling (NRZ) conceptually uses two distinguishable signal levels. Two distinguishable states carry log₂(2) = 1 bit of information per symbol. One symbol, one bit.

PAM4 — Pulse Amplitude Modulation with four levels — uses four distinguishable amplitude levels. Four distinguishable states carry:

log₂(4) = 2 bits per symbol

That is the whole derivation, and the exponent is the point. Information capacity per symbol grows as the logarithm of the number of distinguishable levels, not linearly with it.

3. Symbol Rate Versus Information Rate — The Terminology That Matters

This is where ordinary treatments of Gen6 become unreliable, so it is worth being pedantic.

PCIe expresses Gen6 as 64 GT/s per lane, per direction. With PAM4 carrying two bits per symbol, the corresponding symbol rate is 32 GBd (32 × 10⁹ symbols per second).

Now compare against Gen5:

Gen5Gen6
SignallingNRZ (2 levels)PAM4 (4 levels)
Bits per symbol12
Symbol rate32 GBd32 GBd — unchanged
Symbol interval31.25 ps31.25 ps — unchanged
Information rate32 GT/s64 GT/s
Nyquist frequency≈16 GHz≈16 GHz — unchanged

Read that table twice. Gen6 doubles the information rate while leaving the symbol rate, the symbol interval, and the Nyquist frequency where Gen5 left them.

Three terms, kept distinct throughout the rest of this chapter:

  • Symbol — one signalling interval, taking one of four levels under PAM4.
  • Information bit — one bit represented by that symbol; PAM4 carries two per symbol.
  • Application byte — payload actually delivered to software, which is smaller still after transport overhead. §6 is where that separation is made concrete.

4. Why PAM4 Is Harder

PAM4 relocates the difficulty rather than removing it. The frequency-domain pressure eased; the amplitude-domain pressure grew.

Three decision thresholds instead of one. A binary receiver distinguishes two levels, which requires one decision threshold. A four-level receiver must distinguish four levels, which requires three. Each is a place where a decision can be made incorrectly.

Adjacent levels are closer together. For a given total available signal span, placing four levels within it leaves substantially less separation between neighbours than placing two. The signal must be resolved to a finer amplitude distinction than binary signalling ever required.

The consequences follow directly:

  • Amplitude noise matters more, because a smaller amplitude disturbance suffices to push a level across a threshold.
  • Linearity matters more. Binary signalling needs only two reliably distinguishable states; four levels must remain correctly ordered and spaced, which places real demands on transmitter and receiver linearity.
  • Channel impairment matters more. The same attenuation or distortion consumes a larger share of a smaller inter-level separation.
  • Receiver discrimination is harder, because the receiver resolves among four possibilities with less separation between them.
  • Equalisation and signal processing become more demanding, because more must be compensated and less error in that compensation is tolerable.

On eye diagrams. A binary eye diagram shows one opening, because there is one threshold. A PAM4 signal conceptually presents three openings, one per threshold, and they are not necessarily equal. This chapter shows no eye diagram, because a diagram drawn to look plausible rather than measured would teach a false picture of margin. A correct statement in prose beats a decorative eye.

5. FLIT Mode — Why It Appears

Start with the problem rather than the format.

Earlier generations organised transmission around variable-length packets, delimited by framing information, protected by a check value covering each packet, and recovered by retransmitting a packet whose check failed. That arrangement suited a regime where errors were rare enough that occasional full retransmission cost little.

PAM4's finer amplitude discrimination changes that assumption. Error protection now has to work harder and — crucially — has to work efficiently, because a scheme that responds to every disturbance with a full retransmission would spend significant capacity on recovery.

Efficient, strong protection is much easier to apply to fixed-size blocks than to variable-length ones. A fixed block has a known length, known field positions, and a known amount of space reserved for protection. That is the architectural reason FLIT mode exists.

FLIT — Flow Control Unit — is a fixed-size transport unit. In Gen6 a FLIT is 256 bytes, and those bytes are divided among transaction-layer content, link-layer content, a CRC, and forward error correction.

The framing overhead that disappears. Because a FLIT has a fixed size and fixed internal structure, the receiver knows where everything is without being told. The per-packet framing information that variable-length transmission required is no longer necessary, and the Gen3-era block line encoding — which existed to give the receiver block alignment and control-versus-data information — is likewise not needed. At 64 GT/s the line carries data unencoded, commonly written 1b/1b: one transmitted bit per information bit, with no line-code expansion at all.

That is a genuinely notable outcome. Gen3 introduced 128b/130b to reduce encoding overhead from 20% to about 1.54%. Gen6 removes line-code overhead entirely — and relocates overhead into the FLIT structure, where it buys error correction rather than framing.

6. Protection: FEC, CRC, and Replay Working Together

Gen6 layers three mechanisms, and understanding why there are three is more valuable than any single one.

Forward error correction (FEC) allows certain errors to be corrected at the receiver from the transmitted data itself, without asking for retransmission. Gen6 uses a deliberately lightweight FEC: it is interleaved across three groups so that a burst of errors within a lane is spread across groups rather than concentrated in one, and each group can correct a single-byte error. Lightweight is a design choice, not a limitation — a stronger code would add latency, and PCIe's latency sensitivity makes that an expensive trade.

CRC detects errors that FEC did not correct. The FLIT's transaction and link-layer content is covered by an 8-byte CRC.

Replay remains the final recourse. If the CRC check fails, the FLIT is retransmitted.

The layering is the design: FEC absorbs the common, small disturbances cheaply; CRC catches what FEC could not fix; replay recovers what CRC caught. Removing any layer would either cost latency or cost correctness.

7. The Arithmetic — and Why the Old Formula Stops Applying

Here is the chapter's most important quantitative lesson, and it is a negative one.

Do not apply rate × 128/130 ÷ 8 to Gen6. Two of its three assumptions no longer hold: there is no 128b/130b line encoding at 64 GT/s, and the transport framing is no longer the variable-length arrangement the earlier figures implicitly assumed.

Step 1 — the line rate. 64 GT/s per lane, per direction.

Step 2 — line encoding. 1b/1b. There is no encoding expansion, so the efficiency factor is 1.

Step 3 — convert. 64 Gb/s ÷ 8 = 8.000 GB/s per lane, per direction. Exactly — no repeating fraction, for the first time since Gen2.

Step 4 — and this is the new step. Overhead has not vanished; it moved into the FLIT. Of the 256-byte FLIT, 236 bytes carry transaction-layer content, with the remainder carrying link-layer content, the CRC, and the FEC.

236 / 256 = 0.92187592.1875%

8.000 GB/s × 0.921875 = 7.375 GB/s per lane, per direction.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Compile-time only (elaboration constants and pure functions).
// Integer arithmetic throughout; no `real`. NOT PCIe controller RTL.
package pcie_gen6_capacity_pkg;
 
  // Information rate, expressed as PCIe expresses it.
  localparam int unsigned GEN6_MEGATRANSFERS_PER_S = 64_000;   // 64 GT/s
 
  // PAM4: four distinguishable levels, log2(4) = 2 bits per symbol.
  localparam int unsigned PAM4_BITS_PER_SYMBOL = 2;
 
  // The symbol rate is the information rate divided by bits per symbol.
  // 64_000 / 2 = 32_000 MBd — the same symbol rate as Gen5's NRZ.
  localparam int unsigned GEN6_MEGASYMBOLS_PER_S =
      GEN6_MEGATRANSFERS_PER_S / PAM4_BITS_PER_SYMBOL;
 
  // 1b/1b at 64 GT/s: no line-code expansion, so the factor is 1/1.
  // Deliberately written as an explicit ratio so the contrast with the
  // Gen3-Gen5 128/130 factor stays visible rather than silently absent.
  localparam int unsigned LINE_CODE_NUM = 1;
  localparam int unsigned LINE_CODE_DEN = 1;
 
  // Line byte rate in kB/s. 64_000 Mb/s -> 8_000_000 kB/s, exact.
  localparam int unsigned GEN6_LINE_KBPS =
      (GEN6_MEGATRANSFERS_PER_S * 1000 * LINE_CODE_NUM) / (LINE_CODE_DEN * 8);
 
  // FLIT composition, byte counts only. Field ORDER and bit-level layout are
  // specification detail and are deliberately not modelled here.
  localparam int unsigned FLIT_BYTES       = 256;
  localparam int unsigned FLIT_TLP_BYTES   = 236;  // transaction-layer content
  localparam int unsigned FLIT_OTHER_BYTES = FLIT_BYTES - FLIT_TLP_BYTES;  // 20
 
  // Transaction-layer share of the transport unit: 236/256 = 92.1875%.
  // 8_000_000 * 236 / 256 = 7_375_000 kB/s, exact — no truncation here.
  localparam int unsigned GEN6_TLP_REGION_KBPS =
      (GEN6_LINE_KBPS * FLIT_TLP_BYTES) / FLIT_BYTES;
 
  // Basis marker. Gen1-Gen5 figures are line rates after block line encoding
  // and BEFORE framing and link-layer overhead. GEN6_TLP_REGION_KBPS already
  // excludes link-layer and protection bytes, so the two are NOT directly
  // comparable. Carrying the basis with the number prevents that error.
  typedef enum logic [1:0] {
    BASIS_LINE_AFTER_BLOCK_ENCODING,  // Gen1-Gen5 convention
    BASIS_LINE_UNENCODED,             // Gen6 line rate, 1b/1b
    BASIS_FLIT_TLP_REGION             // Gen6 after FLIT non-TLP bytes
  } capacity_basis_e;
 
endpackage

Classification: compile-time only.

What it teaches: that a capacity figure is meaningless without its basis, and that the basis changed at Gen6. The capacity_basis_e enumeration exists so a comparison cannot silently be made across incompatible bases — a scoreboard comparing two figures should compare their bases first.

Deliberately simplified: byte counts only, no field ordering, no bit layout, and no modelling of what portion of the transaction region a given workload actually fills.

Production implication: a real performance model must also account for transaction headers within the transaction region, workload access patterns, and the behaviour of the rest of the path — none of which this package represents.

8. Module 5 — Six Generations

GenRateSignallingLine encodingPer-lane, per-directionArchitectural change
Gen12.5 GT/sNRZ8b/10b (80%)250 MB/sbaseline
Gen25 GT/sNRZ8b/10b (80%)500 MB/srate doubled
Gen38 GT/sNRZ128b/130b (≈98.46%)≈984.6 MB/sencoding transition
Gen416 GT/sNRZ128b/130b≈1969.2 MB/ssignal integrity becomes central
Gen532 GT/sNRZ128b/130b≈3938.5 MB/smargin becomes a system problem
Gen664 GT/sPAM41b/1b (none)8000 MB/s — basis changedsignalling + framing transition

Every Gen1–Gen5 figure is a line rate after block line encoding, before framing and link-layer overhead. The Gen6 row is marked because its basis genuinely differs: 8000 MB/s is an unencoded line rate, and the overhead that the earlier rows had not yet subtracted now lives inside the FLIT.

That marking is a feature. Forcing Gen6 into a column defined by an encoding it does not use would produce a neater table and a wrong one.

The narrative the table encodes: baseline → rate doubling → encoding transition → signal-integrity pressure → margin as a system problem → signalling and framing transition. Five of six transitions moved one variable. The sixth moved three.

9. Microarchitecture

An illustrative Gen6 digital datapath. On transmit: a transaction source feeds FLIT assembly, then protection insertion, then a digital physical-layer boundary, then a hardened PAM4 physical layer. On receive: the hardened PAM4 physical layer feeds the digital boundary, then FEC and CRC processing, then FLIT extraction, then packet delivery.TransactionsourceTLP semantics —unchangedFLIT assemblyfixed-size transportunitsProtectioninsertionCRC and FEC regionsDigital PHYboundaryposition varies bydesignHardened PHY —PAM4not visible to RTLsimulationPacket deliveryTLP semantics —unchangedFLIT extractionrecover transactioncontentFEC and CRCprocessingcorrect, detect, orrequest replayDigital PHYboundaryposition varies bydesignHardened PHY —PAM4not visible to RTLsimulation12
Figure 2 — an illustrative Gen6-capable digital datapath. Transmit assembles transaction content into fixed-size transport units and attaches protection before the physical-layer boundary; receive reverses it, processing protection before extracting content. Where the boundary sits and how these responsibilities are grouped varies by implementation.

Illustrative architecture — not PCIe Gen6 normative datapath partitioning. Real implementations group, split, and pipeline these responsibilities differently, and the position of the digital boundary varies by vendor. What is stable is the sequence of responsibilities and the fact that the rightmost blocks are not reachable by RTL simulation.

10. RTL — Fixed-Block Assembly

The general hardware problem underneath FLIT assembly is worth solving properly, because it recurs everywhere: collect a stream of variable-sized items into fixed-size transport blocks without losing bytes, without overwriting a block that has not yet been taken, and without the output depending combinationally on the consumer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Generic fixed-size block assembler.
// NOT a PCIe FLIT builder — no field layout, no CRC, no FEC, no protocol.
module block_assembler #(
  parameter int BLOCK_BYTES = 64,   // teaching value, not a PCIe FLIT size
  parameter int BEAT_BYTES  = 8
) (
  input  logic                          clk,
  input  logic                          rst_n,
 
  // Ingress. Canonical valid/ready: in_valid asserts because a beat exists.
  input  logic                          in_valid,
  output logic                          in_ready,
  input  logic [BEAT_BYTES*8-1:0]       in_data,
  input  logic [$clog2(BEAT_BYTES):0]   in_bytes,   // 1 .. BEAT_BYTES
  input  logic                          in_flush,   // close block after this beat
 
  // Egress.
  output logic                          out_valid,
  input  logic                          out_ready,
  output logic [BLOCK_BYTES*8-1:0]      out_block,
  output logic [$clog2(BLOCK_BYTES):0]  out_used,
  output logic                          out_partial
);
 
  localparam int BEATS_PER_BLOCK = BLOCK_BYTES / BEAT_BYTES;
  localparam int OCC_W           = $clog2(BLOCK_BYTES) + 1;
 
  // Parameter legality, checked at elaboration.
  initial begin
    if (BLOCK_BYTES % BEAT_BYTES != 0)
      $fatal(1, "BLOCK_BYTES must be a whole number of BEAT_BYTES");
    if (BEATS_PER_BLOCK < 2)
      $fatal(1, "a block spanning fewer than two beats needs no assembler");
  end
 
  logic [BLOCK_BYTES*8-1:0] acc_q;      // accumulating block
  logic [OCC_W-1:0]         occ_q;      // bytes accumulated so far
 
  logic [BLOCK_BYTES*8-1:0] blk_q;      // completed block, held for the sink
  logic [OCC_W-1:0]         blk_used_q;
  logic                     blk_partial_q;
  logic                     blk_valid_q;
 
  // in_ready depends only on whether the output holding slot is free or is
  // being freed this cycle. It does NOT depend on in_valid, so there is no
  // combinational loop through the handshake.
  assign in_ready = !blk_valid_q || out_ready;
 
  wire accept = in_valid && in_ready;
 
  wire [OCC_W-1:0] occ_next = occ_q + OCC_W'(in_bytes);
  wire             close    = accept &&
                              ((occ_next == OCC_W'(BLOCK_BYTES)) || in_flush);
 
  // Merge the accepted beat at its byte offset within the accumulator.
  logic [BLOCK_BYTES*8-1:0] acc_next;
  always_comb begin
    acc_next = acc_q;
    if (accept)
      for (int b = 0; b < BEAT_BYTES; b++)
        if (b < int'(in_bytes))
          acc_next[(occ_q + OCC_W'(b))*8 +: 8] = in_data[b*8 +: 8];
  end
 
  // out_valid is REGISTERED. It reflects "a completed block exists" and never
  // observes out_ready. This is the handshake discipline the whole PCIe track
  // uses: the source asserts valid because it has an item, and holds it.
  assign out_valid   = blk_valid_q;
  assign out_block   = blk_q;
  assign out_used    = blk_used_q;
  assign out_partial = blk_partial_q;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      acc_q         <= '0;
      occ_q         <= '0;
      blk_q         <= '0;
      blk_used_q    <= '0;
      blk_partial_q <= 1'b0;
      blk_valid_q   <= 1'b0;
    end else begin
      // Release the held block first. The close branch below may re-assert
      // blk_valid_q in the same cycle; last assignment wins, which is exactly
      // the back-to-back case (a block is taken and another completes).
      if (blk_valid_q && out_ready)
        blk_valid_q <= 1'b0;
 
      if (accept) begin
        if (close) begin
          blk_q         <= acc_next;
          blk_used_q    <= occ_next;
          blk_partial_q <= (occ_next != OCC_W'(BLOCK_BYTES));
          blk_valid_q   <= 1'b1;
          acc_q         <= '0;      // start the next block empty
          occ_q         <= '0;
        end else begin
          acc_q <= acc_next;
          occ_q <= occ_next;
        end
      end
    end
  end
 
endmodule

Classification: synthesizable.

What it teaches: fixed-block packing with byte conservation; a registered output that cannot overwrite an untaken block; and the handshake discipline applied to a producer whose output rate differs from its input rate.

Why no overwrite is possible: a block is written only on close, which requires accept, which requires in_ready. When a completed block is held and the sink is not ready, in_ready is low, so no beat is accepted and no close occurs. blk_q is therefore stable across any stall.

Deliberately simplified: a beat is never split across two blocks — the elaboration check requires BEAT_BYTES to divide BLOCK_BYTES, and a short beat must be accompanied by in_flush. Reset discards a partially accumulated block. There is no skid buffer, so ingress stalls whenever a completed block is waiting, even for a beat that would not have completed one.

Production implication: a real design would add a skid stage to recover that throughput, define whether a partial block is padded or aborted on reset, handle beats that straddle a block boundary, and — for an actual FLIT builder — place fields at specified offsets and compute CRC and FEC over them.

11. RTL — Protection Telemetry

Chapter 5.5 established that health counts need their denominator. Gen6 adds a distinction that matters more than the count itself: corrected and uncorrectable are different events with different meanings.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Illustrative observability, not required by PCIe.
// The event inputs are ABSTRACTIONS supplied by surrounding logic; they are
// not PCIe signal names and carry no protocol semantics.
module protection_telemetry #(
  parameter int CNT_W = 32
) (
  input  logic             clk,
  input  logic             rst_n,
 
  input  logic             epoch_start,      // begin a measurement window
  input  logic             running,
 
  input  logic             block_observed,   // one transport block seen
  input  logic             corrected_event,  // error corrected without replay
  input  logic             uncorrectable_event,
  input  logic             recovery_event,   // link recovery activity observed
 
  output logic             snap_valid,
  output logic [CNT_W-1:0] snap_blocks,      // the DENOMINATOR
  output logic [CNT_W-1:0] snap_corrected,
  output logic [CNT_W-1:0] snap_uncorrectable,
  output logic [CNT_W-1:0] snap_recovery,
  output logic             snap_saturated,
  output logic             degraded_sticky
);
 
  logic [CNT_W-1:0] c_blocks, c_corr, c_unc, c_rec;
  logic             sat_q, deg_q;
 
  // Saturating increment: a wrapped counter reports a small number after a
  // large one, which reads as "healthy" and is the worse failure mode.
  function automatic logic [CNT_W-1:0] sat_inc(input logic [CNT_W-1:0] v,
                                               input logic             en);
    sat_inc = (en && v != {CNT_W{1'b1}}) ? v + 1'b1 : v;
  endfunction
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_blocks <= '0; c_corr <= '0; c_unc <= '0; c_rec <= '0;
      sat_q    <= 1'b0;
      deg_q    <= 1'b0;
      snap_valid <= 1'b0;
      snap_blocks <= '0; snap_corrected <= '0;
      snap_uncorrectable <= '0; snap_recovery <= '0; snap_saturated <= 1'b0;
    end else if (epoch_start) begin
      // ATOMIC SNAPSHOT: every field is captured from the same instant, so
      // the numerators and the denominator describe one epoch. Publishing
      // them at different instants yields a ratio describing no real epoch.
      snap_valid         <= 1'b1;
      snap_blocks        <= c_blocks;
      snap_corrected     <= c_corr;
      snap_uncorrectable <= c_unc;
      snap_recovery      <= c_rec;
      snap_saturated     <= sat_q;
 
      c_blocks <= '0; c_corr <= '0; c_unc <= '0; c_rec <= '0;
      sat_q    <= 1'b0;
      // deg_q is NOT cleared here. See below.
    end else if (running) begin
      c_blocks <= sat_inc(c_blocks, block_observed);
      c_corr   <= sat_inc(c_corr,   corrected_event);
      c_unc    <= sat_inc(c_unc,    uncorrectable_event);
      c_rec    <= sat_inc(c_rec,    recovery_event);
 
      if ((block_observed      && c_blocks == {CNT_W{1'b1}}) ||
          (corrected_event     && c_corr   == {CNT_W{1'b1}}) ||
          (uncorrectable_event && c_unc    == {CNT_W{1'b1}}) ||
          (recovery_event      && c_rec    == {CNT_W{1'b1}}))
        sat_q <= 1'b1;
 
      // Sticky degradation survives epoch boundaries. An uncorrectable event
      // in one window must not be erased by the next window starting clean.
      if (uncorrectable_event)
        deg_q <= 1'b1;
    end
  end
 
  assign degraded_sticky = deg_q;
 
endmodule

Classification: synthesizable.

What it teaches: that corrected and uncorrectable events must be counted separately, against a shared denominator, in an atomically published snapshot — and that a degradation indication must outlive the window in which it occurred.

Deliberately simplified: the event inputs are abstractions with no protocol semantics; there is no per-lane attribution; and there is no representation of why an event occurred.

Production implication: real telemetry would attribute events to lanes, distinguish directions, timestamp them to expose burstiness, and expose the operating mode alongside the counts so comparisons across modes remain valid.

12. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the illustrative modules above. Implementation invariants for
// THIS design — not PCIe protocol requirements.
 
// SAFETY — P1: occupancy never exceeds the block. A design that could
// overshoot would corrupt the next block's leading bytes silently.
property p_occupancy_bound;
  @(posedge clk) disable iff (!rst_n)
  occ_q <= OCC_W'(BLOCK_BYTES);
endproperty
a_occ_bound : assert property (p_occupancy_bound);
 
// SAFETY — P2: a held block is never overwritten. If a completed block is
// waiting and the sink is not ready, no beat may be accepted.
property p_no_overwrite;
  @(posedge clk) disable iff (!rst_n)
  (blk_valid_q && !out_ready) |-> !accept;
endproperty
a_no_overwrite : assert property (p_no_overwrite);
 
// STABILITY — P3: the presented block and its metadata are stable while the
// sink is stalling. This is the handshake contract on the egress side.
property p_block_stable_under_stall;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_ready) |=> (out_valid && $stable(out_block)
                                 && $stable(out_used) && $stable(out_partial));
endproperty
a_block_stable : assert property (p_block_stable_under_stall);
 
// CONSERVATION — P4: a block is only ever produced by a close, and a close
// requires an accepted beat. No block may appear from nothing.
property p_block_needs_accept;
  @(posedge clk) disable iff (!rst_n)
  (!blk_valid_q ##1 blk_valid_q) |-> $past(accept);
endproperty
a_block_needs_accept : assert property (p_block_needs_accept);
 
// CONSERVATION — P5: a full block reports exactly BLOCK_BYTES used and is
// not flagged partial. Catches used/partial metadata drifting from reality.
property p_full_block_metadata;
  @(posedge clk) disable iff (!rst_n)
  (out_valid && !out_partial) |-> (out_used == OCC_W'(BLOCK_BYTES));
endproperty
a_full_block_meta : assert property (p_full_block_metadata);
 
// CONSERVATION — P6: corrected and uncorrectable counts are monotonic within
// an epoch. They record occurrences and cannot decrease before the window ends.
property p_counts_monotonic_in_epoch;
  @(posedge clk) disable iff (!rst_n)
  (running && !epoch_start) |=> (c_corr >= $past(c_corr))
                             && (c_unc  >= $past(c_unc))
                             && (c_blocks >= $past(c_blocks));
endproperty
a_counts_monotonic : assert property (p_counts_monotonic_in_epoch);
 
// SAFETY — P7: sticky degradation survives an epoch boundary. Only reset
// clears it. Without this, a clean window erases a real prior failure.
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 — P8: the snapshot is coherent. Numerators and denominator must
// describe one epoch, or every normalised figure derived from them is fiction.
property p_snapshot_coherent;
  @(posedge clk) disable iff (!rst_n)
  (snap_valid && !epoch_start) |=> ($stable(snap_blocks) && $stable(snap_corrected)
                                    && $stable(snap_uncorrectable));
endproperty
a_snapshot_coherent : assert property (p_snapshot_coherent);

P2 is the one that catches the classic assembler bug: a design that completes a new block while the previous one is still waiting, silently destroying it. The symptom is missing data with no error anywhere.

P4 catches a spurious blk_valid_q — a block appearing without input, which under FLIT-like framing would inject content the transmitter never sent.

P8 is the normalisation invariant. If the corrected count and the block count can be published from different instants, "corrected errors per million blocks" describes no window that ever existed, and the figure will look entirely plausible.

13. Verification

Monitors observe: ingress beats with byte counts and flush markers; egress blocks with used/partial metadata; protection events by category; block observations; epoch boundaries; and the published snapshot.

The scoreboard independently predicts: the exact byte sequence expected in each assembled block, reconstructed from the ingress stream by its own accounting; the number of blocks a given ingress sequence must produce; and expected normalised rates from injected event rates. As established since Chapter 5.1, it must not reuse the design's arithmetic — a scoreboard that calls the design's occupancy logic verifies nothing.

Digital block-assembly scenarios:

  • Exact-fit block. Ingress totalling exactly BLOCK_BYTES with no flush. Expect one full block, out_partial low.
  • Partial block via flush. Flush before the block fills. Expect one block with out_used < BLOCK_BYTES and out_partial high.
  • Back-to-back blocks. Continuous ingress with out_ready held high. Expect a block every BEATS_PER_BLOCK accepted beats with no gap and no loss.
  • Sink backpressure. Drop out_ready while a block is held. Verify ingress stalls (P2), the block is stable (P3), and nothing is lost when flow resumes.
  • Completion into a stalled slot. The most valuable case: arrange for a beat that would close a block to arrive exactly while a block is held and out_ready is low. The design must refuse the beat rather than overwrite.
  • Reset mid-block. Reset with a partially accumulated block. Verify the partial block is discarded cleanly and the next block starts at offset zero — a design that retains stale occupancy corrupts the first block after reset.

Protection-abstraction scenarios:

  • No events. Baseline: counts stay zero, degraded_sticky low.
  • Corrected only. Verify corrected increments, uncorrectable does not, and degraded_sticky stays low — the design must not treat correction as failure.
  • Uncorrectable. Verify degraded_sticky sets and survives the next epoch_start (P7).
  • Bursts. All events in a short interval. Verify counting is correct, and observe that the aggregate figure conceals the burstiness — the same limitation Chapter 5.5 identified, unchanged by Gen6.
  • Counter saturation. Drive to maximum; verify saturation and the flag rather than a wrap.

Coverage should include: every in_bytes value; flush at each byte offset within a block; the completion-into-stall case at each offset; out_ready patterns including single-cycle assertion; reset at each occupancy value; and the cross of event category against epoch phase.

14. Debugging

Reference scenario: a Gen6 Link enumerates and operates, but sustained high-load traffic shows errors or throughput below expectation.

1. Confirm the operating mode. Verify the Link is actually at 64 GT/s and in FLIT mode rather than having fallen back. A link operating below its intended rate explains a throughput shortfall completely and makes every other measurement misleading.

2. Confirm the workload saturates the path. From the utilisation reasoning in Chapter 5.1: if the Link is substantially idle, the Link is not the limit and no amount of PHY investigation will help.

3. Inspect the rest of the path. A narrower or slower segment upstream, or a device that cannot source or sink at rate, caps throughput regardless of what this Link can do.

4. Read corrected and uncorrectable counts separately, normalised. This is the Gen6-specific step. A high corrected count with zero uncorrectable is a working link with a margin signal. A rising uncorrectable count is a failing one. Reading them summed loses exactly the information you need.

5. Inspect recovery and replay activity. Replay consumes capacity. If replay activity is significant, throughput loss has a mechanism — and that mechanism points at protection, not at the transaction layer.

6. Compare the same path and workload at Gen5. The controlled experiment. Same devices, same traffic, one variable changed.

7. Determine whether the symptom follows the mode. If errors are absent at Gen5 and present at Gen6 with traffic volume normalised, the symptom tracks the signalling and framing change.

8. Separate logical handling from physical impairment. Errors that appear regardless of rate, or that correlate with specific transaction types rather than with volume, point at logic. Errors that scale with rate and load point at the physical layer.

9. Move to channel and PHY characterisation. If severity tracks Gen6 operation, the remaining work is measurement outside simulation.

15. Common Misconceptions

  • "Gen6 is Gen5 doubled." Gen5→Gen6 changed the signalling scheme, removed the line encoding, and replaced the transport framing. Gen4→Gen5 changed one variable. Treating them as the same kind of step is the error this chapter exists to correct.
  • "PAM4 means four bits per transfer." Four levels, not four bits. Four distinguishable states carry log₂(4) = 2 bits. The relationship is logarithmic — sixteen levels would be needed for four bits.
  • "PAM4 sends two bits simultaneously on the same wire." There is one signal and one decision per symbol interval; that decision has four possible outcomes, and enumerating four outcomes takes two bits. Nothing is sent in parallel.
  • "PAM4 doubles the bandwidth by doubling the speed." The symbol rate is unchanged from Gen5 at 32 GBd, and so is the Nyquist frequency. What doubled is bits carried per symbol. Describing it as "faster signalling" inverts the actual mechanism.
  • "FLIT replaces TLP." TLPs still carry transaction semantics. FLIT mode changes how content is packaged and protected for transport. Different layers, different questions.
  • "FEC means transmission errors no longer matter." FEC corrects certain errors within its capability. Errors beyond it are detected by CRC and recovered by replay — which costs capacity. Errors still matter; they are handled differently.
  • "Corrected errors are harmless and can be ignored." Each one is evidence that the physical layer needed help, and correction capacity is finite. A corrected-error rate rising with load is a margin signal available before anything breaks.
  • "Enumeration at Gen6 proves the link is sound." Discovery involves comparatively little traffic under undemanding conditions. Enumeration proves reachability; it does not prove margin. Chapter 5.5 made this point at Gen5 and PAM4 makes it sharper.
  • "RTL simulation can validate PAM4 behaviour." It cannot represent amplitude levels, thresholds, or channel physics. It validates the digital response to abstracted events, which is a different and much narrower claim.
  • "Gen6 throughput is 64 GT/s × 128/130 ÷ 8." There is no 128b/130b at 64 GT/s. Applying the Gen3–Gen5 formula produces a number that is both wrong and confidently precise.

16. Understanding Check

17. Module 5 in One Page

Gen1 (2.5 GT/s). 8b/10b, 80% efficient, 250 MB/s per lane per direction. The baseline and the vocabulary: GT/s is not GB/s.

Gen2 (5 GT/s). Same encoding, doubled rate, 500 MB/s. The clean controlled experiment — and the chapter where doubled capacity conspicuously failed to double application throughput.

Gen3 (8 GT/s). 128b/130b at ≈98.4615%. Capacity grew 1.969× on a 1.600× rate increase, because rate and efficiency multiply. The only encoding transition in the first five generations.

Gen4 (16 GT/s). Same encoding, capacity doubles exactly. The unit interval halves to 62.5 ps and signal integrity stops being someone else's problem.

Gen5 (32 GT/s). Same again, 31.25 ps, and margin becomes a system-level constraint shaping topology and component selection. Raw error counts require normalisation before they mean anything.

Gen6 (64 GT/s). PAM4 carrying two bits per symbol at an unchanged 32 GBd symbol rate; line encoding removed entirely; FLIT-based transport carrying its own CRC and FEC. Three dimensions at once.

The through-line is not a speed table. It is a sequence of engineering responses to successive constraints: when rate scaling was cheap, PCIe scaled the rate. When encoding overhead became the larger waste, it changed the encoding. When margin became the limit, the difficulty moved into the channel. When frequency content became the wall, it changed what a symbol means — and rebuilt the transport around the reliability that implied.

18. What's Next

Every capacity figure in Module 5 has carried the same qualifier: per lane, per direction. That qualifier has been doing quiet work for six chapters, and it is now time to remove it.

Chapter 6.1 — x1 Links opens Module 6 by defining precisely what a lane is, how a single-lane Link carries traffic in both directions, and — the distinction Module 2 deliberately protected — why a lane is not a Link.

Module 6 then builds outward: Chapter 6.2 introduces the coordination problem that appears the moment one Link has more than one lane, and later chapters take that through wider links, aggregation mechanics, throughput arithmetic across rate and width together, and the system trade-offs that decide how wide a link should actually be.