PCIe · Module 6
x1 Links — One Lane, Two Directions, One Link
A precise definition of a PCIe lane, why a lane is not a Link, how a single-lane x1 Link carries traffic in both directions simultaneously, and why generation and Link width are independent dimensions that must be reasoned about separately.
Every capacity figure in Module 5 carried the same three-word qualifier: per lane, per direction. Six chapters of arithmetic rested on it, and it was never defined.
That was deliberate. Module 5 asked how fast one lane can be driven. Module 6 asks a different question: how many lanes there are, what it takes to make them behave as one connection, and what that costs.
What exactly is one PCIe lane, and how does a single-lane x1 Link carry traffic between two components?
1. What a Lane Is
The imprecise definitions cause real confusion later, so start with the accurate one.
A lane consists of two differential pairs: one pair carrying signals from A to B, and one pair carrying signals from B to A. A differential pair is two conductors carrying a signal as the difference between them, so a lane involves four conductors in total.
Three properties follow, and each matters:
The two pairs are directional. One transmits, one receives. They are not shared or turned around. Each has a dedicated purpose for the lifetime of the connection.
Communication is simultaneous in both directions. Because transmit and receive have separate physical paths, traffic can flow both ways at once. This is why every Module 5 figure said per direction — the capacity applies to each direction independently, not split between them.
Direction is relative to the component. What component A calls its transmit pair, component B calls its receive pair. There is exactly one set of conductors; the naming depends on which end you stand at. This sounds pedantic until you are debugging a link that works one way, at which point it becomes the whole problem.
2. Lane Versus Link
Chapter 2.8 established that PCIe connects components in pairs rather than over a shared medium. This chapter adds the width dimension to that picture, and the two terms must not merge.
| Lane | Link | |
|---|---|---|
| What it is | a unit of physical Link width | the complete connection between two components |
| How many | one or more per Link | one, between a given pair of components |
| What it provides | one transmit path and one receive path | the whole connection, at whatever width it has |
| Notation | counted | described as x1, x2, x4, x8, x16 |
An x1 Link has one lane. An xN Link has N lanes carrying the traffic of one connection.
Note what the second sentence does not say. It does not say N lanes make N connections. All N lanes belong to a single Link, carrying a single logical stream of traffic between a single pair of components. That is the whole subject of Chapter 6.2, and it is the reason the distinction has to be established before width is introduced.
3. The Structure of an x1 Link
Read the figure as two independent flows that happen to be bundled. The top row is entirely A's responsibility to drive and B's to recover; the bottom row is the reverse. Neither waits for the other.
One lane. Two directions. One Link.
4. Why x1 Exists
An x1 Link is the minimum width, and minimum width is a design choice with real justification.
Fewer pins. Each lane requires conductors at both ends. A component that needs only x1 commits far fewer package pins to its Link than one built for a wide Link — and pins are a genuinely scarce, expensive resource.
Less package and board resource. Fewer high-speed pairs to route, fewer length-matching constraints to satisfy, less board area consumed, and fewer layers potentially required.
Less PHY. Each lane needs its own transmitter, receiver, and associated circuitry. One lane means one set, which is silicon area and power not spent.
Smaller connectors and simpler mechanicals. A narrow slot or connector is physically smaller and less demanding.
Lower power. Less high-speed circuitry running is less power consumed, which matters in thermally or budget-constrained designs.
5. Generation and Width Are Independent
This is the most consequential idea in the chapter, and it is routinely collapsed.
Generation determines how fast each lane signals. Width determines how many lanes the Link has. They are set by different mechanisms, constrained by different things, and either can vary while the other is held fixed.
All four combinations are coherent:
- Older generation, narrow — a modest Link at modest signalling.
- Older generation, wide — width compensating for lower per-lane rate.
- Newer generation, narrow — high per-lane rate with minimal resource commitment.
- Newer generation, wide — both dimensions maximised.
Symbolically, and no further than this:
aggregate capacity ∝ (per-lane capacity) × (lane count)
Per-lane capacity is what Module 5 derived from rate and encoding. Lane count is what Module 6 is about. Total capacity depends on both, and knowing one tells you nothing about the other.
6. The Digital Boundary at x1
From the digital designer's side, an x1 Link presents the simplest possible arrangement: one logical stream leaves toward one lane-facing pipeline, and one logical stream arrives from another.
Transmit: local packet stream → lane-facing transmit datapath → PHY → the physical lane. Receive: the physical lane → PHY → lane-facing receive datapath → local packet stream.
What happens inside the PHY is not this chapter's subject. Converting a parallel digital stream into serial form on the lane is Module 17.1; recovering it is 17.2; establishing the operating rate and width is 17.3. This chapter needs only that the boundary exists and that the digital side of it is one lane wide.
That single fact — one — is what makes x1 worth studying before x2. There is no distribution decision, no reassembly, and no relative timing between lanes, because there is only one. Every one of those problems appears in Chapter 6.2, and it is much easier to recognise them as new if you have first seen the case where they are absent.
7. RTL — The Lane-Facing Datapath Boundary
// SYNTHESIZABLE. One logical stream into one lane-facing pipeline.
// NOT PCIe PHY logic: no serialisation, no encoding, no protocol state.
module x1_lane_pipe #(
parameter int DATA_W = 64
) (
input logic clk,
input logic rst_n,
// Abstract availability. Not PCIe signals — stand-ins for "the Link is
// usable" and "this lane is usable", supplied by surrounding logic.
input logic link_ready,
input logic lane_active,
// Ingress from the local packet source.
input logic in_valid,
output logic in_ready,
input logic [DATA_W-1:0] in_data,
input logic in_last,
// Egress toward the lane-facing interface.
output logic out_valid,
input logic out_ready,
output logic [DATA_W-1:0] out_data,
output logic out_last
);
logic [DATA_W-1:0] d_q;
logic last_q;
logic v_q;
wire usable = link_ready && lane_active;
// in_ready reflects whether the holding slot can take an item and whether
// the lane is usable. It does NOT depend on in_valid, so there is no
// combinational path from the source's valid back to its own ready.
assign in_ready = usable && (!v_q || out_ready);
wire accept = in_valid && in_ready;
// out_valid is REGISTERED. It asserts because an item is held, and it never
// observes out_ready. This is the discipline used throughout this track:
// the source asserts valid because it HAS an item, holds valid and payload
// stable until accepted, and the sink decides acceptance with ready.
assign out_valid = v_q;
assign out_data = d_q;
assign out_last = last_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_q <= 1'b0;
d_q <= '0;
last_q <= 1'b0;
end else begin
// Release on acceptance. The accept branch below may refill the slot in
// the same cycle; last assignment wins, giving full-rate back-to-back
// transfer when both sides are ready.
if (v_q && out_ready)
v_q <= 1'b0;
if (accept) begin
d_q <= in_data;
last_q <= in_last;
v_q <= 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: the canonical decoupling stage at a lane boundary — one accepted item produces exactly one presented item, payload is stable while the sink stalls, and availability gates acceptance rather than corrupting an item already in flight.
A deliberate design decision worth noticing: out_valid is not gated by usable. If the lane becomes unavailable while an item is held, this design keeps presenting it rather than withdrawing it. Withdrawing would violate the hold-until-accepted contract and would silently drop an item the source believes was taken. Refusing new work while continuing to present work already accepted is the safer of the two behaviours.
Deliberately simplified: one item of holding, so throughput drops whenever the sink stalls; no width conversion between the logical stream and the lane-facing interface; no defined behaviour for a lane going down mid-packet beyond continuing to hold; and no error path.
Production implication: a real boundary needs a skid buffer to sustain full rate under intermittent backpressure, width and clock-domain conversion, a defined abort or drain policy when a lane becomes unusable mid-packet, and error reporting that distinguishes an item dropped by policy from one lost by defect.
8. Observability at One Lane
A single-lane design still needs to be able to answer "is this Link working, and if not, which part is not."
// CONCEPTUAL — a teaching abstraction, not a PCIe register or encoding.
// Field names and layout are chosen for clarity, not from any specification.
typedef struct packed {
logic link_ready; // the Link as a whole is usable
logic lane_active; // this lane is usable
logic tx_error; // an error observed on the transmit path
logic rx_error; // an error observed on the receive path
} lane_status_t;Classification: conceptual.
What it teaches: that even at the minimum width, status is not a single bit. link_ready and lane_active are different questions, and — the field that earns its place here — transmit and receive errors are tracked separately.
That separation is the entire basis of §11's debugging method. A design that reports one aggregated "link error" bit has discarded the information needed to determine which direction failed, and at x1 the direction is very often the answer.
Deliberately simplified: no counts, no sticky behaviour, no epoch semantics, and no error classification. Chapter 5.5 and Chapter 5.6 developed the counting and snapshot discipline that a real implementation would layer on top of a status view like this.
9. Assertions
// SVA over x1_lane_pipe. Implementation invariants for THIS design — not
// PCIe protocol requirements.
// STABILITY — P1: payload and valid are stable while the sink stalls. This
// is the handshake contract; violating it means the sink can sample a
// different item than the one it was offered.
property p_payload_stable_under_stall;
@(posedge clk) disable iff (!rst_n)
(out_valid && !out_ready) |=> (out_valid && $stable(out_data) && $stable(out_last));
endproperty
a_payload_stable : assert property (p_payload_stable_under_stall);
// CONSERVATION — P2: out_valid may only fall in the cycle after an accepted
// transfer. A valid that drops without acceptance is a silently dropped item.
property p_no_silent_drop;
@(posedge clk) disable iff (!rst_n)
(out_valid ##1 !out_valid) |-> $past(out_ready);
endproperty
a_no_silent_drop : assert property (p_no_silent_drop);
// CONSERVATION — P3: an item is only ever presented because one was accepted.
property p_output_needs_accept;
@(posedge clk) disable iff (!rst_n)
(!v_q ##1 v_q) |-> $past(accept);
endproperty
a_output_needs_accept : assert property (p_output_needs_accept);
// SAFETY — P4: no work is accepted while the Link or the lane is unusable.
// This is the gating contract; accepting here would take ownership of an item
// with no path to deliver it.
property p_no_accept_when_unusable;
@(posedge clk) disable iff (!rst_n)
!usable |-> !accept;
endproperty
a_no_accept_unusable : assert property (p_no_accept_when_unusable);
// SAFETY — P5: the held payload is never modified while the item waits. P1
// covers the output view; this covers the register itself, catching a write
// path that bypasses the accept condition.
property p_held_item_immutable;
@(posedge clk) disable iff (!rst_n)
(v_q && !accept) |=> ($stable(d_q) && $stable(last_q));
endproperty
a_held_immutable : assert property (p_held_item_immutable);
// LIVENESS — P6: an accepted item is eventually presented and taken.
// ASSUMPTION, and it must be stated: this holds only if the sink eventually
// asserts out_ready. Without that assumption the property is false for a
// correct design, because a permanently stalled sink is not a design bug.
// The environment must constrain out_ready to be eventually asserted.
property p_accepted_item_progresses;
@(posedge clk) disable iff (!rst_n)
accept |-> s_eventually (out_valid && out_ready);
endproperty
a_item_progresses : assert property (p_accepted_item_progresses);P2 and P3 are a matched pair. P3 catches items appearing from nowhere; P2 catches items disappearing without being taken. Together they bound the item count in both directions, which is what "no loss and no duplication" actually means at a handshake boundary.
P4 catches the failure that looks like a mysterious data loss much later: the pipeline accepting an item while the lane is unusable, taking ownership of something it cannot deliver.
P6 is the only liveness property here, and its assumption is stated in the code rather than left implicit. A liveness property with an unstated fairness assumption fails against correct designs and gets disabled, which is worse than not writing it.
10. Verification
Monitors observe: ingress transfers, egress transfers, the abstract availability inputs, and the status view. Transmit and receive are monitored independently — see below.
The scoreboard independently predicts: the exact sequence of items that must emerge, in order, from the sequence accepted at ingress. Its model must be its own; a scoreboard that reuses the design's accept expression will agree with the design about a bug.
Scenarios:
- Continuous traffic. Both sides always ready. Verify one output per input, in order, at full rate with no bubbles.
- Intermittent source. Gaps at ingress. Verify no spurious outputs during gaps (P3) and correct resumption.
- Sink backpressure.
out_readylow for varying durations, including a single cycle and a very long stall. Verify stability (P1) and that ingress stalls rather than overwriting. - Availability withdrawn while idle. Drop
lane_activewith nothing held. Verify no acceptance (P4) and clean recovery when it returns. - Availability withdrawn while holding. Drop
lane_activewith an item held. Verify the held item is not corrupted and not silently dropped — this exercises the design decision documented in §7 and is the case most likely to be implemented inconsistently. - Reset mid-transfer. Reset with an item held. Verify a clean restart with no stale item presented afterwards.
- Full-duplex traffic. Run independent traffic in both directions at once, in an environment that models transmit and receive separately. Verify neither direction's behaviour depends on the other's.
Coverage should include: backpressure stall lengths including one cycle and zero; availability transitions in each holding state; in_last at each position in a burst; reset at each holding state; and the cross of ingress activity against egress readiness.
11. Debugging — Direction Is the First Question
At x1 there is exactly one lane, so "which lane" is not a useful question. "Which direction" almost always is.
Symptom: transmit succeeds, receive fails (or the reverse).
This is the most informative single-lane failure signature, because it immediately rules out a large class of causes. Anything shared between the directions — the transaction layer, packet generation, the Link as a whole — cannot easily explain a fault present in one direction and absent in the other. If it were broken, both directions would generally be affected.
The search space narrows to what is not shared: the transmit datapath, the receive datapath, and the two directional physical paths.
Symptom: the Link is established, but data moves only one way.
Establishing a Link generally requires activity in both directions, so a Link that exists is evidence that both paths carried something. That is genuinely useful: it argues against a completely broken path and toward a fault affecting sustained traffic more than initial establishment — which is the classic signature of a marginal path rather than an absent one.
Symptom: light traffic works, sustained traffic stalls.
Two broad classes:
- Flow-control or buffering. Something is not returning or releasing capacity. Sustained traffic exposes it because light traffic never exhausts anything. This is digital and reproducible.
- Margin. Sustained traffic means more activity, more switching, and more thermal load — the reasoning developed in Chapter 5.5.
Distinguishing them: a flow-control problem generally reproduces deterministically at the same point and is visible in simulation. A margin problem is generally load- and condition-dependent, is generation-sensitive, and does not reproduce in RTL simulation at all.
Symptom: the local side looks correct but the far side receives nothing.
Check the direction convention before anything else. A's transmit pair is B's receive pair; the same physical resource has two names. Connection, orientation, and configuration errors around this convention are common, and they produce exactly this signature.
12. Common Misconceptions
- "A lane is one wire." A lane is two differential pairs — one per direction — and therefore four conductors. A single conductor cannot carry a differential signal, and one pair cannot carry both directions at once.
- "Lane and Link mean the same thing." A lane is a unit of width; a Link is the connection between two components. They coincide numerically at x1 and are different ideas, which is why the distinction must be fixed here rather than at x4.
- "x1 is half duplex." Transmit and receive have separate physical paths, so both directions carry traffic simultaneously. This is why Module 5's figures are stated per direction rather than shared between directions.
- "x1 is slow." x1 states the width, not the rate. Per-lane capacity is a generation question, and a single lane at a recent generation carries considerably more than a wide Link at an early one.
- "The generation determines the width" (or the reverse). They are independent dimensions set by different mechanisms. Either can change while the other is held fixed, and knowing one tells you nothing about the other.
- "An x1 Link can only have one transaction outstanding." Width is a property of the physical transport, not a limit on how many operations may be in progress. Concurrency in the transaction layer is a separate mechanism entirely, and Modules 10 onward own it.
- "Adding lanes increases the clock speed." Adding lanes adds width. Rate per lane is the generation's business. Conflating them makes both dimensions unreadable.
- "Both directions of a lane share bandwidth." They use separate physical paths. Capacity in one direction is not consumed by traffic in the other.
13. Understanding Check
14. What's Next
x1 is the case where width is not yet a problem. There is one lane, so nothing has to be distributed across lanes, nothing has to be reassembled, and no two lanes can disagree about anything.
Chapter 6.2 — x2 Links adds the second lane, and with it a genuinely new class of engineering problem: one logical stream must now be spread across two physical paths and correctly reconstructed at the far end, the two paths need not deliver at exactly the same instant, and the lanes must behave as one Link rather than two.
None of that exists at x1. All of it exists at x2, and it does not get fundamentally harder at x4 or x16 — which is why the second lane, not the sixteenth, is where the interesting transition happens.