Ethernet · Module 23
The Spine-Leaf Data-Centre Fabric
One part number builds 2 048 servers and 102.4 Tb/s of bisection — of which 32 large flows can reach 28.4%, because a hash chose the paths and a hash collides.
A modern fabric is two tiers of the same switch, wired so that every server is exactly three hops from every other. Everything about it follows from one part's port count.
| From a single 64-port 100 Gb/s switch | Value |
|---|---|
| leaf ports down to servers | 32 |
| leaf ports up to spines | 32 |
| oversubscription | 1:1 |
| spines required | 32 — one per leaf uplink |
| maximum leaves | 64 — one per spine port |
| servers at 100 Gb/s | 2 048 |
| bisection bandwidth | 102.4 Tb/s |
| distinct part numbers in the fabric | 1 |
Row eight is the property that made the design win and it is not a bandwidth property at all. A spine and a leaf are the same silicon, the same firmware and the same spare; only the cabling differs. Every scaling, sparing, qualification and procurement question collapses into one part number.
Then the honest part, which is that a non-blocking fabric does not deliver non-blocking throughput.
| Large flows crossing a 32-uplink group | Mean per uplink | Mean of the busiest | Effective capacity |
|---|---|---|---|
| 32 | 1.00 | 3.52 | 28.4% |
| 64 | 2.00 | 5.41 | 37.0% |
| 128 | 4.00 | 8.65 | 46.2% |
| 256 | 8.00 | 14.32 | 55.9% |
| 640 | 20.0 | 29.77 | 67.2% |
| 3 200 | 100.0 | 121.36 | 82.4% |
Thirty-two equal flows across thirty-two equal-cost paths deliver 28.4% of the group, because a hash chose the paths and a hash collides.
Every link is up. Every path is equal-cost. Every forwarding table is correct. The capacity is present and unreachable — and Section 20's rejected property is about the assertion that cannot tell the difference.
And one number that explains the topology's other half.
| Lose one element | |
|---|---|
| a 32-spine fabric | 3.1% of the bisection, uniformly, everywhere |
| a two-core three-tier tree | 50% of the core, for whichever traffic used it |
A fabric with no distinguished node has no failure that is worse than any other failure, which is Section 12's subject and is the second reason the design replaced the hierarchy it came from.
1. Scope — A Fabric With No Distinguished Node
Scope: the spine-leaf topology, where its numbers come from, what its oversubscription ratio does and does not promise, and why it is built from identical switches.
Not in scope: what is inside a switch. The forwarding pipeline, the buffer architecture and the table structures are Chapter 23.3's. This chapter treats a switch as a port count and a forwarding function and derives everything else from the topology.
Three properties define the design and only one of them is about bandwidth.
| Property | Consequence | |
|---|---|---|
| 1 | two tiers, fully meshed between them | every server-to-server path is the same length |
| 2 | every switch is the same part | one part number, one spare pool, one qualification |
| 3 | no element is distinguished | every failure removes the same fraction |
Property 1 is what people notice and property 3 is what makes the design operationally different. A three-tier tree has a core, and a core is a thing that can fail in a way nothing else can fail. A spine-leaf fabric has thirty-two spines and losing any one of them is the same event — 3.1% of the bisection, uniformly distributed across every pair of leaves.
And one thing the design deliberately gives up.
Locality. In a tree, two servers under the same access switch are close and two servers in different buildings are far. In a spine-leaf fabric every pair is either one hop or three, and the three-hop case is almost all of them — so there is no such thing as a nearby server, and no benefit to placing two communicating workloads together beyond a single rack.
That is a real loss and it was traded away on purpose. A uniform fabric is easier to reason about, easier to schedule onto, and easier to extend than one where placement matters — and modern schedulers place work anywhere, which only works because the fabric made everywhere equivalent.
The chapter's arc is four questions.
| Question | Section | |
|---|---|---|
| 1 | where do the numbers come from? | 2 and 5 |
| 2 | what does oversubscription promise? | 4 and 13 |
| 3 | why does a non-blocking fabric not deliver? | 9 and 10 |
| 4 | what does a failure cost? | 11 and 12 |
Question 3 is the chapter's hardest and it is where Module 21's instruments come back. Chapter 21.8 named three ceilings below the line rate; a fabric has a fourth, and it is the one in the table at the top of this chapter.
2. The Topology, Derived From One Switch's Port Count
Take a single switch with P ports. Everything else is arithmetic.
Step 1 — split a leaf's ports between down and up.
P = D + U where D ports face servers and U face spines
Step 2 — the number of spines is the number of uplinks. Each leaf sends exactly one uplink to each spine, so the mesh between tiers is complete:
spines = U
Step 3 — the number of leaves is a spine's port count. Each spine has one port per leaf:
leaves = P
Step 4 — and the server count falls out.
servers = leaves × D = P × D
Step 5 — the oversubscription ratio is the ratio of the two halves.
oversubscription = D ÷ U
With P = 64 at 100 Gb/s, three splits.
| D : U | Ratio | Spines | Leaves | Servers | Bisection |
|---|---|---|---|---|---|
| 32 : 32 | 1:1 | 32 | 64 | 2 048 | 102.4 Tb/s |
| 48 : 16 | 3:1 | 16 | 64 | 3 072 | 51.2 Tb/s |
| 56 : 8 | 7:1 | 8 | 64 | 3 584 | 25.6 Tb/s |
Read the last two columns together, because the trade is the whole design decision. Going from 1:1 to 7:1 adds 75% more servers and removes 75% of the bisection — and which is right depends entirely on whether the workload's traffic stays inside a rack.
Bisection bandwidth, derived, because it is the number most often quoted and least often computed.
bisection = leaves × U × rate ÷ 2
The division by two is the part people drop. Bisection is the capacity across a cut that separates the fabric into two halves; the leaf-to-spine links carry traffic in both directions, and only half of them cross any given cut. 64 leaves × 32 uplinks × 100 Gb/s ÷ 2 = 102.4 Tb/s.
And the scaling limit is sharp and worth stating.
| Limit | |
|---|---|
| maximum leaves | P — a spine has one port per leaf |
| maximum servers at 1:1 | P²/2 — 2 048 for P = 64 |
maximum servers at D:U | P × D |
| beyond that | a third tier, and the uniformity is gone |
Row four is why fabrics have a size at which they stop being simple. A two-tier fabric caps at P × D servers; beyond it a super-spine tier is added, path lengths become five hops for cross-pod traffic and three within a pod, and the "every pair is equivalent" property — property 3 of Section 1 — is lost. That boundary is the single most important number in a fabric design and it is P × D.
3. RTL 1 — The Fabric Package and the Uniform Switch
// ---------------------------------------------------------------------
// dcfabric_pkg -- a spine-leaf fabric as arithmetic over one number.
//
// Everything in this package is derived from PORTS and the split
// between down and up. The design's distinguishing property is not in
// any constant: it is that the SAME switch model is used in both
// tiers, so there is exactly one part number in the fabric.
// ---------------------------------------------------------------------
package dcfabric_pkg;
// ---- the one switch ---------------------------------------------------
localparam int PORTS = 64;
localparam int RATE_GBPS = 100;
// ---- the split --------------------------------------------------------
localparam int DOWN_PORTS = 32; // toward servers
localparam int UP_PORTS = PORTS - DOWN_PORTS; // toward spines
// ---- and everything else follows --------------------------------------
localparam int SPINES = UP_PORTS; // one uplink per spine
localparam int LEAVES = PORTS; // one spine port per leaf
localparam int SERVERS = LEAVES * DOWN_PORTS;
// Bisection is HALF the leaf-to-spine capacity: the links are
// bidirectional and only half of them cross any given cut. Dropping
// the division by two is the commonest error in a fabric datasheet.
localparam int BISECT_GBPS = (LEAVES * UP_PORTS * RATE_GBPS) / 2;
// ---- roles ------------------------------------------------------------
// Note that this enum describes CABLING, not silicon. The same part
// is both.
typedef enum logic {
ROLE_LEAF = 1'b0,
ROLE_SPINE = 1'b1
} fabric_role_e;
// ---- paths ------------------------------------------------------------
// Same leaf: one switch. Different leaves: leaf, spine, leaf.
localparam int HOPS_SAME_LEAF = 1;
localparam int HOPS_CROSS = 3;
// The number of equal-cost paths between two different leaves is the
// number of spines, and Section 10 is about how the flows land on
// them.
localparam int ECMP_PATHS = SPINES;
typedef enum logic [1:0] {
FAIL_NONE = 2'd0,
FAIL_SPINE = 2'd1,
FAIL_LEAF = 2'd2,
FAIL_UPLINK = 2'd3
} fabric_fail_e;
function automatic int oversubscription_x10();
return (DOWN_PORTS * 10) / UP_PORTS;
endfunction
// Section 12: losing one spine of N removes 1/N of the bisection,
// uniformly, for every pair of leaves.
function automatic int spine_loss_pct_x10(int spines_up);
if (spines_up >= SPINES) return 0;
return ((SPINES - spines_up) * 1000) / SPINES;
endfunction
// The scaling wall. Beyond PORTS * DOWN_PORTS servers a third tier
// is required and the uniform three-hop property is lost.
localparam int MAX_SERVERS_TWO_TIER = PORTS * DOWN_PORTS;
endpackageClassification: a package in which one enumeration describes cabling rather than hardware, and that is the design's whole economic argument.
What it teaches: that fabric_role_e has no hardware meaning. A spine and a leaf are the same part, the same firmware image and the same spare; the only difference is which ports have servers on them. That collapses the fabric's bill of materials to one line, its spares pool to one bin, and its qualification effort to one campaign — and none of those savings appears in a bandwidth calculation.
And it teaches that MAX_SERVERS_TWO_TIER is the number a fabric design lives or dies by. At P = 64 and a 1:1 split it is 2 048; beyond it a super-spine tier appears, cross-pod paths become five hops while intra-pod stay three, and Section 1's property 3 — every pair equivalent — is gone. The fabric stops being uniform at a number that is entirely a property of the switch somebody bought.
Deliberately simplified: DOWN_PORTS is a constant where real leaves mix rates — 48 ports of 25 Gb/s down and 8 of 100 up is the commonest arrangement of all and needs the ratio computed in bandwidth rather than in ports. BISECT_GBPS assumes every uplink is present and healthy, which Section 12 is about. ECMP_PATHS equals SPINES, which is true only when every spine is reachable from both leaves. And HOPS_CROSS counts switches rather than links, which is the convention that makes "three hops" the familiar phrase.
Production implication: the mixed-rate leaf is the configuration to model properly, because port counts lie about bandwidth. Forty-eight 25 Gb/s down ports is 1 200 Gb/s; eight 100 Gb/s up ports is 800 — an oversubscription of 1.5:1, not the 6:1 the port counts suggest. Computing the ratio from ports rather than from bandwidth is a factor-of-four error in the direction that makes a fabric look worse than it is, and it has caused real designs to buy uplink capacity they did not need. Always take the ratio of total down bandwidth to total up bandwidth.
// ---------------------------------------------------------------------
// uniform_switch_model -- one part, two roles, and the only thing that
// differs is what is plugged into it.
//
// The block exists to make the claim checkable: if a leaf and a spine
// need different behaviour anywhere, the fabric does not have one part
// number and the economic argument for the topology is weaker than
// advertised.
// ---------------------------------------------------------------------
module uniform_switch_model
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input fabric_role_e role,
input logic [PORTS-1:0] port_link_up,
input logic [PORTS-1:0] port_faces_server,
output logic [7:0] ports_up,
output logic [7:0] ports_down_count,
output logic [7:0] ports_uplink_count,
output logic [15:0] down_gbps,
output logic [15:0] up_gbps,
output logic [15:0] oversub_x10,
output logic role_is_cabling_only,
output logic spine_has_servers // a cabling error
);
always_comb begin
ports_up = 8'd0;
ports_down_count = 8'd0;
ports_uplink_count = 8'd0;
for (int i = 0; i < PORTS; i++) begin
if (port_link_up[i]) begin
ports_up = ports_up + 8'd1;
if (port_faces_server[i]) ports_down_count = ports_down_count + 8'd1;
else ports_uplink_count = ports_uplink_count + 8'd1;
end
end
down_gbps = 16'(ports_down_count) * 16'(RATE_GBPS);
up_gbps = 16'(ports_uplink_count) * 16'(RATE_GBPS);
// The ratio is BANDWIDTH over BANDWIDTH, never ports over ports.
oversub_x10 = (up_gbps == 0) ? 16'hFFFF
: (down_gbps * 16'd10) / up_gbps;
// The claim, made checkable: nothing in this block's behaviour
// depends on `role`.
role_is_cabling_only = 1'b1;
spine_has_servers = (role == ROLE_SPINE) && (ports_down_count != 8'd0);
end
endmoduleClassification: a port census with one output that is a claim about the design rather than a measurement of it.
What it teaches: that role is an input the block never reads, and that this is the point rather than an oversight. Every computation — the census, the bandwidths, the ratio — is identical for both tiers. If a future revision needs role in an expression, the fabric has two behaviours and therefore two qualification campaigns, and the economic argument in Section 6 weakens by exactly that much.
And it teaches that oversub_x10 divides bandwidth by bandwidth. A leaf with 48 down ports at 25 Gb/s and 8 up at 100 has a port ratio of 6:1 and a bandwidth ratio of 1.5:1 — a factor of four apart, and only the second one means anything.
Deliberately simplified: all ports are RATE_GBPS, so the mixed-rate case the production note just argued for cannot actually be expressed — a real model needs a per-port rate vector. port_faces_server is an input where a switch discovers it from neighbour discovery and can be wrong. The census is a combinational loop over 64 ports, which is fine as a model and is a 64-input adder tree in silicon. And role_is_cabling_only is a literal, which is honest: it is a claim the surrounding code must keep true, not a fact the block establishes.
Production implication: spine_has_servers is worth wiring to an alarm, because it is the commonest cabling error in a new fabric and it is silent. A server accidentally patched into a spine port works — the spine forwards for it, traffic flows, and nothing complains. What it does is destroy the topology's uniformity: that server's traffic reaches other leaves in one hop while everyone else takes three, its failure domain is a spine rather than a leaf, and a spine reboot takes it down. The detection is one comparison against a port's expected neighbour type, and without it the anomaly is found months later during a failure analysis.
4. Oversubscription Is a Ratio, Not a Promise
The ratio is quoted in every fabric datasheet and almost always misread. This section says exactly what it claims.
What D:U actually states.
If every server on a leaf transmits at line rate to servers on other leaves at the same time, the leaf's uplinks carry
D ÷ Utimes more offered load than they can accept.
That is a statement about a single, specific, and rather unlikely traffic pattern, and four things it is not:
| It does not say | Because |
|---|---|
the fabric can carry 1/ratio of its access bandwidth | that assumes all traffic leaves the leaf |
any given flow gets 1/ratio of line rate | a single flow uses one path, not the group |
| a 1:1 fabric is non-blocking in practice | Section 10 — 28.4% for 32 flows |
| the ratio is the ratio of port counts | Section 3 — 48×25 : 8×100 is 1.5:1, not 6:1 |
Row one is the one that makes the ratio useful when it is applied correctly. Traffic that stays within a leaf never touches an uplink at all — a 32-server rack whose servers mostly talk to each other has an effective oversubscription of infinity and it does not matter. So the honest form of the ratio includes a locality term:
uplink load = D × rate × (fraction of traffic leaving the leaf)
| Fraction leaving the leaf | Uplink load at D = 32, 100 Gb/s | Against 3.2 Tb/s of uplink |
|---|---|---|
| 100% | 3.2 Tb/s | 1:1 — exactly saturating |
| 50% | 1.6 Tb/s | 50% |
| 25% | 0.8 Tb/s | 25% |
And modern workloads have almost no locality, which is why the industry moved toward 1:1: a scheduler that places work anywhere produces traffic that goes anywhere, and the locality term is close to 1.
Row two of the first table deserves its own treatment because it is the commonest practical confusion.
| A 1:1 fabric | |
|---|---|
| aggregate leaf-to-spine capacity | 3.2 Tb/s per leaf |
| capacity available to one TCP flow | 100 Gb/s — one uplink |
| capacity available to one flow if it could stripe | 3.2 Tb/s |
| whether it can stripe | no — ECMP hashes a flow to one path |
A single flow gets exactly one uplink, because Chapter 21.7 §8's ordering requirement runs all the way up the stack: striping one flow across 32 paths reorders it, and a reordered TCP flow collapses. So the fabric's 102.4 Tb/s is available to the aggregate and never to an individual, which is exactly Chapter 21.8 §5's goodput-versus-capacity distinction at fabric scale.
Which gives the three ratios a design should actually compute.
| Ratio | Formula | At 1:1, 64 ports |
|---|---|---|
| port oversubscription | D ÷ U | 1.00 |
| bandwidth oversubscription | down Gb/s ÷ up Gb/s | 1.00 |
| effective oversubscription | bandwidth ratio ÷ ECMP efficiency | 3.52 at 32 flows |
Row three is the chapter's most useful single number and nobody quotes it. A fabric sold as 1:1 delivers, to 32 large flows, the behaviour of a 3.5:1 fabric — and Section 10 derives it.
5. RTL 2 — The Leaf-Spine Topology Model
// ---------------------------------------------------------------------
// leaf_spine_topology -- the fabric as a function from a port count to
// a server count, a hop count and a path set.
//
// The three outputs that matter are: every cross-leaf path is the same
// length, the number of equal-cost paths is the number of spines, and
// the fabric stops scaling at PORTS * DOWN_PORTS.
// ---------------------------------------------------------------------
module leaf_spine_topology
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] src_leaf,
input logic [15:0] dst_leaf,
input logic [15:0] spines_healthy,
input logic [31:0] servers_requested,
output logic [3:0] hops,
output logic [15:0] ecmp_paths,
output logic same_leaf,
output logic [31:0] servers_supported,
output logic needs_third_tier,
output logic [15:0] path_length_variance,
output logic [31:0] bisection_gbps
);
always_comb begin
same_leaf = (src_leaf == dst_leaf);
// The design's defining property: TWO values, and no others.
hops = same_leaf ? 4'(HOPS_SAME_LEAF) : 4'(HOPS_CROSS);
// Every healthy spine is an equal-cost path. There is no notion of
// a better or worse spine, which is Section 6's argument.
ecmp_paths = same_leaf ? 16'd1 : spines_healthy;
servers_supported = 32'(MAX_SERVERS_TWO_TIER);
needs_third_tier = (servers_requested > servers_supported);
// Zero, always, for a two-tier fabric. A third tier makes it
// non-zero, and Section 1's property 3 is what that costs.
path_length_variance = 16'd0;
bisection_gbps = (32'(LEAVES) * 32'(spines_healthy)
* 32'(RATE_GBPS)) / 2;
end
endmoduleClassification: a topology function whose most informative output is permanently zero.
What it teaches: that path_length_variance being zero is the product. In a three-tier tree the distance between two servers depends on where they are: same access switch, same aggregation block, or across the core — three different latencies, three different failure domains, and a placement decision that matters. In a two-tier fabric there are two values and one of them covers almost everything, so every pair of servers is interchangeable. That is what lets a scheduler place work anywhere, and it is a topology property rather than a software one.
And it teaches that ecmp_paths tracks spines_healthy rather than SPINES. A spine that is down is not a path, so a fabric's path diversity degrades gracefully — 32 paths become 31, the bisection falls by 3.1%, and nothing about the topology changes shape. Section 12 is the arithmetic and it is the second reason the design displaced the tree.
Deliberately simplified: spines_healthy is a count where a real fabric has per-leaf-pair reachability — a spine may be reachable from one leaf and not another, which makes the path set asymmetric and ECMP's assumptions false. hops counts switches rather than links or latency. There is no notion of a link being up but degraded — Chapter 21.4's whole fault space collapses into one bit here. And bisection_gbps recomputes from healthy spines but not from healthy uplinks, so a leaf that has lost four of its 32 uplinks is invisible.
Production implication: the missing per-leaf-pair reachability is what turns a graceful degradation into a silent one. A spine that is reachable from leaf A and not from leaf B is a path that ECMP on A believes exists, so A hashes flows onto it and they are dropped or take a longer route — and A's own view of the fabric says everything is healthy. The detection is a fabric-wide reachability matrix, which no single switch can compute; it is the twelfth taxonomy group of Chapter 22.2 §20 in a new setting — a guarantee that spans nodes, evaluated at one — and the practical remedy is the same: import something, in this case the spine's own neighbour list, and compare.
6. Why Every Switch Is the Same Part
The topology's least-discussed property is the one that decided its adoption, and it has nothing to do with packets.
What "one part number" buys.
| Three-tier tree | Spine-leaf | |
|---|---|---|
| distinct switch models | 3 — access, aggregation, core | 1 |
| spare pool | three bins, sized separately | one |
| qualification campaigns | three | one |
| firmware images to track | three | one |
| a failed core switch | a large, expensive, long-lead part | — |
| scaling a tier | buy a different, larger part | buy another of the same |
Row five is the one that shaped procurement. A core switch in a tree is the largest and most expensive box in the building, it is bought in small numbers, its lead time is long, and its failure removes a large fraction of the network's capacity. Replacing it with thirty-two ordinary switches turns a capital decision into a commodity purchase.
And row six is what changed operations. A tree scales by replacing a tier with bigger boxes — a disruptive, planned, capital event. A fabric scales by adding more of the same part, up to the P × D wall of Section 2, and the addition is incremental.
The technical enabler is worth naming: the two tiers need identical behaviour.
| A leaf must | A spine must |
|---|---|
| forward between its ports | forward between its ports |
| participate in ECMP | participate in ECMP |
| hold a forwarding table | hold a forwarding table |
| know about servers | know about leaves |
Row four is the only difference and it is a matter of table contents rather than capability. A spine's table has one entry per leaf — 64 — where a leaf's has one per server plus one per remote prefix. The spine's table is smaller, so a part sized for the leaf role is comfortably sufficient for the spine role, and the uniformity holds in the direction that matters.
One honest qualification. Real fabrics are not always perfectly uniform: leaves often use a cheaper, lower-radix part with mixed rates — 48 ports at 25 Gb/s down and 8 at 100 up — while spines use a uniform high-rate part. That is two part numbers rather than one, and it is still two rather than three, with both being commodity. The argument survives in weakened form, and Section 3's bandwidth-not-ports rule is what makes the mixed-rate leaf analysable at all.
The spine-leaf fabric's central innovation is not a routing idea. It is that a large network can be built entirely out of a part that is small enough to be a commodity.
7. RTL 3 — The Bisection Accountant
// ---------------------------------------------------------------------
// bisection_accountant -- the number every fabric datasheet quotes,
// computed correctly, and then the number that actually matters.
//
// The division by two is the part datasheets drop. The ECMP efficiency
// term is the part nobody includes at all, and Section 10 shows it is
// worth a factor of three and a half.
// ---------------------------------------------------------------------
module bisection_accountant
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] leaves_present,
input logic [15:0] spines_healthy,
input logic [15:0] uplinks_healthy_per_leaf,
input logic [15:0] ecmp_efficiency_pct, // from Section 9's model
output logic [31:0] nominal_bisect_gbps,
output logic [31:0] healthy_bisect_gbps,
output logic [31:0] effective_bisect_gbps,
output logic [15:0] healthy_pct,
output logic [15:0] effective_pct,
output logic datasheet_number_is_double,
output logic [31:0] per_leaf_uplink_gbps
);
always_comb begin
// What a datasheet SHOULD say.
nominal_bisect_gbps = (32'(LEAVES) * 32'(UP_PORTS)
* 32'(RATE_GBPS)) / 2;
// What survives the fabric's actual state.
healthy_bisect_gbps = (32'(leaves_present)
* 32'(uplinks_healthy_per_leaf)
* 32'(RATE_GBPS)) / 2;
// What the traffic can reach. Section 10's term, and the only one
// of the three that predicts throughput.
effective_bisect_gbps = (healthy_bisect_gbps
* 32'(ecmp_efficiency_pct)) / 100;
healthy_pct = (nominal_bisect_gbps == 0) ? 16'd0
: 16'((healthy_bisect_gbps * 100) / nominal_bisect_gbps);
effective_pct = (nominal_bisect_gbps == 0) ? 16'd0
: 16'((effective_bisect_gbps * 100) / nominal_bisect_gbps);
per_leaf_uplink_gbps = 32'(uplinks_healthy_per_leaf) * 32'(RATE_GBPS);
// Always true, and it is a warning rather than a measurement: a
// "bisection" quoted without the division by two is the aggregate
// leaf-to-spine capacity, which is twice the bisection.
datasheet_number_is_double = 1'b1;
end
endmoduleClassification: three bisection figures, of which the third is the only one that predicts anything.
What it teaches: that the gap between healthy_bisect_gbps and effective_bisect_gbps is the chapter. A fabric with every link up and every spine healthy has 100% of its nominal bisection present; the same fabric carrying 32 large flows across a 32-uplink group reaches 28.4% of it. The first number is a property of the hardware; the second is a property of the hash, and only the second appears in a throughput measurement.
And it teaches why datasheet_number_is_double is asserted unconditionally. Bisection names the capacity across a cut that splits the fabric in half; every leaf-to-spine link is bidirectional and only half of them cross a given cut. A figure quoted without the division is the aggregate tier-to-tier capacity, which is a real number and a different one — and the two are routinely used interchangeably in marketing material.
Deliberately simplified: uplinks_healthy_per_leaf is one number for all leaves, so a fabric with one badly degraded leaf reports the same figure as one with the damage spread evenly — and those are very different situations. ecmp_efficiency_pct is an input rather than computed here, which is correct division of labour and means this block is only as good as Section 9's model. And there is no notion of traffic pattern: bisection is a worst-case cut figure, and a fabric whose traffic is all intra-leaf never approaches it.
Production implication: the per-leaf uniformity assumption is what makes a fabric's health dashboard lie in the most reassuring direction. A fabric of 64 leaves where one leaf has lost 16 of its 32 uplinks has 99.2% of its nominal bisection — the arithmetic is honest — and the servers on that one leaf have exactly half their uplink capacity. The aggregate figure is correct and useless; the figure that matters is the minimum across leaves, not the mean, and publishing the minimum alongside the total costs one comparator and prevents a class of investigation that begins with the fabric says it is healthy.
8. Every Path Is Three Hops, and That Is the Product
The topology's defining property, stated as a latency argument using Chapter 17.1's terms.
Two path lengths exist and no others.
| Case | Switches traversed | Fraction of server pairs at 64 leaves × 32 servers |
|---|---|---|
| same leaf | 1 | 31 of 2 047 — 1.5% |
| different leaves | 3 | 2 016 of 2 047 — 98.5% |
So 98.5% of all server pairs are three hops apart and the remaining 1.5% are one. There is no middle case, no "nearby rack", and no placement decision that improves anything beyond keeping a pair in the same rack.
Priced with Chapter 17.1 §4's bounded terms, at 100 Gb/s.
| Term | Per hop at 100 Gb/s | Three hops |
|---|---|---|
| serialisation, 1 518 octets | 0.121 µs | 0.364 µs |
| store-and-forward | 0.121 µs | 0.364 µs |
| blocking, 1 538 octets | 0.123 µs | 0.369 µs |
| propagation, 30 m of fibre | 0.150 µs | 0.450 µs |
| lookup and fabric | 0.030 µs | 0.090 µs |
| bounded total | 0.545 µs | 1.635 µs |
Under two microseconds, bounded, for any pair of servers in a 2 048-server fabric. And the variance across pairs is zero — every three-hop path has the same terms.
Two observations follow and the second is the surprising one.
First: propagation is now the largest term. At 100 Gb/s a maximum frame serialises in 0.121 µs and 30 metres of fibre takes 0.150 — so cable length has overtaken frame length, which is the reverse of Chapter 22.2's automotive case where propagation was 0.076 µs against a 121 µs serialisation. The same two terms, four orders of magnitude apart in ratio, because the rate moved by a thousand.
Second: a cut-through fabric removes a third of the total. Chapter 12.6 established that cut-through drops the store-and-forward term; at three hops that is 0.364 µs of 1.635 — 22% — and it is why low-latency fabrics use it despite the cost of forwarding frames whose check sequence has not been verified.
And one thing the uniform path length does not give.
Uniform latency is not bounded latency. Chapter 17.1 §11's two unbounded terms — interference and queueing — are still unbounded here, and a fabric has no scheduling mechanism at all. The 1.635 µs is the bounded subtotal and the queueing term above it can be milliseconds.
That is the honest difference between this chapter's fabric and Chapter 22.2's vehicle. A vehicle bounds latency with a schedule and pays 19.48% of the link for it. A data-centre fabric does not bound latency at all — it makes the bounded part uniform and small, and leaves the unbounded part to congestion control. Neither approach is available to the other: a fabric cannot schedule 2 048 servers' traffic, and a vehicle cannot tolerate an unbounded tail.
9. RTL 4 — The ECMP Hash Distribution
// ---------------------------------------------------------------------
// ecmp_hash_distribution -- N equal-cost paths, and a hash that
// decides which flow takes which.
//
// Every path is correct. Every path is equal cost. Every forwarding
// table is right. And the throughput a set of flows achieves is set by
// the BUSIEST path, not the mean -- which is a balls-in-bins problem
// and not a networking one.
// ---------------------------------------------------------------------
module ecmp_hash_distribution
import dcfabric_pkg::*;
#(
parameter int PATHS = ECMP_PATHS // 32
)(
input logic clk,
input logic rst_n,
input logic flow_arrives,
input logic [31:0] flow_hash,
input logic [15:0] flow_gbps,
input logic epoch_reset,
output logic [7:0] chosen_path,
output logic [15:0] path_load_gbps [PATHS],
output logic [15:0] busiest_path_gbps,
output logic [15:0] mean_path_gbps,
output logic [15:0] imbalance_x10,
output logic [15:0] efficiency_pct,
output logic [31:0] c_flows,
output logic one_flow_one_path
);
logic [31:0] total_gbps;
assign chosen_path = 8'(flow_hash % PATHS);
// The property Section 4 argued for: a flow uses ONE path, because
// striping it would reorder it, and a reordered TCP flow collapses.
assign one_flow_one_path = 1'b1;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || epoch_reset) begin
c_flows <= '0; total_gbps <= '0;
for (int i = 0; i < PATHS; i++) path_load_gbps[i] <= '0;
end else if (flow_arrives) begin
path_load_gbps[chosen_path] <= path_load_gbps[chosen_path] + flow_gbps;
total_gbps <= total_gbps + 32'(flow_gbps);
c_flows <= c_flows + 32'd1;
end
end
always_comb begin
busiest_path_gbps = 16'd0;
for (int i = 0; i < PATHS; i++)
if (path_load_gbps[i] > busiest_path_gbps)
busiest_path_gbps = path_load_gbps[i];
mean_path_gbps = 16'(total_gbps / PATHS);
imbalance_x10 = (mean_path_gbps == 0) ? 16'd10
: (busiest_path_gbps * 16'd10) / mean_path_gbps;
// THE number. The group saturates when its busiest member does, so
// the fraction of the group's capacity that is reachable is
// mean over max.
efficiency_pct = (busiest_path_gbps == 0) ? 16'd100
: (mean_path_gbps * 16'd100) / busiest_path_gbps;
end
endmoduleClassification: a balls-in-bins model wearing a networking costume, and its last output is the fabric's real capacity.
What it teaches: that efficiency_pct is mean ÷ max and not anything about links. An uplink group saturates when its busiest member saturates; the capacity that can actually be used is therefore the mean divided by the maximum, and for a random assignment of flows to paths that ratio is a well-understood function of how many flows there are. Nothing in the fabric is broken when it reads 28%.
And it teaches that one_flow_one_path is a hard constraint rather than an implementation choice. Striping one flow across 32 paths would use the whole group — and would deliver the frames out of order, because the paths have different queue depths. Chapter 21.7 §8's ordering requirement is a transport-layer one and it reaches all the way down: a reordered TCP flow triggers fast retransmit and collapses. So the fabric's aggregate capacity is structurally unavailable to any individual flow.
Deliberately simplified: flow_hash % PATHS is a modulo where real hardware uses a hash over the five-tuple and a modulo that is not a power of two is expensive. path_load_gbps is an array of 32 registers updated combinationally, which is a model. Flows never end, so the loads only grow and epoch_reset is the only way back — a real measurement uses a decaying average. And flow_gbps is a constant per flow where real flows are bursty and a flow's peak is what collides.
Production implication: the missing flow termination is what makes real imbalance worse than the model's, and the mechanism is worth naming. ECMP assigns a flow to a path when the flow starts and keeps it there for the flow's life — that is what preserves ordering. So a long-lived elephant flow occupies its path for minutes, and the assignment that was random at the start becomes a fixed allocation. Two elephants that collided at 09:00 are still colliding at 09:30. The remedies are flowlet switching — re-hashing at a gap long enough that reordering cannot occur — and load-aware path selection, and both are departures from pure ECMP that exist entirely because of the arithmetic in Section 10.
10. Thirty-Two Paths, Twenty-Eight Per Cent
A fabric with 1:1 oversubscription is non-blocking by construction. This section is why that does not produce non-blocking throughput.
The set-up is exact. A leaf has 32 uplinks, all equal cost, all healthy. n large flows leave that leaf for other leaves. ECMP assigns each flow to an uplink by hashing its five-tuple, which is uniform and independent. The group saturates when its busiest uplink does.
The arithmetic is balls in bins and the results are unintuitive.
| Flows | Mean per uplink | Mean of the busiest | Imbalance | Effective capacity |
|---|---|---|---|---|
| 32 | 1.00 | 3.52 | 3.52× | 28.4% |
| 64 | 2.00 | 5.41 | 2.71× | 37.0% |
| 128 | 4.00 | 8.65 | 2.16× | 46.2% |
| 256 | 8.00 | 14.32 | 1.79× | 55.9% |
| 640 | 20.0 | 29.77 | 1.49× | 67.2% |
| 3 200 | 100.0 | 121.4 | 1.21× | 82.4% |
Row one is the headline and it is the case that matters most. With as many flows as paths — which is exactly the shape of a distributed training job, a shuffle phase, or a storage rebuild — the busiest uplink carries three and a half times the mean, and the group delivers 28.4% of its 3.2 Tb/s.
A 1:1 fabric, fully healthy, every table correct, behaves like a 3.5:1 fabric when the flow count equals the path count.
And the convergence is slow. Getting to 82.4% needs 3 200 flows — a hundred per path — so a fabric only approaches its nominal capacity when the traffic is finely divided. The workloads that need the most bandwidth are exactly the ones with the fewest, largest flows.
Three consequences, and the third is the one that changes designs.
One — the effective oversubscription is the product of two ratios.
effective ratio = bandwidth ratio ÷ ECMP efficiency
| Nominal | Flows | Efficiency | Effective |
|---|---|---|---|
| 1:1 | 32 | 28.4% | 3.52:1 |
| 1:1 | 256 | 55.9% | 1.79:1 |
| 3:1 | 32 | 28.4% | 10.6:1 |
| 3:1 | 256 | 55.9% | 5.37:1 |
Row three is a sobering number for a cost-optimised fabric: a 3:1 build carrying 32 large flows behaves like 10.6:1.
Two — adding uplinks helps less than it looks. Doubling a leaf's uplinks from 32 to 64 doubles the nominal capacity and halves the flows per path, which moves the efficiency the wrong way — more paths means more bins for the same balls, and the imbalance rises. The effective gain is real but well below 2×.
Three — the fix is not more capacity, it is finer flows or better assignment.
| Remedy | Mechanism | Cost |
|---|---|---|
| more, smaller flows | application-level striping over multiple connections | application change |
| flowlet switching | re-hash after a gap long enough to prevent reordering | per-flow timers in the switch |
| load-aware path selection | choose the least-loaded uplink, not a hash | ordering risk, and state |
| larger link groups | bond uplinks so the group is the unit | does not remove the hash |
Row one is the commonest and cheapest and it is why distributed-training frameworks open many connections per peer rather than one. Eight connections instead of one takes 32 flows to 256 — efficiency from 28.4% to 55.9%, a factor of 1.97 in throughput, for a configuration change in a library.
And the whole phenomenon is invisible to every counter in the fabric. Each uplink reports its own utilisation correctly; the aggregate utilisation of the group is 28.4% of line rate and looks like underutilisation, not congestion; the drops happen on one uplink whose counter is one of thirty-two. Chapter 21.6's class G, at fabric scale — and Section 20's rejected property is the assertion that cannot see it.
11. RTL 5 — The Failure Impact Model
// ---------------------------------------------------------------------
// failure_impact_model -- what each kind of failure removes, and the
// property that made the topology replace the tree.
//
// In a fabric with no distinguished node, every failure of a given
// kind costs the same. The three kinds are a spine, a leaf, and a
// single uplink, and their impacts differ by two orders of magnitude.
// ---------------------------------------------------------------------
module failure_impact_model
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input fabric_fail_e fail_kind,
input logic [15:0] spines_healthy,
input logic [15:0] leaves_present,
input logic [15:0] uplinks_lost_on_one_leaf,
output logic [15:0] bisection_lost_pct_x10,
output logic [15:0] worst_leaf_uplink_pct,
output logic [15:0] servers_disconnected,
output logic [15:0] ecmp_paths_remaining,
output logic impact_is_uniform,
output logic aggregate_hides_it
);
always_comb begin
bisection_lost_pct_x10 = 16'd0;
servers_disconnected = 16'd0;
worst_leaf_uplink_pct = 16'd100;
ecmp_paths_remaining = spines_healthy;
impact_is_uniform = 1'b1;
case (fail_kind)
FAIL_SPINE: begin
// 1/N of the bisection, for EVERY pair of leaves equally.
bisection_lost_pct_x10 =
16'(((32'(SPINES) - 32'(spines_healthy)) * 1000) / 32'(SPINES));
worst_leaf_uplink_pct =
16'((32'(spines_healthy) * 100) / 32'(SPINES));
impact_is_uniform = 1'b1;
end
FAIL_LEAF: begin
// Every server on it. Nothing else changes at all.
servers_disconnected = 16'(DOWN_PORTS);
bisection_lost_pct_x10 =
16'((1000) / 32'(LEAVES));
impact_is_uniform = 1'b1;
end
FAIL_UPLINK: begin
// One leaf loses part of its uplink group. The FABRIC's
// aggregate barely moves; that leaf's servers lose a
// proportional share.
worst_leaf_uplink_pct =
16'(((32'(UP_PORTS) - 32'(uplinks_lost_on_one_leaf)) * 100)
/ 32'(UP_PORTS));
bisection_lost_pct_x10 =
16'((32'(uplinks_lost_on_one_leaf) * 1000)
/ (32'(LEAVES) * 32'(UP_PORTS)));
// NOT uniform: one leaf carries all of it.
impact_is_uniform = 1'b0;
end
default: ;
endcase
// The warning. An aggregate bisection figure is nearly unchanged
// by a failure that halves one leaf's uplinks, and Section 7's
// production note is the remedy: publish the MINIMUM across
// leaves, not the total.
aggregate_hides_it = !impact_is_uniform &&
(bisection_lost_pct_x10 < 16'd50) &&
(worst_leaf_uplink_pct < 16'd90);
end
endmoduleClassification: three failure kinds, and the only interesting output is whether the damage is spread or concentrated.
What it teaches: that impact_is_uniform is true for a spine failure and false for an uplink failure, and the two therefore need different reporting. Losing a spine costs 1/32 of the bisection, identically, for every one of 2 016 leaf pairs — it is a fabric-wide event with a fabric-wide number. Losing 16 uplinks on one leaf costs 0.8% of the fabric's bisection and 50% of that leaf's, which no fabric-wide number represents.
And it teaches that aggregate_hides_it is a real condition rather than a rhetorical one. Sixteen uplinks lost on one leaf of 64: the fabric reports 99.2% of its nominal bisection, which is honest, while 32 servers have half their capacity. The dashboard is green and a rack is crippled.
Deliberately simplified: failures are one kind at a time where real events are correlated — a power domain, an optics batch, a firmware rollout — and correlated failures break the uniformity assumption the whole model rests on. FAIL_LEAF disconnects DOWN_PORTS servers with no notion of dual-homing, which real leaves usually have. And the model has no notion of recovery time, which for a fabric is the quantity operations actually cares about.
Production implication: dual-homing is the arrangement that makes FAIL_LEAF survivable and it changes the topology's arithmetic in a way worth stating. A server connected to two leaves survives either one, so a leaf failure disconnects nobody — but it doubles the leaf count needed for the same server count, halves the servers per leaf, and doubles the number of uplink groups, which by Section 10's arithmetic makes each group smaller and its ECMP efficiency worse. The reliability gain is real and it is paid for in effective bandwidth, and the trade is almost never computed because the two effects live in different teams' spreadsheets.
12. Losing a Spine Costs 1/N, Uniformly
The topology's second reason for existing, and it is an availability argument rather than a bandwidth one.
Compare the two structures under a single element failure.
| Structure | Element lost | Capacity removed | Who is affected |
|---|---|---|---|
| 3-tier tree, 2 cores | one core | 50% of the core | everyone crossing the core |
| 3-tier tree, 4 cores | one core | 25% | everyone crossing the core |
| spine-leaf, 8 spines | one spine | 12.5% | everyone, equally |
| spine-leaf, 16 spines | one spine | 6.2% | everyone, equally |
| spine-leaf, 32 spines | one spine | 3.1% | everyone, equally |
Two things change between the halves of the table and only one of them is the percentage.
First, the magnitude: 1/N where N is the number of spines, and N is large because the spines are commodity parts. A tree's core count is small because a core is expensive; a fabric's spine count is 32 because a spine is the same switch as a leaf.
Second, and more important: the shape of the impact. A tree's core failure removes capacity from a specific set of paths — the ones that were using that core — so the damage is concentrated and its distribution depends on the routing. A spine failure removes one of 32 equal-cost paths between every pair of leaves, so every pair loses exactly 1/32. There is no unlucky pair.
In a fabric, the answer to "what happens if this switch fails" does not depend on which switch.
That property is what makes capacity planning tractable. A fabric designed to survive two simultaneous spine failures needs 32 ÷ 30 times the nominal bisection — 6.7% of headroom — and the calculation does not depend on which two.
| Spines lost | Bisection remaining | Headroom needed to hold nominal |
|---|---|---|
| 1 | 96.9% | 3.2% |
| 2 | 93.8% | 6.7% |
| 4 | 87.5% | 14.3% |
| 8 | 75.0% | 33.3% |
And the same is not true of an uplink failure, which is the asymmetry Section 11's model exists to expose.
| Failure | Fabric bisection lost | Worst leaf's uplink capacity |
|---|---|---|
| one spine | 3.1% | 96.9% |
| 4 uplinks on one leaf | 0.2% | 87.5% |
| 16 uplinks on one leaf | 0.8% | 50.0% |
| 31 uplinks on one leaf | 1.5% | 3.1% |
Row four is the case that matters and it is nearly invisible in the aggregate. A leaf reduced to one working uplink has lost 1.5% of the fabric's bisection — a rounding error on a dashboard — and its 32 servers now share 100 Gb/s where they had 3 200. The fabric is 98.5% healthy and a rack is at 3.1%.
The remedy is the one Section 7 named: publish the minimum, not the total. A fabric health figure that is the worst leaf's uplink fraction would read 3.1% here, and it costs one comparator across 64 values. The figure that gets published instead is the aggregate, because the aggregate is the number the topology's marketing is built on.
13. RTL 6 — The Oversubscription Monitor
// ---------------------------------------------------------------------
// oversubscription_model -- the ratio a fabric is sold on, the ratio
// it is built at, and the ratio it behaves like.
//
// Three numbers. The first is a port count, the second is bandwidth,
// and the third includes Section 10's ECMP efficiency. Only the third
// predicts throughput and only the first ever appears in a datasheet.
// ---------------------------------------------------------------------
module oversubscription_model
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] down_ports,
input logic [15:0] up_ports,
input logic [15:0] down_rate_gbps,
input logic [15:0] up_rate_gbps,
input logic [15:0] ecmp_efficiency_pct,
input logic [15:0] leaving_leaf_pct, // traffic locality
output logic [15:0] port_ratio_x10,
output logic [15:0] bandwidth_ratio_x10,
output logic [15:0] effective_ratio_x10,
output logic [15:0] offered_uplink_gbps,
output logic [15:0] available_uplink_gbps,
output logic uplinks_saturate,
output logic port_ratio_misleads,
output logic [15:0] misleads_by_x10
);
logic [31:0] down_bw, up_bw;
always_comb begin
down_bw = 32'(down_ports) * 32'(down_rate_gbps);
up_bw = 32'(up_ports) * 32'(up_rate_gbps);
port_ratio_x10 = (up_ports == 0) ? 16'hFFFF
: (down_ports * 16'd10) / up_ports;
bandwidth_ratio_x10 = (up_bw == 0) ? 16'hFFFF
: 16'((down_bw * 10) / up_bw);
// Section 10: the group saturates at its busiest member, so the
// usable fraction is the efficiency.
effective_ratio_x10 = (ecmp_efficiency_pct == 0) ? 16'hFFFF
: (bandwidth_ratio_x10 * 16'd100)
/ ecmp_efficiency_pct;
// The locality term of Section 4: traffic that stays on the leaf
// never touches an uplink.
offered_uplink_gbps = 16'((down_bw * 32'(leaving_leaf_pct)) / 100);
available_uplink_gbps = 16'((up_bw * 32'(ecmp_efficiency_pct)) / 100);
uplinks_saturate = (offered_uplink_gbps > available_uplink_gbps);
// A mixed-rate leaf makes the port ratio wrong, and wrong in the
// pessimistic direction: 48 x 25 down and 8 x 100 up is 6:1 by
// ports and 1.5:1 by bandwidth.
port_ratio_misleads = (port_ratio_x10 != bandwidth_ratio_x10);
misleads_by_x10 = (bandwidth_ratio_x10 == 0) ? 16'd10
: (port_ratio_x10 * 16'd10) / bandwidth_ratio_x10;
end
endmoduleClassification: three ratios, computed from the same inputs, spanning a factor of four.
What it teaches: that the three ratios can be 6.0, 1.5 and 5.28 for the same leaf, and only the last predicts anything. A 48×25 down, 8×100 up leaf carrying 32 flows: port ratio 6:1, bandwidth ratio 1.5:1, effective ratio 5.28:1. The port ratio is wrong by 4× in the pessimistic direction and the bandwidth ratio is wrong by 3.5× in the optimistic one — and by coincidence the wrong pessimistic number is closer to the truth than the right optimistic one, which is how a bad habit survives.
And it teaches that uplinks_saturate needs the locality term to be honest. Traffic that stays inside a leaf never touches an uplink, so a fabric's real oversubscription is a property of the workload as much as the wiring. The three-way product — bandwidth ratio, ECMP efficiency, locality fraction — is the smallest honest model of a leaf's uplink pressure.
Deliberately simplified: ecmp_efficiency_pct is an input rather than derived from a flow count, so the block cannot warn that the efficiency it was given is optimistic. leaving_leaf_pct is a single number where locality varies enormously by workload and by time of day. All down ports share one rate and all up ports another, which covers the common mixed-rate leaf and not a leaf with three rates. And there is no notion of direction — a leaf's uplink pressure is usually asymmetric, and a storage tier's is heavily so.
Production implication: the missing directionality is worth adding because the asymmetry is large and predictable. A rack of storage servers sends far more than it receives; a rack of compute receives far more than it sends — so the uplink group's two directions can differ by several times, and a fabric sized on a symmetric model over-provisions one direction and under-provisions the other on every leaf. Model the two directions separately, size each against its own offered load, and the same uplink count often supports a noticeably larger fabric.
14. What a Fabric Must Never Promise
Five prohibitions, each derived earlier in the chapter.
One — never promise that a 1:1 fabric is non-blocking in throughput.
| Value | |
|---|---|
| nominal | 1:1 |
| behaviour at 32 flows on 32 paths | 3.52:1 |
| behaviour at 256 flows | 1.79:1 |
| behaviour at 3 200 flows | 1.21:1 |
Non-blocking is a statement about the wiring. Throughput is a statement about where a hash put the flows, and Section 10 is the arithmetic. The honest promise is: the capacity exists and its reachability depends on your flow count.
Two — never quote a bisection figure without the division by two.
A "bisection" quoted as leaves × uplinks × rate is the aggregate tier-to-tier capacity, which is twice the bisection. Both are real numbers, they are routinely interchanged, and the factor of two is exactly the size of the error that matters most in a capacity argument.
Three — never compute oversubscription from port counts.
| Leaf | Port ratio | Bandwidth ratio | Wrong by |
|---|---|---|---|
| 32×100 down, 32×100 up | 1.0:1 | 1.0:1 | — |
| 48×25 down, 8×100 up | 6.0:1 | 1.5:1 | 4× |
| 48×25 down, 4×100 up | 12.0:1 | 3.0:1 | 4× |
Four — never publish a fabric-wide health figure alone.
| Aggregate | Worst leaf | |
|---|---|---|
| 16 uplinks lost on one leaf | 99.2% healthy | 50% |
| 31 uplinks lost on one leaf | 98.5% healthy | 3.1% |
The minimum across leaves costs one comparator over 64 values and is the figure that corresponds to what a user experiences. The aggregate is the figure the topology's marketing is built on, and Section 12 shows it is nearly unmoved by a rack being crippled.
Five — never assume a fabric bounds latency.
It does not. Section 8's 1.635 µs is Chapter 17.1 §4's bounded subtotal; the two unbounded terms — interference and same-class queueing — are present, unmanaged, and can be milliseconds. A fabric has no scheduling mechanism at all, by deliberate choice: Chapter 22.2 showed what one costs, and 2 048 servers' traffic cannot be scheduled. What a fabric provides is a uniform and small bounded part, and everything above it is congestion control's problem.
A fabric's promises are: every pair is equidistant, every failure is uniform, and the capacity exists. It does not promise that the capacity is reachable, that the aggregate describes any particular leaf, or that latency has a tail bound.
15. RTL 7 — Fabric Telemetry
// ---------------------------------------------------------------------
// fabric_telemetry -- what a leaf knows about itself, plus the two
// fields that make a fabric-wide figure honest.
//
// Those two are the ECMP imbalance of this leaf's uplink group, and
// the MINIMUM healthy-uplink fraction across leaves -- which no single
// leaf can compute, and which Section 12 showed is the only figure
// that corresponds to what a user experiences.
// ---------------------------------------------------------------------
module fabric_telemetry
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input logic snapshot,
input logic [15:0] i_uplinks_healthy,
input logic [15:0] i_spines_reachable,
input logic [15:0] i_busiest_path_gbps,
input logic [15:0] i_mean_path_gbps,
input logic [31:0] i_flows,
input logic [31:0] i_drops_on_busiest,
input logic [31:0] i_drops_total,
input logic [15:0] i_leaving_leaf_pct,
input logic [15:0] i_fabric_min_uplink_pct, // IMPORTED, not local
input logic i_fabric_min_valid,
output logic [15:0] o_uplinks_healthy,
output logic [15:0] o_spines_reachable,
output logic [15:0] o_imbalance_x10,
output logic [15:0] o_ecmp_efficiency_pct,
output logic [31:0] o_flows,
output logic [15:0] o_drop_concentration_pct,
output logic [15:0] o_leaving_leaf_pct,
output logic [15:0] o_fabric_min_uplink_pct,
output logic [2:0] o_caveats,
output logic o_capacity_claim_valid
);
localparam int C_IMBALANCE = 0; // ECMP is not spreading the load
localparam int C_DEGRADED = 1; // this leaf has lost uplinks
localparam int C_NOFABRIC = 2; // no fabric-wide minimum available
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
o_uplinks_healthy <= '0; o_spines_reachable <= '0;
o_imbalance_x10 <= 16'd10; o_ecmp_efficiency_pct <= 16'd100;
o_flows <= '0; o_drop_concentration_pct <= '0;
o_leaving_leaf_pct <= '0; o_fabric_min_uplink_pct <= 16'd100;
o_caveats <= '0;
end else if (snapshot) begin
o_uplinks_healthy <= i_uplinks_healthy;
o_spines_reachable <= i_spines_reachable;
o_flows <= i_flows;
o_leaving_leaf_pct <= i_leaving_leaf_pct;
if (i_mean_path_gbps != 0)
o_imbalance_x10 <= (i_busiest_path_gbps * 16'd10)
/ i_mean_path_gbps;
if (i_busiest_path_gbps != 0)
o_ecmp_efficiency_pct <= (i_mean_path_gbps * 16'd100)
/ i_busiest_path_gbps;
// Section 10's invisible failure: drops concentrated on one
// uplink of thirty-two, while the group looks underutilised.
if (i_drops_total != 0)
o_drop_concentration_pct <= 16'((i_drops_on_busiest * 100)
/ i_drops_total);
o_fabric_min_uplink_pct <= i_fabric_min_valid
? i_fabric_min_uplink_pct : 16'd100;
o_caveats[C_IMBALANCE] <= (i_busiest_path_gbps >
((i_mean_path_gbps * 16'd15) / 16'd10));
o_caveats[C_DEGRADED] <= (i_uplinks_healthy < 16'(UP_PORTS));
o_caveats[C_NOFABRIC] <= !i_fabric_min_valid;
end
end
assign o_capacity_claim_valid = (o_caveats == 3'b0);
endmoduleClassification: a leaf's self-report, with one imported field and a verdict that requires it.
What it teaches: that i_fabric_min_uplink_pct is an input rather than a computation, and that is structural. No leaf can know the fabric's worst leaf; the minimum across leaves is a fabric-wide quantity and every switch in the fabric is a node. Chapter 22.2 §20's twelfth taxonomy group, arriving in a data centre: a guarantee that spans nodes cannot be evaluated at one, and the fix is to import the number rather than to compute it more cleverly.
And it teaches that o_drop_concentration_pct is the signature of Section 10's failure. When ECMP has piled flows onto one uplink, that uplink drops and the other 31 do not — so the concentration reads near 100% while the group's aggregate utilisation reads 28%. Drops with low utilisation is a contradiction on any per-link dashboard; it is the expected signature here, and the two fields together name it in one glance.
Deliberately simplified: no read-clear, so counters need external differencing. o_imbalance_x10 uses instantaneous loads where a decaying average is the useful form — a single measurement of a bursty group is noise. The 1.5× threshold for C_IMBALANCE is arbitrary and should scale with the flow count, since Section 10 shows the expected imbalance falls as flows rise. And there is no per-uplink breakdown, only the busiest and the mean, so a group with two hot uplinks looks like one with one.
Production implication: the flow-count-dependent threshold is the refinement that makes this field usable rather than noisy. An imbalance of 3.5× is entirely expected with 32 flows on 32 paths and is a genuine anomaly with 3 200 — so a fixed threshold either cries wolf on every small job or misses a real hash failure on a large one. Compare against the expected imbalance for the observed flow count — Section 10's table is the model — and alarm on the ratio of observed to expected, which is scale-free and needs one division.
16. RTL 8 — The Fabric Conformance Monitor
// ---------------------------------------------------------------------
// fabric_conformance_monitor -- properties for a leaf in a spine-leaf
// fabric.
//
// Properties 1 to 10 are local and every one of them holds while
// Section 10's failure is happening. Properties 11 and 12 are the two
// that do not, and both require a number from outside the switch.
// ---------------------------------------------------------------------
module fabric_conformance_monitor
import dcfabric_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] uplinks_healthy,
input logic [15:0] spines_reachable,
input logic [15:0] ecmp_paths,
input logic [7:0] chosen_path,
input logic flow_arrives,
input logic [31:0] flow_hash,
input logic [3:0] hops,
input logic same_leaf,
input logic [15:0] busiest_path_gbps,
input logic [15:0] mean_path_gbps,
input logic [15:0] ecmp_efficiency_pct,
input logic [31:0] healthy_bisect_gbps,
input logic [31:0] nominal_bisect_gbps,
input logic capacity_claim_valid,
input logic [15:0] fabric_min_uplink_pct,
input logic fabric_min_valid,
input logic spine_has_servers
);
// 1. A chosen path is always inside the path set.
p_fb_path_in_set: assert property (@(posedge clk) disable iff (!rst_n)
flow_arrives |-> (chosen_path < 8'(ecmp_paths)));
// 2. The path set is the reachable spines.
p_fb_paths_are_spines: assert property (@(posedge clk) disable iff (!rst_n)
!same_leaf |-> (ecmp_paths == spines_reachable));
// 3. Same leaf means one hop; otherwise three. There is no third value.
p_fb_hops: assert property (@(posedge clk) disable iff (!rst_n)
hops == (same_leaf ? 4'(HOPS_SAME_LEAF) : 4'(HOPS_CROSS)));
// 4. The same flow always takes the same path -- ordering depends on it.
p_fb_flow_sticky: assert property (@(posedge clk) disable iff (!rst_n)
(flow_arrives && (flow_hash == $past(flow_hash))) |->
(chosen_path == $past(chosen_path)));
// 5. Healthy uplinks never exceed the port count.
p_fb_uplinks_bounded: assert property (@(posedge clk) disable iff (!rst_n)
uplinks_healthy <= 16'(UP_PORTS));
// 6. Reachable spines never exceed the spine count.
p_fb_spines_bounded: assert property (@(posedge clk) disable iff (!rst_n)
spines_reachable <= 16'(SPINES));
// 7. Healthy bisection never exceeds nominal.
p_fb_bisect_bounded: assert property (@(posedge clk) disable iff (!rst_n)
healthy_bisect_gbps <= nominal_bisect_gbps);
// 8. The busiest path is at least the mean.
p_fb_busiest_ge_mean: assert property (@(posedge clk) disable iff (!rst_n)
busiest_path_gbps >= mean_path_gbps);
// 9. Efficiency is mean over max, and never above 100%.
p_fb_efficiency: assert property (@(posedge clk) disable iff (!rst_n)
ecmp_efficiency_pct <= 16'd100);
// 10. A spine never has servers on it -- a cabling check.
p_fb_spine_clean: assert property (@(posedge clk) disable iff (!rst_n)
!spine_has_servers);
// 11. A capacity claim requires a fabric-wide minimum, which this
// switch cannot compute.
p_fb_claim_needs_fabric: assert property (@(posedge clk) disable iff (!rst_n)
capacity_claim_valid |-> fabric_min_valid);
// 12. And it requires the imported minimum to be healthy.
p_fb_claim_needs_min: assert property (@(posedge clk) disable iff (!rst_n)
capacity_claim_valid |-> (fabric_min_uplink_pct >= 16'd90));
endmoduleClassification: ten local properties and two that cannot be discharged without a number from another switch.
What it teaches: that property 4 — a flow always takes the same path — is simultaneously a correctness requirement and the cause of Section 10's problem. It exists because striping a flow across paths reorders it; it means a flow that collided at the start of its life collides for the whole of it. The property is right, it must be kept, and every mitigation in Section 10 is a careful, bounded relaxation of it.
And it teaches that properties 11 and 12 are the whole reason the monitor is not just bookkeeping. Everything else is a range check or an identity. The two that constrain a claim both need fabric_min_valid, which arrives from outside, and a switch that does not receive it may not assert that its fabric has capacity — which is the correct behaviour and is almost never implemented.
Deliberately simplified: property 4 compares against $past(flow_hash) and therefore only catches back-to-back repeats of the same flow — a real check needs a per-flow record, which is exactly the state ECMP is designed not to keep. Property 2 assumes symmetric reachability, which Section 5's production note showed is the assumption that fails silently. Property 12's 90% threshold is arbitrary. And there is no property at all about the imbalance, because Section 15 showed the right threshold depends on the flow count and the monitor does not have it.
Production implication: the missing symmetric-reachability assumption is the one to test for in a real fabric, and the test is cheap. A spine reachable from leaf A and not from leaf B makes A's path set wrong, so A hashes flows onto a path that leads nowhere useful and A's own view reports full health. The check is to compare each leaf's reachable-spine set with every other leaf's, which is 64 bitmaps of 32 bits — 256 bytes of state for the whole fabric — and it converts a silent black hole into a one-line difference. No switch can do it; a controller can, trivially, and most do not.
17. What a Fabric Looks Like From Module 21
Module 21 built a diagnostic method for one link. This section runs it against a fabric and three of its nine chapters change character completely.
| Module 21 chapter | In a fabric |
|---|---|
| Chapter 21.1 — twelve receive-path sites | unchanged, per port — and there are 4 096 ports |
| Chapter 21.2 — the error classes | unchanged |
| Chapter 21.3 — the check sequence's one bit | unchanged |
| Chapter 21.4 — fourteen link sites | unchanged, and now the commonest failure by count |
| Chapter 21.5 — the duplex mismatch | rare — data-centre links are full duplex and fixed-rate |
| Chapter 21.6 — silent drops | transformed — Section 10's concentration |
| Chapter 21.7 — descriptor faults | unchanged, at the servers |
| Chapter 21.8 — three ceilings | a fourth appears, and it is the largest |
| Chapter 21.9 — the capture | transformed — where do you stand? |
Row eight first, because it is the chapter's contribution to Module 21's framework. Chapter 21.8 enumerated three ceilings below the line rate — framing efficiency, the design's frame rate, and the outstanding-request limit — and found the lowest one at 65.5 Gb/s on a 100 Gb/s port. A fabric adds a fourth: the ECMP efficiency of the uplink group, which at 32 flows on 32 paths is 28.4% and therefore the binding ceiling by a wide margin. It is computable in advance from a flow count and a path count, it belongs in Chapter 21.8 §5's table, and it is the one nobody computes.
Row six is transformed rather than unchanged. Chapter 21.6's class G is frames that arrived nowhere and no counter names; in a fabric the drops have a distinctive and diagnostic shape.
| A congested fabric | An imbalanced one | |
|---|---|---|
| group utilisation | high — 80%+ | low — 28% |
| drops | spread across uplinks | concentrated on one |
| the fix | more capacity | more flows, or a different hash |
Drops at low utilisation is a contradiction on a per-link dashboard and it is the expected signature here, which makes it one of the few genuinely easy fabric diagnoses — provided the drop concentration is published, which Section 15's o_drop_concentration_pct is for.
Row nine is the one where Chapter 21.9's arithmetic gets much worse.
| Value | |
|---|---|
| a 1 GB capture at 100 Gb/s | 84 ms — Chapter 21.9 §12 |
| ports in a 2 048-server fabric | 4 096, counting both tiers' links |
| capturing one of 32 uplinks | 1/32 of the traffic, chosen by a hash |
| capturing the right one | requires knowing which the flow hashed to |
Row four is the new difficulty and it is circular in a familiar way. A capture on the wrong uplink of the group contains none of the flow in question, and knowing which uplink to tap requires computing the switch's hash — which needs the five-tuple, the hash function and its seed. Chapter 21.9 §12's filter argument arrives with a twist: in a fabric the filter has to select a location as well as a predicate, and the location is a hash's output.
Row four's row one is the practical difference. Chapter 21.4's link faults become the fabric's commonest event purely by count: 4 096 optical links, each with a failure rate, means a fabric of this size sees link events routinely rather than exceptionally. The per-link diagnosis is unchanged and the operational posture is completely different — a fabric is designed to run continuously with some links down, which is Section 12's whole argument.
18. What the Fabric Assumes
Seven premises, and three of them have no local evidence.
| Assumption | If it is false |
|---|---|
| the hash spreads flows evenly | Section 10 — 28.4% at 32 flows, and that is the expected case |
| every spine is reachable from every leaf | Section 5 — an asymmetric path set, silently |
| every leaf has the same uplink health | Section 12 — 99.2% aggregate, 50% on one leaf |
| a flow's path is stable | required for ordering, and it is why collisions persist |
| traffic leaves the leaf | Section 4's locality term — intra-rack traffic never touches an uplink |
| all switches run the same forwarding behaviour | Section 6 — the one-part-number claim |
| the topology is as cabled | Section 3 — a server on a spine port works and breaks uniformity |
Row one deserves emphasis because it is usually stated as though it were a design goal rather than a statistical fact. A uniform, independent hash does spread flows evenly in the sense of equal expectation; what it does not do is produce equal loads, and the gap between those two statements is the entire content of Section 10. The hash is not broken at 28.4% — it is behaving exactly as specified.
Row two has no local evidence at all. A leaf's view of the fabric is its own uplinks and what they reach; a spine that is reachable from this leaf and not from another looks perfectly healthy here. The check is a comparison across leaves — 64 bitmaps of 32 bits, 256 bytes for the whole fabric — and no switch can do it.
Row three is the same shape. The minimum uplink health across leaves is a fabric-wide quantity; each leaf knows only its own. Section 15 imports it as a field for exactly this reason.
And rows two and three together are Chapter 22.2 §20's twelfth taxonomy group in its second setting. That chapter named it for a vehicle's schedule — a guarantee that is a relation between nodes, evaluated at one — and predicted more members as the track moved to fabrics. Here are two, in one chapter, and the remedy is identical in both settings: import a number rather than compute a cleverer property.
Row seven is worth a sentence because it is a physical assumption. A fabric's uniformity is a property of how it is cabled, and the cabling is done by people, at scale, in a building. A server patched into a spine port works — traffic flows, nothing complains — and that server is now one hop from other leaves while everyone else is three, with a spine as its failure domain. Section 3's spine_has_servers is one comparison and it is the only thing that will ever find it.
19. The Cost, Accounted
Priced against Chapter 19.7 §19's 14 166-flop MAC receive datapath, for the fifth chapter running.
| Block | Flops | Share of 14 166 |
|---|---|---|
uniform_switch_model | 0 — combinational | — |
leaf_spine_topology | 0 — combinational | — |
bisection_accountant | 0 — combinational | — |
oversubscription_model | 0 — combinational | — |
ecmp_hash_distribution | 544 | 3.8% |
failure_impact_model | 0 — combinational | — |
fabric_telemetry | 179 | 1.3% |
fabric_conformance_monitor | 0 — assertions | — |
| total | 723 | 5.1% |
Six of the eight blocks are purely combinational and that is characteristic of the subject. A topology is arithmetic over constants; what carries state is the measurement — the per-path load array of ecmp_hash_distribution, which is 32 × 16 bits = 512 flops, 70.8% of the chapter's total.
And that array is the interesting cost, because it scales with the path count.
| Uplink group | Load array | Against 14 166 |
|---|---|---|
| 8 paths | 128 flops | 0.9% |
| 32 paths | 512 flops | 3.6% |
| 64 paths | 1 024 flops | 7.2% |
| 128 paths | 2 048 flops | 14.5% |
A measurement whose size is the number of things being measured — the fourth time in five chapters a mechanism's cost has been set by data rather than by design. Chapter 21.9 §19's capture buffer, Chapter 22.1 §19's delay line, Chapter 22.3 §19's video encoder, and now a per-path load array. In each case the flop unit degrades from informative to merely arithmetic.
The comparison that does work here is the one the chapter opened with.
| Value | |
|---|---|
| fabric nominal bisection | 102.4 Tb/s |
| reachable by 32 large flows | 29.1 Tb/s |
| the gap | 73.3 Tb/s of capacity that exists and is not reachable |
| what it would cost to buy that gap in hardware | 3.5× the fabric |
| what it costs to recover it in software | opening eight connections instead of one |
Row five against row four is the chapter's practical conclusion. Getting from 28.4% to 55.9% efficiency — a factor of 1.97 in delivered throughput — is a library configuration change, and the alternative is buying twice the fabric. That is the single largest return available anywhere in Modules 19 to 23, and it is invisible to every counter in the network.
20. Properties Worth Asserting, and One Worth Refusing
Six groups. The refused property is the one every fabric validation suite contains, and it is true throughout the failure that costs 73.3 Tb/s.
Group A — topology.
// A1. Two hop counts exist and no others.
p_tp_hops: assert property (@(posedge clk) disable iff (!rst_n)
hops == (same_leaf ? 4'(HOPS_SAME_LEAF) : 4'(HOPS_CROSS)));
// A2. Path length variance is zero in a two-tier fabric.
p_tp_variance: assert property (@(posedge clk) disable iff (!rst_n)
path_length_variance == 16'd0);
// A3. The equal-cost path set is the reachable spines.
p_tp_paths: assert property (@(posedge clk) disable iff (!rst_n)
!same_leaf |-> (ecmp_paths == spines_healthy));
// A4. Servers supported is ports times down ports.
p_tp_scale: assert property (@(posedge clk) disable iff (!rst_n)
servers_supported == 32'(MAX_SERVERS_TWO_TIER));
// A5. Exceeding it demands a third tier.
p_tp_third: assert property (@(posedge clk) disable iff (!rst_n)
needs_third_tier == (servers_requested > servers_supported));
// A6. Nothing in the switch model depends on its role.
p_tp_role_cabling: assert property (@(posedge clk) disable iff (!rst_n)
role_is_cabling_only);Group B — the ratios.
// B1. The bandwidth ratio is bandwidth over bandwidth.
p_rt_bandwidth: assert property (@(posedge clk) disable iff (!rst_n)
(up_gbps != 0) |->
(bandwidth_ratio_x10 == ((down_gbps * 16'd10) / up_gbps)));
// B2. A mixed-rate leaf makes the port ratio disagree.
p_rt_misleads: assert property (@(posedge clk) disable iff (!rst_n)
(down_rate_gbps != up_rate_gbps) |-> port_ratio_misleads);
// B3. The effective ratio includes the ECMP efficiency.
p_rt_effective: assert property (@(posedge clk) disable iff (!rst_n)
(ecmp_efficiency_pct != 0) |->
(effective_ratio_x10 ==
((bandwidth_ratio_x10 * 16'd100) / ecmp_efficiency_pct)));
// B4. Bisection is half the tier-to-tier capacity.
p_rt_bisection: assert property (@(posedge clk) disable iff (!rst_n)
nominal_bisect_gbps ==
((32'(LEAVES) * 32'(UP_PORTS) * 32'(RATE_GBPS)) / 2));
// B5. Healthy bisection never exceeds nominal.
p_rt_bisect_bounded: assert property (@(posedge clk) disable iff (!rst_n)
healthy_bisect_gbps <= nominal_bisect_gbps);
// B6. Effective never exceeds healthy.
p_rt_effective_bounded: assert property (@(posedge clk) disable iff (!rst_n)
effective_bisect_gbps <= healthy_bisect_gbps);Group C — ECMP.
// C1. A chosen path is inside the set.
p_ec_in_set: assert property (@(posedge clk) disable iff (!rst_n)
flow_arrives |-> (chosen_path < 8'(PATHS)));
// C2. The same flow always takes the same path.
p_ec_sticky: assert property (@(posedge clk) disable iff (!rst_n)
(flow_arrives && (flow_hash == $past(flow_hash))) |->
(chosen_path == $past(chosen_path)));
// C3. A flow uses exactly one path.
p_ec_one_path: assert property (@(posedge clk) disable iff (!rst_n)
one_flow_one_path);
// C4. The busiest path is at least the mean.
p_ec_busiest: assert property (@(posedge clk) disable iff (!rst_n)
busiest_path_gbps >= mean_path_gbps);
// C5. Efficiency is mean over max and is capped at 100%.
p_ec_efficiency: assert property (@(posedge clk) disable iff (!rst_n)
ecmp_efficiency_pct <= 16'd100);
// C6. Imbalance is at least 1.0.
p_ec_imbalance: assert property (@(posedge clk) disable iff (!rst_n)
imbalance_x10 >= 16'd10);Group D — failures.
// D1. A spine failure is uniform.
p_fl_spine_uniform: assert property (@(posedge clk) disable iff (!rst_n)
(fail_kind == FAIL_SPINE) |-> impact_is_uniform);
// D2. An uplink failure is not.
p_fl_uplink_local: assert property (@(posedge clk) disable iff (!rst_n)
(fail_kind == FAIL_UPLINK) |-> !impact_is_uniform);
// D3. Losing a spine of N costs 1/N of the bisection.
p_fl_spine_cost: assert property (@(posedge clk) disable iff (!rst_n)
(fail_kind == FAIL_SPINE) |->
(bisection_lost_pct_x10 ==
16'((((32'(SPINES) - 32'(spines_healthy)) * 1000)) / 32'(SPINES))));
// D4. A leaf failure disconnects its servers and nothing else.
p_fl_leaf: assert property (@(posedge clk) disable iff (!rst_n)
(fail_kind == FAIL_LEAF) |-> (servers_disconnected == 16'(DOWN_PORTS)));
// D5. The aggregate-hides-it condition means a small fabric loss and a
// large leaf loss together.
p_fl_hidden: assert property (@(posedge clk) disable iff (!rst_n)
aggregate_hides_it |-> ((bisection_lost_pct_x10 < 16'd50) &&
(worst_leaf_uplink_pct < 16'd90)));
// D6. No failure increases capacity.
p_fl_monotone: assert property (@(posedge clk) disable iff (!rst_n)
(fail_kind != FAIL_NONE) |-> (bisection_lost_pct_x10 >= 16'd0));Group E — telemetry.
// E1. A capacity claim requires all caveats clear.
p_tm_claim: assert property (@(posedge clk) disable iff (!rst_n)
o_capacity_claim_valid |-> (o_caveats == 3'b0));
// E2. And it requires the imported fabric minimum.
p_tm_needs_import: assert property (@(posedge clk) disable iff (!rst_n)
o_capacity_claim_valid |-> !o_caveats[C_NOFABRIC]);
// E3. Drop concentration is a percentage.
p_tm_conc: assert property (@(posedge clk) disable iff (!rst_n)
o_drop_concentration_pct <= 16'd100);
// E4. A degraded leaf sets its caveat.
p_tm_degraded: assert property (@(posedge clk) disable iff (!rst_n)
snapshot |=> (o_caveats[C_DEGRADED] ==
($past(i_uplinks_healthy) < 16'(UP_PORTS))));
// E5. Efficiency and imbalance are reciprocal.
p_tm_reciprocal: assert property (@(posedge clk) disable iff (!rst_n)
(o_imbalance_x10 != 0) |->
(o_ecmp_efficiency_pct == (16'd1000 / o_imbalance_x10)));Group F — cabling.
// F1. A spine never has servers on it.
p_cb_spine_clean: assert property (@(posedge clk) disable iff (!rst_n)
!spine_has_servers);
// F2. Uplinks plus down ports never exceed the port count.
p_cb_ports: assert property (@(posedge clk) disable iff (!rst_n)
(ports_down_count + ports_uplink_count) <= 8'(PORTS));
// F3. Ports up is the sum of the two categories.
p_cb_census: assert property (@(posedge clk) disable iff (!rst_n)
ports_up == (ports_down_count + ports_uplink_count));
// F4. Reachable spines never exceed the spine count.
p_cb_spines: assert property (@(posedge clk) disable iff (!rst_n)
spines_reachable <= 16'(SPINES));
// F5. Healthy uplinks never exceed the uplink port count.
p_cb_uplinks: assert property (@(posedge clk) disable iff (!rst_n)
uplinks_healthy <= 16'(UP_PORTS));Thirty-four properties. Every one of them holds while a fabric delivers 28.4% of its bisection, and the one that is supposed to cover that case is the one to refuse.
21. Verification Scenarios
Fifty-eight scenarios. Group 3's are the ones that need a flow generator rather than a packet generator, and group 5's need more than one switch.
Group 1 — topology arithmetic (10).
| # | Scenario | Expect |
|---|---|---|
| 1 | P = 64, 32 down, 32 up | 32 spines, 64 leaves, 2 048 servers, 102.4 Tb/s |
| 2 | P = 64, 48 down, 16 up | 3:1, 3 072 servers, 51.2 Tb/s |
| 3 | P = 64, 56 down, 8 up | 7:1, 3 584 servers, 25.6 Tb/s |
| 4 | 2 049 servers requested at 1:1 | needs_third_tier |
| 5 | two servers on the same leaf | 1 hop |
| 6 | two servers on different leaves | 3 hops, always |
| 7 | all 2 047 pairs enumerated | 1.5% one hop, 98.5% three |
| 8 | path_length_variance checked | zero — A2 |
| 9 | a fourth hop injected | p_tp_hops fires |
| 10 | ecmp_paths set above spines_healthy | p_tp_paths fires |
Group 2 — the ratios (10).
| # | Scenario | Expect |
|---|---|---|
| 11 | 32×100 down, 32×100 up | port 1.0, bandwidth 1.0, no mislead |
| 12 | 48×25 down, 8×100 up | port 6.0, bandwidth 1.5 — a 4× mislead |
| 13 | 48×25 down, 4×100 up | port 12.0, bandwidth 3.0 — 4× again |
| 14 | bisection computed without dividing by two | double — the datasheet error |
| 15 | locality 100% | uplink offered load equals down bandwidth |
| 16 | locality 25% | a quarter; uplinks comfortable at 1:1 |
| 17 | locality 100%, 3:1 | uplinks saturate |
| 18 | efficiency 28.4% applied to a 1:1 leaf | effective ratio 3.52:1 |
| 19 | efficiency 28.4% applied to a 3:1 leaf | effective ratio 10.6:1 |
| 20 | effective_bisect above healthy_bisect | p_rt_effective_bounded fires |
Group 3 — ECMP, which needs flows rather than packets (12).
| # | Scenario | Expect |
|---|---|---|
| 21 | 32 identical flows over 32 paths | mean 1.0, busiest ≈3.52, efficiency 28.4% |
| 22 | 64 flows | ≈5.41, 37.0% |
| 23 | 128 flows | ≈8.65, 46.2% |
| 24 | 256 flows | ≈14.32, 55.9% |
| 25 | 640 flows | ≈29.77, 67.2% |
| 26 | 3 200 flows | ≈121.4, 82.4% |
| 27 | one flow | one path; 3.2 Tb/s of group, 100 Gb/s usable |
| 28 | the same five-tuple twice | the same path — C2, and it is required |
| 29 | an elephant and 31 mice | the elephant's path is the bottleneck regardless |
| 30 | two elephants colliding, held for 30 minutes | they stay collided — the assignment is sticky |
| 31 | flowlet re-hashing after a 1 ms gap | re-spread; ordering preserved if the gap exceeds the skew |
| 32 | busiest forced below mean | p_ec_busiest fires |
Group 4 — failures (10).
| # | Scenario | Expect |
|---|---|---|
| 33 | one spine of 32 down | 3.1% of bisection, uniformly; impact_is_uniform |
| 34 | eight spines down | 25.0%; still uniform |
| 35 | one leaf down | 32 servers disconnected; nothing else changes |
| 36 | 4 uplinks on one leaf | fabric 0.2%, that leaf 87.5%; not uniform |
| 37 | 16 uplinks on one leaf | fabric 0.8%, that leaf 50.0%; aggregate_hides_it |
| 38 | 31 uplinks on one leaf | fabric 1.5%, that leaf 3.1% |
| 39 | the aggregate dashboard during 38 | 98.5% healthy — the point of the test |
| 40 | the minimum-across-leaves figure during 38 | 3.1% — the figure that matters |
| 41 | a two-spine-failure headroom budget | 6.7% over nominal, independent of which two |
| 42 | FAIL_UPLINK reported as uniform | p_fl_uplink_local fires |
Group 5 — multi-switch, which one testbench cannot do (8).
| # | Scenario | Expect |
|---|---|---|
| 43 | a spine reachable from leaf A and not leaf B | A's path set is wrong; A reports full health |
| 44 | the reachability matrix compared across leaves | the asymmetry is one bitmap difference |
| 45 | fabric_min_uplink_pct not supplied | C_NOFABRIC; no capacity claim — E2 |
| 46 | the same, supplied and healthy | the claim is permitted |
| 47 | a server patched into a spine port | it works; uniformity is destroyed silently |
| 48 | spine_has_servers wired to an alarm | the cabling error is found in minutes |
| 49 | two leaves with different forwarding behaviour | the one-part-number claim is false |
| 50 | a capacity claim with no imported minimum | p_fb_claim_needs_fabric fires |
Group 6 — latency and Module 21's method (8).
| # | Scenario | Expect |
|---|---|---|
| 51 | a three-hop path, bounded terms at 100 Gb/s | 1.635 µs, zero variance |
| 52 | the same with cut-through | 1.271 µs — 22% less |
| 53 | propagation over 30 m against serialisation | 0.150 vs 0.121 µs — the cable now dominates |
| 54 | a congested group | 80% utilisation, drops spread |
| 55 | an imbalanced group | 28% utilisation, drops on one uplink |
| 56 | drop_concentration_pct in scenario 55 | near 100% — the diagnostic |
| 57 | a capture on one uplink of 32 | 1/32 of the traffic, chosen by a hash |
| 58 | a capture on the uplink the flow hashed to | requires the hash function and its seed |
Scenario 39 is the one to show an operations team and scenario 43 is the one no switch can run.
The directed test random stimulus will not produce
This test requires a fabric in perfect health delivering a quarter of its capacity, with every property passing and every dashboard green.
The arrangement:
| Step | Action |
|---|---|
| 1 | a 64-leaf, 32-spine fabric at 1:1, every link up, every table converged |
| 2 | 32 servers on one leaf each open exactly one long-lived connection to a server on another leaf |
| 3 | every flow offers 100 Gb/s — enough to saturate an uplink on its own |
| 4 | ECMP hashes the 32 flows across the 32 uplinks |
| 5 | the run continues for thirty minutes with no topology change |
| 6 | Section 20's full property set is checked throughout |
Random stimulus will not produce this because a randomised traffic generator produces many flows. Coverage-driven stimulus is built to explore state, and exploring a fabric's state means varying the five-tuples, which means hundreds or thousands of distinct flows — and Section 10's table shows that 3 200 flows reach 82.4% efficiency, where the problem is invisible. The interesting case is the smallest flow count that can saturate the group, which is exactly the count a generator has no reason to choose, and it is also exactly what a distributed training job or a storage rebuild produces.
The oracle is in four parts and two of them are expected passes.
| Part | Signal | Required value | Why alone it is not enough |
|---|---|---|---|
| 1 — every property passes | Section 20's groups A–F | all hold, all 30 minutes | this is the failure, not the health |
| 2 — the dashboard is green | healthy_bisect_gbps | 102.4 Tb/s — 100% of nominal | it is honest and useless |
| 3 — the delivered throughput | aggregate goodput | ≈29.1 Tb/s — 28.4% | looks like the servers are slow |
| 4 — the drops are concentrated | o_drop_concentration_pct | near 100% on one uplink | this is the finding |
Part 3 is the assertion worth writing and it is an assertion about a shortfall. Require the delivered aggregate to be within 10% of the value Section 10's model predicts for 32 flows — not within 10% of nominal. A test that expects nominal fails and gets "fixed" by raising the tolerance; a test that expects the model passes, and the model is the thing worth having.
Part 4 is what separates this from congestion. A congested group is at 80%+ utilisation with drops spread; this group is at 28% with drops on one uplink of 32. Those two states are trivially distinguishable once the concentration is published, and indistinguishable without it.
Run the test a second time with each server opening eight connections instead of one. Flows go from 32 to 256, efficiency from 28.4% to 55.9%, delivered throughput from 29.1 to 57.2 Tb/s — a factor of 1.97, for a library configuration change, on identical hardware carrying identical work. That contrast is the chapter's whole practical content, and it is the demonstration to put in front of anyone proposing to buy more fabric.
22. Debugging a Fabric
A procedure, ordered so that the cheapest and most diagnostic reads come first.
| Step | Do | Because |
|---|---|---|
| 1 | read the minimum uplink health across leaves | Section 12 — the aggregate is 98.5% while a rack is at 3.1% |
| 2 | read the group utilisation and the drop concentration together | 28% with concentrated drops is imbalance, not congestion |
| 3 | count the flows | Section 10 — 3.5× imbalance is expected at 32 flows |
| 4 | compare observed imbalance against the model's expectation | a fixed threshold is wrong at every scale but one |
| 5 | compare each leaf's reachable-spine bitmap with the others | Section 18 — an asymmetric path set is silent |
| 6 | check for servers on spine ports | Section 3 — it works, and it destroys uniformity |
| 7 | only now look at links and optics | Chapter 21.4's space, unchanged, per port |
| 8 | check the oversubscription was computed from bandwidth | Section 14 — port counts are wrong by 4× on a mixed-rate leaf |
Step 7 being seventh inverts the instinct, because a fabric of 4 096 optical links has link events routinely and most of them are not the problem being investigated. A fabric is designed to run with some links down — Section 12 — so a failed link is a normal state, not a diagnosis.
Four signatures.
| Signature | Cause |
|---|---|
| low group utilisation, drops on one uplink, throughput at ~28% of nominal | ECMP imbalance with few flows — Section 10 |
| high group utilisation, drops spread across uplinks | genuine congestion — buy capacity or shed load |
| one rack slow, fabric dashboard green | uplinks lost on one leaf — Section 12 |
| intermittent black-holing of some flows and not others | an asymmetric reachable-spine set — Section 5 |
Row one's third clause is the number to memorise. Throughput that lands near 28% of nominal on a healthy 1:1 fabric is not a coincidence — it is the expected value for a flow count equal to the path count, and recognising it turns a week of investigation into a question about how many connections the application opens.
Row four is the hardest and it is the one with no local evidence. A flow hashed onto a spine that its source leaf believes is reachable and its destination leaf cannot reach is black-holed; the source leaf reports full health, the destination reports full health, and only a comparison across leaves shows it. The comparison is 64 bitmaps of 32 bits — 256 bytes for the whole fabric — and it belongs in a controller, because no switch can perform it.
23. Misconceptions
Six, in wrong-model / what-it-costs / corrected-model form.
Misconception 1 — "a 1:1 fabric is non-blocking."
Wrong model: the wiring provides as much uplink capacity as access capacity, so nothing blocks.
What it costs: a factor of 3.5 in throughput, and a wrong diagnosis when it shows up. Section 10: 32 large flows across 32 equal-cost uplinks put 3.52× the mean on the busiest one, and a group saturates at its busiest member — so the group delivers 28.4% of its 3.2 Tb/s.
Corrected model: non-blocking is a statement about the wiring; throughput is a statement about where a hash put the flows. The effective ratio is bandwidth ratio ÷ ECMP efficiency, and at 32 flows a 1:1 fabric behaves like 3.52:1. It converges: 256 flows give 55.9%, 3 200 give 82.4% — so the honest promise is that the capacity exists and its reachability depends on your flow count.
Misconception 2 — "oversubscription is the ratio of down ports to up ports."
Wrong model: count the ports on each side and divide.
What it costs: a factor of four on the commonest leaf in the industry, in the pessimistic direction. A 48×25 Gb/s down, 8×100 Gb/s up leaf is 1 200 Gb/s down and 800 up — 1.5:1 — and its port ratio is 6:1.
Corrected model: the ratio is bandwidth over bandwidth, always. And there are three ratios worth computing rather than one: port (6.0), bandwidth (1.5), and effective (5.28 at 32 flows) — only the third predicts throughput, and by an unhelpful coincidence the wrong port ratio is closer to it than the right bandwidth ratio is, which is how the bad habit survives.
Misconception 3 — "the fabric's bisection bandwidth is leaves × uplinks × rate."
Wrong model: add up the leaf-to-spine capacity and that is the bisection.
What it costs: a factor of two, in every capacity argument. Bisection is the capacity across a cut that splits the fabric in half, and the leaf-to-spine links are bidirectional — only half of them cross a given cut.
Corrected model: bisection = leaves × uplinks × rate ÷ 2. For 64 leaves, 32 uplinks and 100 Gb/s that is 102.4 Tb/s, not 204.8. Both numbers are real and they mean different things; the undivided one is the aggregate tier-to-tier capacity, and the two are used interchangeably often enough that the figure should always be labelled.
Misconception 4 — "the fabric dashboard is green, so the fabric is fine."
Wrong model: an aggregate health figure describes the fabric's users' experience.
What it costs: an entire rack, invisibly. Section 12: a leaf that has lost 31 of its 32 uplinks costs the fabric 1.5% of its bisection — a rounding error — and leaves its 32 servers sharing 100 Gb/s where they had 3 200. The fabric reports 98.5% healthy and a rack is at 3.1%.
Corrected model: publish the minimum across leaves, not the total. It costs one comparator over 64 values, it is the figure that corresponds to what somebody experiences, and the aggregate is the figure the topology's marketing is built on. Both belong on the dashboard and only one of them belongs alone.
Misconception 5 — "adding uplinks fixes an imbalanced group."
Wrong model: the group is short of capacity, so widen it.
What it costs: money, for less than half the expected return. Doubling a leaf's uplinks from 32 to 64 doubles the nominal capacity and halves the flows per path — more bins for the same balls — so the imbalance gets worse and the efficiency falls. The net gain is real and well under 2×.
Corrected model: the constraint is flows per path, not paths. Eight connections per peer instead of one takes 32 flows to 256, efficiency from 28.4% to 55.9%, a factor of 1.97 in delivered throughput for a library configuration change on identical hardware. The hardware alternative — buying the 73.3 Tb/s gap — is 3.5× the fabric.
Misconception 6 — "a spine-leaf fabric bounds latency."
Wrong model: every path is the same length, so latency is predictable.
What it costs: any design that depends on a tail bound. Section 8's 1.635 µs is Chapter 17.1 §4's bounded subtotal — serialisation, store-and-forward, blocking, propagation and lookup. The two unbounded terms, interference and same-class queueing, are present and entirely unmanaged, and a fabric has no scheduling mechanism at all.
Corrected model: a fabric makes the bounded part uniform and small and leaves the rest to congestion control. That is a deliberate choice and the alternative was priced in Chapter 22.2: a time-aware schedule costs 19.48% of the link and cannot be built for 2 048 servers. Uniform latency is not bounded latency, and the fabric promises the first.
24. Interview Questions
Six, with what a strong answer contains.
1. Derive a spine-leaf fabric's size from one switch's port count.
P = D + U; spines = U; leaves = P; servers = P × D; bisection = leaves × U × rate ÷ 2. For a 64-port 100 Gb/s switch split 32:32 that is 32 spines, 64 leaves, 2 048 servers and 102.4 Tb/s. A strong answer names the division by two and why it is there — bisection is a cut, and only half the bidirectional links cross it — and names the scaling wall: P × D, beyond which a third tier appears and the uniform three-hop property is lost.
2. Why is every switch in the fabric the same part?
Because a spine and a leaf differ only in what is plugged into them. A spine's forwarding table is smaller — one entry per leaf rather than one per server — so a part sized for the leaf role is sufficient for the spine role. A strong answer gives the operational consequences: one part number, one spare pool, one qualification campaign, one firmware image, and scaling by adding commodity parts rather than replacing a tier with larger ones. It is an economic argument, not a packet-forwarding one, and it is why the topology displaced the tree.
3. Your 1:1 fabric is delivering 28% of its bisection with every link up. What is happening?
ECMP has hashed a small number of large flows onto a small number of paths. With 32 flows on 32 uplinks the busiest carries 3.52× the mean, and a group saturates at its busiest member — so 28.4% is the expected value, not a fault. A strong answer names the signature — low group utilisation with drops concentrated on one uplink, which contradicts every per-link dashboard's intuition — and the fix: more, smaller flows. Eight connections instead of one gives 55.9%, a factor of 1.97.
4. What does losing one spine cost, and why does the answer not name the spine?
1/N of the bisection — 3.1% for 32 spines — uniformly, for every one of 2 016 leaf pairs. A strong answer contrasts with a tree: a core failure removes capacity from the specific paths that used it, so the damage is concentrated and depends on routing. In a fabric there is no unlucky pair, which is what makes capacity planning tractable: surviving two spine failures needs 6.7% of headroom and the calculation does not depend on which two.
5. A fabric reports 98.5% healthy and one rack is unusably slow. Explain.
A leaf has lost most of its uplinks. Thirty-one of 32 uplinks on one leaf of 64 is 1.5% of the fabric's bisection — arithmetically honest — and 3.1% of that leaf's uplink capacity, shared among its 32 servers. A strong answer names the remedy: publish the minimum across leaves, not the total — one comparator over 64 values — and observes that the aggregate is exactly the figure the topology's marketing is built on, which is why it is the one that gets published.
6. Why can a single switch not verify that the fabric has its capacity?
Because two of the premises are fabric-wide. Whether every spine is reachable from every leaf is a comparison across leaves; the minimum uplink health is a minimum over leaves. A leaf's own view reports full health in both failure cases. A strong answer names the remedy — import the number: a reachability bitmap per leaf is 256 bytes for the whole fabric, and the minimum is one comparison — and connects it to Chapter 22.2 §20's taxonomy group: a guarantee that spans nodes cannot be evaluated at one.
25. Questions and Answers
26. What's Next
The fabric's shape is established. What is inside it, what runs on it, and what it is built from are the rest of Module 23.
| This chapter established | |
|---|---|
| the topology | P = D + U decides everything; 2 048 servers from a 64-port part |
| the economics | one part number in both tiers, and that is why it won |
| the distance | 1 or 3 hops, zero variance, 1.635 µs bounded |
| the honest ratio | bandwidth ÷ ECMP efficiency — 3.52:1 for a 1:1 fabric at 32 flows |
| the failure model | 1/N per spine, uniformly; and an aggregate that hides a crippled leaf |
| the refused property | capacity asserted by verifying the paths that carry it |
The chapter's one number to carry forward is 28.4%. A fabric in perfect health, with every property in Section 20 passing, delivers 29.1 Tb/s of a 102.4 Tb/s bisection to the flow pattern that a distributed job actually produces — and the 73.3 Tb/s gap is closed by opening eight connections instead of one, not by buying 3.5× the hardware.
Chapter 23.2 is where that gap stops being an inefficiency and becomes the design constraint. An AI training cluster's traffic is the worst possible case for everything this chapter derived: a small number of enormous, simultaneous, synchronised flows, arriving in a collective pattern where the slowest path sets the pace of every participant, and where a single imbalanced uplink stalls a job across thousands of accelerators. The questions are what that traffic pattern actually is, why ECMP's statistics fail hardest exactly there, and what a fabric has to add — scheduling, telemetry, or a different path-selection rule — to carry it.
Continue learning
Related tutorials
- Related topic
Ethernet for AI Training Clusters
A collective runs at its slowest member, so the same 32 flows deliver 63.9% as independent traffic and 28.4% as a ring all-reduce — and one uplink stalls 1 024 accelerators.
- Related topic
What a Switch Does
One decision per frame multiplied a 24-station network's capacity by 48. It did not remove contention — it moved it from the wire into a queue, where dropping is the mechanism working, not a fault.
- Related topic
A High-Rate Data-Centre NIC
Descriptors are 38% of the payload at minimum frame size and break a Gen5 host interface at 400 Gb/s — and the part's dominant state is a context cache with a cliff at 39.4%.
- Related topic
Data Center Systems — The Endpoint Is Not the System
Every endpoint passes its own benchmark and the system still fails. An aggregate counter reporting 98.75% utilisation is hiding a class served 0.1 GB/s, because a work-conserving metric is maximised by starvation.
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.
