Skip to content

PCIe · Module 5

PCIe Gen3 — Why the Encoding Changed

Gen3 raised the signalling rate to 8 GT/s and replaced 8b/10b with 128b/130b. Deriving why capacity grew by 1.97× when the rate only grew by 1.6×, what the encoding change cost in PHY complexity, and how to measure whether a workload used the headroom.

Chapter 5.2 was a clean controlled experiment: Gen2 doubled the signalling rate and changed nothing else, so capacity doubled exactly. Gen3 is not that.

Why did Gen3 not simply double Gen2's signalling rate, and why did changing the encoding become architecturally important?

Two things moved at once, which makes the arithmetic more interesting and the engineering considerably harder.

1. The Pressure Gen2 Left Behind

Recall where Chapter 5.2 ended. Gen2's derivation:

5 GT/s × 8/10 = 4.0 Gb/s per lane, per direction.

The 8b/10b encoding costs 20% of everything transmitted, and that cost is a fraction, not a fixed quantity. So it scales with the rate: at 2.5 GT/s it consumes 0.5 GT/s worth of signalling; at 5 GT/s it consumes 1.0 GT/s worth.

Extend that mentally. Had PCIe kept 8b/10b and simply doubled again to 10 GT/s, the encoding would have consumed the equivalent of 2 GT/s — and the cost keeps growing in absolute terms with every generation, because the percentage never moves.

2. 128b/130b Efficiency

Gen3's encoding groups 128 bits of data into a 130-bit transmitted block.

The efficiency follows directly:

128 / 130 = 0.984615384615...

As a percentage: ≈ 98.4615%.

Compare with 8b/10b's 80%. Encoding overhead falls from 20% to approximately 1.54% — a reduction of roughly thirteen-fold in the fraction of the wire spent on encoding.

That is the headline, and it is worth pausing on how large it is. At Gen2, one byte in five of transmitted capacity went to encoding. At Gen3, it is closer to one bit in sixty-five.

3. The Derivation

The Gen3 rate derivation compared with Gen2: Gen2 starts at 5 giga-transfers per second with 8 over 10 encoding giving 4.0 gigabits per second and 500 megabytes per second; Gen3 starts at 8 giga-transfers per second with 128 over 130 encoding giving approximately 7.877 gigabits per second and approximately 984.6 megabytes per second.1Gen2 5 GT/s · Gen3 8 GT/ssignalling rate — first input that changed2Gen2 ×8/10 · Gen3 ×128/130encoding — second input that changed34.0 Gb/s · ≈7.877 Gb/sencoding-adjusted data bit rate4500 MB/s · ≈984.6 MB/stheoretical, per lane, per direction
Figure 1 — the Gen3 derivation compared with Gen2. Two inputs changed, not one: the signalling rate rose from 5 to 8 GT/s, and the encoding efficiency rose from 80% to about 98.46%. Both contribute to the capacity increase, which is why the result exceeds what the rate change alone would produce. All figures are per lane, per direction, theoretical, before higher-layer overhead.

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

Step 2 — apply encoding. 8 GT/s × (128/130) = 7.876923076923... Gb/s Rounded for use: ≈ 7.8769 Gb/s.

Step 3 — convert to bytes. 7.876923... Gb/s ÷ 8 bits/byte = 0.984615384615... GB/s

Step 4 — express conventionally. ≈ 984.615 MB/s, decimal units.

4. Why Capacity Grew More Than the Rate Did

Here is the quantitative payoff, and it is the thing most Gen3 explanations skip.

The signalling rate went from 5 to 8 GT/s — a ratio of 1.6×. It would be natural to expect capacity to grow by 1.6×.

It does not:

(8 GT/s × 128/130) ÷ (5 GT/s × 8/10) = 7.876923... Gb/s ÷ 4.0 Gb/s = 1.969230769...

≈ 1.969× — very nearly a doubling.

Gen2Gen3Ratio
Signalling rate5 GT/s8 GT/s1.600×
Encoding efficiency0.8≈ 0.9846151.2308×
Encoding-adjusted bit rate4.0 Gb/s≈ 7.8769 Gb/s1.969×
Encoding-adjusted byte rate500 MB/s≈ 984.6 MB/s1.969×

The capacity ratio is the product of the two:

1.600 × 1.23077 = 1.96923

That decomposition is the chapter's central arithmetic. Gen3 delivered close to a doubling of capacity while raising the signalling rate by only 60%, because the encoding change supplied a further ≈23% multiplicatively on top of it — the two factors multiply, they do not add. The physical layer was asked to do substantially less than a doubling of rate, and the system still got substantially a doubling of capacity.

