Skip to content
VLSI Mentor

Ethernet · Module 24

Ethernet against PCIe

An address that cannot miss, a read that comes back, and the 425 088 bits of state a 400 Gb/s return path must hold — against an Ethernet transmitter that holds nothing.

Two interconnects sit inside every server in this track's Module 23, and engineers move between them daily without noticing that they disagree about what an address is.

Module 24 is a comparison module, and a comparison chapter is where a track goes soft. The failure mode is a feature table: two columns, a row per attribute, a tick in each. It teaches nothing, because a tick is not a quantity and a design decision is not a preference. This chapter's rule — and Module 24's — is that a comparison earns its place only by deriving a number the compared thing forces that Ethernet does not, and by building the adapter between them in RTL so the difference is a structure rather than an adjective.

Two differences do all the work here and everything else follows from them.

EthernetPCIe
an address isa namea destination
a lookupcan miss — Chapter 12.4 floodscannot miss — an unmatched address is an error
a transfercompletes when the last octet leavescompletes when something comes back
the transmitter is gated bynothinga credit counter the far end owns
state held after sendingnoneeverything, until the completion returns

Row five is this chapter's centre and Section 4 prices it.


1. Scope — A Name, a Destination, and a Return Path

Two addressing models side by side. On the Ethernet path a forty eight bit destination address enters a media access control table of one hundred and twenty eight thousand entries by ninety six bits, which is twelve point six megabits or one hundred and ninety six thousand six hundred and eight bitcell equivalents per port across sixty four ports. The lookup either hits and selects one port, or misses and floods to sixty three ports. On the PCIe path a sixty four bit memory address enters a decode of three windows, each a base and a limit at sixty four bits, which is three hundred and eighty four flip-flops or seven thousand six hundred and eighty bitcell equivalents per port. The decode either matches one window, or matches none and returns an unsupported request, which is a reported error rather than a delivery. The ratio between the two structures is twenty five point six times.48-bit DAa nameMAC table128k × 96 bHitone portMissflood to 63196 608 BCEper port64-bit addressa position3 windowsbase and limitMatchone windowNo matchUnsupported Request7 680 BCEper port25.6×the price of a name12
Figure 1 — a name is resolved and can fail; a position is decoded and cannot. Everything else in the chapter follows from that one difference.

This chapter owns three derivations and one bridge.

What is derived
Section 2what a name costs against a destination — 196 608 BCE per port against 7 680
Sections 4 to 5what a return path costs at 400 Gb/s — 196 tags and 425 088 BCE
Sections 6 to 9what credits and ordering cost, in the same unit
Sections 10 to 13the bridge, built, and the property that does not survive it

What this chapter does not own. It is not a PCIe tutorial: the transaction-layer packet format, the physical layer's training sequences, the configuration space's structure and the enumeration algorithm are all outside it. It uses exactly as much PCIe as the comparison needs, and every PCIe quantity it uses is derived here rather than quoted.

It also does not argue that either interconnect is better. They answer different questions and the chapter's closing position is that the question each answers is visible in the state each holds, which is a structural fact rather than an opinion.


2. Addressing: What a Lookup Costs That a Decode Does Not

An Ethernet destination address is a name. A PCIe address is a position in a space. The difference is not philosophical — it is 25.6 times the silicon, per port, and one failure mode.

Start with what each one is asked to do.

EthernetPCIe
the field48-bit destination address64-bit memory address
the questionwhich port is this name at?which port's window contains this position?
answered bya table populated by observation — Chapter 12.2a range comparison against registers written at enumeration
if nothing matchesflood — Chapter 12.4Unsupported Request — a reported error
the table's contents come fromthe trafficsoftware

Row three is the whole difference and rows four and five are its consequences.

Price the Ethernet side first, from Chapter 23.3 §2's table.

SizeBCE
MAC table, 128k × 96 b12.6 Mbit1.26 × 10⁷
across 64 ports196 608 per port

Then the PCIe side, derived. A PCIe switch port decodes an address against three windows — non-prefetchable memory, prefetchable memory, and I/O — and each window is a base and a limit. At 64 bits each that is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3 windows x 2 registers x 64 bits = 384 flip-flops per port
384 x 20 BCE = 7 680 BCE per port
Per portBCE× the datapath
Ethernet MAC table's share196 6080.694
PCIe address decode7 6800.027
ratio25.6×

Naming a station costs twenty-six times what locating one does, and the name can still not be found.

So the trade is stated exactly.

Ethernet buysBy paying
zero configurationa station may be plugged anywherea table that can miss, and a flood when it does
PCIe buysa decode that cannot missan enumeration pass before anything works

And neither is a preference. A fabric whose endpoints are enumerated at boot by one authority can afford the decode. A fabric whose endpoints arrive and leave without telling anyone cannot, and that is the difference between a backplane and a network.


