Skip to content

PCIe · Module 21

Routing — How a Switch Decides Which Port a Packet Leaves By

A switch has several ports and must pick exactly one for every packet. Three independent methods decide it — by address, by identifier, or implicitly — and each is a different lookup against a different set of registers.

Every chapter so far has assumed one Link. Two ports, point to point, and a packet leaving one end arrives at the other because there is nowhere else for it to go.

A switch breaks that assumption. It has several ports, and for every packet it must answer one question: which port does this leave by?

The question is harder than it sounds, because different packet types carry entirely different information about where they are going. A Memory Write carries an address. A Completion carries an identifier. A power-management message carries neither — and still has a destination.

So how does a switch decide, and what happens when the answer is "none of them" or "more than one"?

1. The Verified Sources

2. Every Port Is a Bridge

§1's model sentence is the foundation: "each PCI Express port is a virtual PCI-to-PCI bridge device, with its own set of PCI Express Configuration registers."

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
                    upstream port
                 (virtual P2P bridge)

                 ─────────┴─────────  internal virtual PCI bus
                    │     │     │
                   P2P   P2P   P2P     downstream ports
                    │     │     │       (virtual P2P bridges)

Three consequences, and they explain almost everything that follows.

Each port has its own windows. §1: "each downstream bridge has its own Base and Limit." Routing is therefore not one table but a set of per-port configurations — which is why §9's hardware is a CAM per method rather than a single decoder.

Software configures a switch by configuring bridges. There is no "switch driver" — enumeration walks P2P bridges, and the routing configuration is a side effect of assigning bus numbers and address windows. Chapter 21.4 owns enumeration at scale.

And the internal bus is a real routing destination. §1 shows Type 1 Configuration Requests targeting "the internal virtual PCI Bus" by matching the upstream bridge's Secondary Bus Number — so "which port" sometimes means "none; it is for a bridge register here" (§6).

3. Three Methods, Three Lookups

Different TLP types carry different destination information, so a switch performs different lookups.

MethodUsed byLooks upAgainst
AddressMemory and I/O Requeststhe addressBase/Limit windows (§4)
IDCompletions, Configuration Requeststhe Bus NumberSecondary/Subordinate ranges (§5)
Implicitcertain Messagesthe TLP typeno window at all (§7)

§1's three CAMs map one-to-one onto the first two rows — AMCAM for memory addresses, IOAMCAM for I/O addresses, BusNoCAM for bus numbers.

And the third row is the one people forget. A message with no address and no meaningful destination ID still has to go somewhere, and the answer comes from what kind of packet it is rather than from any field being compared.

4. Address Routing

§1's rule, in both directions:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
downstream   if the address falls within a downstream bridge's Base..Limit window
upstream     if it does not fall within the upstream bridge's window

Two windows per bridge, per §1: a non-prefetchable Memory Base/Limit pair, and a Prefetchable Memory Base/Limit pair — and the upstream decision explicitly accounts for both ("provided the address is not within the range defined by the Prefetchable Memory Base… and Prefetchable Memory Limit").

On the window-enable convention (§1: "when the Base is less than or equal to the Limit"): a bridge whose Base exceeds its Limit has no window — the range is disabled, not wrapped.

§12 measured what treating it as a wrapped range costs: 33.3% of random (base, limit, address) combinations produce a spurious match — so a disabled window would capture a third of all traffic.

5. ID Routing

§1's rule is two sentences and complete:

"Completions route by the Bus Number established in the Configuration registers. If the Bus Number is in the Secondary or Subordinate range, the packet goes downstream; otherwise, the packet goes upstream."

So ID routing is the same shape as address routing — a range check with an upstream default — against different registers.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Secondary Bus Number    the bus immediately below this bridge
Subordinate Bus Number  the highest bus number reachable below it

A Completion's destination is a Requester it must return to, and that Requester lives at some Bus/Device/Function (Chapter 11.3). The switch compares only the Bus Number against each bridge's range.

Which is why the ranges must nest properly. Enumeration assigns them so that a bridge's Subordinate covers everything below it; §12 verified that non-overlapping ranges produce zero ambiguity across 200,000 lookups — and §8 is about what happens when they overlap.

And §1 shows where a switch's own identity comes from: the upstream port "captures the Type 0 Configuration Write Target Bus Number and Device Number" and uses them "as part of the Requester ID and Completer ID for the requests and completions generated by the upstream port." A switch learns its own address by being configured, not by knowing it.

6. Configuration Requests: Convert, Forward, or Refuse

§1 gives a complete three-way rule, and it is the most precisely specified routing decision in this chapter.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
target Bus == this port's Secondary Bus Number
        →  CONVERT the Type 1 Request into a Type 0 Request
           (the target is ON the bus directly below this bridge)
 