Gen2→Gen3 is not a rate story. It is a rate story multiplied by an efficiency story.

5. What the Encoding Change Actually Involves

The ratio is the easy part. Being honest about what it costs matters more.

Moving from a scheme that maps small groups to a scheme that operates on much larger blocks changes what the physical layer has to do. Regardless of the specific mechanisms, a receiver still needs to solve problems that do not disappear:

  • Block alignment — finding where each transmitted block begins in an arriving bit stream.
  • Distinguishing data from control — some transmitted blocks carry data and others carry control information, and the receiver must tell them apart.
  • Maintaining suitable signal characteristics — a serial stream with no accompanying clock needs sufficient transition density and spectral properties for timing to be recovered, which 8b/10b provided partly as a by-product of its mapping. With far less encoding overhead available, that property has to be obtained another way.

The point is not the specific answers. It is that a more efficient encoding does not remove those requirements; it changes how they are met, and generally makes the implementation more sophisticated.

6. Computing Rates When the Ratio Is Not Round

The rate package from Chapter 5.1 needs revisiting, because 128/130 exposes a precision problem that 8/10 hid.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Compile-time only (elaboration constants and pure functions).
// Extends the Gen1/Gen2 package to handle ratios that do not divide evenly.
//
// PRECISION NOTE: with 8b/10b every result was an exact integer in MB/s.
// 128/130 is not. Computing in MB/s would give floor(984.615) = 984, losing
// 0.6 MB/s per lane — small alone, but it compounds across comparisons and
// makes ratios wrong in the third digit. Working in KILOBYTES/s keeps three
// more digits of the fraction, which is sufficient for teaching comparisons.
package pcie_gen3_rate_pkg;
 
  // Rates in MEGA-transfers/s so every generation is an exact integer input.
  localparam longint unsigned GEN1_MTPS = 2500;
  localparam longint unsigned GEN2_MTPS = 5000;
  localparam longint unsigned GEN3_MTPS = 8000;
 
  // Encoding ratios as explicit numerator/denominator pairs.
  localparam int unsigned ENC_8B10B_NUM    = 8;
  localparam int unsigned ENC_8B10B_DEN    = 10;
  localparam int unsigned ENC_128B130B_NUM = 128;
  localparam int unsigned ENC_128B130B_DEN = 130;
 
  // Encoding-adjusted bit rate in KILOBITS/s, per lane, per direction.
  // Multiplication precedes division so truncation cannot discard
  // significance; the ×1000 scaling happens before the divide for the same
  // reason. Result is floored — see the note above.
  function automatic longint unsigned encoded_kbits_per_s(
    input longint unsigned mtps,
    input int unsigned     enc_num,
    input int unsigned     enc_den
  );
    if (enc_den == 0 || enc_num > enc_den) return 0;   // illegal configuration
    return (mtps * 1000 * enc_num) / enc_den;
  endfunction
 
  // Encoding-adjusted byte rate in KILOBYTES/s, per lane, per direction.
  function automatic longint unsigned encoded_kbytes_per_s(
    input longint unsigned mtps,
    input int unsigned     enc_num,
    input int unsigned     enc_den
  );
    return encoded_kbits_per_s(mtps, enc_num, enc_den) / 8;
  endfunction
 
  // Per lane, per direction, theoretical, encoding-adjusted.
  //   Gen2: 4_000_000 kb/s ->   500_000 kB/s  =  500.000 MB/s  (exact)
  //   Gen3: 7_876_923 kb/s ->   984_615 kB/s  ≈  984.615 MB/s  (floored)
  localparam longint unsigned GEN2_KBPS  =
      encoded_kbits_per_s(GEN2_MTPS, ENC_8B10B_NUM, ENC_8B10B_DEN);
  localparam longint unsigned GEN3_KBPS  =
      encoded_kbits_per_s(GEN3_MTPS, ENC_128B130B_NUM, ENC_128B130B_DEN);
  localparam longint unsigned GEN2_KBYTES =
      encoded_kbytes_per_s(GEN2_MTPS, ENC_8B10B_NUM, ENC_8B10B_DEN);
  localparam longint unsigned GEN3_KBYTES =
      encoded_kbytes_per_s(GEN3_MTPS, ENC_128B130B_NUM, ENC_128B130B_DEN);
 
  // Capacity ratio in thousandths — 1969 means 1.969x. Computed from the
  // kB/s figures so the fractional part survives into the ratio.
  localparam longint unsigned GEN3_OVER_GEN2_X1000 =
      (GEN3_KBYTES * 1000) / GEN2_KBYTES;   // 1969
 