3. RTL 1 — The Comparison Package and the Address Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pciecmp_pkg -- the constants this chapter's comparison needs, with
// every derived figure computed here rather than written as a literal.
//
// The unit is Chapter 23.3 Section 2's bitcell equivalent:
//   1 BCE = one bit of usable on-die SRAM = 0.35 GE
//   1 flip-flop = 20 BCE
//   Chapter 19.7 Section 19's MAC receive datapath = 283 320 BCE
// ---------------------------------------------------------------------
package pciecmp_pkg;

  // ---- the anchor ------------------------------------------------------
  localparam int unsigned DATAPATH_BCE   = 283_320;
  localparam int unsigned BCE_PER_FLOP   = 20;

  // ---- the Ethernet side ----------------------------------------------
  localparam int unsigned MAC_TBL_ENTRIES = 128 * 1024;
  localparam int unsigned MAC_TBL_WIDTH   = 96;
  localparam int unsigned SWITCH_PORTS    = 64;

  // ---- the PCIe side ---------------------------------------------------
  // Three decode windows -- non-prefetchable memory, prefetchable
  // memory, and I/O -- each a base and a limit, at 64 bits.
  localparam int unsigned DECODE_WINDOWS  = 3;
  localparam int unsigned DECODE_REGS     = 2;   // base, limit
  localparam int unsigned DECODE_WIDTH    = 64;

  // ---- the return path -------------------------------------------------
  // PCIe Gen5 supports 10-bit tags; the number a link NEEDS is set by
  // Little's law and is derived in Section 4 rather than assumed.
  localparam int unsigned TAG_BITS        = 10;
  localparam int unsigned TAG_ENTRY_BITS  = 128; // tag, requester, addr,
                                                 // length, BE, ptr, valid
  localparam int unsigned MAX_PAYLOAD_B   = 256;

  // ---- credits ---------------------------------------------------------
  // Three credit types (posted, non-posted, completion) x two units
  // (header 8 b, data 12 b) x three registers (limit, consumed,
  // allocated), per virtual channel, per direction.
  localparam int unsigned CREDIT_TYPES    = 3;
  localparam int unsigned CREDIT_HDR_BITS = 8;
  localparam int unsigned CREDIT_DAT_BITS = 12;
  localparam int unsigned CREDIT_REGS     = 3;
  localparam int unsigned VIRTUAL_CHANS   = 8;

  typedef enum logic [1:0] {
    XACT_POSTED     = 2'd0,   // memory write -- no completion
    XACT_NONPOSTED  = 2'd1,   // memory read  -- a completion is owed
    XACT_COMPLETION = 2'd2    // the completion itself
  } xact_e;

  typedef enum logic [1:0] {
    CPL_SUCCESSFUL      = 2'd0,
    CPL_UNSUPPORTED_REQ = 2'd1,
    CPL_ABORT           = 2'd2,
    CPL_RETRY           = 2'd3
  } cpl_status_e;

  // ---- derived: the Ethernet lookup, per port -------------------------
  function automatic int unsigned mac_table_bce();
    return MAC_TBL_ENTRIES * MAC_TBL_WIDTH;          // an array: 1 BCE/bit
  endfunction

  function automatic int unsigned mac_table_bce_per_port();
    return mac_table_bce() / SWITCH_PORTS;
  endfunction

  // ---- derived: the PCIe decode, per port ------------------------------
  function automatic int unsigned decode_flops();
    return DECODE_WINDOWS * DECODE_REGS * DECODE_WIDTH;
  endfunction

  function automatic int unsigned decode_bce_per_port();
    return decode_flops() * BCE_PER_FLOP;            // registers: 20/bit
  endfunction

  // ---- derived: Little's law at the return path ------------------------
  // bytes in flight = rate x round trip. Expressed in the units the
  // callers have: Gb/s and nanoseconds give bytes directly, because
  // 1 Gb/s x 1 ns = 0.125 bytes and the /8 does the conversion.
  function automatic int unsigned bytes_in_flight(int unsigned gbps,
                                                  int unsigned rtt_ns);
    return (gbps * rtt_ns) / 8;
  endfunction

  function automatic int unsigned tags_needed(int unsigned gbps,
                                              int unsigned rtt_ns);
    int unsigned b;
    b = bytes_in_flight(gbps, rtt_ns);
    return (b + MAX_PAYLOAD_B - 1) / MAX_PAYLOAD_B;  // round up
  endfunction

  function automatic int unsigned return_path_bce(int unsigned gbps,
                                                  int unsigned rtt_ns);
    // The tag table is an array, and so is the reorder buffer.
    return (tags_needed(gbps, rtt_ns) * TAG_ENTRY_BITS)
         + (bytes_in_flight(gbps, rtt_ns) * 8);
  endfunction

  // ---- derived: credit state -------------------------------------------
  function automatic int unsigned credit_flops_per_port();
    return VIRTUAL_CHANS * CREDIT_TYPES
         * (CREDIT_HDR_BITS + CREDIT_DAT_BITS)
         * CREDIT_REGS * 2;                          // two directions
  endfunction

  function automatic int unsigned credit_bce_per_port();
    return credit_flops_per_port() * BCE_PER_FLOP;
  endfunction

  // ---- reporting -------------------------------------------------------
  // Datapaths, in thousandths, so an integer output can carry a ratio
  // smaller than one without becoming zero.
  function automatic int unsigned datapaths_milli(int unsigned bce);
    return (bce * 1000) / DATAPATH_BCE;
  endfunction

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// address_model -- what a NAME costs against a POSITION, and the
// failure mode each one has.
//
// The area numbers are the easy half. The output that matters is
// lookup_can_miss, which is 1 on one side and 0 on the other and is
// the reason the two sides need different diagnostic chapters.
// ---------------------------------------------------------------------
module address_model
  import pciecmp_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        is_ethernet,       // 1 = name lookup, 0 = decode
  input  logic [15:0] ports,
  input  logic        lookup_hit,
  input  logic        decode_hit,

  output logic [31:0] addressing_bce_per_port,
  output logic [31:0] addressing_dp_milli,
  output logic        lookup_can_miss,
  output logic [15:0] copies_on_miss,
  output logic        miss_is_reported,
  output logic        miss_occurred,
  output logic [31:0] c_misses,
  output logic [31:0] c_flooded_copies
);
  always_comb begin
    addressing_bce_per_port = is_ethernet ? 32'(mac_table_bce_per_port())
                                          : 32'(decode_bce_per_port());
    addressing_dp_milli     = 32'(datapaths_milli(addressing_bce_per_port));

    // THE difference. A name can be absent from a table; a position
    // cannot be absent from a space.
    lookup_can_miss = is_ethernet;

    // Chapter 12.4 Section 6: a miss replicates to every other port in
    // the VLAN. A decode miss produces exactly one completion.
    copies_on_miss  = is_ethernet ? (ports - 16'd1) : 16'd1;

    // And the one that decides whether a chapter like 21.6 has to exist.
    miss_is_reported = !is_ethernet;

    miss_occurred = is_ethernet ? !lookup_hit : !decode_hit;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_misses <= '0; c_flooded_copies <= '0;
    end else if (miss_occurred) begin
      c_misses         <= c_misses + 32'd1;
      c_flooded_copies <= c_flooded_copies + 32'(copies_on_miss);
    end
  end
endmodule

Classification: an area model with one boolean that is the chapter's thesis.

What it teaches: that lookup_can_miss is a one-bit difference with a two-order-of-magnitude consequence. The area comparison — 196 608 BCE against 7 680, a factor of 25.6 — is the part that is easy to quote and the less interesting half. copies_on_miss is the other half: an Ethernet miss on a 64-port switch produces 63 copies, a PCIe miss produces one completion, and the ratio there is 63.

And it teaches why miss_is_reported is the field that decides how a fabric is debugged. Chapter 21.6 exists because an Ethernet loss has no reporter; that chapter's entire apparatus — the selectivity index, the drop-shape classifier, the clustering probe — is an attempt to infer a cause from a count. A fabric whose every failure names itself needs none of it, and a PCIe engineer reading Module 21 would find it strange in a way that is worth noticing.

Deliberately simplified: copies_on_miss assumes the whole port set is one VLAN, where Chapter 13.3 makes the flood domain the VLAN's member set. The decode path assumes three windows, where a real switch port also has bus-number ranges for routing configuration transactions downstream. And addressing_bce_per_port divides the MAC table by the port count, which is the right way to compare a shared resource against a per-port one and hides that the table is shared — so a single port can consume all of it, which the per-port figure cannot express.

Production implication: the per-port division is where a real capacity argument goes wrong. A 128k-entry table on a 64-port switch is 2 048 entries per port only if the traffic is uniform, and Chapter 12.5 §7 showed it never is: one port facing a server farm can hold tens of thousands of addresses while its neighbours hold four. The table then thrashes for everyone, and the symptom is flooding on ports whose own address count is tiny. A PCIe switch cannot have this failure at all, because a port's decode windows are its own and no other port can consume them — which is the same trade as before, stated as a capacity property instead of a latency one.


4. The Return Path, and the State It Forces

The return path at four hundred gigabits per second. A memory read request leaves the requester, crosses a round trip of one microsecond, and a completion returns. Little's law gives fifty thousand bytes in flight, which at a two hundred and fifty six byte maximum payload is one hundred and ninety six outstanding completions. The state this forces is a tag table of one hundred and ninety six entries at one hundred and twenty eight bits, which is twenty five thousand and eighty eight bitcell equivalents, plus a completion reorder buffer of fifty thousand bytes, which is four hundred thousand bitcell equivalents. The total is four hundred and twenty five thousand and eighty eight bitcell equivalents, or one point five zero zero media access control receive datapaths. The reorder buffer is ninety four per cent of the total. The Ethernet comparison holds zero bits after transmission.Read requestnon-postedRound trip1 µs, not yoursCompletionreturns, maybereordered50 000 B inflightrate × latency196 tags25 088 BCEReorder buffer400 000 BCE — 94%425 088 BCE1.500 datapathsEthernet, afterTX0 bits held12
Figure 2 — Little's law at a boundary the design owns neither end of, and the 425 088 bits it produces.

Ethernet's transmit path holds nothing after the last octet leaves. PCIe's holds every outstanding read until its data comes back, and at 400 Gb/s that is 425 088 bits.

Start with what each protocol calls completion.

EthernetPCIe, posted writePCIe, non-posted read
the transfer is done whenthe last octet is on the wirethe packet is handed to the link layerthe completion returns
who decidesthis portthis portthe far end
state held afterwardsnonenonethe whole request
a failure isinvisiblereported at a third partya timeout at the requester

Column four is the one that costs, and Little's law sizes it.

bytes in flight = rate × round trip

At 400 Gb/s — 50 gigabytes per second — against three round trips.

Round tripBytes in flightCompletions at 256 B
0.5 µs25 00098
1.0 µs50 000196
2.0 µs100 000391

Take the middle row, which is the same microsecond Chapter 23.4 §7 used, and price the state it forces.

StructureSizeBCE× the datapath
tag table, 196 × 128 b25 088 bits25 0880.089
completion reorder buffer50 000 B = 400 000 bits400 0001.412
total return-path state425 0881.500

A 400 Gb/s load/store link must hold one and a half MAC receive datapaths of state simply to have asked questions it has not yet had answered.

And the comparison the direction of this module demands.

StructureBCE× the datapath
Chapter 23.4 §7149 descriptors × 16 B19 0720.067
this section196 completions + reorder425 0881.500
an Ethernet MAC, after transmissionnothing00

The middle row is 22.3 times the top one, and the top one is the number that chapter called large.

The reorder buffer is 94.1% of the return-path cost and it is worth saying why it cannot be avoided.

PCIe completions for different requests may return out of order. A requester that issued reads A, B and C may receive B's data first, and it must place B's data where B's requester wanted it — which means either holding the data until the gaps fill, or having somewhere to put each completion the moment it arrives.

StrategyBuffer neededWhat it costs elsewhere
hold until in orderthe full bandwidth-delay productnothing
scatter on arrivalalmost nonethe consumer must tolerate out-of-order arrival
one outstanding at a timeone completionChapter 21.8 §5's ceiling — 2.0 Gb/s at 1 µs

Row three is the honest bound and it is why nobody does it. One 256-byte completion per microsecond is 2.048 Gb/s, which is 0.5% of a 400 Gb/s link.


5. RTL 2 — The Outstanding-Read Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// outstanding_reads -- the tag table and the reorder buffer a return
// path forces, sized by Little's law and reported in BCE.
//
// The important outputs are ethernet_equivalent_bce, which is zero and
// stays zero, and reorder_share_pct, which says where the cost is.
// ---------------------------------------------------------------------
module outstanding_reads
  import pciecmp_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [15:0] rate_gbps,
  input  logic [15:0] rtt_ns,
  input  logic [15:0] tag_limit,        // what the design implements

  input  logic        read_issued,
  input  logic        completion_rx,

  output logic [15:0] tags_required,
  output logic [31:0] bytes_inflight,
  output logic [31:0] tag_table_bce,
  output logic [31:0] reorder_bce,
  output logic [31:0] return_path_bce_o,
  output logic [15:0] reorder_share_pct,
  output logic [31:0] return_path_dp_milli,
  output logic [31:0] ethernet_equivalent_bce,
  output logic        tag_limit_binds,
  output logic [15:0] achievable_gbps,
  output logic [15:0] outstanding_now,
  output logic [31:0] c_tag_exhausted
);
  always_comb begin
    bytes_inflight    = 32'(bytes_in_flight(int'(rate_gbps), int'(rtt_ns)));
    tags_required     = 16'(tags_needed(int'(rate_gbps), int'(rtt_ns)));

    tag_table_bce     = 32'(tags_required) * 32'(TAG_ENTRY_BITS);
    reorder_bce       = bytes_inflight * 32'd8;
    return_path_bce_o = tag_table_bce + reorder_bce;

    reorder_share_pct = (return_path_bce_o == 0) ? 16'd0
                      : 16'((reorder_bce * 100) / return_path_bce_o);

    return_path_dp_milli = 32'(datapaths_milli(return_path_bce_o));

    // The comparison, and it is a constant. An Ethernet transmitter
    // holds nothing about a frame it has sent.
    ethernet_equivalent_bce = 32'd0;

    // Chapter 21.8 Section 5's ceiling, at this boundary.
    tag_limit_binds = (tag_limit < tags_required);
    achievable_gbps = (rtt_ns == 0) ? rate_gbps
                    : 16'(((32'(tag_limit) * 32'(MAX_PAYLOAD_B) * 8) * 1000)
                          / (32'(rtt_ns) * 1000));
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      outstanding_now <= '0; c_tag_exhausted <= '0;
    end else begin
      case ({read_issued, completion_rx})
        2'b10: outstanding_now <= outstanding_now + 16'd1;
        2'b01: if (outstanding_now != 0)
                 outstanding_now <= outstanding_now - 16'd1;
        default: ;
      endcase
      if (read_issued && (outstanding_now >= tag_limit))
        c_tag_exhausted <= c_tag_exhausted + 32'd1;
    end
  end
endmodule

Classification: Little's law at a boundary where the design owns neither end, producing an area rather than a rate.

What it teaches: that ethernet_equivalent_bce is a hard-wired zero and that is the comparison. Every other output in the module is a number a PCIe designer must compute and an Ethernet designer never encounters. At 400 Gb/s and 1 µs the module reports 425 088 BCE — 1.500 datapaths — against zero.

And it teaches that reorder_share_pct is 94%, so a conversation about tag counts is a conversation about 6% of the cost. Doubling the tag limit from 196 to 392 adds 25 088 BCE; halving the round trip removes 200 000. The lever that matters is latency and it belongs to somebody else.

Deliberately simplified: achievable_gbps assumes every completion carries a full MAX_PAYLOAD_B, where a read that crosses a 4 kB boundary is split and the pieces are smaller. The reorder buffer is sized at the full bandwidth-delay product, which assumes the worst reordering; a design whose consumer tolerates out-of-order data needs a fraction of it. And outstanding_now counts reads without distinguishing their sizes, where a real tracker weights by payload because one 4 kB read and sixteen 256-byte reads consume the same tag count and sixteen times the buffer.

Production implication: the tag limit is the parameter that gets set from a previous generation and it is the one Chapter 21.8 §5's rejected property was written about. A part with 64 tags at 400 Gb/s and 1 µs achieves 64 × 256 × 8 ÷ 1 000 = 131 Gb/s — 32.8% of its link — and every assertion in it passes, because the design is doing exactly what it was built to do as fast as it is allowed to. tag_limit_binds is the one bit that says the shortfall is structural, and a design that reports it turns a three-week performance investigation into a one-line answer.


6. Credits: Gating a Transmitter on a Number It Does Not Own

An Ethernet transmitter with a frame ready and a link up transmits. A PCIe transmitter with a packet ready and a link up transmits only if a counter the far end last updated says it may.

That is a difference in liveness and it is worth writing both properties down to see it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Ethernet. This is true, provable locally, and nothing can make it false.
p_eth_progress: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_ready && link_up && !paused) |-> ##[1:IFG_CYCLES] tx_start);