Secondary < target Bus ≤ Subordinate
        →  FORWARD the Type 1 Request unchanged
           (the target is further down; some bridge below will convert it)
 
anything else
        →  UNSUPPORTED REQUEST

Computed boundary cases (§12), with Secondary = 8 and Subordinate = 11:

Target busResult
7Unsupported Request
8convert to Type 0
9forward Type 1
11forward Type 1
12Unsupported Request

And the degenerate case is instructive — Secondary = 8, Subordinate = 8, meaning nothing exists below that bus:

Target busResult
8convert to Type 0
9Unsupported Request

Read the middle row of the first table again. The conversion happens exactly at the Secondary Bus Number, not across the range — because Type 0 means "you are the target", and only the devices on the immediately-adjacent bus are.

And the third outcome is a real, defined response. §1: "invalid, and is terminated… as an Unsupported Request (UR)." A Configuration Request to a bus that does not exist is answered, not dropped — which is what lets enumeration discover the shape of the hierarchy by probing.

7. Implicit Routing

Some Messages carry no address and no meaningful destination identifier. They are still routed — by what they are.

§1's example, verbatim: "PM_PME messages are always routed toward the Root Complex."

No lookup, no window, no comparison. The TLP type determines the direction, and every switch on the path applies the same rule independently.

Why this method has to exist: a device signalling a power-management event does not know the Root Complex's address, and there is no identifier it could put in a header that would mean "whoever is in charge." The routing information is the packet's kind.

This chapter publishes one sourced example and does not enumerate the rest (§1). What generalizes is the mechanism: a third routing class whose lookup is a type decode rather than a range check — which is why §9's RTL has three parallel paths and not two.

8. Exactly One Port, or It Is Misconfigured

9. The Routing Path

Switch routing decision. An arriving TLP enters a type classifier, which selects one of three lookups. Address routing compares the address against per-port memory and I O base and limit windows. ID routing compares the bus number against per-port secondary and subordinate ranges. Implicit routing decodes the message type. All three feed an egress resolver that produces exactly one port, an upstream default when nothing matched, or an ambiguity error. Configuration requests additionally choose between converting to Type 0, forwarding unchanged, or terminating as an Unsupported Request.arriving TLPtype classifieraddress lookupID lookupimplicit decodeegress resolverconfig decisionegress port12
Figure 1 — the routing decision inside a switch. An arriving TLP is classified by type, selecting one of three independent lookups: addresses against per-port base/limit windows, bus numbers against secondary/subordinate ranges, and messages by type alone. Each returns exactly one egress port, no match — which routes upstream by default — or an ambiguity that must be reported rather than resolved. Configuration Requests take a further three-way decision: convert, forward, or terminate as an Unsupported Request.

Three things to read out of the figure.

The classifier comes first, because the method is determined by the TLP type before any comparison happens.

The three lookups are parallel and independent — §1's three CAMs — and exactly one is consulted per packet.

And the resolver is a separate stage with three possible outcomes (§8), not a mux.

10. RTL — Types, Window Decoder and ID Decoder

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SYNTHESIZABLE. Normalized switch-routing types.
// THE ROUTING METHODS are canonical (§3, §1's three CAMs). The struct
// layouts and widths here are internal; no TLP header encoding is
// published in this chapter.
package switch_route_pkg;
 
  parameter int N_DOWN = 4;                       // downstream ports
  parameter int PW = (N_DOWN <= 1) ? 1 : $clog2(N_DOWN);
 
  typedef enum logic [1:0] {
    RM_ADDRESS  = 2'd0,   // Memory / I/O Requests      -> AMCAM / IOAMCAM
    RM_ID       = 2'd1,   // Completions, Config        -> BusNoCAM
    RM_IMPLICIT = 2'd2    // certain Messages           -> by TLP type
  } route_method_e;
 
  typedef enum logic [2:0] {
    TT_MEM_REQ = 3'd0, TT_IO_REQ = 3'd1, TT_CFG0 = 3'd2,
    TT_CFG1    = 3'd3, TT_CPL    = 3'd4, TT_MSG  = 3'd5
  } tlp_kind_e;
 
  // Per-port window set. Section 1: "each downstream bridge has its own
  // Base and Limit", and there are separate non-prefetchable and
  // prefetchable Memory windows plus an I/O window.
  typedef struct packed {
    logic [63:0] mem_base,  mem_limit;
    logic [63:0] pref_base, pref_limit;
    logic [31:0] io_base,   io_limit;
    logic [7:0]  sec_bus,   sub_bus;      // ID routing (§5)
  } port_cfg_t;
 
  // ==================================================================
  // A WINDOW IS ENABLED ONLY WHEN Base <= Limit.
  //
  // Section 1: "when the Base is less than or equal to the Limit".
  // Section 12 measured the alternative -- treating Base > Limit as a
  // WRAPPED range -- at 33.3% spurious matches, so a disabled window
  // would capture a third of all traffic.
  // ==================================================================
  function automatic bit win_hit(input logic [63:0] base,
                                 input logic [63:0] limit,
                                 input logic [63:0] addr);
    return (base <= limit) && (addr >= base) && (addr <= limit);
  endfunction
 
  // Section 1: "If the Bus Number is in the Secondary or Subordinate
  // range, the packet goes downstream; otherwise, the packet goes
  // upstream." Note the range is INCLUSIVE at both ends.
  function automatic bit bus_hit(input logic [7:0] sec,
                                 input logic [7:0] sub,
                                 input logic [7:0] bus);
    return (bus >= sec) && (bus <= sub);
  endfunction
 
endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import switch_route_pkg::*;
 
// SYNTHESIZABLE. Address routing -- section 1's AMCAM / IOAMCAM.
// THREE OUTCOMES, never a priority encode (§8). Section 12 measured
// randomized configurations: 26.1% one match, 67.3% NO match (upstream by
// default), 6.6% MORE THAN ONE -- and the last is a misconfiguration that
// a priority encoder would answer silently.
module route_address (
  input  port_cfg_t [N_DOWN-1:0] cfg,
  input  logic                   port_enabled [N_DOWN],
  input  tlp_kind_e              kind,
  input  logic [63:0]            addr,
 
  output logic [N_DOWN-1:0] match_vec,
  output logic              hit_down,
  output logic              route_upstream,
  output logic              ambiguous,
  output logic [PW-1:0]     egress
);
  wire is_mem = (kind == TT_MEM_REQ);
  wire is_io  = (kind == TT_IO_REQ);
 
  always_comb begin
    for (int i = 0; i < N_DOWN; i++) begin
      match_vec[i] = port_enabled[i] && (
          (is_mem && (win_hit(cfg[i].mem_base,  cfg[i].mem_limit,  addr)
                   || win_hit(cfg[i].pref_base, cfg[i].pref_limit, addr)))
       || (is_io  &&  win_hit({32'd0, cfg[i].io_base},
                              {32'd0, cfg[i].io_limit}, addr)));
    end
  end
 
  // ==================================================================
  // NO MATCH IS AN ANSWER, NOT AN ERROR (§4).
  //
  // Section 1: "The Request goes upstream if it does not target anything
  // within the upstream bridge's Base and Limit range." A bridge knows
  // only what is BELOW it; anything else is somebody else's problem.
  // Section 12: this is the majority path at 67.3%.
  // ==================================================================
  assign hit_down       = (is_mem || is_io) && $onehot(match_vec);
  assign route_upstream = (is_mem || is_io) && (match_vec == '0);
  assign ambiguous      = (is_mem || is_io) && !$onehot0(match_vec);
 
  // Meaningful ONLY on hit_down -- an ambiguous vector must not produce a
  // plausible index that downstream logic might use anyway.
  always_comb begin
    egress = '0;
    if (hit_down)
      for (int i = 0; i < N_DOWN; i++) if (match_vec[i]) egress = PW'(i);
  end
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import switch_route_pkg::*;
 
// SYNTHESIZABLE. ID routing -- section 1's BusNoCAM.
// Same three-outcome shape, different registers: Secondary and
// Subordinate Bus Number rather than Base and Limit (§5).
module route_id (
  input  port_cfg_t [N_DOWN-1:0] cfg,
  input  logic                   port_enabled [N_DOWN],
  input  logic [7:0]             target_bus,
 
  output logic [N_DOWN-1:0] match_vec,
  output logic              hit_down,
  output logic              route_upstream,
  output logic              ambiguous,
  output logic [PW-1:0]     egress
);
  always_comb
    for (int i = 0; i < N_DOWN; i++)
      match_vec[i] = port_enabled[i]
                  && bus_hit(cfg[i].sec_bus, cfg[i].sub_bus, target_bus);
 
  assign hit_down       = $onehot(match_vec);
  assign route_upstream = (match_vec == '0);      // §1: "otherwise... upstream"
  assign ambiguous      = !$onehot0(match_vec);
 
  always_comb begin
    egress = '0;
    if (hit_down)
      for (int i = 0; i < N_DOWN; i++) if (match_vec[i]) egress = PW'(i);
  end
  // Section 12: with properly nested ranges, 0 ambiguity across 200,000
  // lookups. With deliberately overlapping ranges (1-5 and 3-8), 29.9%.
endmodule

Classification: all three synthesizable.

win_hit encodes §1's enable conventionbase <= limit — and §12 measured the alternative at 33.3% spurious matches.

Both decoders return the same three outcomes, because §8's rule is method-independent: one, none, or misconfigured.

Failure — five. A priority encoder answers ambiguity silently (29.9% exposure). Treating a miss as an error breaks every upstream transfer (67.3% of traffic). Treating base > limit as a wrapped range (33.3%). Ignoring the prefetchable window, so half the address map misroutes. And an egress computed regardless of hit_down.

11. RTL — Configuration Decision and Egress Resolver

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import switch_route_pkg::*;
 
// SYNTHESIZABLE. Section 1's three-way Configuration Request rule.
// This is the most precisely sourced decision in the chapter, and the
// boundaries matter: section 12 walked them.
module route_config (
  input  tlp_kind_e   kind,
  input  logic [7:0]  target_bus,
  input  logic [7:0]  this_sec_bus,      // this bridge's Secondary
  input  logic [7:0]  this_sub_bus,      // this bridge's Subordinate
 
  output logic convert_to_type0,
  output logic forward_type1,
  output logic unsupported_request
);
  wire is_cfg1 = (kind == TT_CFG1);
 
  // ==================================================================
  // EXACTLY AT THE SECONDARY BUS NUMBER -> CONVERT.
  //
  // Section 1: "If the Configuration Request matches the ... downstream
  // port Secondary Bus Number, the [switch] converts the Type 1
  // Configuration Request into a Type 0 Configuration Request."
  //
  // Type 0 means "you are the target", and only devices on the
  // immediately-adjacent bus are. Converting across the whole range
  // would address the wrong devices entirely (§13, mutation 6).
  // ==================================================================
  assign convert_to_type0 = is_cfg1 && (target_bus == this_sec_bus);
 
  // Section 1: "if the Bus Number does not match the Secondary Bus
  // Number, but falls within the Subordinate Bus Number range, the
  // Type 1 Configuration Request is forwarded ... unchanged."
  assign forward_type1    = is_cfg1 && (target_bus > this_sec_bus)
                                    && (target_bus <= this_sub_bus);
 
  // Section 1: "A Type 1 Configuration Request that targets a Bus Number
  // that is not within range is invalid, and is terminated ... as an
  // Unsupported Request (UR)."
  //
  // A DEFINED RESPONSE, not a drop -- which is what lets enumeration
  // discover the hierarchy by probing (§6).
  assign unsupported_request = is_cfg1 && !convert_to_type0 && !forward_type1;
endmodule
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
import switch_route_pkg::*;
 
// SYNTHESIZABLE. Combine the three methods into ONE egress decision.
// Exactly one lookup is consulted per packet, chosen by TLP type (§3).
module egress_resolver (
  input  tlp_kind_e     kind,
 
  input  logic          addr_hit,  addr_up,  addr_amb,
  input  logic [PW-1:0] addr_port,
  input  logic          id_hit,    id_up,    id_amb,
  input  logic [PW-1:0] id_port,
 
  // Implicit: section 1 states PM_PME "always routed toward the Root
  // Complex". No window is consulted; the TLP type IS the routing
  // information (§7).
  input  logic          msg_toward_root,
 
  output route_method_e method,
  output logic          egress_valid,
  output logic          egress_upstream,
  output logic [PW-1:0] egress_port,
  output logic          err_ambiguous,
  output logic          err_no_route
);
  always_comb begin
    method          = RM_ADDRESS;
    egress_valid    = 1'b0;
    egress_upstream = 1'b0;
    egress_port     = '0;
    err_ambiguous   = 1'b0;
    err_no_route    = 1'b0;
 
    unique case (kind)
      TT_MEM_REQ, TT_IO_REQ : begin
        method          = RM_ADDRESS;
        egress_valid    = addr_hit;
        egress_port     = addr_port;
        egress_upstream = addr_up;        // §4's default, NOT an error
        err_ambiguous   = addr_amb;
      end
 
      TT_CPL, TT_CFG1 : begin
        method          = RM_ID;
        egress_valid    = id_hit;
        egress_port     = id_port;
        egress_upstream = id_up;
        err_ambiguous   = id_amb;
      end
 
      TT_MSG : begin
        method          = RM_IMPLICIT;
        // No lookup, no comparison -- the type decides.
        egress_upstream = msg_toward_root;
        egress_valid    = !msg_toward_root;
        err_no_route    = 1'b0;
      end
 
      // A Type 0 Configuration Request targets THIS bridge's own
      // registers; it is not forwarded anywhere (§2).
      TT_CFG0 : begin
        method       = RM_ID;
        egress_valid = 1'b0;
        egress_upstream = 1'b0;
      end
 
      default : err_no_route = 1'b1;
    endcase
  end
  // INVARIANT (§8): at most one of {egress_valid, egress_upstream,
  // err_ambiguous} is ever true. P7 asserts it.
endmodule

Classification: both synthesizable.

route_config's three outputs are mutually exclusive by construction, and §12 walked the boundaries: at Secondary 8 / Subordinate 11, bus 7 → UR, bus 8 → convert, bus 9 → forward, bus 11 → forward, bus 12 → UR.

And the resolver keeps upstream separate from valid. egress_upstream is a successful outcome, not a failure — which is §4's point expressed in signals.

Failure — four. Converting across the whole range rather than at the Secondary Bus Number exactly. >= instead of > in the forward condition, so the Secondary bus is both converted and forwarded. Dropping a UR instead of terminating it, which breaks enumeration's probing. And merging upstream into an error path.

12. Assertions

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SVA over the routing blocks. LOCAL contract only. Nothing asserts that
// software configures the windows correctly, that a downstream port
// accepts, or that any packet is ever delivered.
 
// ---- WINDOW SEMANTICS -------------------------------------------------
 
// P1: A WINDOW WITH Base > Limit IS DISABLED, not wrapped. Section 12
// measured the wrap interpretation at 33.3% spurious matches.
property p_disabled_window;
  @(posedge clk) disable iff (!rst_n)
  (cfg[i].mem_base > cfg[i].mem_limit) |-> !addr_match_mem[i];
endproperty
a_dis : assert property (p_disabled_window);
 
// P2: a match means the address really is inside an enabled window.
property p_match_sound;
  @(posedge clk) disable iff (!rst_n)
  match_vec[i] |-> (win_hit(cfg[i].mem_base,  cfg[i].mem_limit,  addr)
                 || win_hit(cfg[i].pref_base, cfg[i].pref_limit, addr)
                 || win_hit({32'd0,cfg[i].io_base}, {32'd0,cfg[i].io_limit}, addr));
endproperty
a_sound : assert property (p_match_sound);
 
// P2b: BOTH memory windows participate -- ignoring the prefetchable range
// misroutes half the address map (mutation 5).
property p_pref_window_used;
  @(posedge clk) disable iff (!rst_n)
  (win_hit(cfg[i].pref_base, cfg[i].pref_limit, addr) && port_enabled[i]
                                                     && (kind == TT_MEM_REQ))
    |-> match_vec[i];
endproperty
a_pref : assert property (p_pref_window_used);
 
// ---- THE THREE OUTCOMES -----------------------------------------------
 
// P3: NO MATCH ROUTES UPSTREAM. Section 1's default, and section 12's
// majority path at 67.3% -- it must never be an error.
property p_miss_is_upstream;
  @(posedge clk) disable iff (!rst_n)
  ((kind inside {TT_MEM_REQ, TT_IO_REQ}) && (match_vec == '0))
    |-> (route_upstream && !err_no_route);
endproperty
a_up : assert property (p_miss_is_upstream);
 
// P4: MORE THAN ONE MATCH IS REPORTED, NOT RESOLVED. Section 12: 29.9%
// against deliberately overlapping ranges.
property p_ambiguity_reported;
  @(posedge clk) disable iff (!rst_n)
  !$onehot0(match_vec) |-> (ambiguous && !hit_down);
endproperty
a_amb : assert property (p_ambiguity_reported);
 
// P5: a downstream route implies exactly one match, and the index is in
// range and actually set.
property p_onehot_egress;
  @(posedge clk) disable iff (!rst_n)
  hit_down |-> ($onehot(match_vec) && (egress < PW'(N_DOWN))
                                   && match_vec[egress]);
endproperty
a_one : assert property (p_onehot_egress);
 
// ---- ID ROUTING -------------------------------------------------------
 
// P6: the bus range is INCLUSIVE at both ends (§1, §5).
property p_bus_range_inclusive;
  @(posedge clk) disable iff (!rst_n)
  ((target_bus == cfg[i].sec_bus) || (target_bus == cfg[i].sub_bus))
    && port_enabled[i] |-> id_match_vec[i];
endproperty
a_incl : assert property (p_bus_range_inclusive);
 
// ---- RESOLUTION -------------------------------------------------------
 
// P7: AT MOST ONE OUTCOME. valid, upstream and ambiguous are mutually
// exclusive -- the resolver's core invariant (§11).
property p_exclusive_outcome;
  @(posedge clk) disable iff (!rst_n)
  $onehot0({egress_valid, egress_upstream, err_ambiguous});
endproperty
a_excl : assert property (p_exclusive_outcome);
 
// P8: THE METHOD IS CHOSEN BY TLP TYPE, and only that method's result is
// used (§3).
property p_method_by_type;
  @(posedge clk) disable iff (!rst_n)
  ((kind inside {TT_MEM_REQ, TT_IO_REQ}) |-> (method == RM_ADDRESS))
  && ((kind inside {TT_CPL, TT_CFG1})    |-> (method == RM_ID))
  && ((kind == TT_MSG)                   |-> (method == RM_IMPLICIT));
endproperty
a_meth : assert property (p_method_by_type);
 
// P8b: an implicitly-routed Message consults NO window (§7).
property p_implicit_no_lookup;
  @(posedge clk) disable iff (!rst_n)
  (kind == TT_MSG) |-> (!err_ambiguous && (egress_upstream == msg_toward_root));
endproperty
a_imp : assert property (p_implicit_no_lookup);
 
// ---- CONFIGURATION REQUESTS -------------------------------------------
 
// P9: THE THREE OUTCOMES ARE MUTUALLY EXCLUSIVE AND EXHAUSTIVE for a
// Type 1 Configuration Request (§6).
property p_cfg_exclusive;
  @(posedge clk) disable iff (!rst_n)
  (kind == TT_CFG1) |-> $onehot({convert_to_type0, forward_type1,
                                 unsupported_request});
endproperty
a_cfg : assert property (p_cfg_exclusive);
 
// P10: CONVERSION HAPPENS EXACTLY AT THE SECONDARY BUS NUMBER -- not
// across the range (mutation 6).
property p_convert_exact;
  @(posedge clk) disable iff (!rst_n)
  convert_to_type0 |-> (target_bus == this_sec_bus);
endproperty
a_conv : assert property (p_convert_exact);
 
// P10b: forwarding is strictly ABOVE the Secondary and at most the
// Subordinate -- so the two never both fire (mutation 7).
property p_forward_range;
  @(posedge clk) disable iff (!rst_n)
  forward_type1 |-> ((target_bus > this_sec_bus) && (target_bus <= this_sub_bus));
endproperty
a_fwd : assert property (p_forward_range);
 
// P11: AN OUT-OF-RANGE CONFIGURATION REQUEST IS TERMINATED, NOT DROPPED.
// Section 1: "terminated ... as an Unsupported Request (UR)". Enumeration
// depends on getting a response (§6).
property p_ur_not_dropped;
  @(posedge clk) disable iff (!rst_n)
  ((kind == TT_CFG1) && (target_bus != this_sec_bus)
                     && !((target_bus > this_sec_bus) && (target_bus <= this_sub_bus)))
    |-> unsupported_request;
endproperty
a_ur : assert property (p_ur_not_dropped);
 
// P12: a Type 0 Configuration Request is NOT forwarded -- it targets this
// bridge's own registers (§2).
property p_type0_not_forwarded;
  @(posedge clk) disable iff (!rst_n)
  (kind == TT_CFG0) |-> (!egress_valid && !egress_upstream);
endproperty
a_t0 : assert property (p_type0_not_forwarded);
 
// P13: routing is COMBINATIONAL and stateless here -- the same inputs
// always give the same egress. Any future pipelining must snapshot the
// configuration, which is Chapter 19.4 P5's rule.
property p_deterministic;
  @(posedge clk) disable iff (!rst_n)
  ($stable(kind) && $stable(addr) && $stable(target_bus) && $stable(cfg))
    |-> $stable({egress_valid, egress_upstream, egress_port});
endproperty
a_det : assert property (p_deterministic);
 
// P14: reset leaves no route asserted.
property p_reset;
  @(posedge clk) !rst_n |=> (!egress_valid && !err_ambiguous);
endproperty
a_reset : assert property (p_reset);

P3 and P4 are the pair that defines correct routing. P3 makes a miss a successful upstream route — §12's 67.3% majority path. P4 makes an overlap an error rather than a preference — 29.9% exposure.

P10 with P10b and P11 are §1's Configuration rule as three properties, and together they are exhaustive: every Type 1 Request converts, forwards, or is terminated.

And P13 is the forward-looking one. This decoder is combinational; Chapter 21.2's store-and-forward will pipeline it, and at that point Chapter 19.4 §13's snapshot rule applies — the configuration must be captured at the ownership boundary, not re-read.

No liveness. "The egress port accepts" is Chapter 21.2's problem, not this chapter's.

13. Verification, Fault Injection, and Model Verification

Executed before publication.

Address routing — 200,000 randomized configurations

OutcomeCases
exactly one window52,13026.1% → downstream
no window134,60767.3% → upstream by default
more than one13,2636.6% → misconfiguration

Two things this measures. The upstream default is the majority path, so treating a miss as an error breaks two thirds of traffic. And ambiguity is not rare in randomized configurations, so a priority encoder answers 6.6% of lookups silently and wrongly.

The window-enable convention — 100,000 cases

Interpretation of Base > LimitSpurious matches
disabled (§1's rule)0
a wrapped range33,294 — 33.3%

A disabled window read as wrapped captures a third of all traffic.

ID routing — 200,000 lookups

Properly nested ranges: 56.5% downstream, 43.5% upstream, 0 ambiguous.

Deliberately overlapping ranges (1–5 and 3–8), 100,000 lookups: 29,894 ambiguous — 29.9%.

Type 1 Configuration — 200,000 randomized cases

OutcomeShare
convert to Type 01.4%
forward Type 15.6%
Unsupported Request92.9%

Boundary walk at Secondary 8 / Subordinate 11: 7 → UR · 8 → convert · 9 → forward · 11 → forward · 12 → UR.

Degenerate case, Secondary 8 / Subordinate 8 (nothing below): 8 → convert · 9 → UR.

One-hot egress — 200,000 lookups across all three methods

With non-overlapping configuration: 0 results that were not exactly one egress.

Directed tests

  • Address exactly at base, exactly at limit, one below, one above — the four boundaries, for both the non-prefetchable and prefetchable windows (P2, P2b). Required.
  • A window with base > limit — verify it never matches (P1). Required.
  • An address in no window — verify upstream, and that no error is raised (P3). Required.
  • Two overlapping windows — verify ambiguity is reported and nothing is forwarded (P4). Required.
  • Bus number exactly at Secondary and exactly at Subordinate — verify both match (P6).
  • Type 1 at every boundary from §13's table (P9–P11). Required.
  • Type 1 with sec == sub — the degenerate case.
  • Type 0 — verify it is not forwarded (P12).
  • A Message — verify no window is consulted and no ambiguity can arise (P8b).
  • N_DOWN = 1, 2, 4 — index widths and one-hot logic at the boundary.
  • All ports disabled — verify everything routes upstream.

The scoreboard maintains an independent routing dictionary — a plain map from (method, key) to expected port, built from the configuration alone — and never reads match_vec, egress or the decoder outputs.

Mutations

#MutationCaught bySystem symptom
1overlapping windows priority-encodedP4packet to a real but wrong port — 29.9% exposure (measured)
2base > limit treated as a wrapped rangeP1a disabled window captures 33.3% of traffic (measured)
3a miss treated as an errorP3every upstream transfer breaks — 67.3% of traffic (measured)
4a miss defaulted to port 0P3packets delivered to an unrelated endpoint
5prefetchable window ignoredP2bhalf the memory map misroutes
6Type 1 converted across the whole rangeP10Type 0 to devices that are not on the adjacent bus
7forward condition uses >= instead of >P10bthe Secondary bus both converts and forwards
8an out-of-range Config Request droppedP11enumeration hangs — the probe never answers (§6)
9Type 0 forwarded downstreamP12a bridge's own register access leaves the bridge
10ID routing uses Device/Function as well as BusscoreboardCompletions to the right bus, wrong port
11bus range treated as exclusive at one endP6the boundary bus is unreachable
12Completions routed by addressP8a Completion has no address to route by
13Messages routed through the address CAMP8ban implicit Message misroutes or is dropped
14egress used without checking hit_downP5ambiguous lookups produce a plausible index
15disabled ports still matchedP2traffic to a port that is not operational
16upstream and valid both assertedP7the packet is duplicated or ambiguous downstream
17configuration re-read mid-decision (once pipelined)P1319.4 §16's 57.8% class

Same-cycle audit

CaseDeclared resolution
address matches a window and the configuration is being rewrittenthis decoder is combinational and stateless; the result reflects the configuration presented that cycle (P13). Pipelining it requires a snapshot — 21.2's problem
address in both the non-prefetchable and prefetchable window of one portone match — the same port; match_vec[i] is an OR, so no ambiguity arises
address in windows of two different portsambiguous (P4), reported
Type 1 with target_bus == sec == subconvert — the equality test wins, and forward requires strictly greater (P10, P10b)
a Message while an address lookup would also hitthe method is chosen by type (P8); the address result is not consulted
all ports disabledeverything routes upstream (P3)

14. Debugging

Symptom → which method → which register → experiment.

The first question is always which routing method should this packet have used (§3), because the three consult entirely different registers and a wrong answer in one says nothing about the others.

Downstream traffic works, upstream traffic vanishes

Suspect the upstream default (§13's first counterexample) — mutation 3 or 4.

The asymmetry is the diagnosis. Downstream traffic matches windows and is routed; upstream traffic matches nothing and depends on the default being upstream rather than an error.

The distinguishing experiment: issue a Memory Write from an endpoint to an address deliberately outside every downstream window, and watch whether it appears at the upstream port. If it is dropped, the miss path is wrong — and P3 is the permanent check.

One endpoint is unreachable; its neighbours are fine

Read that bridge's windows and bus range.

If its Memory Base exceeds its Memory Limit, the window is disabled (§4) — which may be correct (nothing was allocated) or may be a configuration that never completed.

If its Secondary/Subordinate range does not contain the endpoint's bus, ID-routed traffic — Completions — cannot reach it, so Requests may go out and nothing comes back. That produces completion timeouts at the requester, which look like an endpoint fault.

The distinguishing experiment: compare the endpoint's assigned Bus Number against every bridge's sec/sub on the path. One gap anywhere breaks the return path only.

Traffic reaches the wrong endpoint

Overlapping configuration (§8) — mutation 1.

Read every port's windows and check for intersection. §13 measured 29.9% ambiguity against two overlapping bus ranges, and 6.6% against randomized address windows.

A switch that priority-encodes will not tell you, so the detection has to be external: dump the configuration and intersect the ranges yourself. P4 is what makes the switch report it instead.

And check when it happens. If it appears only during bring-up or after a hot-plug event, it is a transient overlap while one bridge has been updated and its neighbour has not (§8) — which is a software sequencing issue rather than a static misconfiguration.

Enumeration is extremely slow or never completes

§13's second counterexample — mutation 8.

Software probes buses that do not exist, and 92.9% of the probe space is out of range. If those are dropped instead of terminated with a UR, every probe costs a completion timeout instead of a fast response.

The distinguishing experiment: issue a Configuration Request to a bus number known to be outside every range and watch for a Completion. A UR should return promptly; silence is the bug.

A Completion never returns, though the Request clearly went out

ID routing, not address routing (§5).

The Request routed by address and arrived. The Completion routes by Bus Number — and if the bridges' Secondary/Subordinate ranges do not cover the requester's bus, the return path fails while the forward path works.

This asymmetry is the signature, and it is why §5 exists as a separate section: a packet and its Completion can take different routing methods and therefore fail independently.

15. Common Misconceptions

  • "A switch has a routing table." It has per-port bridge windows — three lookups against three register sets (§1, §3).
  • "Routing is one mechanism." Address, ID and implicit are independent (§3).
  • "A miss is an error." It is the upstream default, and the majority path at 67.3% (§4).
  • "A miss should go to port 0." It goes upstream (§4).
  • "Overlapping windows are a priority question." They are a misconfiguration — 29.9% ambiguity measured (§8).
  • "Base > Limit means a wrapped range." It means disabled — 33.3% spurious matches otherwise (§4).
  • "Completions route by address." They carry none; they route by Bus Number (§5).
  • "Type 1 converts anywhere in the range." Exactly at the Secondary Bus Number (§6).
  • "An out-of-range Configuration Request can be dropped." It is terminated as a UR — enumeration depends on it (§6).
  • "Messages need a destination field." Implicit routing uses the TLP type (§7).
  • "A switch runs a routing protocol." Software configures bridge registers; the switch compares (§2).
  • "If the Request arrived, the Completion will return." Different methods, independent failure (§14).

16. Understanding Check

17. What Module 21 Still Owns

This chapter answered one question: which port does a packet leave by?

ChapterWhat it uniquely owns
21.2 Packet Forwardingstore-and-forward mechanics — buffering a TLP through a port, per-port flow control, the forwarding datapath, and the pipelining that makes §12's P13 matter
21.3 Fabric Scalingswitch trees — depth, bandwidth aggregation, oversubscription
21.4 Multi-Endpoint Systemsmany endpoints — enumeration at scale, resource allocation, and how the windows this chapter reads get assigned in the first place

What this chapter deliberately kept: the virtual-bridge model (§2), the three methods (§3), address windows and the upstream default (§4), bus ranges (§5), the Configuration three-way rule (§6), implicit routing (§7), and the one-hot requirement (§8).

What it deliberately declined: buffering and flow control between ports, anything about depth or aggregate bandwidth, how software computes the window assignments, non-transparent bridging, and peer-to-peer policy between downstream ports.

18. What's Next

A switch decides one thing per packet, and it decides it three different ways.

Address routing compares against per-port Base/Limit windows, with upstream as the default — §13's 67.3% majority path, and the rule that makes hierarchy scale.

ID routing compares a Bus Number against Secondary/Subordinate ranges, which is why a Request and its Completion can fail independently (§16).

Implicit routing consults nothing at all — the packet's type is the destination.

And every lookup must produce exactly one egress (§8), because ambiguity is a misconfiguration rather than a preference — 29.9% measured against overlapping ranges, and a priority encoder answers every one of them silently.

Chapter 21.2 — Packet Forwarding takes the next question. Deciding where a packet goes is not moving it there. A switch must buffer it, respect the egress port's flow control, and hand it on — and the moment that decision becomes pipelined, the configuration it was based on can change underneath it, which is Chapter 19.4 §13's snapshot problem arriving in a new subsystem.

The idea to carry forward: "not mine" is an answer, and a protocol that can say "nothing is there" should.