endpackage

Classification: compile-time only.

What it teaches: that unit scaling is a precision decision, not a cosmetic one. The same arithmetic in MB/s gives a ratio of 984/500 = 1.968; in kB/s it gives 1.969. The second is right, and the difference came entirely from where the truncation landed.

Deliberately simplified: no lane count — Link width is Module 6's subject and including it here would invite the width tables this chapter avoids. No protocol overhead, which is workload-dependent rather than constant. Results are floored rather than rounded, which biases slightly low by a bounded and stated amount.

What to notice: the guard if (enc_den == 0 || enc_num > enc_den) return 0 rejects a configuration claiming efficiency above 100%. Silent nonsense from a bad parameter is worse than a zero, because a zero is obviously wrong.

Production implication: a real design that needs these figures at runtime would either precompute them or store them, not divide at runtime — division is expensive in hardware and the operands are known at elaboration.

7. Selecting an Encoding Mode

A controller supporting several generations needs the ratio to be selectable. That is a small piece of logic with one real correctness concern.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative internal configuration — NOT a PCIe configuration encoding.
// PCIe defines its own normative means of establishing and reporting the
// operating rate; this is how a design might organise the arithmetic internally.
typedef enum logic {
  ENC_8B10B    = 1'b0,   // Gen1, Gen2
  ENC_128B130B = 1'b1    // Gen3, Gen4, Gen5 — NOT Gen6, which is unencoded
} encoding_mode_e;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative synthesizable RTL — encoding ratio selection.
// NOT a PCIe controller. The ratio must be stable while any measurement or
// calculation depends on it, which is the only subtle requirement here.
module encoding_select (
  input  logic           clk,
  input  logic           rst_n,
 
  input  logic           mode_valid,      // a new mode is being programmed
  input  encoding_mode_e mode_in,
  input  logic           epoch_active,    // a measurement epoch is in progress
 
  output encoding_mode_e mode,
  output logic [7:0]     enc_num,
  output logic [7:0]     enc_den,
  output logic           mode_locked
);
 
  encoding_mode_e mode_q;
  logic           locked_q;
 
  assign mode        = mode_q;
  assign mode_locked = locked_q;
 
  // Ratio is derived from mode rather than programmed separately, so an
  // inconsistent pair (say 8b/10b mode with a 128/130 ratio) cannot be
  // expressed at all.
  always_comb begin
    unique case (mode_q)
      ENC_8B10B:    begin enc_num = 8'd8;   enc_den = 8'd10;  end
      ENC_128B130B: begin enc_num = 8'd128; enc_den = 8'd130; end
    endcase
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      mode_q   <= ENC_8B10B;   // defined power-on default
      locked_q <= 1'b0;
    end else begin
      // The mode may only change while no measurement depends on it. A change
      // mid-epoch would make the epoch's derived figures meaningless without
      // anything reporting an error.
      if (mode_valid && !epoch_active) begin
        mode_q <= mode_in;
      end
      locked_q <= epoch_active;
    end
  end
endmodule

Classification: synthesizable.

What it teaches: deriving the ratio from the mode rather than programming them independently, so an inconsistent combination is unrepresentable — the same principle Chapter 4.1 applied to role and capability.

Deliberately simplified: two modes only; no representation of how the operating rate is actually established, which is Module 17's subject; no reporting of the mode to software.

What to notice: the mode cannot change while epoch_active. Without that guard, a rate change part-way through a measurement window produces derived figures computed against two different assumptions, and nothing signals a problem — the numbers simply come out wrong in a plausible-looking way.

Production implication: a real controller would tie the mode to the actual operating rate rather than accepting it as an independent input, and would expose it through the normative status mechanism.

8. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the illustrative encoding selection. Implementation invariants for
// THIS design — not PCIe protocol requirements.
 
// LEGALITY — P1: the ratio never claims efficiency above 100%.
property p_ratio_legal;
  @(posedge clk) disable iff (!rst_n)
  (enc_den != 0) && (enc_num <= enc_den);
endproperty
a_ratio_legal : assert property (p_ratio_legal);
 
// LEGALITY — P2: the ratio always corresponds to the selected mode. Catches
// a decode that drifts out of step with the mode register.
property p_ratio_matches_mode;
  @(posedge clk) disable iff (!rst_n)
  (mode == ENC_8B10B)    -> (enc_num == 8   && enc_den == 10)  and
  (mode == ENC_128B130B) -> (enc_num == 128 && enc_den == 130);
