Ethernet · Module 25
"Switches Eliminate Broadcasts"
One flooding port takes 98.4% of a 64-port switch's frame budget, a 128k table refuses 25 607 of 131 072 addresses, and the occupancy counter reads 80.5% throughout.
Chapter 12.1 said what a switch does and Chapter 12.4 said what it does when it cannot. This chapter is about the gap between those two sentences, which is larger than anybody expects and is a property of a table rather than of the traffic.
The myth, stated as the people who hold it would state it. A hub repeats every frame to every port. A switch learns where stations are and forwards only to the right one. So switching replaced broadcasting, and a switched network does not flood.
The first two sentences are true. The third confuses two different things a switch replicates.
| What a switch does | Why | |
|---|---|---|
| a known unicast | forwards to one port | the table has an entry — Chapter 12.2 |
| a broadcast | replicates to every port in the VLAN | by specification; the address means everyone |
| an unknown unicast | replicates to every port in the VLAN | because the table has no entry |
| a multicast with no membership | the same | Chapter 12.4 §13 — no local fix |
Rows two to four are the same operation and only row two is deliberate. Row three is this chapter's subject: a broadcast in every respect except its address, and it happens whenever a table cannot hold what the network offers it.
Three numbers dispose of the myth.
| The number | Section | |
|---|---|---|
| what one flooding port costs a 64-port switch | 9.375 Gpps of emission — 98.4% of its 9.524 Gpps budget | 2 |
| what a 128k four-way table stores when offered 131 072 addresses | 105 465 — it refuses 25 607, or 19.5% | 6 |
| what the occupancy counter reads while that happens | 80.5% — comfortably below capacity | 8 |
A switch does not eliminate flooding. It reduces it to the cases the table cannot cover, and the size of that set is a property of a hash nobody chose.
1. Scope — Two Replications, One Mechanism
This chapter owns four derivations.
| What is derived | |
|---|---|
| Sections 2 to 3 | the replication at Chapter 23.3's scale — 98.4% of the frame budget from one port |
| Sections 4 to 7 | the table's effective capacity at 128k entries, and the occupancy at which refusals begin |
| Sections 8 to 11 | why the occupancy counter reads healthy, and what a design must report instead |
| Sections 12 to 13 | the interaction with Chapter 25.2 §8's stale entries — 833 sets held for stations that no longer exist |
What this chapter does not own. Chapter 12.4 owns the three flood causes, the replication engine, the receiver's three fates and the domain-size limit; Chapter 12.5 owns the CAM, the hash, the set-associative store and the ageing sweep. This chapter re-derives neither at their own scale — it takes Chapter 12.4 §3's amplification from a 24-port gigabit switch to Chapter 23.3's 64-port 100 Gb/s part, and Chapter 12.5 §9's capacity arithmetic from 8 192 entries to 131 072.
2. The Replication, at 64 Ports and 100 Gb/s
Chapter 12.4 §3 derived the amplification on a 24-port gigabit switch. Take it to the part Chapter 23.3 built.
One port flooding minimum-size frames at 100 Gb/s.
84 octets on the wire → 100 × 10⁹ ÷ (84 × 8) = 148.81 Mpps
× 63 copies = 9.375 Gpps of frame emissionAnd Chapter 23.3 §2 derived that switch's aggregate frame budget from its port count: 9.524 Gpps, which is what sets its eight-wide pipeline.
| Value | |
|---|---|
| one port, minimum-size frames at 100 Gb/s | 148.81 Mpps |
| × 63 copies | 9.375 Gpps |
| the switch's whole frame budget | 9.524 Gpps |
| share consumed by ONE flooding port | 98.4% |
| left for the other 63 ports | 1.6% |
One port, flooding at line rate, consumes 98.4% of a 6.4 Tb/s switch's entire frame-handling capacity — and Chapter 12.4 §3's observation holds: buying a bigger switch does not help, because the amplification scales with the port count.
In bandwidth rather than frames the arithmetic is the same shape.
| Value | |
|---|---|
| 1 port in at 100 Gb/s | 100 Gb/s |
| 63 copies out | 6 300 Gb/s |
| aggregate egress capacity | 6 400 Gb/s |
| share | 98.4% |
Chapter 12.4 §3's convergence holds: (N − 1) ÷ N → 1 in bandwidth and (N − 1) ÷ 2N → ½ when the ingress is counted too. At 64 ports the egress share is 98.4% and it never exceeds 100%.
And the frame-rate view is the binding one, because Chapter 23.3 §2's pipeline is sized in packets per second: an eight-wide pipeline at 1.2 GHz handles 9.6 Gpps and a single flooding port asks for 9.375.
3. RTL 1 — The Broadcast Package and the Amplification Model
// ---------------------------------------------------------------------
// bcastmyth_pkg -- the constants a flooding argument needs at Chapter
// 23.3's scale, with every derived figure computed here.
//
// Unit: Chapter 23.3 Section 2's bitcell equivalent.
// 1 BCE = 0.35 GE = one bit of usable on-die SRAM
// 1 flip-flop = 20 BCE
// Chapter 19.7 Section 19's MAC receive datapath = 283 320 BCE
// Chapter 23.3's switch: 64 ports, 9.524 Gpps, table 128k x 96 b
// ---------------------------------------------------------------------
package bcastmyth_pkg;
localparam int unsigned DATAPATH_BCE = 283_320;
localparam int unsigned BCE_PER_FLOP = 20;
localparam int unsigned PORTS = 64;
localparam int unsigned MIN_SLOT_OCT = 84; // Chapter 24.3 Sec 1
localparam int unsigned BUDGET_PPS_K = 9_524_000;
// ---- Chapter 12.5's table, at Chapter 23.3's size --------------------
localparam int unsigned TBL_ENTRIES = 128 * 1024;
localparam int unsigned TBL_WAYS = 4;
localparam int unsigned TBL_SETS = TBL_ENTRIES / TBL_WAYS; // 32 768
localparam int unsigned TBL_WIDTH = 96;
// ---- Chapter 12.5 Section 5's ageing, Chapter 25.2 Section 8's churn --
localparam int unsigned AGEING_SECONDS = 300;
typedef enum logic [1:0] {
FLOOD_NONE = 2'd0,
FLOOD_BCAST = 2'd1, // by specification
FLOOD_UNKNOWN = 2'd2, // because the table has no entry
FLOOD_MCAST = 2'd3 // no membership -- Chapter 12.4 Section 13
} floodcause_e;
// ---- derived: replication ---------------------------------------------
function automatic int unsigned copies(int unsigned members);
return (members == 0) ? 0 : (members - 1);
endfunction
function automatic int unsigned pps_at(int unsigned gbps,
int unsigned slot_oct);
// frames per second, in thousands, to keep the product in range.
return (gbps * 1_000_000) / (slot_oct * 8);
endfunction
// ---- derived: effective capacity, Chapter 12.5 Section 9 --------------
// stored = S x E[min(Load, W)] with Load ~ Poisson(n/S), so the
// refused share depends only on the LOAD -- offered addresses per
// set -- and not on the key's width. Chapter 13.4 Section 8's result.
//
// Integer hardware cannot evaluate exp, so the curve is tabulated at
// eight anchors and interpolated between them. The anchors, at W = 4:
// load 1.00 -> 0.435% load 2.00 -> 3.757%
// load 3.00 -> 10.645% load 4.00 -> 19.537%
// load 5.00 -> 28.737% load 6.00 -> 37.217%
// load 8.00 -> 50.744% load 16.00 -> 75.001%
// Worst interpolation error is about six per cent, at load 12.
function automatic int unsigned load_x100(int unsigned offered,
int unsigned sets);
return (offered * 100) / sets;
endfunction
function automatic int unsigned refused_ppm(int unsigned offered,
int unsigned sets);
int unsigned l;
l = load_x100(offered, sets);
if (l <= 100) return (l * 43);
if (l <= 200) return 4_349 + ((l - 100) * 332);
if (l <= 300) return 37_571 + ((l - 200) * 688);
if (l <= 400) return 106_452 + ((l - 300) * 889);
if (l <= 500) return 195_367 + ((l - 400) * 920);
if (l <= 600) return 287_369 + ((l - 500) * 847);
if (l <= 800) return 372_167 + ((l - 600) * 676);
if (l <= 1600) return 507_436 + ((l - 800) * 303);
return 750_007;
endfunction
function automatic int unsigned datapaths_milli(int unsigned bce);
return (bce * 1000) / DATAPATH_BCE;
endfunction
endpackage// ---------------------------------------------------------------------
// flood_amplification_model -- what a flooding port asks of a switch,
// at the scale Chapter 23.3 built.
//
// The output that matters is budget_share_ppm, which reaches 984 252
// for one port at 64 -- and the fact that it cannot be delivered is
// why the symptom is "the network is slow" rather than "port 7 is".
// ---------------------------------------------------------------------
module flood_amplification_model
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] members,
input logic [15:0] port_gbps,
input logic [15:0] slot_octets,
input logic [31:0] budget_pps_k,
output logic [15:0] copies_o,
output logic [31:0] port_pps_k,
output logic [31:0] emissions_pps_k,
output logic [31:0] budget_share_ppm,
output logic [31:0] egress_bw_gbps,
output logic [31:0] egress_share_ppm,
output logic exceeds_budget,
output logic bigger_switch_helps,
output logic [31:0] c_over_budget
);
always_comb begin
copies_o = 16'(copies(int'(members)));
port_pps_k = 32'(pps_at(int'(port_gbps), int'(slot_octets)));
emissions_pps_k = port_pps_k * 32'(copies_o);
budget_share_ppm = (budget_pps_k == 0) ? 32'd0
: ((emissions_pps_k * 1_000_000) / budget_pps_k);
egress_bw_gbps = 32'(port_gbps) * 32'(copies_o);
egress_share_ppm = (members == 0) ? 32'd0
: ((32'(copies_o) * 1_000_000) / 32'(members));
exceeds_budget = (emissions_pps_k > budget_pps_k);
// Chapter 12.4 Section 3's observation: the amplification scales
// WITH the port count, so a larger part suffers the same share.
bigger_switch_helps = 1'b0;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_over_budget <= '0;
else if (exceeds_budget) c_over_budget <= c_over_budget + 32'd1;
end
endmoduleClassification: a multiplication by the member count, with a constant that refuses the obvious remedy.
What it teaches: that budget_share_ppm is 984 252 for one flooding port on a 64-port switch — 98.4% — and that bigger_switch_helps is hard-wired zero. Chapter 12.4 §3 established why: the amplification is the port count less one, so a 128-port switch faced with the same flooding port suffers the same proportional damage and inflicts it on twice as many stations.
And it teaches that egress_share_ppm converges on unity rather than on a half. Chapter 12.4 §3's (N − 1) ÷ 2N → ½ counts the ingress in the denominator; (N − 1) ÷ N → 1 counts only the egress, which is the capacity the flood actually consumes. At 64 ports it is 98.4% and at 8 ports 87.5%.
Deliberately simplified: members is the VLAN's member count and the model takes it as given, where Chapter 25.4 §4 derived that segmentation sets it. slot_octets is a constant where a real flood carries a mixture and the frame-rate arithmetic is dominated by the small end. And the model computes a demand rather than a delivery — it says what the switch is asked for, not what comes out, and the callout in Section 2 is the account of what actually happens.
Production implication: the demand-against-delivery distinction is what makes this failure hard to attribute in the field. A switch asked for 9.375 Gpps and capable of 9.524 delivers frames from every port at a reduced rate, and every port's drop counter increments — including the sixty-three ports that are victims. So a per-port drop count ranks the victims and never names the cause, which is Chapter 14.3 §8's second_order_pct argument arriving in a third mechanism: the place a shortage is felt and the place it is caused are different ports, and the obvious counter reports the first. The counter that names the cause is flooded frames per INGRESS port, and Chapter 12.4 §4's classifier already computes it.
4. Unknown Unicast Is a Broadcast With a Different Address
Section 2 priced the replication. This section establishes that the switch performs it for two entirely different reasons, and that only one of them is anybody's decision.
The two are identical inside the switch.
| Broadcast | Unknown unicast | |
|---|---|---|
| destination address | all ones | a specific station's |
| the forwarding decision | replicate to every member | replicate to every member |
| the replication engine used | Chapter 12.4 §5's | the same |
| copies at 64 ports | 63 | 63 |
| egress capacity consumed | 98.4% at line rate | 98.4% |
| the buffer pressure | identical | identical |
And they differ completely at the receiver — Chapter 12.4 §6.
| Broadcast | Unknown unicast | |
|---|---|---|
| the card's decision | none — always accepted | exact 48-bit compare |
| cost to the host | full, on every station | zero — discarded in hardware |
| who notices | every user | nobody |
| who reports it | a help desk, within minutes | no one, ever |
The two floods are the same operation with the same cost to the switch and opposite visibility to the people who pay for the switch.
And the causes differ in kind.
A broadcast floods because it is supposed to. ARP, DHCP discovery, and every neighbour-discovery protocol depend on it; Chapter 12.4 §6 established that a card cannot discard it without breaking them. The rate is a property of the hosts and it is bounded by what they choose to send.
An unknown unicast floods because the table failed. The destination exists, it is reachable, the switch has forwarded to it before — and the entry is not there now. The rate is a property of the table and it is bounded by nothing the operator configured.
Four reasons an entry is absent, and only the first is benign.
| Reason | Duration | Chapter |
|---|---|---|
| the station has not spoken yet | until it does — one frame | Chapter 12.2 |
| the entry aged out during a quiet interval | until the station speaks again | Chapter 12.5 §13 |
| a topology change flushed the table | seconds | Chapter 12.5 |
| the table REFUSED the insert | permanently | Chapter 12.5 §9 — Sections 6 to 8 |
Row four is this chapter's subject and it is the one with no expiry. A station whose address the table refused floods every frame sent to it, forever, on a switch whose occupancy counter reads comfortably below capacity.
5. RTL 2 — The Flood-Cause Classifier
// ---------------------------------------------------------------------
// unknown_unicast_classifier -- why this frame is flooding, and how
// long the reason will last.
//
// The output that matters is reason_is_permanent, which is true for
// exactly one of the four causes and is the one nothing reports.
// ---------------------------------------------------------------------
module unknown_unicast_classifier
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_valid,
input logic dest_is_broadcast,
input logic dest_is_multicast,
input logic lookup_hit,
input logic insert_was_refused,
input logic entry_aged_out,
input logic topology_changed,
input logic never_seen,
input logic [15:0] members,
output logic [1:0] cause,
output logic is_flooding,
output logic [15:0] copies_now,
output logic reason_is_permanent,
output logic host_cost_is_zero,
output logic anybody_will_notice,
output logic [31:0] c_bcast,
output logic [31:0] c_unknown,
output logic [31:0] c_unknown_permanent,
output logic [31:0] c_mcast
);
always_comb begin
cause = dest_is_broadcast ? 2'(FLOOD_BCAST)
: (!lookup_hit && !dest_is_multicast) ? 2'(FLOOD_UNKNOWN)
: (dest_is_multicast && !lookup_hit) ? 2'(FLOOD_MCAST)
: 2'(FLOOD_NONE);
is_flooding = (cause != 2'(FLOOD_NONE));
copies_now = is_flooding ? 16'(copies(int'(members))) : 16'd1;
// Section 4's table: four reasons an entry is absent and one has
// no expiry. A refused insert is not retried and never ages.
reason_is_permanent = (cause == 2'(FLOOD_UNKNOWN)) && insert_was_refused;
// Chapter 12.4 Section 6: an exact 48-bit compare discards it.
host_cost_is_zero = (cause == 2'(FLOOD_UNKNOWN));
// Which is why nobody reports it.
anybody_will_notice = (cause == 2'(FLOOD_BCAST));
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_bcast <= '0; c_unknown <= '0; c_unknown_permanent <= '0; c_mcast <= '0;
end else if (frame_valid && is_flooding) begin
case (cause)
2'(FLOOD_BCAST): c_bcast <= c_bcast + 32'd1;
2'(FLOOD_MCAST): c_mcast <= c_mcast + 32'd1;
default: begin
c_unknown <= c_unknown + 32'd1;
if (reason_is_permanent)
c_unknown_permanent <= c_unknown_permanent + 32'd1;
end
endcase
end
end
endmoduleClassification: a four-way classifier over a forwarding outcome, with a duration attached to each cause.
What it teaches: that reason_is_permanent separates a flood that will stop from one that will not, and that nothing in a deployed switch computes it. Chapter 12.4 §4's classifier answers why is this frame flooding; this one answers and how long for, which is the question that decides whether an operator should act.
And it teaches that anybody_will_notice is true for exactly one cause. A broadcast flood reaches every host's CPU and produces complaints; an unknown-unicast flood is discarded in hardware at every station and produces none — so the counters are the only witness, and c_unknown_permanent is the one that matters.
Deliberately simplified: insert_was_refused must come from Chapter 12.5 §6's store, which reports TI_SET_FULL when a set is full and eviction is not permitted — so the classifier depends on a signal a real table does produce and most designs do not route out. The four causes are treated as exclusive where a topology change and an ageing expiry can coincide. And multicast is classified crudely, ignoring Chapter 12.4 §13's membership table.
Production implication: TI_SET_FULL is the signal that makes this chapter actionable and it is almost never exposed. Chapter 12.5 §6's insert path already computes it — the set was full, eviction was not permitted, the address was not learned — and a design that counts it per set index rather than in aggregate gives an operator the hash distribution directly. 32 768 sets at a 4-bit saturating counter is 131 072 bits, 131 072 BCE, 0.46 datapaths, 0.023% of Chapter 23.3's switch — and it turns "the table refuses inserts at 80% occupancy" from a theorem into a histogram somebody can read.
6. The Thrash Threshold, at 128k Entries
Section 4 named the permanent cause. This section says when it starts, and the answer is a property of a hash rather than of a capacity.
Chapter 12.5 §9 derived the effective capacity of a four-way set-associative table at 8 192 entries. Take the same arithmetic to Chapter 23.3's 128k-entry table — 32 768 sets of 4 ways — and offer it addresses.
stored = S × E[min(Load, W)], Load ~ Poisson(n ÷ S), S = 32 768, W = 4| Offered addresses | Load per set | Stored | Refused | Refused share |
|---|---|---|---|---|
| 32 768 | 1.00 | 32 626 | 142 | 0.435% |
| 65 536 | 2.00 | 63 074 | 2 462 | 3.757% |
| 98 304 | 3.00 | 87 840 | 10 464 | 10.645% |
| 131 072 | 4.00 | 105 465 | 25 607 | 19.537% |
| 163 840 | 5.00 | 116 759 | 47 081 | 28.737% |
| 196 608 | 6.00 | 123 448 | 73 160 | 37.217% |
| 262 144 | 8.00 | 129 123 | 133 021 | 50.744% |
Offered exactly its nominal capacity — 131 072 addresses into 131 072 entries — the table stores 105 465 and refuses 25 607. Nineteen and a half per cent of the network's destinations flood every frame, permanently.
Three properties of this curve decide the chapter.
One — the refusals begin long before the table is full. At a load of 1.00 — 32 768 addresses in a 131 072-entry table, 25% occupancy — the table already refuses 142. Chapter 12.5 §7's observation at 8 192 entries scales: sets fill unevenly because the hash is not the operator's to choose.
Two — the refused share is a function of the LOAD and not of the table size. Chapter 13.4 §8 established why: the arithmetic is balls in bins over S sets and W ways, and the key's width appears nowhere in it. A 128k table at a load of 4.00 refuses the same 19.537% an 8k table does at a load of 4.00.
Three — the effective capacity is 80.5% and it is reached by refusing three addresses for every one it keeps. At 262 144 offered, the table holds 129 123 of 131 072 entries — 98.5% of nominal — and has refused 133 021 addresses to get there.
Which gives the threshold a deployment needs, in the unit it has.
| If the network offers | The table refuses | And the flood share is |
|---|---|---|
| half the nominal capacity | 0.4% | 0.4%, plus broadcast |
| the nominal capacity | 19.5% | 19.5% |
| twice it | 50.7% | 50.7% |
A datasheet saying "128 000 MAC addresses" describes the memory. Chapter 12.5 §9's sentence, at a larger scale: a four-way implementation of it holds 105 465 when asked for 131 072.
7. RTL 3 — The Table Capacity Model
// ---------------------------------------------------------------------
// table_capacity_model -- Chapter 12.5 Section 9's arithmetic at
// Chapter 23.3's scale, reporting the refused share as a flood rate.
//
// The output that matters is flood_share_is_permanent, because the
// refusals in this model do not age, retry or heal.
// ---------------------------------------------------------------------
module table_capacity_model
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] offered_addresses,
input logic [31:0] sets,
input logic [15:0] ways,
input logic [15:0] members,
output logic [31:0] nominal_entries,
output logic [31:0] load_x100_o,
output logic [31:0] refused_ppm_o,
output logic [31:0] stored_estimate,
output logic [31:0] refused_estimate,
output logic [15:0] effective_capacity_pct,
output logic [31:0] occupancy_pct_x100,
output logic [31:0] mean_copies_x1000,
output logic flood_share_is_permanent,
output logic occupancy_reads_healthy,
output logic [31:0] c_evaluations
);
always_comb begin
nominal_entries = sets * 32'(ways);
load_x100_o = 32'(load_x100(int'(offered_addresses), int'(sets)));
refused_ppm_o = 32'(refused_ppm(int'(offered_addresses), int'(sets)));
refused_estimate = (offered_addresses * refused_ppm_o) / 32'd1_000_000;
stored_estimate = offered_addresses - refused_estimate;
effective_capacity_pct = (offered_addresses == 0) ? 16'd100
: 16'((stored_estimate * 32'd100)
/ offered_addresses);
// THE field that reads healthy while a fifth of the network floods.
occupancy_pct_x100 = (nominal_entries == 0) ? 32'd0
: ((stored_estimate * 32'd10_000) / nominal_entries);
// Chapter 12.4 Section 3's amplification, weighted by the refusals.
mean_copies_x1000 = 32'd1000
+ ((refused_ppm_o * (32'(copies(int'(members))) - 32'd1)) / 32'd1000);
// Section 4's table, row four: a refused insert is not retried.
flood_share_is_permanent = (refused_ppm_o != 32'd0);
// And the observation this chapter exists for.
occupancy_reads_healthy = (occupancy_pct_x100 < 32'd9000)
&& (refused_ppm_o > 32'd100_000);
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_evaluations <= '0;
else c_evaluations <= c_evaluations + 32'd1;
end
endmoduleClassification: a balls-in-bins model whose two most useful outputs are a permanence flag and a contradiction detector.
What it teaches: that occupancy_reads_healthy is a real, reachable state — occupancy below 90% while more than 10% of addresses are refused — and that it is exactly the situation at a load of 4.00: occupancy 80.5%, refusals 19.5%. A design that reports occupancy alone has told the operator the table is fine while a fifth of the destinations flood.
And it teaches that mean_copies_x1000 turns a table statistic into an egress demand. At 19.5% refusals and 64 members the module reports 13 112 — 13.11 mean copies per frame against 1.0 on a healthy switch — which is Chapter 12.4 §3's amplification weighted by a quantity nobody measures.
Deliberately simplified: refused_ppm is the eight-anchor interpolation the package documents, worst error about six per cent at a load of 12. The model assumes every offered address is equally likely to be addressed, where in practice a refused address that nobody sends to costs nothing — so mean_copies_x1000 is an upper bound. And the arrival process is Poisson, which Chapter 12.5 §5's production note warns is optimistic: a hash reducing to the low index bits clusters severely in a real rack and refuses at 30% occupancy.
Production implication: the traffic-weighting simplification cuts both ways and the direction depends on something the switch could measure. If the refused addresses are ones nobody sends to — silent stations, stale entries — the flood cost is far below the model's estimate. If they are popular servers, it is at the estimate or worse, because a refused address that everybody sends to floods on every one of those frames. The measurement that distinguishes them is flooded frames per destination address, which is a per-address counter nobody builds — and the practical substitute is a small, sampled cache of the most-flooded destinations. Sixteen entries of 48 bits plus a 32-bit count is 1 280 bits, 1 280 BCE, 0.005 datapaths, and it turns "19.5% of addresses are refused" into "these sixteen destinations are costing you the switch."
8. Why the Occupancy Counter Reads Healthy
Section 6 established that a table at its nominal load refuses 19.5% of what it is offered. This section is about the instrument, and the instrument is the reason nobody notices.
A switch reports table occupancy: entries used against entries available. At a load of 4.00 — 131 072 addresses offered to a 131 072-entry four-way table — it reports:
| Value | |
|---|---|
| entries used | 105 465 |
| entries available | 131 072 |
| occupancy | 80.5% |
| addresses refused | 25 607 |
| destinations flooding, permanently | 19.5% |
The alarm threshold in every monitoring system is set at 90% and the metric's ceiling is 80.5%. The alarm cannot fire, and the failure it was built to catch is happening.
The ceiling is not a coincidence and it is not 80.5% by luck. Chapter 12.5 §9 derived it: the effective capacity of a four-way set-associative table is E[min(Load, W)] ÷ W at the load where the table stops accepting, and at a load of 4.00 that is 80.5%. Pushing more addresses at it raises occupancy toward 100% and raises refusals faster — at a load of 8.00 the table is 98.5% occupied and refusing 50.7%.
| Offered | Occupancy | Refused | Would a 90% alarm fire? |
|---|---|---|---|
| 32 768 | 24.9% | 0.4% | no |
| 65 536 | 48.1% | 3.8% | no |
| 131 072 | 80.5% | 19.5% | NO |
| 196 608 | 94.2% | 37.2% | yes — far too late |
| 262 144 | 98.5% | 50.7% | yes |
Row three is the state a deployment sits in and the alarm is silent. Row four is where the alarm fires, and by then more than a third of the destinations are flooding.
So the metric is not merely a lagging indicator; it is a metric whose informative range is above the range the design operates in.
9. RTL 4 — The Miss Amplifier
// ---------------------------------------------------------------------
// miss_amplifier -- how a table's refusal rate becomes an egress
// demand, and what the occupancy metric says while it does.
//
// The output that matters is alarm_can_fire, which is low across the
// whole operating range of a four-way table -- the metric's ceiling
// is beneath the threshold, and Section 20's refused property is the
// assertion built on it.
// ---------------------------------------------------------------------
module miss_amplifier
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] refused_ppm_i,
input logic [31:0] occupancy_pct_x100_i,
input logic [15:0] occupancy_alarm_pct,
input logic [15:0] members,
input logic [31:0] frames_per_s_k,
output logic [31:0] mean_copies_x1000,
output logic [31:0] egress_demand_pps_k,
output logic [31:0] demand_vs_flat_x100,
output logic [31:0] effective_ceiling_pct,
output logic alarm_can_fire,
output logic alarm_is_silent_at_failure,
output logic refusal_is_an_event,
output logic [31:0] c_silent_failures
);
always_comb begin
// Chapter 12.4 Section 3's amplification, weighted by the refusals.
mean_copies_x1000 = 32'd1000
+ ((refused_ppm_i * (32'(copies(int'(members))) - 32'd1)) / 32'd1000);
egress_demand_pps_k = (frames_per_s_k * mean_copies_x1000) / 32'd1000;
demand_vs_flat_x100 = mean_copies_x1000 / 32'd10;
// Chapter 12.5 Section 9's effective capacity for this structure.
// At four ways it is 80.5%, and it is a CEILING.
effective_ceiling_pct = 32'd80;
// THE finding. An alarm above the ceiling is unreachable.
alarm_can_fire = (32'(occupancy_alarm_pct) <= effective_ceiling_pct);
alarm_is_silent_at_failure = !alarm_can_fire
&& (refused_ppm_i > 32'd100_000);
// The metric that CAN see it is an event, not a level.
refusal_is_an_event = 1'b1;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_silent_failures <= '0;
else if (alarm_is_silent_at_failure) c_silent_failures <= c_silent_failures + 32'd1;
end
endmoduleClassification: an amplifier with a ceiling constant, whose output is a statement about an instrument rather than about traffic.
What it teaches: that alarm_can_fire is low for any threshold above 80%, and that 90% is the value every monitoring system ships with. The module makes the ceiling an explicit constant so the comparison is visible: a level alarm set above a metric's ceiling is not a conservative alarm, it is an absent one.
And it teaches that demand_vs_flat_x100 reports 1 311 at a 19.5% refusal rate and 64 members — 13.11 times the egress demand of a switch that forwards everything to one port — while occupancy reads 80.5%. The two numbers describe the same switch and only one of them is alarming.
Deliberately simplified: effective_ceiling_pct is hard-coded at 80 for a four-way structure, where Chapter 12.5 §10's table gives 63.2% direct-mapped, 72.9% two-way, 86.0% eight-way and 90.1% sixteen-way — so the right constant depends on the associativity and a general model would compute it. The amplifier assumes every refused address is addressed at the same rate as every stored one, which Section 7's production note identifies as an upper bound. And frames_per_s_k is a scalar where the flood's own copies contend with the traffic being measured.
Production implication: the ceiling's dependence on associativity is worth exposing as a register, because it makes the alarm threshold derivable rather than inherited. A part that reports its effective capacity — E[min(Load, W)] ÷ W for its own W, a constant per design — lets a monitoring system set the threshold below it. At four ways the alarm belongs at 70%, not 90; at sixteen ways it could sit at 85. One read-only register, and the alarm becomes reachable. Without it every deployment inherits a 90% default that is correct for a memory and wrong for every set-associative structure, which is the same shape as Chapter 14.2 §9's 75% watermark: a fraction of a resource standing in for a derivation nobody performed.
10. Stale Entries, and the Sets They Hold
Sections 6 and 8 assumed every entry belongs to a station that exists. Chapter 25.2 §8 established that in a container fleet many of them do not.
That chapter's arithmetic: a fleet reissuing addresses holds stale forwarding entries in proportion to its churn against Chapter 12.5 §5's 300-second ageing.
teardown interval = 3 600 ÷ churn per hour
stale entries held = 300 ÷ teardown interval| Churn | Teardown every | Stale entries | Of 131 072 entries |
|---|---|---|---|
| 100/hour | 36 s | 8.3 | 0.006% |
| 1 000/hour | 3.6 s | 83.3 | 0.064% |
| 10 000/hour | 0.36 s | 833.3 | 0.636% |
As a share of the table it is negligible, and that is the wrong denominator.
Chapter 12.5 §7's arithmetic says the scarce resource is not entries; it is SETS WITH ROOM. A stale entry does not consume 1/131 072 of the table's capacity — it consumes one way of one set, and if that set was already at three of four ways, it is the difference between accepting the next address hashing there and refusing it.
Work the effect at a load of 4.00.
| Without stale entries | With 833 stale | |
|---|---|---|
| live addresses offered | 131 072 | 131 072 |
| plus stale | — | 833 |
| total keys competing | 131 072 | 131 905 |
| load per set | 4.000 | 4.025 |
| refused share | 19.537% | 19.773% |
| refusals | 25 607 | 26 081 |
| extra refusals | — | 474 |
Eight hundred and thirty-three stale entries cause four hundred and seventy-four additional refusals — one for every 1.76 dead entries held — and the ones that fall on live stations flood forever.
The harm of a stale entry is not the memory it occupies. It is the live address it displaces, and the displacement is permanent while the stale entry is transient.
11. RTL 5 — The Stale-Entry Model
// ---------------------------------------------------------------------
// stale_entry_model -- Chapter 25.2 Section 8's stale entries, priced
// in the unit Chapter 12.5 Section 7 says is scarce: sets with room.
//
// The output that matters is live_refusals_caused, because it is the
// harm, and it is not the memory the stale entries occupy.
// ---------------------------------------------------------------------
module stale_entry_model
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] churn_per_hour,
input logic [15:0] ageing_seconds,
input logic [31:0] live_addresses,
input logic [31:0] sets,
input logic eviction_permitted,
output logic [31:0] teardown_interval_ms,
output logic [31:0] stale_entries,
output logic [31:0] stale_share_ppm,
output logic [31:0] keys_competing,
output logic [31:0] refused_without_stale,
output logic [31:0] refused_with_stale,
output logic [31:0] live_refusals_caused,
output logic [31:0] dead_per_live_x100,
output logic refusal_outlives_the_cause,
output logic eviction_would_fix_it,
output logic [31:0] c_displacements
);
always_comb begin
teardown_interval_ms = (churn_per_hour == 0) ? 32'hFFFF_FFFF
: (32'd3_600_000 / churn_per_hour);
stale_entries = (teardown_interval_ms == 0) ? 32'd0
: ((32'(ageing_seconds) * 32'd1000) / teardown_interval_ms);
stale_share_ppm = (live_addresses == 0) ? 32'd0
: ((stale_entries * 1_000_000) / live_addresses);
keys_competing = live_addresses + stale_entries;
refused_without_stale = (live_addresses
* 32'(refused_ppm(int'(live_addresses),
int'(sets)))) / 32'd1_000_000;
refused_with_stale = (keys_competing
* 32'(refused_ppm(int'(keys_competing),
int'(sets)))) / 32'd1_000_000;
live_refusals_caused = (refused_with_stale > refused_without_stale)
? (refused_with_stale - refused_without_stale)
: 32'd0;
dead_per_live_x100 = (live_refusals_caused == 0) ? 32'd0
: ((stale_entries * 32'd100) / live_refusals_caused);
// THE asymmetry: the cause ages out and the consequence does not.
refusal_outlives_the_cause = 1'b1;
// Chapter 12.5 Section 11's LRU eviction turns a permanent refusal
// into a transient one, at the cost of being pushable.
eviction_would_fix_it = !eviction_permitted;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) c_displacements <= '0;
else if (live_refusals_caused != 0) c_displacements <= c_displacements
+ live_refusals_caused;
end
endmoduleClassification: a difference between two evaluations of the same capacity curve, with the difference being the harm.
What it teaches: that live_refusals_caused is 474 for 833 stale entries at a load of 4.00, so dead_per_live_x100 reports 175 — about 1.76 dead entries displace one address. The ratio is not one-to-one because the stale entries land on random sets and only those already at the ways limit cause a refusal.
And it teaches that refusal_outlives_the_cause is a hard-wired one. The stale entry is gone in 300 seconds; the refused station floods until something re-attempts its insert, and Chapter 12.2's silent-station case means that may be never. The cause is transient and the effect is not, which is the asymmetry Section 10's callout develops.
Deliberately simplified: stale entries are assumed uniformly distributed across sets, which they are, and the refusal difference is computed from the aggregate curve rather than per set — so the model gives an expectation and a particular table's answer varies. eviction_permitted is a single bit where Chapter 12.5 §11's policy has several variants. And the model does not distinguish a stale entry whose set was already full — which causes no additional refusal at all — from one that filled the last way.
Production implication: the per-set view is where the fix lives and it is cheaper than it sounds. A sweep that ages entries in the fullest sets first, rather than uniformly by timestamp, removes stale entries from exactly the sets where they are doing harm — and Chapter 12.5 §13's ageing sweep already walks every set, so the change is to order the walk by occupancy rather than by index. The cost is a per-set occupancy count, which Section 5's production note already argued for at 131 072 bits, 0.46 datapaths, and the two features share it. One structure, two uses: an operator-visible histogram of set fullness, and a sweep that prioritises the sets that are refusing.
12. What Storm Control Actually Bounds
Chapter 12.4 §11 called storm control the one mitigation available at this layer, and §12 asked what a setting actually delivers. This section answers it at 64 ports and 100 Gb/s.
Storm control is a rate limiter on flooded traffic, per ingress port. A setting of p per cent permits p per cent of the port's line rate to be flooded and discards the excess.
| Setting | Flood admitted per port | Egress demand at 63 copies | Of the 9.524 Gpps budget |
|---|---|---|---|
| 100% | 148.81 Mpps | 9.375 Gpps | 98.4% |
| 10% | 14.88 Mpps | 0.938 Gpps | 9.8% |
| 1% | 1.49 Mpps | 0.094 Gpps | 0.98% |
| 0.1% | 0.15 Mpps | 0.009 Gpps | 0.10% |
A 1% setting caps one port's flood contribution at 0.98% of the switch's frame budget, which is the mechanism working exactly as intended.
And then the question Chapter 12.4 §12 asked: what does that do to the traffic it limits?
| Broadcast | Unknown unicast | |
|---|---|---|
| what is discarded | ARP requests, DHCP discovery, neighbour discovery | ordinary application traffic to a station the table refused |
| the consequence | a retry, usually invisible | a connection that stalls |
| who sees it | the protocol's own timers | the user |
| is a limiter appropriate? | yes — the rate is a host property and bounded | NO — the rate is a table property and the traffic is legitimate |
Row four is the uncomfortable result and it inverts the usual configuration. Storm control on broadcast is correct: the traffic is control-plane, the protocols retry, and a runaway rate is a fault. Storm control on unknown unicast discards real user data to work around a table that is refusing inserts, and the frames it drops are the ones a correct switch would have delivered to one port.
A limiter on unknown unicast converts a bandwidth problem into a loss problem, and the traffic it drops is exactly the traffic whose destination the switch failed to learn.
So the correct configuration is asymmetric and it is usually set the other way round, because the two are the same replication and a configuration interface that offers one knob invites one setting.
13. RTL 6 — The Storm Budget
// ---------------------------------------------------------------------
// storm_budget -- what a storm-control setting admits, and which
// traffic it discards to get there.
//
// The output that matters is discards_legitimate_traffic, which is
// high whenever the limiter is applied to unknown unicast -- because
// that traffic's destination exists and the switch failed to learn it.
// ---------------------------------------------------------------------
module storm_budget
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic tick_us,
input logic [15:0] setting_pct_x10, // tenths of a per cent
input logic [31:0] port_pps_k,
input logic [15:0] members,
input logic [31:0] budget_pps_k,
input logic applies_to_broadcast,
input logic applies_to_unknown,
input logic frame_flooded,
input logic [1:0] cause_i,
output logic [31:0] admitted_pps_k,
output logic [31:0] demand_pps_k,
output logic [31:0] budget_share_ppm,
output logic limiter_active,
output logic discards_legitimate_traffic,
output logic treats_the_cause,
output logic [31:0] c_discarded_bcast,
output logic [31:0] c_discarded_unknown,
output logic [31:0] c_admitted
);
logic [31:0] tokens;
always_comb begin
admitted_pps_k = (port_pps_k * 32'(setting_pct_x10)) / 32'd1000;
demand_pps_k = admitted_pps_k * 32'(copies(int'(members)));
budget_share_ppm = (budget_pps_k == 0) ? 32'd0
: ((demand_pps_k * 1_000_000) / budget_pps_k);
limiter_active = (setting_pct_x10 < 16'd1000);
// Section 12's table, row four. A broadcast the limiter drops is
// a control-plane frame whose protocol retries; an unknown unicast
// it drops is application data to a station that exists.
discards_legitimate_traffic = limiter_active && applies_to_unknown;
// And neither setting changes how many addresses the table refused.
treats_the_cause = 1'b0;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tokens <= '0; c_discarded_bcast <= '0;
c_discarded_unknown <= '0; c_admitted <= '0;
end else begin
if (tick_us) tokens <= admitted_pps_k / 32'd1000;
if (frame_flooded) begin
if (tokens != 32'd0) begin
tokens <= tokens - 32'd1;
c_admitted <= c_admitted + 32'd1;
end else begin
if ((cause_i == 2'(FLOOD_BCAST)) && applies_to_broadcast)
c_discarded_bcast <= c_discarded_bcast + 32'd1;
if ((cause_i == 2'(FLOOD_UNKNOWN)) && applies_to_unknown)
c_discarded_unknown <= c_discarded_unknown + 32'd1;
end
end
end
end
endmoduleClassification: a token bucket with two discard counters, and the separation between them is the module's contribution.
What it teaches: that treats_the_cause is a hard-wired zero. A limiter caps the damage and changes no property of the table: the same 25 607 addresses are refused before and after, and the same destinations flood — they simply flood less often, because their frames are being dropped.
And it teaches that c_discarded_bcast and c_discarded_unknown need separate counters because they mean opposite things. A discarded broadcast is the mechanism working; a discarded unknown unicast is user data lost to work around a table failure. One counter for both reports a rate and hides which of the two it is.
Deliberately simplified: the token bucket refills once per microsecond tick with no burst allowance, where Chapter 12.4 §11's limiter has a burst parameter that dominates the behaviour on bursty traffic. Multicast is not limited at all in this model, though real implementations usually offer a third setting. And the limiter is per ingress port where the damage is an aggregate across ports — sixty-four ports each flooding at 1% together demand 63% of the budget, and no per-port setting bounds that sum.
Production implication: the per-port-against-aggregate gap is the one that produces a surprise during an incident. A deployment that sets 1% per port and reasons that flooding is capped at 1% has capped it at 63%, because the amplification applies to each port independently and the budget is shared. The bound a switch actually needs is an aggregate flood budget, which no standard configuration model offers: the natural form is a second token bucket at the replication engine, shared across ingress ports, costing one counter and one comparison. Without it the per-port setting is correct about a quantity nobody cares about, and the arithmetic that matters — ports × setting × copies — is left for an operator to do during the incident.
14. What a Flooding Claim Must Never Do
Five prohibitions.
| # | Never | Because |
|---|---|---|
| 1 | report table occupancy as table health | Section 8: the ceiling is 80.5% and every default alarm sits at 90% |
| 2 | count broadcast and unknown-unicast floods in one counter | Section 4: identical in the switch, opposite at the receiver, different causes, different fixes |
| 3 | apply storm control to unknown unicast as a remedy | Section 12: it discards legitimate data to work around a table refusing inserts |
| 4 | reason about a per-port limiter as an aggregate bound | Section 13: 64 ports at 1% each is 63% of the budget |
| 5 | treat a stale entry as costing only its own memory | Section 10: 833 stale entries cause 474 additional refusals |
Row one is the one this chapter exists for and it is a one-register fix: report the effective capacity beside the occupancy.
15. RTL 7 — Broadcast Telemetry
// ---------------------------------------------------------------------
// bcast_telemetry -- the counters that let an operator tell a switch
// that is forwarding from one that is flooding.
//
// Design rule: a level cannot see this failure, so every counter here
// is an EVENT or a per-set distribution.
// ---------------------------------------------------------------------
module bcast_telemetry
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic clear,
input logic frame_valid,
input logic is_flooding_i,
input logic [1:0] cause_i,
input logic [15:0] copies_i,
input logic insert_attempted,
input logic insert_refused,
input logic [15:0] set_index,
input logic [3:0] set_occupancy,
input logic [15:0] ingress_port,
output logic [47:0] c_frames,
output logic [47:0] c_flooded,
output logic [47:0] c_bcast_o,
output logic [47:0] c_unknown_o,
output logic [47:0] c_mcast_o,
output logic [47:0] c_copies_emitted,
output logic [47:0] c_insert_refused,
output logic [47:0] c_sets_at_capacity,
output logic [31:0] flood_share_ppm,
output logic [31:0] mean_copies_x1000,
output logic [31:0] refused_rate_ppm,
output logic refusals_are_occurring,
output logic occupancy_would_have_alarmed,
output logic [15:0] worst_ingress_port
);
logic [47:0] per_port_flood [64];
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n || clear) begin
c_frames <= '0; c_flooded <= '0; c_bcast_o <= '0; c_unknown_o <= '0;
c_mcast_o <= '0; c_copies_emitted <= '0; c_insert_refused <= '0;
c_sets_at_capacity <= '0; worst_ingress_port <= '0;
for (int i = 0; i < 64; i++) per_port_flood[i] <= '0;
end else begin
if (frame_valid) begin
c_frames <= c_frames + 48'd1;
if (is_flooding_i) begin
c_flooded <= c_flooded + 48'd1;
c_copies_emitted <= c_copies_emitted + 48'(copies_i);
// Section 3's production note: the counter that names the
// cause is flooded frames per INGRESS port.
per_port_flood[ingress_port[5:0]] <=
per_port_flood[ingress_port[5:0]] + 48'd1;
if (per_port_flood[ingress_port[5:0]] >
per_port_flood[worst_ingress_port[5:0]])
worst_ingress_port <= ingress_port;
case (cause_i)
2'(FLOOD_BCAST): c_bcast_o <= c_bcast_o + 48'd1;
2'(FLOOD_MCAST): c_mcast_o <= c_mcast_o + 48'd1;
default: c_unknown_o <= c_unknown_o + 48'd1;
endcase
end
end
// The event a level cannot see.
if (insert_attempted && insert_refused)
c_insert_refused <= c_insert_refused + 48'd1;
if (set_occupancy == 4'd4)
c_sets_at_capacity <= c_sets_at_capacity + 48'd1;
end
end
always_comb begin
flood_share_ppm = (c_frames == 0) ? 32'd0
: 32'((c_flooded * 48'd1_000_000) / c_frames);
mean_copies_x1000 = (c_frames == 0) ? 32'd0
: 32'((c_copies_emitted * 48'd1000) / c_frames);
refused_rate_ppm = (c_frames == 0) ? 32'd0
: 32'((c_insert_refused * 48'd1_000_000) / c_frames);
// Section 8: this is nonzero long before any level alarm.
refusals_are_occurring = (c_insert_refused != 48'd0);
// And the counterfactual, reported so the gap is visible.
occupancy_would_have_alarmed = 1'b0;
end
endmoduleClassification: eight counters, of which the two that see the failure are an event count and a per-set distribution.
What it teaches: that refusals_are_occurring is nonzero at a load of 1.00 — 142 refusals in a table 24.9% occupied — and every level alarm is silent. Chapter 12.5 §8's argument for watching the distribution rather than the total, rendered as the difference between a counter that can report the fault and one that cannot.
And it teaches that worst_ingress_port is the attribution Section 3's production note argued for. A per-port drop count ranks the victims; flooded frames per ingress port names the source, and Chapter 14.3 §8's second_order_pct made exactly this point about a different mechanism: the obvious counter reports where the shortage is felt and the useful one names a party other than the one reporting.
Deliberately simplified: occupancy_would_have_alarmed is hard-wired zero rather than computed, which makes it a claim and not a check — it is there so the counterfactual has a name in the register map. The per-port array is declared for 64 ports and indexed by the low six bits. And c_sets_at_capacity increments on every observation of a full set rather than counting distinct sets, so it is a rate rather than a census — the census needs Chapter 12.5 §13's sweep to walk the table.
Production implication: the census-against-rate distinction decides whether the number is actionable. A rate of full-set observations rises with traffic and tells an operator nothing about the table's state; a census — how many of 32 768 sets currently hold four entries — is a direct measurement of how close the table is to refusing. Chapter 12.5 §13's ageing sweep already visits every set on a timer, so the census costs one accumulator reset at the start of the sweep and one increment per full set — and it produces the number Section 8's callout says is the only level that can see the failure. One counter, folded into a sweep that already runs.
16. RTL 8 — The Flooding Conformance Monitor
// ---------------------------------------------------------------------
// bcast_conformance -- the checks that hold a switch's flooding
// behaviour to what it can actually claim.
//
// There is no check that the switch does not flood, because Chapter
// 12.1 Section 13's class 49 established that flooding is the
// specified response to an unknown destination.
// ---------------------------------------------------------------------
module bcast_conformance
import bcastmyth_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [15:0] occupancy_alarm_pct,
input logic [15:0] effective_capacity_pct,
input logic counters_merged,
input logic storm_control_on_unknown,
input logic [15:0] per_port_setting_pct_x10,
input logic [15:0] ports_i,
input logic [15:0] aggregate_budget_pct,
input logic refusals_reported,
input logic claims_no_flooding,
input logic [47:0] c_flooded_i,
output logic v_alarm_unreachable,
output logic v_counters_merged,
output logic v_limiter_on_unknown,
output logic v_aggregate_unbounded,
output logic v_refusals_unreported,
output logic v_claims_no_flooding,
output logic [5:0] violations,
output logic conformant
);
logic [31:0] aggregate_pct_x10;
always_comb begin
// 1. Section 8 -- a threshold above the ceiling cannot fire.
v_alarm_unreachable = (occupancy_alarm_pct > effective_capacity_pct);
// 2. Prohibition 2 -- two causes, two fixes, one counter.
v_counters_merged = counters_merged;
// 3. Section 12 -- the limiter discards legitimate data.
v_limiter_on_unknown = storm_control_on_unknown;
// 4. Section 13 -- a per-port setting is not an aggregate bound.
aggregate_pct_x10 = 32'(per_port_setting_pct_x10) * 32'(ports_i);
v_aggregate_unbounded = (aggregate_pct_x10
> (32'(aggregate_budget_pct) * 32'd10));
// 5. Section 15 -- the only metric that can see it.
v_refusals_unreported = !refusals_reported;
// 6. Chapter 12.1 Section 13's class 49: flooding is specified.
v_claims_no_flooding = claims_no_flooding && (c_flooded_i != 48'd0);
violations = { v_claims_no_flooding, v_refusals_unreported,
v_aggregate_unbounded, v_limiter_on_unknown,
v_counters_merged, v_alarm_unreachable };
conformant = (violations == 6'b000000);
end
endmoduleClassification: six checks, of which the first is about an instrument's range rather than a design's behaviour.
What it teaches: that v_alarm_unreachable compares a threshold against a ceiling and fires for any occupancy alarm above 80% on a four-way table. That is every default in every monitoring system, and the check turns a silent misconfiguration into a boot-time message.
And it teaches that v_claims_no_flooding is the check against the myth itself. Chapter 12.1 §13's class 49 established that asserting a switch never floods is asserting the absence of a behaviour the design was built to perform; this monitor does not assert it and catches a design that claims it.
Deliberately simplified: effective_capacity_pct must be supplied and is a design constant — Chapter 12.5 §10's table gives 63.2%, 72.9%, 80.5%, 86.0% and 90.1% for one, two, four, eight and sixteen ways — so a part that does not expose it leaves this check unbindable. counters_merged and refusals_reported are configuration facts the monitor trusts. And v_aggregate_unbounded assumes every port is configured identically, which a real deployment does not guarantee.
Production implication: effective_capacity_pct is the register this chapter most wants and it costs nothing. It is a compile-time constant of the table's geometry — one number per design, derivable by the designer in an afternoon with Chapter 12.5 §9's balls-in-bins sum — and exposing it read-only lets every downstream tool set a reachable threshold. Without it each deployment inherits 90%, which is right for a memory and wrong for every set-associative structure, and the failure it was meant to catch happens at 80.5% with the alarm silent. The general habit is the one Chapter 25.2 §3's production note named for a different structure: a design that cannot report its own limits has made them unauditable, and the fix is a read-only register rather than a document.
17. The Two Floods, Priced Side by Side
Everything this chapter derived, at 64 ports and 100 Gb/s.
| Broadcast | Unknown unicast | |
|---|---|---|
| copies per frame | 63 | 63 |
| egress demand at line rate | 9.375 Gpps — 98.4% of the budget | the same |
| cost to each receiving host | full — every frame reaches the CPU | zero — an exact 48-bit compare discards it |
| who complains | every user, within minutes | nobody, ever |
| the cause | a host chose to send it | the table refused an insert |
| the rate is bounded by | what hosts send | nothing the operator configured |
| duration | the sender's own | permanent, for a refused address |
| storm control is | correct | a way of discarding user data |
| the real fix | Chapter 12.4 §10's domain size | a bigger or more associative table |
Rows three and four together are why the myth survives, and rows five to seven are why the second flood is the dangerous one.
And the table arithmetic, at Chapter 23.3's 128k entries.
| Offered | Load | Stored | Refused | Occupancy | Mean copies/frame |
|---|---|---|---|---|---|
| 32 768 | 1.00 | 32 625 | 0.4% | 24.9% | 1.27 |
| 65 536 | 2.00 | 63 074 | 3.8% | 48.1% | 3.33 |
| 131 072 | 4.00 | 105 465 | 19.5% | 80.5% | 13.11 |
| 196 608 | 6.00 | 123 437 | 37.2% | 94.2% | 24.07 |
| 262 144 | 8.00 | 129 123 | 50.7% | 98.5% | 32.46 |
Row three is a switch at its rated capacity, with an occupancy counter reading 80.5%, no alarm firing, and thirteen times the egress demand of a switch that forwards everything.
18. What the Correction Assumes
Eight assumptions.
| # | Assumption | If it is false |
|---|---|---|
| 1 | Chapter 23.3's 64-port 100 Gb/s part | the amplification is N − 1 either way; the budget share converges on 1 |
| 2 | a four-way 32 768-set table | Section 6's callout: the refused share is 36.8% direct-mapped and 9.9% at sixteen ways |
| 3 | addresses arrive Poisson across sets | Chapter 12.5 §5: a poor hash clusters and refuses at 30% occupancy |
| 4 | the eight-anchor interpolation of the capacity curve | worst error about six per cent, at a load of 12 |
| 5 | every refused address is addressed at the same rate | Section 7's production note: the flood cost is an upper bound |
| 6 | Chapter 12.5 §5's 300-second ageing | Section 10's stale-entry count scales inversely with it |
| 7 | minimum-size frames for the frame-rate arithmetic | larger frames lower the packet rate and the budget share |
| 8 | BCE prices the table and the counters | Section 19 examines it and it holds cleanly |
Assumption 3 is the one that makes every figure in this chapter optimistic. A Poisson arrival is the best case for a hash, and Chapter 12.5 §5's production note gives the failure: a hash that reduces to the low index bits works perfectly in simulation with random addresses and clusters severely in a real rack, refusing inserts at 30% occupancy rather than 80.5%. Every refusal figure here is a floor.
19. The Cost, Accounted — in BCE
This chapter's blocks.
| Block | Flops | BCE | × the datapath |
|---|---|---|---|
flood_amplification_model | 32 | 640 | 0.002 |
unknown_unicast_classifier | 128 | 2 560 | 0.009 |
table_capacity_model | 32 | 640 | 0.002 |
miss_amplifier | 32 | 640 | 0.002 |
stale_entry_model | 32 | 640 | 0.002 |
storm_budget | 128 | 2 560 | 0.009 |
bcast_telemetry, 64 ports | 3 456 | 69 120 | 0.244 |
bcast_conformance | 0 — combinational | 0 | 0 |
| this chapter's additions | 3 840 | 76 800 | 0.271 |
bcast_telemetry is 90.0% of the total and most of it is the per-ingress-port flood array — 64 counters of 48 bits, priced as registers at 20 BCE per bit. A production design puts that bank in an SRAM with a read-modify-write and pays 3 072 BCE instead of 61 440, which is the second table's figure and the reason the two differ by twenty.
And the structures the chapter argues for.
| BCE | × the datapath | % of the switch | |
|---|---|---|---|
| per-set occupancy, 32 768 × 4 bits | 131 072 | 0.463 | 0.023% |
| per-ingress-port flood counters, 64 × 48, as an array | 3 072 | 0.011 | 0.001% |
| a 16-entry most-flooded cache | 1 280 | 0.005 | 0.000% |
| an effective-capacity register | 8 | 0.000 | — |
| all four | 135 432 | 0.478 | 0.024% |
| for comparison — the MAC table | 1.26 × 10⁷ | 44.4 | 2.24% |
| doubling the table to 256k | +1.26 × 10⁷ | +44.4 | +2.24% |
Every diagnostic this chapter asks for costs 0.024% of the switch together, and the fix they diagnose costs 2.24%. The instruments are a hundredth of the remedy, which is the same ratio Chapter 24.2 §19 found and the same argument for keeping all of them.
20. Properties Worth Asserting, and One Worth Refusing
Fifty-one properties in six groups, and the refused one is an alarm whose metric cannot reach its threshold.
Group A — the replication (9).
// A1. Copies are the member count less one.
p_rp_copies: assert property (@(posedge clk) disable iff (!rst_n)
(members != 16'd0) |-> (copies_o == members - 16'd1));
// A2. Emissions are copies times the ingress rate.
p_rp_emissions: assert property (@(posedge clk) disable iff (!rst_n)
(emissions_pps_k == port_pps_k * 32'(copies_o)));
// A3. One flooding port at 64 members takes almost the whole budget.
p_rp_budget: assert property (@(posedge clk) disable iff (!rst_n)
((members == 16'd64) && (port_gbps == 16'd100) &&
(slot_octets == 16'd84) && (budget_pps_k == 32'd9_524_000))
|-> (budget_share_ppm >= 32'd980_000));
// A4. And it exceeds the budget.
p_rp_exceeds: assert property (@(posedge clk) disable iff (!rst_n)
(emissions_pps_k > budget_pps_k) |-> exceeds_budget);
// A5. A bigger switch does not help.
p_rp_bigger_no: assert property (@(posedge clk) disable iff (!rst_n)
(bigger_switch_helps == 1'b0));
// A6. The egress share converges on unity, not on a half.
p_rp_converges: assert property (@(posedge clk) disable iff (!rst_n)
(members >= 16'd8) |-> (egress_share_ppm >= 32'd875_000));
// A7. Egress bandwidth is the port rate times the copies.
p_rp_bandwidth: assert property (@(posedge clk) disable iff (!rst_n)
(egress_bw_gbps == 32'(port_gbps) * 32'(copies_o)));
// A8. A single-member VLAN floods nowhere.
p_rp_single: assert property (@(posedge clk) disable iff (!rst_n)
(members == 16'd1) |-> (copies_o == 16'd0));
// A9. Over-budget states are counted.
p_rp_counted: assert property (@(posedge clk) disable iff (!rst_n)
exceeds_budget |=> (c_over_budget == $past(c_over_budget) + 32'd1));Group B — the flood causes (9).
// B1. A broadcast destination always floods.
p_fc_bcast: assert property (@(posedge clk) disable iff (!rst_n)
(frame_valid && dest_is_broadcast) |-> (cause == 2'(FLOOD_BCAST)));
// B2. A unicast miss floods too.
p_fc_unknown: assert property (@(posedge clk) disable iff (!rst_n)
(frame_valid && !lookup_hit && !dest_is_multicast && !dest_is_broadcast)
|-> (cause == 2'(FLOOD_UNKNOWN)));
// B3. A hit does not flood.
p_fc_hit: assert property (@(posedge clk) disable iff (!rst_n)
(frame_valid && lookup_hit && !dest_is_broadcast) |-> !is_flooding);
// B4. Only a refused insert is permanent.
p_fc_permanent: assert property (@(posedge clk) disable iff (!rst_n)
reason_is_permanent |-> ((cause == 2'(FLOOD_UNKNOWN)) && insert_was_refused));
// B5. An unknown unicast costs the host nothing.
p_fc_host_zero: assert property (@(posedge clk) disable iff (!rst_n)
(cause == 2'(FLOOD_UNKNOWN)) |-> host_cost_is_zero);
// B6. And only a broadcast will be noticed.
p_fc_notice: assert property (@(posedge clk) disable iff (!rst_n)
anybody_will_notice |-> (cause == 2'(FLOOD_BCAST)));
// B7. Copies are emitted only when flooding.
p_fc_copies_gated: assert property (@(posedge clk) disable iff (!rst_n)
!is_flooding |-> (copies_now == 16'd1));
// B8. The permanent counter is a subset of the unknown counter.
p_fc_subset: assert property (@(posedge clk) disable iff (!rst_n)
(c_unknown_permanent <= c_unknown));
// B9. Causes are mutually exclusive.
p_fc_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
frame_valid |-> !(dest_is_broadcast && dest_is_multicast));Group C — table capacity (9).
// C1. The nominal entry count is sets times ways.
p_tc_nominal: assert property (@(posedge clk) disable iff (!rst_n)
(nominal_entries == sets * 32'(ways)));
// C2. Refusals rise with the load.
p_tc_monotone: assert property (@(posedge clk) disable iff (!rst_n)
(load_x100_o > $past(load_x100_o)) |-> (refused_ppm_o >= $past(refused_ppm_o)));
// C3. At a load of 4.00 the refused share is about 19.5%.
p_tc_at_nominal: assert property (@(posedge clk) disable iff (!rst_n)
(load_x100_o == 32'd400) |-> ((refused_ppm_o > 32'd190_000) &&
(refused_ppm_o < 32'd201_000)));
// C4. Refusals begin well below full occupancy.
p_tc_early: assert property (@(posedge clk) disable iff (!rst_n)
(load_x100_o >= 32'd100) |-> (refused_ppm_o != 32'd0));
// C5. Stored plus refused is what was offered.
p_tc_partition: assert property (@(posedge clk) disable iff (!rst_n)
(stored_estimate + refused_estimate == offered_addresses));
// C6. Occupancy never exceeds the nominal entry count.
p_tc_occupancy_bounded: assert property (@(posedge clk) disable iff (!rst_n)
(stored_estimate <= nominal_entries));
// C7. A refused insert is permanent.
p_tc_permanent: assert property (@(posedge clk) disable iff (!rst_n)
(refused_ppm_o != 32'd0) |-> flood_share_is_permanent);
// C8. The contradiction state is reachable.
p_tc_contradiction: assert property (@(posedge clk) disable iff (!rst_n)
((occupancy_pct_x100 < 32'd9000) && (refused_ppm_o > 32'd100_000))
|-> occupancy_reads_healthy);
// C9. Mean copies rise with refusals.
p_tc_copies: assert property (@(posedge clk) disable iff (!rst_n)
(refused_ppm_o > $past(refused_ppm_o)) |->
(mean_copies_x1000 >= $past(mean_copies_x1000)));Group D — stale entries and the amplifier (8).
// D1. Stale entries scale with churn against the ageing interval.
p_se_count: assert property (@(posedge clk) disable iff (!rst_n)
(churn_per_hour != 32'd0) |->
(stale_entries == (32'(ageing_seconds) * 32'd1000) / teardown_interval_ms));
// D2. They add to the keys competing for sets.
p_se_competing: assert property (@(posedge clk) disable iff (!rst_n)
(keys_competing == live_addresses + stale_entries));
// D3. And cause additional refusals.
p_se_causes: assert property (@(posedge clk) disable iff (!rst_n)
(stale_entries != 32'd0) |-> (refused_with_stale >= refused_without_stale));
// D4. The refusal outlives its cause.
p_se_outlives: assert property (@(posedge clk) disable iff (!rst_n)
refusal_outlives_the_cause);
// D5. Eviction would convert a permanent refusal into a transient one.
p_se_eviction: assert property (@(posedge clk) disable iff (!rst_n)
!eviction_permitted |-> eviction_would_fix_it);
// D6. Zero churn means no stale entries.
p_se_zero: assert property (@(posedge clk) disable iff (!rst_n)
(churn_per_hour == 32'd0) |-> (stale_entries == 32'd0));
// D7. The alarm cannot fire above the ceiling.
p_ma_unreachable: assert property (@(posedge clk) disable iff (!rst_n)
(occupancy_alarm_pct > effective_ceiling_pct) |-> !alarm_can_fire);
// D8. And at a real failure it is silent.
p_ma_silent: assert property (@(posedge clk) disable iff (!rst_n)
(!alarm_can_fire && (refused_ppm_i > 32'd100_000)) |-> alarm_is_silent_at_failure);Group E — storm control (8).
// E1. The admitted rate is the setting times the port rate.
p_sc_admitted: assert property (@(posedge clk) disable iff (!rst_n)
(admitted_pps_k == (port_pps_k * 32'(setting_pct_x10)) / 32'd1000));
// E2. A setting below 100% makes the limiter active.
p_sc_active: assert property (@(posedge clk) disable iff (!rst_n)
(setting_pct_x10 < 16'd1000) |-> limiter_active);
// E3. Applying it to unknown unicast discards legitimate traffic.
p_sc_legitimate: assert property (@(posedge clk) disable iff (!rst_n)
(limiter_active && applies_to_unknown) |-> discards_legitimate_traffic);
// E4. It never treats the cause.
p_sc_not_cause: assert property (@(posedge clk) disable iff (!rst_n)
(treats_the_cause == 1'b0));
// E5. Broadcast and unknown discards are counted apart.
p_sc_apart: assert property (@(posedge clk) disable iff (!rst_n)
(frame_flooded && (cause_i == 2'(FLOOD_BCAST)) && (tokens == 32'd0)
&& applies_to_broadcast)
|=> (c_discarded_unknown == $past(c_discarded_unknown)));
// E6. An admitted frame consumes a token.
p_sc_token: assert property (@(posedge clk) disable iff (!rst_n)
(frame_flooded && (tokens != 32'd0)) |=> (c_admitted == $past(c_admitted) + 32'd1));
// E7. The demand is the admitted rate times the copies.
p_sc_demand: assert property (@(posedge clk) disable iff (!rst_n)
(demand_pps_k == admitted_pps_k * 32'(copies(int'(members)))));
// E8. A per-port setting is not an aggregate bound.
p_sc_aggregate: assert property (@(posedge clk) disable iff (!rst_n)
((per_port_setting_pct_x10 * ports_i) > (aggregate_budget_pct * 16'd10))
|-> v_aggregate_unbounded);Group F — telemetry and conformance (8).
// F1. Flooded frames are a subset of frames.
p_tl_subset: assert property (@(posedge clk) disable iff (!rst_n)
(c_flooded <= c_frames));
// F2. Causes partition the floods.
p_tl_partition: assert property (@(posedge clk) disable iff (!rst_n)
(c_flooded == c_bcast_o + c_unknown_o + c_mcast_o));
// F3. A refusal is an event and a nonzero count is the signal.
p_tl_event: assert property (@(posedge clk) disable iff (!rst_n)
(c_insert_refused != 48'd0) |-> refusals_are_occurring);
// F4. Mean copies is at least one.
p_tl_mean: assert property (@(posedge clk) disable iff (!rst_n)
(c_frames != 48'd0) |-> (mean_copies_x1000 >= 32'd1000));
// F5. An alarm above the effective capacity is a violation.
p_cf_alarm: assert property (@(posedge clk) disable iff (!rst_n)
(occupancy_alarm_pct > effective_capacity_pct) |-> v_alarm_unreachable);
// F6. Merged counters are a violation.
p_cf_merged: assert property (@(posedge clk) disable iff (!rst_n)
counters_merged |-> v_counters_merged);
// F7. A no-flooding claim with floods counted is a violation.
p_cf_claim: assert property (@(posedge clk) disable iff (!rst_n)
(claims_no_flooding && (c_flooded_i != 48'd0)) |-> v_claims_no_flooding);
// F8. Conformance is the disjunction of its six checks.
p_cf_vector: assert property (@(posedge clk) disable iff (!rst_n)
conformant |-> (violations == 6'b000000));Coverage — the states a table reaches without any alarm firing.
c_tc_load1: cover property (@(posedge clk) load_x100_o == 32'd100);
c_tc_load4: cover property (@(posedge clk) load_x100_o == 32'd400);
c_tc_load8: cover property (@(posedge clk) load_x100_o == 32'd800);
c_tc_silent: cover property (@(posedge clk) occupancy_reads_healthy);
c_fc_permanent: cover property (@(posedge clk) reason_is_permanent);
c_fc_unknown: cover property (@(posedge clk) cause == 2'(FLOOD_UNKNOWN));
c_rp_saturated: cover property (@(posedge clk) exceeds_budget);
c_se_stale: cover property (@(posedge clk) stale_entries >= 32'd800);
c_se_displaced: cover property (@(posedge clk) live_refusals_caused != 32'd0);
c_ma_unreachable:cover property (@(posedge clk) !alarm_can_fire);
c_sc_unknown_lim:cover property (@(posedge clk) discards_legitimate_traffic);
c_sc_aggregate: cover property (@(posedge clk) v_aggregate_unbounded);
c_tl_refusals: cover property (@(posedge clk) refusals_are_occurring);
c_cf_alarm: cover property (@(posedge clk) v_alarm_unreachable);21. Verification Scenarios
Fifty-eight scenarios in six groups, plus one directed test random stimulus will not produce.
Group 1 — the replication (10).
| # | Scenario | Expect |
|---|---|---|
| 1 | 64 members, 100 Gb/s, 84-octet slot | 148.81 Mpps; 63 copies; 9.375 Gpps |
| 2 | the same against a 9.524 Gpps budget | 984 252 ppm — 98.4%; exceeds_budget |
| 3 | 128 members | 127 copies; 99.2% egress share |
| 4 | 8 members | 7 copies; 87.5% egress share |
| 5 | 1 member | 0 copies; no flood |
| 6 | bigger_switch_helps at any setting | 0 |
| 7 | 1 518-octet frames instead of minimum | 8.13 Mpps; 0.512 Gpps; 5.4% of the budget |
| 8 | 9 018-octet frames | 1.38 Mpps; 0.087 Gpps; 0.91% |
| 9 | egress bandwidth at 64 members | 6 300 Gb/s of 6 400 |
| 10 | over-budget observed | c_over_budget increments |
Group 2 — the flood causes (9).
| # | Scenario | Expect |
|---|---|---|
| 11 | a broadcast destination | FLOOD_BCAST; anybody_will_notice high |
| 12 | a unicast miss | FLOOD_UNKNOWN; host_cost_is_zero high |
| 13 | a unicast hit | not flooding; one copy |
| 14 | a miss with insert_was_refused | reason_is_permanent |
| 15 | a miss with never_seen | not permanent — one frame |
| 16 | a miss after entry_aged_out | not permanent |
| 17 | a miss after topology_changed | not permanent |
| 18 | anybody_will_notice on an unknown unicast | LOW — the finding |
| 19 | 1 000 floods, 200 permanent | c_unknown_permanent = 200 |
Group 3 — table capacity (10).
| # | Scenario | Expect |
|---|---|---|
| 20 | 32 768 offered, 32 768 sets | load 1.00; refused 0.435%; occupancy 24.9% |
| 21 | 65 536 offered | load 2.00; 3.757%; 48.1% |
| 22 | 98 304 offered | load 3.00; 10.645% |
| 23 | 131 072 offered | load 4.00; 19.537%; occupancy 80.5% |
| 24 | 163 840 offered | load 5.00; 28.737% |
| 25 | 196 608 offered | load 6.00; 37.217%; occupancy 94.2% |
| 26 | 262 144 offered | load 8.00; 50.744%; occupancy 98.5% |
| 27 | the load-4.00 case | occupancy_reads_healthy HIGH |
| 28 | mean copies at load 4.00, 64 members | 13.11 |
| 29 | 8-way, 16 384 sets, 131 072 offered | refused 14.0%; effective 86.0% |
Group 4 — stale entries (9).
| # | Scenario | Expect |
|---|---|---|
| 30 | 100 teardowns/hour, 300 s ageing | 8.3 stale entries |
| 31 | 1 000/hour | 83.3 |
| 32 | 10 000/hour | 833.3 |
| 33 | 833 stale against 131 072 live | keys 131 905; load 4.025; refused 19.773% |
| 34 | the difference | 474 additional refusals |
| 35 | the ratio | 1.76 dead entries per additional refusal |
| 36 | refusal_outlives_the_cause | 1, at every setting |
| 37 | eviction_permitted low | eviction_would_fix_it high |
| 38 | zero churn | no stale entries; no extra refusals |
Group 5 — storm control (10).
| # | Scenario | Expect |
|---|---|---|
| 39 | 100% setting | 148.81 Mpps admitted; 98.4% of the budget |
| 40 | 10% | 14.88 Mpps; 9.8% |
| 41 | 1% | 1.49 Mpps; 0.98% |
| 42 | 0.1% | 0.15 Mpps; 0.10% |
| 43 | 1% on 64 ports | 63% of the budget — the aggregate nobody bounds |
| 44 | applied to broadcast only | discards_legitimate_traffic low |
| 45 | applied to unknown unicast | HIGH — user data discarded |
| 46 | treats_the_cause at any setting | 0 |
| 47 | a broadcast discarded at the limit | c_discarded_bcast moves; unknown counter still |
| 48 | an unknown unicast discarded | c_discarded_unknown moves |
Group 6 — telemetry and conformance (10).
| # | Scenario | Expect |
|---|---|---|
| 49 | 142 refusals at load 1.00 | refusals_are_occurring high; no level alarm |
| 50 | a 90% alarm on a four-way table | v_alarm_unreachable |
| 51 | a 70% alarm on the same table | clean |
| 52 | merged flood counters | v_counters_merged |
| 53 | storm control on unknown unicast | v_limiter_on_unknown |
| 54 | 64 ports at 1% against a 10% aggregate budget | v_aggregate_unbounded |
| 55 | refusals not reported | v_refusals_unreported |
| 56 | a no-flooding claim with floods counted | v_claims_no_flooding |
| 57 | one ingress port dominating the floods | worst_ingress_port names it |
| 58 | all six checks clear | conformant high |
22. Debugging a Flooding Switch
Six symptoms, and five of them present on ports that are not the cause.
| Symptom | First question | Where to look |
|---|---|---|
| every port dropping, no port obviously at fault | which ingress port is flooding? | Section 15 — worst_ingress_port, not the drop counters |
| the table reads 80% and the network is slow | what is c_insert_refused? | Section 8 — the alarm's ceiling is below its threshold |
| flooding after a container fleet grew | did the key count grow past the table's load? | Section 6 — 19.5% refused at a load of 4.00 |
| flooding that storm control "fixed" | is user traffic being discarded? | Section 12 — a limiter on unknown unicast drops real data |
| one station unreachable, everything else fine | was its insert refused? | Section 4 — a refused address floods forever |
| broadcast complaints but no unknown-unicast complaints | would anybody notice the second? | Section 1's callout — no host sees it |
Row six is the structural point of the chapter. Unknown-unicast flooding produces no host-visible symptom at all, so the absence of complaints is not evidence of its absence — and the only witness is a counter inside the switch.
23. Misconceptions
Misconception 1 — "switches do not flood."
The wrong model: a switch learns where stations are and forwards to one port, so flooding belongs to hubs.
What it costs: a capacity plan with no term for replication. Section 2: one port flooding minimum-size frames at 100 Gb/s emits 9.375 Gpps, which is 98.4% of a 64-port switch's entire 9.524 Gpps frame budget — and Chapter 12.4 §3's observation holds: buying a bigger switch does not help, because the amplification scales with the port count.
The corrected model: a switch floods whenever the table has no entry, and Chapter 12.1 §13's class 49 already established that asserting otherwise is asserting the absence of a behaviour the design was built to perform.
Misconception 2 — "unknown unicast is a corner case."
The wrong model: the table learns quickly, so misses are transient.
What it costs: a fifth of the network's destinations flooding permanently. Section 6: a 128k-entry four-way table offered 131 072 addresses stores 105 465 and refuses 25 607. Section 4: a refused insert is not retried, does not age, and does not heal.
The corrected model: there are four reasons an entry is absent and only one of them expires. A station that has not spoken, an aged-out entry and a topology flush all resolve; a refused insert is permanent.
Misconception 3 — "the table is fine, it is only 80% full."
The wrong model: occupancy below the alarm threshold means capacity to spare.
What it costs: Section 20's class 119. The effective capacity of a four-way table is 80.5% — Chapter 12.5 §9 — so an alarm at 90% cannot fire at the load where refusals become serious. The metric's ceiling is beneath the threshold.
The corrected model: watch the event, not the level. c_insert_refused is nonzero at a load of 1.00 with the table 24.9% occupied, and an event count needs no threshold at all.
Misconception 4 — "storm control fixed the flooding."
The wrong model: a rate limiter on flooded traffic solves the problem.
What it costs: discarded user data and an unchanged table. Section 12: a limiter applied to unknown unicast drops frames whose destination exists and whose address the switch failed to learn — and Section 13's treats_the_cause is a hard-wired zero. Section 13 also derives that 64 ports at a 1% setting together demand 63% of the budget, so a per-port bound is not an aggregate one.
The corrected model: storm control on broadcast is correct and on unknown unicast is a way of hiding a table problem, and the two are the same replication with different classifications.
Misconception 5 — "a few stale entries cost a few entries of memory."
The wrong model: 833 stale entries in a 131 072-entry table is 0.6% and negligible.
What it costs: 474 additional permanent refusals. Section 10: the scarce resource is not entries, it is sets with room — Chapter 12.5 §7 — and a stale entry that fills the last way of a set is the difference between accepting the next address and refusing it.
The corrected model: the stale entry ages out in 300 seconds and the refusal it caused may never be retried, because Chapter 12.2's silent station only transmits when addressed, and it is addressed by a flood.
Misconception 6 — "nobody is complaining, so nothing is flooding."
The wrong model: a flood would be noticed.
What it costs: the entire failure, indefinitely. Chapter 12.4 §6: an unknown unicast is discarded by an exact 48-bit compare in every receiving card's hardware, at zero cost to every host. Sixty-three copies land and sixty-two evaporate before anything wakes.
The corrected model: a broadcast storm produces complaints within minutes and an unknown-unicast flood produces none, ever — the switch does all the suffering — so the absence of complaints is evidence about the hosts and not about the fabric.
24. Interview Questions
Six, with what a strong answer contains.
1. Do switches eliminate broadcasts?
No, and they add a second kind of flood that looks identical inside the switch. A strong answer separates them: a broadcast is replicated by specification and an unknown unicast because the table has no entry, and both go to every port in the VLAN. At 64 ports both are 63 copies and 98.4% of the switch's frame budget at line rate. The best answers give the receiver-side difference: a broadcast reaches every host's CPU and an unknown unicast is discarded by a 48-bit compare, so only one of them produces complaints.
2. When does a switch flood unknown unicast?
Whenever the table has no entry, and there are four reasons. A station that has not transmitted yet; an entry that aged out; a topology change that flushed the table; and an insert the table refused. A strong answer notes that only the last is permanent — it is not retried, does not age and does not heal — and that Chapter 12.5 §9's arithmetic says how often it happens: a 128k four-way table offered its nominal capacity refuses 25 607 addresses, 19.5%.
3. Why does the table refuse inserts with memory free?
Because a set-associative table's capacity is a property of the hash, not of the memory. Addresses land in sets by a hash the operator does not choose; sets fill unevenly; a set at its way limit refuses every further insert into it while other sets have room. A strong answer gives the effective capacities: 63.2% direct-mapped, 72.9% two-way, 80.5% four-way, 86.0% eight-way, 90.1% sixteen-way — and notes that Chapter 12.5 §5 established a better hash cannot fix it, because the unevenness is in the arrival process rather than the function.
4. Your switch reports 80% table occupancy and the network is slow. What do you check?
The refusal count, not the occupancy. A strong answer names the structural point: the effective capacity of a four-way table is 80.5%, so an occupancy alarm at 90% cannot fire at the load where refusals matter. That is Section 20's class 119 — a health property whose metric cannot reach its own threshold — and the repair is an event count and a threshold derived from the structure: near 70% at four ways, not 90.
5. Does storm control fix it?
It caps the damage and discards real data to do it. A strong answer distinguishes the two classes: on broadcast it is correct, because the traffic is control-plane and the protocols retry; on unknown unicast it discards application data to a station that exists. And it names the arithmetic gap: a per-port setting of 1% on 64 ports permits 63% of the budget, because the amplification applies to each port independently and no standard configuration offers an aggregate bound.
6. What is the cheapest thing that would have caught this?
Four counters costing 0.024% of the switch. c_insert_refused — an event, needing no threshold. The flood share split by cause, which selects the fix. Flooded frames per ingress port, which names the source where the drop counters rank the victims 63 to 1. And a per-set occupancy census folded into Chapter 12.5 §13's ageing sweep, which is the only level metric that can see the failure. The best answers add the fifth: a read-only register reporting the table's effective capacity, so a monitoring system can set a reachable threshold.
25. Questions and Answers
26. What's Next
One myth left, and it is the oldest one in the track.
Chapter 25.6 takes "full-duplex Ethernet uses CSMA/CD". Chapter 1.5 §2 already listed what full duplex removed from the MAC — the slot-time counter, the collision classifier, the jam generator and the backoff engine all become unreachable — so the correction itself is settled. What that chapter did not do is derive what the slot time was FOR, and that is where the number is.
512 bit times is a round-trip budget, and a round-trip budget is a distance. At 10 Mb/s it is 51.2 µs, which at Chapter 8.2 §3's 5.05 ns per metre of copper is 5 069 metres of one-way cable before repeaters and detection latency are charged against it. At 100 Mb/s the same 512 bit times is 507 metres and at 1 Gb/s it is 51 — which is why gigabit half duplex needed carrier extension to 4 096 bit times, and why the industry stopped extending shared segments.
On a point-to-point link the bound is not merely satisfied; it is satisfied by four orders of magnitude — a 3-metre link's round trip is 30.3 ns against a 51 200 ns budget — and there is no second transmitter for it to bound.
And then the chapter finds where the vestige still costs something, which is the minimum frame size. 64 octets exists because 512 bit times had to be a frame, and it survives the timing argument that created it — at 45.24% of the wire per Chapter 24.3 §1, and, more expensively, as the packet rate that sets Chapter 23.3 §2's eight-wide pipeline.
Continue learning
Related tutorials
- Related topic
Flooding — Unknown Unicast, Multicast and Broadcast
One port flooding at line rate consumes 47.9% of a 24-port switch's aggregate capacity. Three causes produce identical flooding, only one costs every host's CPU, and the switch can observe none of it.
- Related topic
Full Link Bring-Up Sequence
Eight stages from power-on to a legally transmitting MAC, and the ordering is a data dependency. Each stage measures what the next assumes, and a stage run early does not fail — it succeeds against garbage.
- Related topic
The MAC Table — CAM, Hashing, Capacity and Aging
An 8192-entry four-way table refuses inserts while 4096 slots are free, and holds 6592 addresses when asked for 8192. Capacity is a property of the hash, not of the memory.
- Related topic
"MAC Addresses Are Globally Unique Forever"
The birthday arithmetic everybody quotes uses 46 free bits; the generator in every hypervisor uses 24 — a factor of 1 335 907 — and a table indexed by the address cannot record the collision.
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.