// PCIe. The same shape, and the antecedent now contains a term whose
// value the far end decides.
p_pcie_progress: assert property (@(posedge clk) disable iff (!rst_n)
  (tlp_ready && link_up && credits_available) |-> ##[1:ARB_CYCLES] tx_start);

!paused in the first and credits_available in the second look like the same term and are not. Chapter 14.2's PAUSE is exceptional: it is asserted during congestion, it has a timer, and its default state is not-paused. Credits are continuous: the transmitter is gated on every packet, always, and the default state at link-up is zero credits — nothing may be sent until the partner advertises.

Ethernet PAUSEPCIe credits
state at link-uptransmit permittedtransmit forbidden
granularitythe whole link, or a priorityper type, per virtual channel
the gate is checkedwhen a PAUSE is in forcebefore every packet
if the mechanism fails silentthe link runsthe link stops

Row four is the one that decides how each is debugged. A broken PAUSE receiver runs at full rate and drops under congestion — a performance bug. A broken credit return stops the link entirely and is found in the first second of bring-up. The lossy protocol's flow-control bug is a field escape; the lossless protocol's is a bring-up failure.

Price the credit state, derived.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
per virtual channel, per direction:
  3 types (posted, non-posted, completion)
  x (8-bit header credit + 12-bit data credit)
  x 3 registers (limit, consumed, allocated)
  = 3 x 20 x 3 = 180 flip-flops
FlopsBCE× the datapath
one VC, one direction1803 6000.013
8 VCs, both directions, per port2 88057 6000.203
64 ports184 3203 686 40013.01

Thirteen MAC datapaths of pure bookkeeping, and it does not move a single bit of data.


7. RTL 3 — The Credit Accountant

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// credit_accountant -- the three-register credit loop, per type, and
// the buffer obligation the advertisement creates.
//
// The counters are exact and small. The output that matters is
// buffer_obligation_bce, which is the promise the counters imply.
// ---------------------------------------------------------------------
module credit_accountant
  import pciecmp_pkg::*;
#(
  parameter int unsigned VCS = 8
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [15:0] rate_gbps,
  input  logic [15:0] rtt_ns,
  input  logic        per_vc_guaranteed,   // each VC gets its own space

  input  logic        tlp_sent,
  input  logic  [1:0] tlp_type,            // xact_e
  input  logic [11:0] tlp_data_credits,
  input  logic        credit_return,
  input  logic  [1:0] ret_type,
  input  logic [11:0] ret_data_credits,

  output logic [31:0] credit_flops_o,
  output logic [31:0] credit_bce_o,
  output logic [31:0] buffer_obligation_bce,
  output logic [15:0] obligation_ratio,    // buffer / counters
  output logic        may_transmit,
  output logic        stalled_on_credit,
  output logic [31:0] c_credit_stall_cycles
);
  // limit, consumed, allocated -- per type, header and data.
  logic  [7:0] hdr_limit    [3];
  logic  [7:0] hdr_consumed [3];
  logic [11:0] dat_limit    [3];
  logic [11:0] dat_consumed [3];

  always_comb begin
    credit_flops_o = 32'(credit_flops_per_port());
    credit_bce_o   = 32'(credit_bce_per_port());

    // The obligation: a round trip of buffer, times the VCs that need
    // their own guaranteed share.
    buffer_obligation_bce =
      32'(bytes_in_flight(int'(rate_gbps), int'(rtt_ns))) * 32'd8
      * (per_vc_guaranteed ? 32'(VCS) : 32'd1);

    obligation_ratio = (credit_bce_o == 0) ? 16'd0
                     : 16'(buffer_obligation_bce / credit_bce_o);

    // The gate. Note that it is checked on EVERY packet, and that at
    // reset every limit is zero -- so the default is "do not transmit".
    may_transmit = (hdr_consumed[tlp_type] < hdr_limit[tlp_type])
                && ({4'd0, dat_consumed[tlp_type]} + {4'd0, tlp_data_credits}
                    <= {4'd0, dat_limit[tlp_type]});

    stalled_on_credit = tlp_sent && !may_transmit;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int i = 0; i < 3; i++) begin
        hdr_limit[i]    <= '0;   // zero at reset: nothing may be sent
        hdr_consumed[i] <= '0;
        dat_limit[i]    <= '0;
        dat_consumed[i] <= '0;
      end
      c_credit_stall_cycles <= '0;
    end else begin
      if (tlp_sent && may_transmit) begin
        hdr_consumed[tlp_type] <= hdr_consumed[tlp_type] + 8'd1;
        dat_consumed[tlp_type] <= dat_consumed[tlp_type] + tlp_data_credits;
      end
      if (credit_return) begin
        hdr_limit[ret_type] <= hdr_limit[ret_type] + 8'd1;
        dat_limit[ret_type] <= dat_limit[ret_type] + ret_data_credits;
      end
      if (stalled_on_credit)
        c_credit_stall_cycles <= c_credit_stall_cycles + 32'd1;
    end
  end
endmodule

Classification: six counters and one comparison, wrapped around a promise that costs 6.9 times what the counters do.

What it teaches: that obligation_ratio is the number nobody computes. The module reports 57 600 BCE of counters against 400 000 BCE of buffer at 400 Gb/s and 1 µs — 6.9× — and with eight guaranteed virtual channels, 3 200 000 BCE against 57 600, a ratio of 55.6. A design review that examines the credit logic has examined 1.8% of what the mechanism costs.

And it teaches that the reset state is hdr_limit = 0, which means the link comes up unable to transmit. That is not a quirk; it is the only safe initial condition, because a credit is a claim about a buffer and no buffer is known to exist until the partner says so. Chapter 11.3 established that Ethernet's bring-up ends with a link that may immediately carry traffic; PCIe's ends with a link that may carry nothing until a DLLP has crossed it in each direction.

Deliberately simplified: the credit counters are modelled as saturating adders where the real mechanism uses modular arithmetic with a wrap comparison, so that a returned credit count can be compared against a consumed count across a wrap without either being reset. may_transmit checks one virtual channel where a real arbiter considers all of them and picks among those that may go. And the three transaction types are treated symmetrically, where completions have a special rule: a completer must never be unable to return a completion, so completion credits are conventionally advertised as infinite.

Production implication: c_credit_stall_cycles separates two failures that look identical from outside. A link running at half rate because the partner returns credits slowly and a link running at half rate because the partner's buffer is genuinely small produce the same throughput and the same everything else. The stall counter distinguishes them only in combination with the advertised limit: stalls with a high limit means slow return; stalls with a low limit means a small buffer. A design that reports both turns the question into a one-glance answer, and a design that reports neither sends two teams to instrument a link that is behaving exactly as specified.


8. Ordering: What PCIe Enforces and Ethernet Does Not

Ethernet guarantees ordering within a flow on a path and nothing else. PCIe guarantees a set of relations between transaction types, always, and the guarantee is what makes a load/store programming model work.

State each precisely, because the loose version of either is wrong.

Ethernet's guaranteePCIe's guarantee
what is orderedframes taking the same pathtransactions in the same traffic class
against whateach othereach other, by type
who may reorderany switch choosing a different path — Chapter 15.2nobody, for the required relations
how a design relies on itit must notproducer-consumer correctness depends on it

Row four is the consequence and it is why a bridge between them is hard.

The producer-consumer pattern, which is the reason the ordering rules exist:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
producer:   write data to memory        (posted)
            write a flag to memory      (posted)
consumer:   read the flag               (non-posted)
            if set, read the data       (non-posted)

This is correct on PCIe and only on PCIe. The rule that makes it work is that a posted write may not pass an earlier posted write in the same traffic class — so the data write reaches memory before the flag write does, and a consumer that sees the flag is guaranteed to see the data.

The same pattern over Ethernet is a race, and the reason is Chapter 15.2: two frames from the same source to the same destination may hash to different members of a link aggregation group and arrive in either order. A protocol above Ethernet that needs ordering must implement sequence numbers, which is what TCP does and what Chapter 24.2 will find InfiniBand's transport doing too.

Price the enforcement.

The naive structure is a pairwise comparison: each outstanding transaction against every other, to decide whether it may pass. At Section 4's 196 outstanding:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
196 x 195 / 2 = 19 110 comparisons

Nobody builds that, and the reason is worth understanding because the alternative is a general technique. The ordering rules are per type, not per transaction, so a counter per type is sufficient: a transaction may proceed if no earlier transaction of a blocking type is still pending.

StructureFlopsBCE
pairwise matrix, 196 outstanding19 110 comparatorsprohibitive
three type counters, 8 bits each24480
ratiothe comparison collapses

The ordering rules cost 480 BCE because they are stated over types rather than over transactions, and a specification written the other way would have been unimplementable.


9. RTL 4 — The Ordering Scoreboard

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ordering_scoreboard -- PCIe's ordering rules, enforced with a counter
// per transaction type instead of a comparison per transaction pair.
//
// The module also reports what the naive structure would have cost, so
// the specification's shape is visible as a number.
// ---------------------------------------------------------------------
module ordering_scoreboard
  import pciecmp_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [15:0] outstanding_max,

  input  logic        xact_issue,
  input  logic  [1:0] xact_kind,          // xact_e
  input  logic        xact_retire,
  input  logic  [1:0] retire_kind,

  output logic        may_proceed,
  output logic        blocked_by_posted,
  output logic [15:0] pending_posted,
  output logic [15:0] pending_nonposted,
  output logic [15:0] pending_completion,
  output logic [31:0] pairwise_comparisons,
  output logic [31:0] counter_flops,
  output logic [31:0] counter_bce,
  output logic        ethernet_orders_this,
  output logic [31:0] c_order_blocks
);
  always_comb begin
    // What the rules cost, stated over types.
    counter_flops = 32'd3 * 32'd8;
    counter_bce   = counter_flops * 32'(BCE_PER_FLOP);

    // What they would have cost, stated over transactions.
    pairwise_comparisons = (32'(outstanding_max)
                            * (32'(outstanding_max) - 32'd1)) / 32'd2;

    // The rule that makes producer-consumer work: a posted write may
    // not pass an earlier posted write, and a completion may not pass
    // an earlier posted write.
    blocked_by_posted = (pending_posted != 16'd0)
                     && ((xact_kind == 2'(XACT_COMPLETION))
                      || (xact_kind == 2'(XACT_POSTED)));

    may_proceed = xact_issue && !blocked_by_posted;

    // And the comparison this chapter exists to make.
    ethernet_orders_this = 1'b0;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      pending_posted     <= '0;
      pending_nonposted  <= '0;
      pending_completion <= '0;
      c_order_blocks     <= '0;
    end else begin
      if (xact_issue && may_proceed) begin
        case (xact_kind)
          2'(XACT_POSTED):     pending_posted     <= pending_posted + 16'd1;
          2'(XACT_NONPOSTED):  pending_nonposted  <= pending_nonposted + 16'd1;
          default:             pending_completion <= pending_completion + 16'd1;
        endcase
      end
      if (xact_retire) begin
        case (retire_kind)
          2'(XACT_POSTED):
            if (pending_posted != 0)     pending_posted <= pending_posted - 16'd1;
          2'(XACT_NONPOSTED):
            if (pending_nonposted != 0)  pending_nonposted <= pending_nonposted - 16'd1;
          default:
            if (pending_completion != 0) pending_completion <= pending_completion - 16'd1;
        endcase
      end
      if (xact_issue && blocked_by_posted)
        c_order_blocks <= c_order_blocks + 32'd1;
    end
  end
endmodule

Classification: three counters standing in for a quadratic relation, and a constant-zero output that names the other protocol.

What it teaches: that pairwise_comparisons and counter_flops are both reported so the specification's shape is a measured quantity. At 196 outstanding the module reports 19 110 against 24, and the reason the second number is achievable is entirely that the rules are written over three classes instead of over 196 objects.

And it teaches that ethernet_orders_this is zero and that this is not a deficiency. Ethernet does not order across paths because Chapter 15.2 lets a flow take several, and that choice bought the bisection bandwidth Chapter 23.1 is built on. A fabric that ordered strictly could not spread a flow across 32 paths at all, and Chapter 23.2 §12's striping — the remedy that took a collective from 28.4% to 77.1% — is only legal because nothing downstream requires order.

Deliberately simplified: the module enforces two of PCIe's ordering relations and the full table has more, including the rules about I/O and configuration transactions and the relaxed-ordering and ID-based-ordering attribute bits that let software opt out. blocked_by_posted blocks on any pending posted write, where the real rule is scoped to the same traffic class. And pending_* are flat counters where a real implementation must also respect the ordering rules within a completer, which is a second scoreboard at the other end.

Production implication: the relaxed-ordering bit is the field that turns this module's guarantee off, and it is set by software on a per-transaction basis for exactly the traffic where ordering is not needed — bulk data whose flag is written separately. A design that ignores the bit is correct and slow; a design that honours it is correct and fast; a design that honours it incorrectly produces the worst bug class in this chapter, because the failure is a consumer reading a flag and finding stale data, which is silent, intermittent and reproduces on one machine in fifty. c_order_blocks counting zero on a workload that should be ordered is the signal that the bit is being honoured where it should not be, and it costs one counter.


10. The Bridge, and What Crosses It

A network interface card as a bridge. A received Ethernet frame of one thousand five hundred and eighteen octets arrives and the Ethernet side declares it complete when its last octet lands. The bridge fetches a descriptor with one non-posted read, issues six posted writes of two hundred and fifty six octets each for the payload, and one posted write to return the descriptor. That is eight transaction layer packets per frame, of which two are overhead regardless of the frame size, so the overhead share is sixty six point seven per cent at sixty four octets, twenty five per cent at one thousand five hundred and eighteen, and five point three per cent at nine thousand and eighteen. The PCIe side declares the frame complete only when the descriptor write back is acknowledged, which is a host round trip later. At thirty two point five one million packets per second the two completion events are about thirty two frames apart at all times, and both are correct.Frame arrives1 518 octetsEthernet: donelast octet sentDescriptor fetch1 non-posted read6 posted writes256 octets eachWrite-back1 posted write8 TLPs per frame2 are overheadPCIe: donewrite-back acked≈ 32 frames aparta host round trip12
Figure 3 — one frame becomes eight transactions, and the two protocols disagree about when it is finished.

A network interface card is a bridge between these two protocols, and every NIC in Chapter 23.4 is one. Building the bridge makes the differences into signals.

The bridge's job, stated as a mapping.

Ethernet sidePCIe sideWhat the bridge must supply
a received framea posted write to host memoryan address, from a descriptor
a descriptora non-posted read, then a completiona tag, and somewhere to put the answer
a transmitted framea posted read of the payloadthe same, per fragment
an interrupta posted write to a message addressnothing — it is fire and forget
a frame dropnothinga counter, because there is no other reporter

Row five is the interesting one and Section 12 is about it.

Four quantities have to be reconciled at the boundary and none of them match.

EthernetPCIeThe bridge's problem
unit of transfera frame, 64 to 9 018 octetsa TLP, up to MAX_PAYLOAD_Bone frame is many TLPs
addressa name, resolved by a tablea position, supplied by softwarethe descriptor is the translation
orderingnone across pathsby typethe bridge must impose what it needs
completionlocal, at transmissionremote, on returntwo different "done" events

Row four is the one that does not reconcile, and the rest of this chapter follows from it.

Work the numbers for one received frame at 400 Gb/s.

Value
a 1 518-octet frame1 518 octets of payload to write
at 256 octets per TLP6 posted writes
plus the descriptor write-back1 posted write
plus the descriptor fetch, amortised1 non-posted read per descriptor
TLPs per frame8
at 32.51 Mpps260.1 million TLPs per second

And for a minimum frame the arithmetic inverts.

64-octet frame1 518-octet frame9 018-octet frame
payload TLPs1636
overhead TLPs222
overhead share66.7%25.0%5.3%

The bridge's own overhead is two transactions per frame regardless of the frame's size, which is Chapter 8.3's hyperbola again, in a different protocol, with a different constant.


11. RTL 5 — The Ethernet-to-PCIe Bridge

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// eth_pcie_bridge -- one received frame becomes a sequence of posted
// writes and one descriptor write-back, and the module reports what
// the conversion cost.
//
// The output that matters is frame_complete_ethernet against
// frame_complete_pcie: the two protocols disagree about when this
// frame is done, and Section 12 is about the disagreement.
// ---------------------------------------------------------------------
module eth_pcie_bridge
  import pciecmp_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        frame_start,
  input  logic        frame_end,
  input  logic [15:0] frame_octets,
  input  logic [63:0] descriptor_addr,
  input  logic        descriptor_valid,

  input  logic        tlp_accepted,      // the PCIe side took one
  input  logic        writeback_acked,   // the descriptor write landed

  output logic        tlp_request,
  output logic  [1:0] tlp_kind,
  output logic [63:0] tlp_addr,
  output logic [15:0] tlp_octets,
  output logic [15:0] tlps_for_frame,
  output logic [15:0] payload_tlps,
  output logic [15:0] overhead_tlps,
  output logic [15:0] overhead_share_pct,
  output logic        frame_complete_ethernet,
  output logic        frame_complete_pcie,
  output logic        completions_disagree,
  output logic [31:0] c_frames_bridged,
  output logic [31:0] c_tlps_issued
);
  logic [15:0] tlps_sent;
  logic [63:0] cursor;
  logic        in_frame;

  always_comb begin
    payload_tlps  = (frame_octets + 16'(MAX_PAYLOAD_B) - 16'd1)
                  / 16'(MAX_PAYLOAD_B);
    overhead_tlps = 16'd2;                    // fetch + write-back
    tlps_for_frame = payload_tlps + overhead_tlps;

    overhead_share_pct = (tlps_for_frame == 0) ? 16'd0
                       : (overhead_tlps * 16'd100) / tlps_for_frame;

    tlp_request = in_frame && descriptor_valid
               && (tlps_sent < payload_tlps);
    tlp_kind    = 2'(XACT_POSTED);
    tlp_addr    = cursor;
    tlp_octets  = ((frame_octets - (tlps_sent * 16'(MAX_PAYLOAD_B)))
                   > 16'(MAX_PAYLOAD_B))
                ? 16'(MAX_PAYLOAD_B)
                : (frame_octets - (tlps_sent * 16'(MAX_PAYLOAD_B)));

    // THE two events. On Ethernet the frame is done when its last
    // octet arrived. On PCIe it is done when the far end says so.
    frame_complete_ethernet = frame_end;
    frame_complete_pcie     = writeback_acked;

    // They are never the same cycle, and the gap is a host round trip.
    completions_disagree = frame_complete_ethernet ^ frame_complete_pcie;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      tlps_sent <= '0; cursor <= '0; in_frame <= 1'b0;
      c_frames_bridged <= '0; c_tlps_issued <= '0;
    end else begin
      if (frame_start) begin
        in_frame  <= 1'b1;
        tlps_sent <= '0;
        cursor    <= descriptor_addr;
      end
      if (tlp_accepted) begin
        tlps_sent     <= tlps_sent + 16'd1;
        cursor        <= cursor + 64'(MAX_PAYLOAD_B);
        c_tlps_issued <= c_tlps_issued + 32'd1;
      end
      if (writeback_acked) begin
        in_frame         <= 1'b0;
        c_frames_bridged <= c_frames_bridged + 32'd1;
      end
    end
  end
endmodule

Classification: a fragmenter with an address cursor, whose real output is a pair of complete signals that never coincide.

What it teaches: that completions_disagree is asserted for the whole interval between the two protocols' notions of done, and that interval is a host round trip — 1 µs, which at 32.51 Mpps is 32 frames. A design that reports "frame received" on the Ethernet event and a design that reports it on the PCIe event disagree by thirty-two frames at all times, and both are correct.

And it teaches where the descriptor sits in the addressing argument. Chapter 23.4 §5 priced descriptors as a bandwidth overhead — 38.1% of payload at minimum frame size. Section 2 of this chapter says what they are: the descriptor is the translation from a name to a position. Ethernet delivered a frame to a name; the host wants it at a position; the descriptor is the only place those two are related, and it was written by software before the frame existed.

Deliberately simplified: the bridge issues payload TLPs strictly in address order where a real design interleaves several frames' writes to keep the link busy. overhead_tlps is a constant two, where batching amortises the descriptor fetch across many frames — Chapter 23.4 §6 showed a batch of 8 takes TLP efficiency from 40.0% to 72.7% without moving the descriptor bandwidth at all. And cursor advances linearly, which assumes the descriptor points at a contiguous buffer; scatter-gather descriptors point at a list, and the bridge must walk it.

Production implication: the bridge is where a frame drop becomes invisible, and it is worth being explicit about the mechanism. If descriptor_valid is low when a frame arrives, the frame cannot be written anywhere and is discarded — this is Chapter 23.4 §7's prefetch underrun. On the Ethernet side there is no signal for it: the frame was received correctly, its FCS was good, and it is gone. On the PCIe side nothing happened at all, because no transaction was ever issued. So the event is visible only in a counter that the bridge chooses to keep — which is why c_frames_bridged must be compared against the MAC's receive counter rather than trusted alone, and a design that reports only one of them has made a class of loss unobservable.


12. Which Ethernet Property Becomes Unstateable

Section 11 built the bridge. This section takes one Ethernet property across it and finds that it cannot be written on the other side — not that it is false, that there is no way to say it.

The property is Ethernet's most basic liveness claim and every MAC in Module 19 carries some version of it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Ethernet. The transfer is complete when the last octet has left,
// and this port knows it without asking anyone.
p_tx_complete_is_local: assert property (@(posedge clk) disable iff (!rst_n)
  last_octet_sent |-> ##1 frame_complete);

Three things are true of it that are worth naming separately.

On Ethernet
every term is a local signallast_octet_sent and frame_complete are both in this MAC
the guarantee is about this portnothing about the far end is claimed
it is checkable in the fielda hardware monitor can evaluate it forever

Now carry it across the bridge. Every term has a counterpart.

Ethernet termPCIe counterpartWell-defined?
last_octet_senttlp_handed_to_link_layeryes
frame_completetransaction_completeyes

So the translated property is well-formed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// PCIe. Every term translated, the property compiles, and it asserts
// something the original never said.
p_xact_complete_is_local: assert property (@(posedge clk) disable iff (!rst_n)
  tlp_handed_to_link_layer |-> ##1 transaction_complete);

It is true for a posted write and false for a non-posted read, and that is not the interesting part. The interesting part is that transaction_complete for a read is not a fact about this port at all. It is a fact about a relation between this port and a completer that may be four switch hops away, and the local signal that carries it is a report of something that arrived, not an observation of something that happened here.

The Ethernet property says I am done. The translated property says somebody told me I am done. Both are written as a local implication and only one of them is one.

State the general form, because it is this batch's first rejected class and Section 20 develops it.

Before the portAfter the port
what the property assertsa local facta relation between two endpoints
where it is evaluatedat the port that owns the factat one of the two endpoints
does it pass?yes, correctlyyes, and it means something else
who introduced the errornobody — it was rightthe translation

Four Ethernet properties and what happens to each at the boundary.

Ethernet propertyFate on PCIe
p_tx_complete_is_localunstateable — completion is remote for reads
p_progress_when_readyfalse — the transmitter is credit-gated
p_frame_dropped_silentlyunstateable — there is no silent drop
p_fcs_covers_the_frametranslates cleanly — LCRC is the counterpart

Row four is the one that works and it is worth saying why. The FCS property is about the contents of a thing in one place at one time, and both protocols have such a thing. The three that fail are all about an event whose owner differs between the protocols.

13. RTL 6 — The Completion Timeout

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// completion_timer -- the only liveness a requester can claim.
//
// Section 20 establishes that a remote completion is a report rather
// than an observation. The structural consequence is that every
// outstanding read needs a timer, and Ethernet needs none because
// nothing is outstanding.
// ---------------------------------------------------------------------
module completion_timer
  import pciecmp_pkg::*;
#(
  parameter int unsigned TAGS        = 256,
  parameter int unsigned TIMEOUT_US  = 50
)(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        tick_us,

  input  logic        read_issued,
  input  logic  [7:0] issued_tag,
  input  logic        completion_rx,
  input  logic  [7:0] cpl_tag,
  input  logic  [1:0] cpl_status,

  output logic        timeout_fired,
  output logic  [7:0] timeout_tag,
  output logic [15:0] outstanding_now,
  output logic [31:0] timer_flops,
  output logic [31:0] timer_bce,
  output logic [31:0] ethernet_timer_bce,
  output logic [31:0] c_timeouts,
  output logic [31:0] c_unexpected_cpl,
  output logic [31:0] c_error_cpl
);
  logic [15:0] age  [TAGS];
  logic        live [TAGS];

  always_comb begin
    // One age counter per tag, and the counter must span the timeout.
    timer_flops = 32'(TAGS) * 32'd16 + 32'(TAGS);
    timer_bce   = timer_flops * 32'(BCE_PER_FLOP);

    // The comparison. An Ethernet transmitter has nothing to time.
    ethernet_timer_bce = 32'd0;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      for (int i = 0; i < TAGS; i++) begin
        age[i] <= '0; live[i] <= 1'b0;
      end
      timeout_fired <= 1'b0; timeout_tag <= '0; outstanding_now <= '0;
      c_timeouts <= '0; c_unexpected_cpl <= '0; c_error_cpl <= '0;
    end else begin
      timeout_fired <= 1'b0;

      if (read_issued) begin
        live[issued_tag] <= 1'b1;
        age[issued_tag]  <= '0;
        outstanding_now  <= outstanding_now + 16'd1;
      end

      if (completion_rx) begin
        // Property 4 of Section 20, as a counter: a completion for a
        // tag nobody requested is evidence about the completer.
        if (!live[cpl_tag]) c_unexpected_cpl <= c_unexpected_cpl + 32'd1;
        else begin
          live[cpl_tag]   <= 1'b0;
          outstanding_now <= outstanding_now - 16'd1;
        end
        if (cpl_status != 2'(CPL_SUCCESSFUL))
          c_error_cpl <= c_error_cpl + 32'd1;
      end

      if (tick_us) begin
        for (int i = 0; i < TAGS; i++) begin
          if (live[i]) begin
            if (age[i] >= 16'(TIMEOUT_US)) begin
              live[i]       <= 1'b0;
              timeout_fired <= 1'b1;
              timeout_tag   <= 8'(i);
              c_timeouts    <= c_timeouts + 32'd1;
            end else begin
              age[i] <= age[i] + 16'd1;
            end
          end
        end
      end
    end
  end
endmodule

Classification: one timer per outstanding transaction, which is a structure with no Ethernet counterpart at all.

What it teaches: that ethernet_timer_bce is zero and timer_bce is 87 040 at 256 tags — 256 × 17 = 4 352 flops at 20 BCE each, 0.307 datapaths. It is a small number and it is a structure that exists only because completion is remote, so it belongs in the same column as Section 4's reorder buffer rather than in a list of implementation details.

And it teaches that c_unexpected_cpl is the counter Section 20's property 4 turns into hardware. A completion for a tag that was never issued is either a completer misbehaving, a tag reused before its predecessor's completion returned, or a completion from a previous reset epoch arriving late — and all three are serious, all three are silent, and none of them produces a symptom that any other counter reports.

Deliberately simplified: the timer is a per-tag age counter ticking at one microsecond, where a real design uses a coarse timer wheel because 256 sixteen-bit counters that all increment on the same tick is a large amount of switching for very little information. TIMEOUT_US is one value where the specification defines four programmable ranges spanning 50 µs to 64 seconds, because a completer behind several switches and a retry-capable endpoint may legitimately take very long. And the loop over TAGS on every tick is written for clarity rather than for synthesis; the synthesisable form is a rotating pointer that ages one group per cycle.

Production implication: the timeout value is where a working system becomes an intermittently failing one, and the mechanism is worth stating because it catches teams out. A completion timeout is a fatal, unrecoverable error for most drivers — the transaction is abandoned and the device is usually reset. So a timeout set too short turns a slow completer into a device removal, and the symptom is a NIC that disappears from the system under load and reappears after a reset, with nothing in any log but the timeout itself. c_timeouts counting at all is the signal, and a design that reports the age distribution rather than just the count tells the operator whether the value is marginal or the completer is genuinely dead — which is the difference between a configuration change and a hardware replacement.


14. What a Bridge Must Never Do

Five prohibitions, and each is a bug this chapter's structures make possible.

#NeverBecause
1report a frame received before its write-back is acknowledgedSection 11: the two completions are a host round trip apart, and the earlier one is a claim about the wrong protocol
2reuse a tag before its completion has returned or timed outa late completion then lands on a live tag and the data goes to the wrong buffer, silently
3advertise credits for buffer that is shared with another virtual channelSection 6: a credit is a promise, and a promise backed by somebody else's space is a deadlock
4drop a frame because a descriptor was unavailable without counting itSection 11: neither protocol reports it, so an uncounted one is unobservable
5assume a completion's data is correct because its status is Successfulstatus is the completer's claim about itself — Section 20's property 4

Row two is the one that produces the worst bug in this chapter and it is worth expanding.

A tag is reused legally the moment its completion returns. A tag is reused illegally the moment its completion times out, because a timeout does not mean the completion will not arrive — it means it has not arrived yet. A completer that was merely slow may return the data after the requester has reassigned the tag, and the requester will accept it, match it to the new request, and write somebody else's data into the new request's buffer.

What happens
the timeout firestag 17 is freed
a new read is issuedtag 17 is reallocated to a different address
the old completion arrivesit matches tag 17 and is accepted
the resultthe new read's buffer holds the old read's data
what any assertion seesa successful completion for a live tag

The defence is an epoch bit or a generation counter on the tag, so that a completion carries evidence of which allocation it belongs to — and the mechanism is the same one Chapter 19.5 §7 used to distinguish a full FIFO from an empty one, which is that a pointer that wraps needs one more bit than the space it indexes.


15. RTL 7 — Bridge Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pcie_cmp_telemetry -- what a bridge must report for the two sides to
// be reconcilable after the fact.
//
// The design rule this module follows: every counter that exists on
// one side alone is useless, because the question at a bridge is
// always "where did the difference come from".
// ---------------------------------------------------------------------
module pcie_cmp_telemetry
  import pciecmp_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        clear,

  // the Ethernet side
  input  logic        eth_frame_rx,
  input  logic        eth_frame_dropped,
  input  logic [15:0] eth_frame_octets,

  // the PCIe side
  input  logic        tlp_issued,
  input  logic        tlp_credit_stalled,
  input  logic        cpl_received,
  input  logic        cpl_timeout,
  input  logic        cpl_unexpected,

  // the bridge's own
  input  logic        descriptor_starved,

  output logic [47:0] c_eth_frames,
  output logic [47:0] c_eth_dropped,
  output logic [47:0] c_tlps,
  output logic [47:0] c_completions,
  output logic [47:0] c_timeouts_o,
  output logic [47:0] c_unexpected_o,
  output logic [47:0] c_desc_starved,
  output logic [31:0] c_credit_stalls,
  output logic [15:0] tlps_per_frame_x100,
  output logic [15:0] loss_attributable_pct,
  output logic        sides_reconcile
);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n || clear) begin
      c_eth_frames <= '0; c_eth_dropped <= '0; c_tlps <= '0;
      c_completions <= '0; c_timeouts_o <= '0; c_unexpected_o <= '0;
      c_desc_starved <= '0; c_credit_stalls <= '0;
    end else begin
      if (eth_frame_rx)       c_eth_frames    <= c_eth_frames + 48'd1;
      if (eth_frame_dropped)  c_eth_dropped   <= c_eth_dropped + 48'd1;
      if (tlp_issued)         c_tlps          <= c_tlps + 48'd1;
      if (cpl_received)       c_completions   <= c_completions + 48'd1;
      if (cpl_timeout)        c_timeouts_o    <= c_timeouts_o + 48'd1;
      if (cpl_unexpected)     c_unexpected_o  <= c_unexpected_o + 48'd1;
      if (descriptor_starved) c_desc_starved  <= c_desc_starved + 48'd1;
      if (tlp_credit_stalled) c_credit_stalls <= c_credit_stalls + 32'd1;
    end
  end

  always_comb begin
    // The ratio Section 10 derived, measured rather than assumed.
    tlps_per_frame_x100 = (c_eth_frames == 0) ? 16'd0
                        : 16'((c_tlps * 48'd100) / c_eth_frames);

    // Of the frames that were lost, how many can be attributed to a
    // named cause? Anything below 100% is loss with no reporter.
    loss_attributable_pct = (c_eth_dropped == 0) ? 16'd100
                          : 16'((c_desc_starved * 48'd100) / c_eth_dropped);

    // The two sides agree only if every issued read got an answer.
    sides_reconcile = (c_completions + c_timeouts_o) >= (c_tlps - c_eth_frames);
  end
endmodule

Classification: a counter bank whose design rule is that no counter is meaningful alone.

What it teaches: that loss_attributable_pct is the field that separates the two protocols' debugging cultures. On the PCIe side every failure has a name — a timeout, an unexpected completion, an error status — so the attributable share is 100% by construction. On the Ethernet side it is whatever fraction of the drops the bridge happened to instrument, and Chapter 21.6 §13 established that the reason field does not exist in the frame, the standard or most implementations. A bridge that reports 60% attributable is reporting that 40% of its losses have no explanation anywhere in the system.

And it teaches that tlps_per_frame_x100 is Section 10's hyperbola as a measurement. The module reports 800 on a link carrying 1 518-octet frames and 300 on one carrying minimum frames, and the same part reports the same two numbers whether it is healthy or not — so the counter is a workload descriptor rather than a health indicator, and reading it as the latter is Chapter 23.4 §20's class 109 in a new place.

Deliberately simplified: sides_reconcile uses a subtraction that assumes exactly one descriptor read per frame, which Section 11's batching breaks. All counters are free-running 48-bit where a real part needs clear-on-read for some and sticky for others — Chapter 19.7 §6's argument about which semantics belong to which counter applies unchanged. And the module observes both sides from one clock domain, where a real bridge has the MAC's clock on one side and the PCIe core clock on the other — Chapter 18.1 §19's class 74 is exactly this hazard and the counters must be synchronised before they are compared.

Production implication: the counter that is always missing is c_desc_starved, and its absence is what makes Chapter 23.4 §7's prefetch underrun a three-week investigation. The symptom is receive drops at line rate with an idle host and a non-full ring, which looks like a fabric problem and is not — and the only structure that distinguishes the two is a counter in the bridge that nobody thought to specify, because the bridge is where two teams' ownership ends and neither one's test plan covers the gap.


16. RTL 8 — The Bridge Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// pcie_cmp_conformance -- the checks that only make sense at the
// boundary, gathered in one place.
//
// Every check here is about a RELATION between the two sides. None of
// them can be written on either side alone, which is the point.
// ---------------------------------------------------------------------
module pcie_cmp_conformance
  import pciecmp_pkg::*;
#(
  parameter int unsigned TAGS = 256
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        eth_frame_end,
  input  logic        pcie_writeback_ack,
  input  logic        read_issued,
  input  logic  [7:0] issued_tag,
  input  logic        tag_live_in,
  input  logic        completion_rx,
  input  logic  [7:0] cpl_tag,
  input  logic        cpl_tag_live,
  input  logic  [1:0] cpl_status,
  input  logic        credits_available,
  input  logic        tlp_transmitted,
  input  logic        frame_dropped,
  input  logic        drop_counted,
  input  logic [15:0] advertised_credits,
  input  logic [15:0] buffer_entries,

  output logic        v_premature_complete,
  output logic        v_tag_reuse,
  output logic        v_orphan_completion,
  output logic        v_credit_overrun,
  output logic        v_overadvertised,
  output logic        v_uncounted_drop,
  output logic  [5:0] violations,
  output logic        conformant
);
  always_comb begin
    // 1. Section 14 prohibition 1 -- the Ethernet completion is not
    //    the PCIe one, so it must not be reported as the transfer.
    v_premature_complete = eth_frame_end && !pcie_writeback_ack;

    // 2. Prohibition 2 -- a tag must be free before it is issued.
    v_tag_reuse = read_issued && tag_live_in;

    // 3. Section 20 property 4 -- a completion for a tag nobody holds.
    v_orphan_completion = completion_rx && !cpl_tag_live;

    // 4. Section 6 -- the gate is checked on every packet.
    v_credit_overrun = tlp_transmitted && !credits_available;

    // 5. Prohibition 3 -- credits may not exceed the buffer behind them.
    v_overadvertised = (advertised_credits > buffer_entries);

    // 6. Prohibition 4 -- a drop with no reporter anywhere.
    v_uncounted_drop = frame_dropped && !drop_counted;

    violations = { v_uncounted_drop, v_overadvertised, v_credit_overrun,
                   v_orphan_completion, v_tag_reuse, v_premature_complete };
    conformant = (violations == 6'b000000);
  end

  // v_premature_complete is a WARNING rather than an error: the two
  // events are legitimately a host round trip apart. It fires only if
  // something downstream treats the Ethernet event as the transfer.
endmodule

Classification: six checks, none of which can be written on either side alone.

What it teaches: that a conformance monitor at a boundary is a different object from one inside a block. Chapter 19.1 §16's monitor checks a MAC against its own specification; this one checks two specifications against each other, and every one of its six signals mentions terms from both sides. A monitor that could be partitioned onto one side would not be a bridge monitor.

And it teaches why v_premature_complete is documented as a warning in the code rather than treated as a violation. The two completion events are a host round trip apart and that is correct — the violation is not the gap, it is a consumer that treats the earlier event as the transfer. The monitor cannot see that consumer, so it reports the condition and leaves the judgement to whoever integrated it, which is the honest thing for a check whose correctness depends on something outside its scope.

Deliberately simplified: the tag-liveness inputs are supplied from outside rather than tracked here, so the monitor trusts a structure it is partly checking — a real deployment binds it to completion_timer's live array directly. v_overadvertised compares two numbers that are set once at configuration, so it is an elaboration check masquerading as a runtime one. And there is no check for prohibition 5 — that a Successful status does not imply correct data — because no monitor at this boundary can write it: verifying the data requires knowing what the completer should have returned, which is information the bridge does not have and Section 20's class 112 is exactly about pretending otherwise.

Production implication: v_orphan_completion is the check worth putting in silicon rather than in simulation, and the reason is the tag-reuse bug in Section 14. An orphan completion in the field is evidence that a timeout fired and the completer answered anyway, which is a system-integration fact that no amount of pre-silicon verification will produce, because pre-silicon completers are models that answer or do not. One sticky bit and a captured tag is the whole cost, and it converts the worst class of silent data corruption in this chapter into a named event with a timestamp.


17. The Two Fabrics, Priced Side by Side

Six mechanisms priced on both sides in bitcell equivalents. Addressing per port costs Ethernet one hundred and ninety six thousand six hundred and eight and PCIe seven thousand six hundred and eighty. State held after a transfer is issued costs Ethernet nothing and PCIe four hundred and twenty five thousand and eighty eight. Flow control counters per port cost Ethernet about six hundred and PCIe fifty seven thousand six hundred. Ordering enforcement costs Ethernet nothing and PCIe four hundred and eighty. Per transaction timers cost Ethernet nothing and PCIe eighty seven thousand and forty at two hundred and fifty six tags. And failure reporting costs Ethernet nothing, which is why an entire module of this track exists to infer causes from counts, while PCIe names every failure at the moment it happens.Ethernetholds nothingPCIeholds everythingAddressing196 608 vs 7 680State after issue0 vs 425 088Flow control~600 vs 57 600Ordering0 vs 480Timers0 vs 87 040Failure reportingnone vs named12
Figure 4 — four of the six mechanisms cost Ethernet nothing, and the fifth is the reason Module 21 exists.

Everything this chapter derived, in one table, in one unit.

MechanismEthernet, BCEPCIe, BCERatio
addressing, per port196 6087 680PCIe 25.6× cheaper
state after a transfer is issued0425 088Ethernet holds nothing
flow-control counters, per port~600 — a PAUSE timer and a watermark57 600PCIe 96× more
flow-control buffer obligation, per port7.67 KiB at 100 Gb/s — Chapter 14.2 §950 000 B at 400 Gb/sboth are bandwidth-delay products
ordering enforcement0480Ethernet enforces nothing
per-transaction timers087 040 at 256 tagsEthernet times nothing

Four of the six rows are zero on the Ethernet side and that is the chapter's summary.

A load/store fabric's cost is the state it holds about things it has not yet finished. A packet network holds nothing, and its cost is that nobody can tell it whether anything finished.

Restate that as the design question each answers.

Ethernet answersPCIe answers
the questionhow do I deliver to a station whose location I do not know?how do I read a location and get the value back?
what it must havea learning table and a flood pathtags, ordering, credits and timers
what it gives upany notion of completionany tolerance for an unknown address

And the cost of each side's answer, totalled at a 64-port switch.

BCE× the datapath
Chapter 23.3's Ethernet switch, total5.62 × 10⁸1 985
its forwarding tables — the naming cost5.04 × 10⁷178
a PCIe switch's decode, 64 ports491 5201.73
a PCIe switch's credit counters, 64 ports3 686 40013.01

Row two against row three is 102 times, and the reason is exactly one bit: an Ethernet address can be absent from the table and a PCIe address cannot be absent from the space.


18. What the Comparison Assumes

Eight assumptions, and the chapter is wrong in a stated direction if any of them fails.

#AssumptionIf it is false
1the round trip is 1 µsevery return-path figure scales linearly — 2 µs doubles 425 088 BCE
2completions carry 256 octetsa smaller MAX_PAYLOAD_B raises the tag count proportionally and leaves the reorder buffer unchanged
3the MAC table is 128k × 96 bChapter 23.3 §2's figure; a smaller table narrows the 25.6× and raises the flood rate
4a switch port has three decode windowsa port that also routes configuration transactions needs bus-number ranges too, adding perhaps 64 flops
5credits are per virtual channel and there are eighta single-VC link divides Section 6's 57 600 BCE by eight
6the reorder buffer is the full bandwidth-delay producta consumer tolerating out-of-order data needs a fraction of it — and most do not
7an Ethernet transmitter holds no state after transmissiontrue of the MAC; false of anything above it, and TCP's retransmission buffer is the counterexample
8BCE applies to both sidesSection 19 examines this and it holds

Assumption 7 is the one that deserves its own paragraph, because it is the sentence most likely to be quoted out of context.

An Ethernet MAC holds nothing after transmission. An Ethernet system usually holds a great deal, because TCP above it holds every unacknowledged segment until it is acknowledged — which is a return path, implemented in software, over a protocol that has none. The retransmission buffer at 400 Gb/s and 1 µs is the same 50 kB Section 4 derived.

Where the return-path state lives
PCIein the requester's hardware, mandatory
Ethernet plus TCPin the sender's host memory, by the transport's choice
Ethernet plus UDPnowhere, and the application accepts loss

So the difference is not whether the state exists. It is whether the protocol requires it, and therefore whether a design that omits it is broken or merely making a different trade. Chapter 25.1 is about the confusion this creates.


19. The Cost, Accounted — in BCE

This chapter's blocks, and the design each describes.

BlockFlopsBCE× the datapath
address_model64 — two counters1 2800.005
outstanding_reads489600.003
credit_accountant, 3 types60 + 32 stall1 8400.006
ordering_scoreboard48 + 321 6000.006
eth_pcie_bridge1442 8800.010
completion_timer, 256 tags4 35287 0400.307
pcie_cmp_telemetry3527 0400.025
pcie_cmp_conformance0 — combinational00
this chapter's additions5 132102 6400.362

completion_timer is 84.8% of the total and it is the only block that scales with the tag count, which is the chapter's structure showing through: the expensive thing about a return path is having one entry per outstanding question.

And the designs the blocks describe, which are three orders of magnitude larger.

BCE× the datapath
a 400 Gb/s PCIe requester's return path425 0881.500
its credit buffer obligation, 1 VC400 0001.412
the same, 8 guaranteed VCs3 200 00011.29
Chapter 23.3's Ethernet switch5.62 × 10⁸1 985
Chapter 23.4's NIC5.07 × 10⁷179

Row five is a part that contains both columns of this chapter, and the reason a NIC is harder than either a switch port or a root complex is that it must hold the return-path state of one protocol while terminating a protocol that has none.


20. Properties Worth Asserting, and One Worth Refusing

Fifty-one properties in six groups, and every one of them is about a boundary rather than about a block.

Group A — addressing (8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A1. An Ethernet lookup may miss. A PCIe decode may not.
p_ad_eth_may_miss: assert property (@(posedge clk) disable iff (!rst_n)
  (is_ethernet && !lookup_hit) |-> miss_occurred);

// A2. And a decode miss is an error rather than a delivery.
p_ad_pcie_miss_is_error: assert property (@(posedge clk) disable iff (!rst_n)
  (!is_ethernet && !decode_hit) |-> (miss_occurred && miss_is_reported));

// A3. The replication factor is the port count less one, on Ethernet.
p_ad_flood_width: assert property (@(posedge clk) disable iff (!rst_n)
  (is_ethernet && miss_occurred) |-> (copies_on_miss == (ports - 16'd1)));

// A4. And exactly one, on PCIe.
p_ad_one_completion: assert property (@(posedge clk) disable iff (!rst_n)
  (!is_ethernet && miss_occurred) |-> (copies_on_miss == 16'd1));

// A5. lookup_can_miss is the addressing model, not a runtime condition.
p_ad_model_is_static: assert property (@(posedge clk) disable iff (!rst_n)
  $stable(is_ethernet) |-> $stable(lookup_can_miss));

// A6. The area figure follows the model and nothing else.
p_ad_area_follows_model: assert property (@(posedge clk) disable iff (!rst_n)
  is_ethernet |-> (addressing_bce_per_port == 32'(mac_table_bce_per_port())));

// A7. A miss always increments, so the count is never behind.
p_ad_miss_counted: assert property (@(posedge clk) disable iff (!rst_n)
  miss_occurred |=> (c_misses == $past(c_misses) + 32'd1));

// A8. Flooded copies accumulate at the replication factor.
p_ad_copies_accumulate: assert property (@(posedge clk) disable iff (!rst_n)
  miss_occurred |=> (c_flooded_copies ==
                     $past(c_flooded_copies) + 32'($past(copies_on_miss))));

Group B — the return path (10).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// B1. Little's law, evaluated rather than assumed.
p_rp_littles_law: assert property (@(posedge clk) disable iff (!rst_n)
  (rate_gbps != 0) |-> (bytes_inflight == ((32'(rate_gbps) * 32'(rtt_ns)) / 32'd8)));

// B2. The tag count rounds up, never down.
p_rp_tags_round_up: assert property (@(posedge clk) disable iff (!rst_n)
  (32'(tags_required) * 32'(MAX_PAYLOAD_B)) >= bytes_inflight);

// B3. The reorder buffer dominates the tag table.
p_rp_reorder_dominates: assert property (@(posedge clk) disable iff (!rst_n)
  (bytes_inflight > 32'd1024) |-> (reorder_share_pct >= 16'd80));

// B4. The Ethernet comparison is a constant and stays one.
p_rp_ethernet_zero: assert property (@(posedge clk) disable iff (!rst_n)
  (ethernet_equivalent_bce == 32'd0));

// B5. Outstanding never exceeds the limit the design implements.
p_rp_within_limit: assert property (@(posedge clk) disable iff (!rst_n)
  (outstanding_now <= tag_limit));

// B6. Issue and retire move the count by exactly one.
p_rp_count_moves_by_one: assert property (@(posedge clk) disable iff (!rst_n)
  (read_issued && !completion_rx) |=>
    (outstanding_now == $past(outstanding_now) + 16'd1));

// B7. And a completion with no issue may not decrement below zero.
p_rp_no_underflow: assert property (@(posedge clk) disable iff (!rst_n)
  (completion_rx && ($past(outstanding_now) == 16'd0)) |=>
    (outstanding_now == 16'd0));

// B8. Chapter 21.8 Section 5's ceiling, stated as an implication.
p_rp_ceiling: assert property (@(posedge clk) disable iff (!rst_n)
  tag_limit_binds |-> (achievable_gbps < rate_gbps));

// B9. A tag issued beyond the limit is counted as exhaustion.
p_rp_exhaustion_counted: assert property (@(posedge clk) disable iff (!rst_n)
  (read_issued && (outstanding_now >= tag_limit)) |=>
    (c_tag_exhausted == $past(c_tag_exhausted) + 32'd1));

// B10. The return path's total is the sum of its two parts and nothing more.
p_rp_total_is_sum: assert property (@(posedge clk) disable iff (!rst_n)
  (return_path_bce_o == tag_table_bce + reorder_bce));

Group C — credits (9).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// C1. At reset nothing may be transmitted.
p_cr_reset_forbids: assert property (@(posedge clk)
  $rose(rst_n) |-> !may_transmit);

// C2. A packet is sent only with credit for it.
p_cr_gate_honoured: assert property (@(posedge clk) disable iff (!rst_n)
  (tlp_sent && !may_transmit) |-> stalled_on_credit);

// C3. Consumed never passes limit.
p_cr_never_overrun: assert property (@(posedge clk) disable iff (!rst_n)
  (hdr_consumed[0] <= hdr_limit[0]) &&
  (hdr_consumed[1] <= hdr_limit[1]) &&
  (hdr_consumed[2] <= hdr_limit[2]));

// C4. A credit return raises the limit and nothing else.
p_cr_return_raises_limit: assert property (@(posedge clk) disable iff (!rst_n)
  (credit_return && (ret_type == 2'd0)) |=>
    (hdr_limit[0] == $past(hdr_limit[0]) + 8'd1));

// C5. The obligation is the bandwidth-delay product, times the VCs.
p_cr_obligation: assert property (@(posedge clk) disable iff (!rst_n)
  !per_vc_guaranteed |->
    (buffer_obligation_bce ==
     32'(bytes_in_flight(int'(rate_gbps), int'(rtt_ns))) * 32'd8));

// C6. And it is always larger than the counters that account for it.
p_cr_buffer_exceeds_counters: assert property (@(posedge clk) disable iff (!rst_n)
  (rate_gbps >= 16'd100) |-> (buffer_obligation_bce > credit_bce_o));

// C7. A stall is counted on the cycle it occurs.
p_cr_stall_counted: assert property (@(posedge clk) disable iff (!rst_n)
  stalled_on_credit |=>
    (c_credit_stall_cycles == $past(c_credit_stall_cycles) + 32'd1));

// C8. The flop figure is a function of the parameters, not of traffic.
p_cr_flops_static: assert property (@(posedge clk) disable iff (!rst_n)
  (credit_flops_o == 32'(credit_flops_per_port())));

// C9. Eight guaranteed VCs is eight times one.
p_cr_vc_scaling: assert property (@(posedge clk) disable iff (!rst_n)
  (per_vc_guaranteed && (VCS == 8)) |->
    (buffer_obligation_bce ==
     32'd8 * 32'(bytes_in_flight(int'(rate_gbps), int'(rtt_ns))) * 32'd8));

Group D — ordering (8).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// D1. A completion may not pass a pending posted write.
p_or_cpl_blocked: assert property (@(posedge clk) disable iff (!rst_n)
  (xact_issue && (xact_kind == 2'(XACT_COMPLETION)) && (pending_posted != 0))
    |-> !may_proceed);

// D2. A non-posted read is never blocked by a posted write.
p_or_np_not_blocked: assert property (@(posedge clk) disable iff (!rst_n)
  (xact_issue && (xact_kind == 2'(XACT_NONPOSTED))) |-> may_proceed);

// D3. Ethernet orders nothing, and the model says so.
p_or_ethernet_none: assert property (@(posedge clk) disable iff (!rst_n)
  (ethernet_orders_this == 1'b0));

// D4. The counter form costs three counters, always.
p_or_counter_cost: assert property (@(posedge clk) disable iff (!rst_n)
  (counter_flops == 32'd24));

// D5. And the pairwise form is quadratic in the outstanding count.
p_or_pairwise_quadratic: assert property (@(posedge clk) disable iff (!rst_n)
  (outstanding_max >= 16'd2) |->
    (pairwise_comparisons ==
     ((32'(outstanding_max) * (32'(outstanding_max) - 32'd1)) / 32'd2)));

// D6. Issue increments exactly one class.
p_or_one_class: assert property (@(posedge clk) disable iff (!rst_n)
  (xact_issue && may_proceed && (xact_kind == 2'(XACT_POSTED))) |=>
    (pending_posted == $past(pending_posted) + 16'd1));

// D7. No pending count goes negative.
p_or_no_underflow: assert property (@(posedge clk) disable iff (!rst_n)
  (pending_posted >= 16'd0) && (pending_nonposted >= 16'd0) &&
  (pending_completion >= 16'd0));

// D8. A block is counted.
p_or_block_counted: assert property (@(posedge clk) disable iff (!rst_n)
  (xact_issue && blocked_by_posted) |=>
    (c_order_blocks == $past(c_order_blocks) + 32'd1));

Group E — the bridge (10).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// E1. The payload TLP count rounds up.
p_br_tlps_round_up: assert property (@(posedge clk) disable iff (!rst_n)
  (payload_tlps * 16'(MAX_PAYLOAD_B)) >= frame_octets);

// E2. Overhead is two transactions per frame, whatever the size.
p_br_overhead_constant: assert property (@(posedge clk) disable iff (!rst_n)
  (overhead_tlps == 16'd2));

// E3. So the overhead share falls as the frame grows.
p_br_share_falls: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_octets > 16'd1500) |-> (overhead_share_pct <= 16'd25));

// E4. A TLP is requested only inside a frame with a descriptor.
p_br_needs_descriptor: assert property (@(posedge clk) disable iff (!rst_n)
  tlp_request |-> descriptor_valid);

// E5. The cursor advances by the payload size on every acceptance.
p_br_cursor_advances: assert property (@(posedge clk) disable iff (!rst_n)
  tlp_accepted |=> (tlp_addr == $past(tlp_addr) + 64'(MAX_PAYLOAD_B)));

// E6. The last TLP of a frame is the remainder, not a full payload.
p_br_last_is_remainder: assert property (@(posedge clk) disable iff (!rst_n)
  (tlp_request && (tlps_for_frame - overhead_tlps == payload_tlps) &&
   (frame_octets % 16'(MAX_PAYLOAD_B) != 0)) |->
    (tlp_octets <= 16'(MAX_PAYLOAD_B)));

// E7. The two completions are never the same event.
p_br_completions_differ: assert property (@(posedge clk) disable iff (!rst_n)
  (frame_complete_ethernet && frame_complete_pcie) |-> !completions_disagree);

// E8. A frame is counted only when the PCIe side finished.
p_br_count_on_pcie: assert property (@(posedge clk) disable iff (!rst_n)
  writeback_acked |=> (c_frames_bridged == $past(c_frames_bridged) + 32'd1));

// E9. Every accepted TLP is counted.
p_br_tlps_counted: assert property (@(posedge clk) disable iff (!rst_n)
  tlp_accepted |=> (c_tlps_issued == $past(c_tlps_issued) + 32'd1));

// E10. Section 14 prohibition 1, as a bound rather than a prohibition:
//      the gap between the two completions is at most a timeout.
p_br_gap_bounded: assert property (@(posedge clk) disable iff (!rst_n)
  frame_complete_ethernet |-> ##[1:CPL_TIMEOUT_CYCLES]
    (frame_complete_pcie || cpl_timeout));

Group F — timers, telemetry and conformance (6).

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// F1. Every issued read becomes live.
p_tm_issue_makes_live: assert property (@(posedge clk) disable iff (!rst_n)
  read_issued |=> live[$past(issued_tag)]);

// F2. The class-112 replacement property 4: a completion for a dead tag is an orphan.
p_tm_orphan: assert property (@(posedge clk) disable iff (!rst_n)
  (completion_rx && !live[cpl_tag]) |=>
    (c_unexpected_cpl == $past(c_unexpected_cpl) + 32'd1));

// F3. A timeout frees the tag.
p_tm_timeout_frees: assert property (@(posedge clk) disable iff (!rst_n)
  timeout_fired |=> !live[$past(timeout_tag)]);

// F4. Ethernet needs no timers, and the model reports zero.
p_tm_ethernet_zero: assert property (@(posedge clk) disable iff (!rst_n)
  (ethernet_timer_bce == 32'd0));

// F5. The conformance monitor's violation vector is its own disjunction.
p_cf_vector: assert property (@(posedge clk) disable iff (!rst_n)
  conformant |-> (violations == 6'b000000));

// F6. Loss attribution is 100% only when every drop has a named cause.
p_tl_attribution: assert property (@(posedge clk) disable iff (!rst_n)
  (loss_attributable_pct == 16'd100) |->
    ((c_eth_dropped == 48'd0) || (c_desc_starved >= c_eth_dropped)));

Coverage — the states a bridge only reaches under load.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_ad_flood:        cover property (@(posedge clk) is_ethernet && miss_occurred);
c_ad_unsupported:  cover property (@(posedge clk) !is_ethernet && miss_occurred);
c_rp_tag_bound:    cover property (@(posedge clk) tag_limit_binds);
c_rp_at_limit:     cover property (@(posedge clk) outstanding_now == tag_limit);
c_cr_stalled:      cover property (@(posedge clk) stalled_on_credit);
c_cr_zero_credit:  cover property (@(posedge clk) hdr_limit[0] == 8'd0);
c_or_blocked:      cover property (@(posedge clk) blocked_by_posted);
c_br_min_frame:    cover property (@(posedge clk) frame_start && (frame_octets <= 16'd64));
c_br_jumbo:        cover property (@(posedge clk) frame_start && (frame_octets >= 16'd9000));
c_br_disagree:     cover property (@(posedge clk) completions_disagree);
c_tm_timeout:      cover property (@(posedge clk) timeout_fired);
c_tm_orphan_cpl:   cover property (@(posedge clk) completion_rx && !live[cpl_tag]);
c_cf_overadv:      cover property (@(posedge clk) v_overadvertised);
c_cf_uncounted:    cover property (@(posedge clk) v_uncounted_drop);


21. Verification Scenarios

Fifty-eight scenarios in six groups, plus one directed test that random stimulus will not produce.

Group 1 — addressing (9).

#ScenarioExpect
1Ethernet mode, lookup hitsno miss; 196 608 BCE per port reported
2Ethernet mode, lookup misses63 copies on a 64-port switch; c_flooded_copies += 63
3PCIe mode, decode hitsno miss; 7 680 BCE per port
4PCIe mode, decode misses1 completion; miss_is_reported high
5the ratio between modes25.6× — p_ad_area_follows_model
6is_ethernet toggled mid-runp_ad_model_is_static fires
7a 128-port switch, Ethernet miss127 copies
8a 2-port switch, Ethernet miss1 copy — the flood degenerates
91 000 consecutive misses, Ethernet63 000 flooded copies

Group 2 — the return path (11).

#ScenarioExpect
10400 Gb/s, 1 µs round trip50 000 B in flight, 196 tags
11400 Gb/s, 0.5 µs25 000 B, 98 tags
12400 Gb/s, 2 µs100 000 B, 391 tags
13100 Gb/s, 1 µs12 500 B, 49 tags — Chapter 19.6 §4's figure
14the return-path total at 400 Gb/s, 1 µs425 088 BCE, 1.500 datapaths
15reorder_share_pct at the same point94 — p_rp_reorder_dominates
16ethernet_equivalent_bce at any setting0
17a tag limit of 64 at 400 Gb/s, 1 µstag_limit_binds; 131 Gb/s achievable — 32.8%
18a tag limit of 8 at 400 Gb/s, 1 µs16.4 Gb/s — 4.1% of the link
19a tag limit of 256 at the same pointdoes not bind
20issue at the limitc_tag_exhausted increments

Group 3 — credits (10).

#ScenarioExpect
21reset released, no DLLP yetmay_transmit low — p_cr_reset_forbids
22one header credit returnedone packet may go, the next may not
23a data credit return of 16dat_limit rises by 16
24transmit with may_transmit lowstalled_on_credit; p_cr_gate_honoured
25credit counters, 8 VCs, both directions2 880 flops, 57 600 BCE per port
2664 ports3 686 400 BCE, 13.01 datapaths
27the obligation at 400 Gb/s, 1 µs, 1 VC400 000 BCE — 6.9× the counters
28the same with 8 guaranteed VCs3 200 000 BCE — 55.6× the counters
29a partner that returns credits every 10 µsthe link runs at a tenth of its rate and nothing fails
30a partner that never returns creditsthe link stops; c_credit_stall_cycles climbs forever

Group 4 — ordering (9).

#ScenarioExpect
31a completion issued with a posted write pendingblocked — p_or_cpl_blocked
32a non-posted read with a posted write pendingproceeds — p_or_np_not_blocked
33the producer-consumer sequencethe data write retires before the flag write
34the same sequence with relaxed ordering setno block; c_order_blocks stays at zero
35196 outstanding, pairwise model19 110 comparisons reported
36the counter model at any depth24 flops, 480 BCE
37ethernet_orders_this at any setting0
38retire with nothing pendingno underflow — p_or_no_underflow
391 024 outstanding, pairwise model523 776 comparisons — the shape, at scale

Group 5 — the bridge (10).

#ScenarioExpect
40a 64-octet frame1 payload TLP, 2 overhead, 66.7% overhead share
41a 1 518-octet frame6 payload TLPs, 25.0% overhead share
42a 9 018-octet frame36 payload TLPs, 5.3% overhead share
431 518-octet frames at 400 Gb/s32.51 Mpps, 260.1 MTLP/s
44descriptor_valid low when a frame arrivesno TLP issued; the frame is lost with no protocol event
45the same, with descriptor_starved wiredloss_attributable_pct stays at 100
46the same, without itattribution falls; the drop has no reporter anywhere
47frame_end without writeback_ackcompletions_disagree for a host round trip — 32 frames
48v_premature_complete observeda warning, not an error — the gap is correct
49a frame whose length is an exact multiple of 256no remainder TLP; p_br_last_is_remainder vacuous

Group 6 — timers and conformance (9).

#ScenarioExpect
50256 tags, 16-bit ages4 352 flops, 87 040 BCE, 0.307 datapaths
51a read with no completion for 50 µstimeout_fired; the tag is freed
52a completion for a tag never issuedc_unexpected_cpl increments; v_orphan_completion
53a completion with CPL_UNSUPPORTED_REQc_error_cpl increments; the tag still frees
54advertised_credits above buffer_entriesv_overadvertised
55a frame dropped with no counterv_uncounted_drop
56a TLP transmitted without creditv_credit_overrun
57ethernet_timer_bce at any setting0
58all six violations clearconformant high

22. Debugging a Bridge

Six symptoms, and each is a place where the two protocols' vocabularies stop translating.

SymptomFirst questionWhere to look
receive drops at line rate, idle host, non-full ringis c_desc_starved counting?Chapter 23.4 §7 — the prefetch store is sized for a previous generation
a link that runs at exactly a third of its rateis tag_limit_binds set?Section 5 — the ceiling is tags × payload ÷ latency and the latency is not yours
a link that runs at exactly a third of its rate, and the tag limit is finewhat is c_credit_stall_cycles against hdr_limit?Section 7 — stalls with a high limit is slow return; with a low limit it is a small buffer
a device that disappears under load and returns after resetis c_timeouts non-zero?Section 13 — the timeout is probably marginal rather than the completer dead
intermittent wrong data, one machine in fiftyis c_unexpected_cpl non-zero?Section 21's directed test — tag reuse after a timeout
the two sides' frame counts differ by a constantwhich side counts first?Section 11 — they are a host round trip apart and both are right

Row six is the one that wastes the most time and it is not a bug.

A bridge's Ethernet frame counter and its PCIe write-back counter differ by the number of frames in flight, which at 400 Gb/s, 1 µs and 1 518-octet frames is 32.5 — so a snapshot taken at any instant shows a difference of about 32 and it never converges while traffic flows. The correct procedure is to quiesce, wait a round trip, and then compare, and a team that compares under load concludes that frames are being lost at a rate of thirty-two per sample.


23. Misconceptions

Misconception 1 — "PCIe is faster than Ethernet."

The wrong model: a load/store fabric has lower latency and higher bandwidth, so it is the better interconnect wherever both would fit.

What it costs: a design that reaches for PCIe over distance and discovers that its round-trip-proportional state is unaffordable. At 400 Gb/s, Section 4's return path is 425 088 BCE at 1 µs and 850 176 at 2 µs — and 2 µs is barely thirty metres of fibre plus two switch hops. A fabric that spans a data-centre row does not get to hold a bandwidth-delay product of state per link.

The corrected model: PCIe's advantages are properties of short distances and fixed membership, and both terms appear in its cost. A comparison that measures only rate and latency is measuring the two quantities that do not distinguish them.

Misconception 2 — "the credit counters are the cost of credit-based flow control."

The wrong model: credits are a few registers per port and are therefore cheap.

What it costs: a buffer budget that is wrong by a factor between 7 and 56. Section 7 derives 57 600 BCE of counters against 400 000 BCE of obligation at one VC, and 3 200 000 at eight guaranteed VCs. A design review that inspects the credit logic has inspected 1.8% of the mechanism.

The corrected model: a credit is a promise that buffer exists, and the promise is the cost. The counters merely record it.

Misconception 3 — "an Ethernet address and a PCIe address do the same job."

The wrong model: both identify where a transfer is going, so the difference is encoding.

What it costs: a bridge design that assumes a translation table can be built, and a debugging model that expects the wrong failures. Section 2: a name can be absent and a position cannot, and that one bit produces the flood path, the aging rule, Chapter 12.5's capacity argument and Chapter 21.6's entire diagnostic apparatus on one side, and none of it on the other.

The corrected model: the descriptor is the translation and software wrote it before the frame existed. There is no hardware mapping between the two address spaces and there cannot be one.

Misconception 4 — "Ethernet holds no state, so it is simpler."

The wrong model: four of Section 17's six rows are zero on the Ethernet side, so an Ethernet design has less to get right.

What it costs: the expectation that Module 21 should have been short. Ethernet holds no state about a transfer and therefore has no reporter for its failure, which is why Chapter 21.6 needs a selectivity index, a shape classifier and a clustering probe to infer from counts what PCIe reads off a status field.

The corrected model: the state a protocol holds is also the evidence it can produce. Holding nothing is cheap in silicon and expensive in diagnosis, and the two costs land on different teams in different years.

Misconception 5 — "Chapter 23.4's 149 descriptors in flight is a NIC's Ethernet sizing problem."

The wrong model: a high-rate NIC needs a deep prefetch store because Ethernet frames arrive quickly.

What it costs: looking for the fix on the wrong side. Section 4 establishes that the 149 are non-posted reads awaiting completions — every term is PCIe vocabulary — and that the Ethernet side of the same part holds nothing at all. A team that treats it as a MAC problem tunes the MAC and the underruns continue.

The corrected model: it is PCIe's return path measured inside an Ethernet part, and it scales with the host's latency rather than with the line rate alone.

Misconception 6 — "verification IP ports across a bridge if the signal names map."

The wrong model: a property is a relation between signals; if every signal has a counterpart, the property has one.

What it costs: a passing regression on the side that was not verified. Section 20's class 112: frame_complete and transaction_complete both exist, the ported property compiles and passes, and it certifies that a message arrived rather than that an event occurred.

The corrected model: check whether each term's OWNER is the same on both sides. A property whose terms are local on one side and remote on the other has changed subject, and the vocabulary will not tell you.


24. Interview Questions

Six, with what a strong answer contains.

1. What is the difference between an Ethernet address and a PCIe address?

An Ethernet address is a name and a PCIe address is a position. A strong answer goes to the consequence rather than the definition: a name is resolved by a lookup that can fail, and the failure produces a flood; a position is decoded against a range and cannot fail, and an unmatched one is a reported error. The best answers put a number on it — 196 608 BCE per port of MAC table against 7 680 of decode windows, 25.6× — and then observe that the expensive side is the one that can still not find the destination.

2. Why does a 400 Gb/s PCIe link need hundreds of tags?

Little's law. bytes in flight = rate × round trip; at 50 GB/s and 1 µs that is 50 000 bytes, and at 256 bytes per completion, 196 outstanding reads. A strong answer continues to the part people forget: the tags are 25 088 BCE and the reorder buffer behind them is 400 000 — 94% of the cost — so a conversation about tag counts is a conversation about 6% of the problem.

3. A 400 Gb/s link is delivering 131 Gb/s and every assertion passes. What do you check?

The outstanding limit against the round trip. 131 = 64 × 256 × 8 ÷ 1 000, so 64 tags at a microsecond is exactly 131 Gb/s and the design is working perfectly. A strong answer names Chapter 21.8 §20's class 101 — a rate asserted on a mechanism that bounds a count — and says that the useful output is not a failing assertion but a reported tag_limit_binds bit.

4. Why can a producer-consumer pattern be written directly on PCIe and not on Ethernet?

Because PCIe orders posted writes against each other and Ethernet orders nothing across paths. A strong answer names Chapter 15.2 as the reason — two frames between the same pair may hash to different link-aggregation members — and then makes the positive case: that lack of ordering is what lets Chapter 23.2 §12 stripe a collective across eight paths and take it from 28.4% to 77.1%. The ordering is not missing; it was traded.

5. What does the ordering specification's shape cost?

Twenty-four flip-flops, because it is stated over three transaction types rather than over transactions. The strong answer contrasts it with the specification a naive author writes — transactions complete in issue orderwhich is a relation between individuals and would need 19 110 comparisons at 196 outstanding. The general point: a relation over a small set of classes is linear where the same relation over individuals is quadratic, and most expensive requirements have been stated over the wrong objects.

6. You port your Ethernet assertion suite onto the PCIe side of a NIC and it all passes. What have you verified?

The bridge's own reporting. A strong answer walks one property: last_octet_sent |-> ##1 frame_complete becomes tlp_handed_to_link_layer |-> ##1 transaction_complete, every term has a counterpart, and transaction_complete is a message from a completer rather than an observation here. The best answers name the repair — split it into a local half, a relation stated as a relation, and a timeout — and note that the property that catches a completer inventing completions is the one the ported version made impossible to write.


25. Questions and Answers


26. What's Next

This chapter set Ethernet against a fabric that answers a different question. Chapter 24.2 sets it against one that answers the same question differently.

InfiniBand carries packets between endpoints, like Ethernet, and it does not drop them. Credit-based flow control at the link layer means a sender transmits only into space the receiver has guaranteed — which removes the event Chapter 21.6 built an entire chapter around, and appears at first reading to be strictly better.

It is not, and the arithmetic says why. The buffer a lossless link must reserve is a round trip of the link's own bandwidth, per virtual lane, per hop — Section 6 of this chapter derived that obligation for one PCIe port and found it 6.9 times the counters that account for it. Chapter 24.2 takes the same derivation to a 64-port fabric at 400 Gb/s and asks what fraction of Chapter 23.3's 5.62 × 10⁸ BCE switch it consumes. The answer at data-centre link lengths is a large fraction, and at two kilometres it exceeds the whole switch.

And it asks what the credit scheme puts back in exchange for the drop it removed, which is Chapter 14.3's head-of-line blocking and congestion spreading made deterministic rather than probabilistic — because with credits the upstream is not merely likely to stall, it is required to. Then it places RoCE precisely: which parent it takes its transport from, which its link layer, and what it inherits from each.

Continue learning

Standards & specifications

Governing standard
IEEE Std 802.3 (Ethernet)(opens IEEE in a new tab)

Defines the Ethernet MAC, the media-independent interfaces and the physical-layer sublayers, including framing, access control, auto-negotiation and per-rate PHY specifications. VLAN tagging, priority and time-sensitive shaping are defined by IEEE 802.1, not by 802.3.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the Ethernet curriculum.