endproperty
a_ratio_matches : assert property (p_ratio_matches_mode);
 
// STABILITY — P3: the mode does not change while a measurement epoch is
// active. A mid-epoch change silently invalidates every derived figure.
property p_mode_stable_in_epoch;
  @(posedge clk) disable iff (!rst_n)
  epoch_active |=> $stable(mode);
endproperty
a_mode_stable : assert property (p_mode_stable_in_epoch);

P1 catches a parameter or decode error that would make every downstream calculation nonsense. Simulation may not flag it because the arithmetic still produces a number.

P2 catches the mode and ratio drifting apart — the specific failure that motivated deriving one from the other. It is the assertion that proves the design decision was actually implemented.

P3 is the one worth dwelling on. A mode change mid-epoch does not crash anything; it produces a measurement computed against two different encoding assumptions, which looks entirely plausible and is wrong. This is the class of bug assertions exist for, because inspection will not find it.

9. Did the Workload Use the Headroom?

Gen3 provides roughly 1.97× Gen2's capacity. Whether an application sees that depends on whether the Link was the constraint — the governing question from Chapter 5.2, which applies here unchanged.

The utilisation instrumentation from 5.1 and 5.2 answers it directly, and one addition is worth making for Gen3: distinguishing payload bytes from other traffic, where the observed interface exposes the difference.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Illustrative synthesizable RTL — capacity-utilisation instrumentation.
// NOT a PCIe controller. Separates payload bytes from other transferred bytes
// so "the Link is busy" and "the Link is carrying useful data" are distinct.
module capacity_monitor #(
  parameter int unsigned CNT_W = 48
) (
  input  logic             clk,
  input  logic             rst_n,
 
  // Observed interface — watched only.
  input  logic             obs_valid,
  input  logic             obs_ready,
  input  logic [15:0]      obs_bytes,
  input  logic             obs_is_payload,  // abstraction of the observation point
 
  input  logic             epoch_start,
  input  logic             epoch_stop,
 
  output logic [CNT_W-1:0] snap_cycles,
  output logic [CNT_W-1:0] snap_transfer,
  output logic [CNT_W-1:0] snap_stalled,
  output logic [CNT_W-1:0] snap_idle,
  output logic [CNT_W-1:0] snap_payload_bytes,
  output logic [CNT_W-1:0] snap_other_bytes,
  output logic             snap_valid
);
 
  logic [CNT_W-1:0] c_cycles, c_transfer, c_stalled, c_idle, c_pay, c_oth;
  logic             running_q;
 
  wire transfer = obs_valid &&  obs_ready;
  wire stalled  = obs_valid && !obs_ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      running_q  <= 1'b0;
      snap_valid <= 1'b0;
      c_cycles   <= '0; c_transfer <= '0; c_stalled <= '0;
      c_idle     <= '0; c_pay      <= '0; c_oth     <= '0;
    end else if (epoch_start) begin
      running_q  <= 1'b1;
      snap_valid <= 1'b0;
      c_cycles   <= '0; c_transfer <= '0; c_stalled <= '0;
      c_idle     <= '0; c_pay      <= '0; c_oth     <= '0;
    end else if (running_q && epoch_stop) begin
      // Freeze the whole set in one cycle so software reads a coherent epoch.
      running_q          <= 1'b0;
      snap_cycles        <= c_cycles;
      snap_transfer      <= c_transfer;
      snap_stalled       <= c_stalled;
      snap_idle          <= c_idle;
      snap_payload_bytes <= c_pay;
      snap_other_bytes   <= c_oth;
      snap_valid         <= 1'b1;
    end else if (running_q) begin
      c_cycles <= c_cycles + 1'b1;
 
      if (transfer)     c_transfer <= c_transfer + 1'b1;
      else if (stalled) c_stalled  <= c_stalled  + 1'b1;
      else              c_idle     <= c_idle     + 1'b1;
 
      if (transfer) begin
        if (obs_is_payload) c_pay <= c_pay + obs_bytes;
        else                c_oth <= c_oth + obs_bytes;
      end
    end
  end
endmodule

Classification: synthesizable.

What it teaches: that a highly efficient encoding does not make a Link efficient. Encoding overhead fell from 20% to ~1.5%, but packet and control traffic did not change, and a busy Link carrying mostly non-payload bytes is a different problem from an idle one.

Deliberately simplified: one direction; a single epoch rather than rolling statistics; obs_is_payload assumed available at the observation point.

What to notice: payload and other bytes are counted separately rather than as a total plus a fraction. Separate counters let you compute the split after the fact at whatever granularity you want, and they make it impossible to lose one component to rounding.

10. Verification

What the monitor observes: the handshake, bytes per accepted beat, the payload/other split, and epoch boundaries.

What the scoreboard computes independently: expected capacity from first principles — its own copy of rate × encoding ÷ 8 — and expected payload throughput from the stimulus it generated. As Chapter 5.1 argued, the scoreboard must not call the design's rate function, because a shared implementation makes its own arithmetic bugs invisible, and arithmetic bugs produce plausible numbers rather than crashes.

Scenarios:

  • Same workload at Gen2 and Gen3 rate models. Measure Gen2 utilisation first. If Gen2 was saturated, expect substantial headroom at Gen3; if it was not, expect little change and treat that as the correct result.
  • Low-demand workload. Similar application throughput at both, high idle at both. This is the case most often misreported as a failed upgrade.
  • Small transactions. Encoding efficiency improved dramatically; packet overhead did not. Expect efficiency to remain poor and the payload/other split to show it — this is the scenario the split counters exist for.
  • Sustained large transfers. Closest to the ceiling, and still below it because packet and link-level traffic remain.
  • Rate-model consistency. Verify the design's computed capacity against the scoreboard's independent figure for both generations, including the ratio.

Corner cases: mode changed while an epoch is active (should be prevented); illegal encoding configuration; epoch boundaries during each cycle class; payload-only and other-only epochs.

Coverage: each cycle class dominant; both encoding modes exercised; payload fraction across its range; ratio verified for both generations.

11. Debugging: Expected a Large Gain, Got a Small One

Ordered by how often each is the answer.

1. Was Gen2 actually saturated? If not, the Link was not the constraint and raising it changes nothing. Check idle and stall from the previous configuration before anything else.

2. Is the operating rate actually Gen3? Verify rather than assume — in a model, that the correct parameters are in use; in a system, that the Link is operating at the expected rate.

3. Is the source generating enough demand? High idle means the Link is waiting. A faster Link does not make a device produce data faster.

4. Are transactions too small? Check the payload/other split. Encoding improved; packet overhead did not. A small-transaction workload will show a poor payload fraction regardless of generation.

5. Is the topology the limit? If the device sits behind a Switch, its own Link is one hop (Chapter 4.2). A faster first hop does not help if a shared upstream segment is binding.

6. Is memory or the host side the limit? Everything converges there (Chapter 4.1).

7. Is backpressure high? Check stall. Something downstream is the constraint.

8. Is the measurement counting the right bytes? Payload versus total makes a large difference at small transaction sizes, and comparing a payload figure against a total-byte figure produces an apparent shortfall that is purely an accounting error.

12. Common Misconceptions

13. Understanding Check

14. Summary

Gen3 changed two things at once: the signalling rate rose to 8 GT/s, and the encoding moved from 8b/10b to 128b/130b, raising efficiency from 80% to ≈98.4615%.

The derivation:

8 GT/s × 128/130 ≈ 7.8769 Gb/s÷ 8 ≈ 0.9846 GB/s≈ 984.6 MB/s

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

The central arithmetic is the decomposition: capacity grew by ≈1.969×, which is the rate ratio (1.600×) multiplied by the efficiency ratio (≈1.2308×). Gen3 delivered close to a doubling while asking the physical layer for only a 60% rate increase — the encoding change bought the rest more cheaply than another rate doubling would have.

The efficiency gain was not free. A far more efficient encoding still has to solve block alignment, distinguishing data from control, and obtaining suitable signal characteristics — generally with more sophisticated implementation, which is why Module 17 exists.

And efficiency at the wire is not efficiency at the application. Encoding overhead fell from 20% to ~1.5%; packet and control overhead did not change, so small-transaction workloads remain inefficient. Measuring the difference requires separating payload bytes from other transferred bytes.

Hold the model: Gen3 is a rate increase multiplied by an efficiency increase, and the second was the cheaper half.

15. What Comes Next

Gen3 established the efficient encoding that Gen4 and Gen5 retain unchanged — and that Gen6 eventually removes altogether, having taken encoding overhead about as far as it could usefully go. Chapter 5.4 — PCIe Gen4 therefore returns to a single-variable change — the rate doubles to 16 GT/s with the encoding unchanged — and takes up the question this chapter deferred: what actually becomes difficult when the signalling rate doubles. That is where the channel stops being background and becomes part of the architecture.

Revisit PCIe Gen2 for the baseline this chapter compares against, or Physical Layer for where encoding sits in the stack. Browse the full path on the PCIe tutorials index.