Ethernet · Module 12
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.
Chapter 12.3 deferred this twice. A miss floods, an unresolved lookup floods, and both were called the safe answer without examining the bill.
Here is the bill. One gigabit port flooding at line rate on a 24-port switch generates 23 × 1.4881 M = 34.23 million frame-emissions per second and demands 23 Gb/s of egress bandwidth — 47.9% of the switch's entire 48 Gb/s aggregate, from one port.
And that is only the switch's half of the cost. Every copy lands on a station, and what the station does with it depends entirely on which of the three causes produced the flood — a distinction invisible in the switch, identical on the wire, and worth several orders of magnitude in host CPU.
1. Scope — What This Chapter Owns
This chapter owns the cost of not knowing: what a flood does to every port in the domain, where each of the three flood types is actually discarded, why broadcast is different in kind rather than in degree, the domain-size limit that follows, and the one mitigation available at this layer.
It does not own the decision to flood — Chapter 12.3 §2 and §3 established when a flood is the answer and why a miss is not an absence. This chapter takes the decision as given and prices it.
It does not own the table — Chapter 12.5 owns the structure whose capacity determines how often a miss happens at all.
It does not own segmentation. The mitigation that actually works is to make the domain smaller, and Chapter 13.1 owns why that is the answer and what it costs. This chapter establishes the problem that VLANs exist to solve, and stops at the boundary.
And it does not own group membership. A mechanism that lets a switch learn which ports want a multicast group exists, and it lives above the layer this track covers. Section 14 explains precisely why no mechanism at this layer can do better, which is a different claim from nothing can.
2. Three Causes, One Behaviour
A flood is N−1 copies of a frame, one to each forwarding port except the ingress. Chapter 12.3 §9 built the mask. Three unrelated conditions produce it.
Unknown unicast — the switch does not know. The destination is an individual address with no table entry. Chapter 12.3 §2 gave five reasons a present station produces a miss, and flooding is the only response correct under all five.
Multicast — the switch cannot know. A group address names a set of stations, and nothing in a basic switch tells it which ports lead to members. Chapter 12.2 §4's first eligibility rule forbids learning a group address, because nothing ever transmits from one — so the table can never contain a group destination, and every multicast misses forever by construction.
Broadcast — the address means everyone. FF:FF:FF:FF:FF:FF is not a lookup failure. It is a successful classification with a known answer, and Chapter 12.3 §10's composer skips the lookup entirely for it.
Three causes. Identical wire behaviour. And the difference between them is everything.
| will stop | table's role | fixable by a bigger table | fixable by segmentation | |
|---|---|---|---|---|
| unknown unicast | on the station's next frame | a miss | yes | yes |
| multicast | never | cannot be learned | no | yes |
| broadcast | never | not consulted | no | yes — the only fix |
The right-hand columns are the operational content of this chapter. An engineer who sees an elevated flood rate and buys a switch with a larger table has fixed exactly one of the three, and the one they fixed is the one that was self-limiting anyway.
What each cause would need in order to stop
Asking what information would end each flood makes the three causes' differences structural rather than descriptive.
| Cause | What would end it | Does that information exist at this layer |
|---|---|---|
| unknown unicast | the station transmitting once | yes — and it happens by itself |
| multicast | which ports contain group members | no — nothing transmits from a group address |
| broadcast | nothing | the address means every port; there is nothing to know |
The right-hand column is the whole taxonomy. One cause is cured by information the network generates on its own. One is cured by information that does not exist at this layer and must come from a protocol above it. And one cannot be cured at all, because the flood is not a consequence of ignorance — it is the address's definition.
Which is why the only remedy that touches all three is to make the domain smaller. Segmentation does not require any information the switch lacks; it changes how many ports are in scope, and it therefore works identically on a cause the switch understands, a cause it cannot know, and a cause that is not a question at all.
3. The Amplification Arithmetic
A flood is the only operation in a switch where one frame in produces many frames out, and the multiplier is the port count.
| Switch | Copies per flooded frame | 1 Gb/s of flood in demands | as a share of aggregate |
|---|---|---|---|
| 8 ports | 7 | 7 Gb/s | 7 ÷ 16 = 43.8% |
| 24 ports | 23 | 23 Gb/s | 23 ÷ 48 = 47.9% |
| 48 ports | 47 | 47 Gb/s | 47 ÷ 96 = 49.0% |
The share converges on 50% and never exceeds it, which is a fact worth holding: (N−1) ÷ 2N → ½. One port, flooding at line rate, can demand very close to half of any switch's total egress capacity regardless of how many ports it has.
In frames rather than bits, on the 24-port case at minimum length:
1.4881 M frames/s × 23 copies = 34.23 M frame-emissions per second
And Chapter 12.1 §12 established that a 24-port gigabit switch's total frame-handling requirement is 35.71 Mpps. So one port flooding minimum-length frames at line rate consumes 95.9% of the switch's entire frame-emission budget — leaving 4.1% for the other 23 ports combined.
Buying a bigger switch does not help. The amplification scales with the port count, so a 48-port switch faced with the same single flooding port suffers the same proportional damage and inflicts it on twice as many stations.
4. RTL 1 — Classifying Why This Frame Is Flooding
One behaviour, three causes, three counters. The classification costs two comparators and it is the difference between a diagnosable switch and an undiagnosable one.
// -----------------------------------------------------------------------
// flood_pkg -- shared types for flood handling.
//
// The cause encoding exists because Chapter 12.3 Section 3's lesson
// applies again here: three conditions produce identical wire behaviour,
// and folding them into one counter destroys the only information that
// tells an operator which of three unrelated remedies to reach for.
// -----------------------------------------------------------------------
package flood_pkg;
typedef enum logic [2:0] {
FC_NONE = 3'd0,
FC_UNKNOWN_UNICAST = 3'd1, // a miss -- self-limiting, table-shaped fix
FC_MULTICAST = 3'd2, // cannot be learned -- permanent
FC_BROADCAST = 3'd3, // means everyone -- permanent, CPU cost
FC_UNRESOLVED = 3'd4 // Chapter 12.3's lookup deadline
} flood_cause_e;
// What a RECEIVING station does with a frame. Section 7 models this,
// and it is where the three causes stop being equivalent.
typedef enum logic [1:0] {
RX_DROP_EXACT = 2'd0, // 48-bit address filter, hardware, free
RX_DROP_HASH = 2'd1, // multicast hash filter, hardware, imperfect
RX_TO_CPU = 2'd2, // reached the host -- costs real time
RX_WANTED = 2'd3 // addressed to this station, legitimately
} rx_fate_e;
localparam int ADDR_W = 48;
endpackage// -----------------------------------------------------------------------
// flood_classifier -- says WHY this frame is being flooded.
//
// The decision to flood was made by Chapter 12.3's composer. This module
// adds nothing to that decision and changes no behaviour. It exists
// entirely so that the three causes can be counted apart, because their
// remedies are unrelated and their costs differ by orders of magnitude.
// -----------------------------------------------------------------------
module flood_classifier
import flood_pkg::*;
#(
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic flood_valid,
input logic [ADDR_W-1:0] dst,
input logic lookup_was_performed,
input logic lookup_timed_out,
output flood_cause_e cause,
output logic cause_valid,
output logic [CNT_W-1:0] c_unknown_unicast,
output logic [CNT_W-1:0] c_multicast,
output logic [CNT_W-1:0] c_broadcast,
output logic [CNT_W-1:0] c_unresolved,
output logic permanent_flood // multicast or broadcast
);
logic is_broadcast, is_group;
assign is_broadcast = (dst == {ADDR_W{1'b1}});
// Chapter 5.3's I/G bit. Broadcast is tested FIRST because it is also a
// group address, and the two need different counters.
assign is_group = dst[40];
always_comb begin
cause = FC_NONE;
if (flood_valid) begin
if (is_broadcast) cause = FC_BROADCAST;
else if (is_group) cause = FC_MULTICAST;
else if (lookup_timed_out) cause = FC_UNRESOLVED;
else if (lookup_was_performed) cause = FC_UNKNOWN_UNICAST;
else cause = FC_UNKNOWN_UNICAST;
end
end
// A flood that will never stop on its own. The distinction matters
// operationally: an unknown-unicast flood ends when the station next
// transmits, and these do not end at all.
assign permanent_flood = flood_valid &&
((cause == FC_MULTICAST) || (cause == FC_BROADCAST));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cause_valid <= 1'b0;
c_unknown_unicast <= '0;
c_multicast <= '0;
c_broadcast <= '0;
c_unresolved <= '0;
end else begin
cause_valid <= flood_valid;
if (flood_valid) begin
unique case (cause)
FC_UNKNOWN_UNICAST:
if (!(&c_unknown_unicast)) c_unknown_unicast <= c_unknown_unicast + 1'b1;
FC_MULTICAST:
if (!(&c_multicast)) c_multicast <= c_multicast + 1'b1;
FC_BROADCAST:
if (!(&c_broadcast)) c_broadcast <= c_broadcast + 1'b1;
FC_UNRESOLVED:
if (!(&c_unresolved)) c_unresolved <= c_unresolved + 1'b1;
default: ;
endcase
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that broadcast must be tested before the group bit, because FF:FF:FF:FF:FF:FF has the I/G bit set and would otherwise be counted as multicast. Chapter 12.1 §4's classifier made the same ordering choice for the same reason; here the consequence is a counter that silently misattributes the single most expensive flood type to the second most expensive one.
And it teaches that permanent_flood is the operationally useful bit. An unknown-unicast flood is a transient the network will resolve without help. Multicast and broadcast floods are steady-state, and an operator who does not know which kind they are looking at cannot tell a problem that will clear itself from one that will not.
Deliberately simplified: the cause is derived combinationally from the destination address and two status bits. A production design carries the cause forward as a field of the frame descriptor Chapter 12.1 §4 introduced, so that the egress side can apply per-cause policy — for example, dropping unknown-unicast floods on a port configured to hold only known stations while still passing broadcast.
Production implication: the four counters answer a question that no port statistic can: is this switch flooding because it does not know, or because it has been asked to? c_unknown_unicast high with c_broadcast low is a learning-side problem — Chapter 12.2 §18's table applies. c_broadcast high is not a switch problem at all; it is a statement about what the attached stations are saying, and the only remedies are above this layer or in Section 12's rate limiter.
5. RTL 2 — Replicating a Frame Without Duplicating It
A flood emits N−1 copies and Chapter 12.1's P10 forbids two copies on one port. Those two sentences are in tension only if the replication is done carelessly, and the careless version is the natural one.
// -----------------------------------------------------------------------
// flood_replicator -- turns one frame and a mask into exactly one emission
// per set bit, in a bounded number of cycles, with no port served twice
// and no port skipped.
//
// The frame is stored ONCE and referenced N-1 times. Copying the payload
// per port would multiply the buffer bandwidth by the port count, which
// is the resource Section 3 already showed is 95.9% consumed.
// -----------------------------------------------------------------------
module flood_replicator
import flood_pkg::*;
#(
parameter int N_PORTS = 24,
parameter int PORT_BITS = 5,
parameter int BUF_BITS = 12,
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic flood_start,
input logic [N_PORTS-1:0] flood_mask, // Chapter 12.3's built mask
input logic [BUF_BITS-1:0] buf_handle, // the ONE stored copy
input flood_cause_e cause,
output logic emit_valid,
output logic [PORT_BITS-1:0] emit_port,
output logic [BUF_BITS-1:0] emit_handle,
input logic emit_ready,
output logic busy,
output logic release_buffer, // all copies emitted
output logic [CNT_W-1:0] c_floods,
output logic [CNT_W-1:0] c_copies,
output logic [7:0] last_fanout
);
logic [N_PORTS-1:0] pending_q;
logic [BUF_BITS-1:0] handle_q;
logic [7:0] fanout_q;
// Lowest set bit of the pending mask. A fixed priority is acceptable
// here BECAUSE every set bit is served before the operation completes --
// there is no starvation in a drain, only an order.
logic [PORT_BITS-1:0] next_port;
logic has_pending;
always_comb begin
next_port = '0;
has_pending = |pending_q;
for (int p = N_PORTS-1; p >= 0; p--)
if (pending_q[p]) next_port = PORT_BITS'(p);
end
assign busy = |pending_q;
assign emit_valid = has_pending;
assign emit_port = next_port;
assign emit_handle = handle_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pending_q <= '0;
handle_q <= '0;
fanout_q <= '0;
release_buffer <= 1'b0;
c_floods <= '0;
c_copies <= '0;
last_fanout <= '0;
end else begin
release_buffer <= 1'b0;
// A new flood is accepted only when the previous one has fully
// drained. Accepting a second flood while the first is incomplete
// would drop copies of the first -- silently, since nothing counts
// an unsent copy.
if (flood_start && !busy) begin
pending_q <= flood_mask;
handle_q <= buf_handle;
fanout_q <= 8'($countones(flood_mask));
if (!(&c_floods)) c_floods <= c_floods + 1'b1;
last_fanout <= 8'($countones(flood_mask));
end
// CLEAR THE BIT ON ACCEPTANCE. Clearing on issue instead would lose
// a copy whenever the egress applied backpressure -- the frame
// would be counted as emitted and never appear on the wire.
if (emit_valid && emit_ready) begin
pending_q[next_port] <= 1'b0;
if (!(&c_copies)) c_copies <= c_copies + 1'b1;
// The last copy releases the single stored frame.
if ($countones(pending_q) == 1) release_buffer <= 1'b1;
end
end
end
wire _unused_cause = |cause;
endmoduleClassification: synthesizable.
What it teaches: that a flood stores the frame once and emits a handle N−1 times. Copying the payload per port would multiply the buffer's read bandwidth by 23 on a 24-port switch — and Section 3 showed the frame-emission budget is already 95.9% consumed by a single flooding port. The buffer is the resource that cannot afford the copies; the descriptor is what gets replicated.
And it teaches why the pending bit clears on emit_ready rather than on emit_valid. An egress port can apply backpressure. Clearing on issue counts the copy as emitted while the port never took it — and nothing downstream would ever notice, because a flood has no completion signal and no receiver reports back. The lost copy is one station that silently did not get the frame.
Deliberately simplified: one flood in flight, drained at one copy per cycle, with a fixed-priority scan. A production replicator keeps several floods in flight against a shared buffer with reference counts, because a 23-cycle drain at 500 MHz is 46 ns and Chapter 12.1 §12's per-frame budget is 28 ns — the drain alone exceeds the budget, which is why replication in real switches happens in the egress scheduler rather than as a serial loop.
Production implication: last_fanout is worth exposing because it is the only place the flood's actual reach is visible. Chapter 12.3 §9's flood_reduced said the mask was smaller than the full set; last_fanout says by how much. A flood with a fanout of 4 on a 24-port switch reached four stations, and if the destination was behind one of the other nineteen, the frame is legitimately lost with no counter calling it a loss — the switch flooded correctly, and the flood did not reach.
6. Where Each Flood Type Is Actually Discarded
This is the section the chapter exists for. The three causes are identical inside the switch and diverge completely at the receiver.
Every flooded copy arrives at a station's network card, and the card asks one question: is this frame for me? The answer is computed in hardware, before the host CPU is involved, and the mechanism is different for each of the three address types.
| how the card decides | cost to the host | can it be wrong | |
|---|---|---|---|
| unknown unicast | exact 48-bit compare against the card's own address | zero — discarded in hardware | no |
| multicast | hash of the address into a small bucket table | near zero, degrading | yes — false accepts |
| broadcast | no decision — always accepted | full | not applicable |
Unknown-unicast flooding costs the receiving stations nothing at all. The card compares 48 bits, finds no match, and discards the frame without waking anything. Twenty-three copies of a frame land on twenty-three cards and twenty-two of them evaporate in hardware. The cost is entirely bandwidth, and it is entirely inside the network.
Broadcast is the opposite, and it is not a matter of degree. A card cannot discard a broadcast — the address means everyone, and discarding it would break ARP, DHCP, and every discovery protocol ever written. Every broadcast frame reaches the host's CPU, on every station, always.
Multicast sits between them, imperfectly. The card keeps a small hash table — 64 buckets is a common size — with a bit set for each bucket that any joined group maps into. A frame whose group hashes to an enabled bucket is accepted and passed to the CPU, which then checks whether it is really a member.
7. RTL 3 — Modelling the Receiver, Because the Switch Cannot See It
This module is not part of a switch. It is a model of what happens to every copy the switch emits, and it exists because the switch has no way to obtain any of it.
// -----------------------------------------------------------------------
// receiver_filter_model -- what a station's network card does with an
// arriving frame.
//
// NOT SYNTHESIZABLE INTO A SWITCH. This is the far end of every flooded
// copy, and Ethernet provides no path by which the switch could learn any
// of it. It is here because the cost of flooding is paid at this module
// and measured nowhere.
// -----------------------------------------------------------------------
module receiver_filter_model
import flood_pkg::*;
#(
parameter int HASH_BUCKETS = 64,
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic rx_valid,
input logic [ADDR_W-1:0] rx_dst,
input logic [ADDR_W-1:0] my_address,
input logic [HASH_BUCKETS-1:0] mcast_buckets, // set by joined groups
input int joined_group_count,
output rx_fate_e fate,
output logic to_cpu,
output logic [CNT_W-1:0] c_wanted,
output logic [CNT_W-1:0] c_dropped_exact,
output logic [CNT_W-1:0] c_dropped_hash,
output logic [CNT_W-1:0] c_to_cpu_broadcast,
output logic [CNT_W-1:0] c_to_cpu_false_accept
);
logic is_broadcast, is_group, is_mine;
assign is_broadcast = (rx_dst == {ADDR_W{1'b1}});
assign is_group = rx_dst[40];
assign is_mine = (rx_dst == my_address);
// A typical card hashes the address -- often the top bits of a CRC --
// into a small bucket table. The table cannot express "this exact
// group"; only "some joined group lands in this bucket".
logic [5:0] bucket;
assign bucket = rx_dst[47:42] ^ rx_dst[41:36] ^ rx_dst[23:18] ^ rx_dst[5:0];
always_comb begin
fate = RX_DROP_EXACT;
if (rx_valid) begin
if (is_broadcast) begin
// NO FILTER EXISTS. Broadcast means everyone, and a card that
// discarded it would break ARP, DHCP and every discovery
// protocol. This branch has no alternative.
fate = RX_TO_CPU;
end else if (is_group) begin
// The hash filter. Enabled bucket -> accept and let software
// decide. A collision means a group this station never joined
// costs it a CPU interrupt and a stack traversal.
fate = mcast_buckets[bucket] ? RX_TO_CPU : RX_DROP_HASH;
end else if (is_mine) begin
fate = RX_WANTED;
end else begin
// EXACT 48-BIT COMPARE. Every unknown-unicast flood copy dies
// here, in hardware, for free. This is why unknown-unicast
// flooding costs bandwidth and nothing else.
fate = RX_DROP_EXACT;
end
end
end
assign to_cpu = rx_valid && ((fate == RX_TO_CPU) || (fate == RX_WANTED));
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_wanted <= '0;
c_dropped_exact <= '0;
c_dropped_hash <= '0;
c_to_cpu_broadcast <= '0;
c_to_cpu_false_accept <= '0;
end else if (rx_valid) begin
unique case (fate)
RX_WANTED: if (!(&c_wanted)) c_wanted <= c_wanted + 1'b1;
RX_DROP_EXACT: if (!(&c_dropped_exact)) c_dropped_exact <= c_dropped_exact + 1'b1;
RX_DROP_HASH: if (!(&c_dropped_hash)) c_dropped_hash <= c_dropped_hash + 1'b1;
RX_TO_CPU: begin
if (is_broadcast) begin
if (!(&c_to_cpu_broadcast))
c_to_cpu_broadcast <= c_to_cpu_broadcast + 1'b1;
end else begin
// Accepted by the hash, and software will have to decide
// whether this station actually joined the group.
if (!(&c_to_cpu_false_accept))
c_to_cpu_false_accept <= c_to_cpu_false_accept + 1'b1;
end
end
default: ;
endcase
end
end
wire _unused_groups = (joined_group_count != 0);
endmoduleClassification: model only — this is the station, not the switch, and it is deliberately outside the device under test.
What it teaches: that the broadcast branch has no alternative implementation. Every other address type reaches a decision that can discard; broadcast reaches RX_TO_CPU unconditionally, and there is no card, no driver and no configuration that changes it without breaking the protocols broadcast exists for. That single unconditional branch is why Section 8 argues broadcast differs in kind rather than degree.
And it teaches why the multicast filter degrades. The bucket table expresses "a joined group lands here", never "this exact group". With 64 buckets and g joined groups, the probability that an arbitrary unwanted group collides into an enabled bucket is 1 − (1 − 1/64)^g:
| Groups joined | Unwanted multicast reaching the CPU |
|---|---|
| 1 | 1.6% |
| 4 | 6.1% |
| 16 | 22.3% |
| 64 | 63.5% |
| 256 | 98.2% |
A station that has joined 256 groups has a multicast filter that passes 98.2% of everything — it has effectively no filter at all, and every multicast frame in the domain costs it an interrupt.
Deliberately simplified: a fixed 64-bucket table and an XOR-folded hash. Real cards use a CRC-derived index and offer a small number of exact-match slots alongside the hash, so the first handful of groups are filtered precisely and the rest fall back to the hash. The degradation curve is the same shape, displaced by the number of exact slots.
Production implication: none of these counters exists in the switch, and that is the point of including the module. An engineer sizing a broadcast domain is making a decision whose cost lands entirely in this module — on hardware they do not own, measured by counters they cannot read, on stations that will never report back. Section 17's rejected property is exactly the attempt to assert something about this module from inside the switch.
8. Why Broadcast Is Different in Kind
Put the receiver model together with the amplification arithmetic and the difference stops being quantitative.
A flooded unknown unicast costs N−1 copies of bandwidth and zero host CPU. The frame is discarded by an exact comparator in every card it does not belong to. The waste is confined to the network, and the network is the party that can measure it.
A broadcast costs N−1 copies of bandwidth and a full protocol-stack traversal on every station in the domain. Take a plausible cost of 5 µs per frame for an interrupt, a driver hand-off and a stack traversal to the point where the frame is recognised as uninteresting:
| Broadcast rate reaching a station | Host CPU consumed |
|---|---|
| 1 000 /s | 1000 × 5 µs = 0.5% of one core |
| 10 000 /s | 5% of one core |
| 100 000 /s | 50% of one core |
| 1.4881 M /s — one port at line rate | 1.4881 M × 5 µs = 744%, or 7.4 cores |
The bottom row is the one that reframes a broadcast storm. It is not a network that has become slow. It is every attached machine simultaneously spending several cores' worth of time discarding frames — which is why a broadcast storm takes down hosts, management interfaces and monitoring systems at the same moment the network degrades, and why the tools an operator would use to diagnose it are among the casualties.
The same storm, measured two ways
A broadcast storm is small in bits and enormous in frames, and every standard network instrument measures bits.
| Broadcast rate | As Mb/s at 64 octets | % of a gigabit link | Host CPU at 5 µs |
|---|---|---|---|
| 1 000 /s | 0.7 Mb/s | 0.1% | 0.01 cores |
| 10 000 /s | 6.7 Mb/s | 0.7% | 0.05 cores |
| 50 000 /s | 33.6 Mb/s | 3.4% | 0.25 cores |
| 100 000 /s | 67.2 Mb/s | 6.7% | 0.50 cores |
| 500 000 /s | 336 Mb/s | 33.6% | 2.50 cores |
| 1 488 095 /s | 1000 Mb/s | 100% | 7.44 cores |
Read across the 100 000 row. A utilisation graph shows 6.7% — a link that looks almost idle. Every station in the domain is spending half a core discarding frames, and every monitoring system in the environment is reporting green.
The divergence is because broadcast's cost is per event and a link's utilisation is per bit. Section 8's 5 µs is an interrupt, a driver hand-off and a stack traversal, and it is the same 5 µs whether the frame is 64 octets or 1518. A storm made of minimum-length frames is the cheapest possible use of the link and the most expensive possible use of the hosts.
Which is why c_broadcast per port per second — a counter incremented on one address comparison — is the instrument, and utilisation is not.
9. RTL 4 — Measuring the Amplification the Switch Is Producing
The switch cannot see what flooding costs the stations. It can see exactly what flooding costs itself, and that number is not on any port statistic.
// -----------------------------------------------------------------------
// flood_cost_accountant -- ingress bits against egress bits, and the
// amplification factor between them.
//
// A port statistic reports bytes in and bytes out PER PORT. Neither
// reveals that one arriving frame became twenty-three departing ones. The
// amplification factor is a switch-level quantity and has to be computed
// deliberately.
// -----------------------------------------------------------------------
module flood_cost_accountant
import flood_pkg::*;
#(
parameter int N_PORTS = 24,
parameter int CNT_W = 40,
parameter int WINDOW = 1_000_000 // frames per evaluation window
)(
input logic clk,
input logic rst_n,
input logic frame_in,
input logic [13:0] frame_bits, // on-wire bits, incl overhead
input flood_cause_e cause,
input logic is_flood,
input logic [7:0] fanout, // copies this flood produced
input logic emit_copy,
output logic [CNT_W-1:0] bits_in,
output logic [CNT_W-1:0] bits_out,
output logic [CNT_W-1:0] flood_bits_out, // egress bits due to flooding
output logic [CNT_W-1:0] c_frames_in,
output logic [CNT_W-1:0] c_copies_out,
output logic window_valid,
output logic [15:0] amplification_x100, // egress/ingress, x100
output logic [15:0] flood_share_pct, // flood bits / all egress bits
output logic amplification_high
);
logic [CNT_W-1:0] win_frames;
logic [13:0] last_bits_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bits_in <= '0;
bits_out <= '0;
flood_bits_out <= '0;
c_frames_in <= '0;
c_copies_out <= '0;
win_frames <= '0;
last_bits_q <= '0;
window_valid <= 1'b0;
amplification_x100 <= 16'd100;
flood_share_pct <= '0;
amplification_high <= 1'b0;
end else begin
window_valid <= 1'b0;
if (frame_in) begin
bits_in <= bits_in + CNT_W'(frame_bits);
c_frames_in <= c_frames_in + 1'b1;
win_frames <= win_frames + 1'b1;
last_bits_q <= frame_bits;
// The flood's egress cost is charged at the moment the flood is
// launched, using the fanout, so that the number is available
// even if the drain is still in progress.
if (is_flood)
flood_bits_out <= flood_bits_out +
(CNT_W'(frame_bits) * CNT_W'(fanout));
end
if (emit_copy) begin
bits_out <= bits_out + CNT_W'(last_bits_q);
c_copies_out <= c_copies_out + 1'b1;
end
if (win_frames >= CNT_W'(WINDOW)) begin
// Egress bits per ingress bit, scaled by 100. A switch doing pure
// unicast forwarding sits at 100. Every point above that is
// replication.
amplification_x100 <= (bits_in == '0) ? 16'd100
: 16'((bits_out * CNT_W'(100)) / bits_in);
flood_share_pct <= (bits_out == '0) ? 16'd0
: 16'((flood_bits_out * CNT_W'(100)) / bits_out);
// Above 2x means more than half of all egress bits are replicas.
amplification_high <= (bits_out > (bits_in << 1));
win_frames <= '0;
window_valid <= 1'b1;
end
end
end
wire _unused_ports = (N_PORTS != 0);
wire _unused_cause = |cause;
endmoduleClassification: synthesizable, with the divides implemented as a multi-cycle sequence in a real design — they occur once per million frames and need no throughput at all.
What it teaches: that amplification is a switch-level quantity that no per-port statistic can express. Port 3 reports bytes in; ports 1, 2 and 4 through 24 each report bytes out. Nothing in that set says that port 3's arriving frame became twenty-three departing ones, and an operator summing the columns sees a switch emitting far more than it received without any indication of why.
And it teaches the calibration. A switch doing pure unicast forwarding sits at exactly 100 — one egress bit per ingress bit. Every point above 100 is replication, and on a healthy network the excess is small: a few percent of broadcast plus the occasional unknown-unicast flood before a station is learned.
Deliberately simplified: the flood's egress cost is charged at launch using the fanout rather than accumulated per copy, and last_bits_q approximates the emitted frame's size. A production accountant carries the size with the buffer handle, because floods and unicast forwards interleave and the last arriving frame is not the one being emitted.
Production implication: amplification_x100 is the single number that distinguishes the three network conditions an operator most often confuses. Near 100 with high utilisation is a busy network — nothing is wrong. Near 300 is a domain with heavy broadcast or a lot of unknown unicast, and flood_share_pct with Section 4's cause counters says which. Above 1000 on a 24-port switch means something close to one port's worth of continuous flooding, and Section 8's arithmetic says the attached hosts are already in trouble whether or not anybody has noticed.
10. The Domain-Size Limit
Broadcast is the only flood type that every station must process, so it is the one that sets an upper bound on how many stations may share a domain. The bound is arithmetic and it is smaller than people expect.
Every station in a broadcast domain receives every other station's broadcasts. With N stations each emitting B broadcasts per second, each station receives (N − 1) × B per second. Set a CPU budget in frames per second and solve for N:
| Per-station broadcast rate | Budget 500/s | Budget 1000/s | Budget 2000/s |
|---|---|---|---|
| 2 /s — quiet hosts | N ≤ 251 | N ≤ 501 | N ≤ 1001 |
| 5 /s — typical | N ≤ 101 | N ≤ 201 | N ≤ 401 |
| 10 /s — chatty | N ≤ 51 | N ≤ 101 | N ≤ 201 |
The middle cell is the one that matches the rule of thumb every network engineer carries: a few hundred stations per broadcast domain. It is not folklore. It is budget ÷ B + 1, and the reason nobody quotes the derivation is that both inputs are estimates.
Notice what the limit does not depend on. Not the switch's capacity — a 48-port switch with a 96 Gb/s fabric hits the same wall. Not the link speed — the constraint is frames per second at the receiver's CPU, and a 10 Gb/s link does not make a host's interrupt handler faster. Not the table size — broadcast never consults the table.
The limit depends only on how many stations can hear each other, which is precisely what a broadcast domain is.
The domain is not the switch
Section 10's N is the number of stations that can hear each other, and that is almost never the port count of one switch.
A broadcast domain spans every switch reachable without passing through a device that stops broadcast. Four 24-port switches, each with one port used as an uplink, put 4 × 23 = 92 stations in one domain — and every one of them receives every other's broadcasts.
| one switch | four switches, one domain | |
|---|---|---|
stations N | 23 | 92 |
each station receives at B = 5/s | 22 × 5 = 110 /s | 91 × 5 = 455 /s |
| host CPU at 5 µs | 0.06% of a core | 0.2% of a core |
| the uplink carries | — | all 460 domain broadcasts/s, 0.31 Mb/s |
The numbers are comfortable here and the shape is the point: N grows with the topology, not with any one device, so a network built by adding switches grows its broadcast load quadratically in aggregate — N stations each receiving (N−1) × B.
And the uplink row is the one that surprises people. Every broadcast in the domain crosses every inter-switch link, exactly once per link. A trunk between two buildings carries the broadcast traffic of both, and at 92 stations that is trivial — at 2000 stations and B = 5/s it is 10 000 × 672 = 6.7 Mb/s of pure broadcast on every uplink, permanently, before any user traffic.
11. RTL 5 — The One Mitigation Available at This Layer
A switch cannot reduce broadcast, cannot filter it, and cannot tell whether any copy was wanted. It can cap the rate at which any one port injects it.
// -----------------------------------------------------------------------
// broadcast_rate_limiter -- storm control, per port, per cause.
//
// This does not make the network correct. It makes a catastrophe bounded.
// Section 12 derives what a given cap actually delivers at the receiver,
// and the answer is that the settings real switches offer are one to two
// orders of magnitude coarser than the receiver budget requires.
// -----------------------------------------------------------------------
module broadcast_rate_limiter
import flood_pkg::*;
#(
parameter int N_PORTS = 24,
parameter int PORT_BITS = 5,
parameter int BC_PPS_LIMIT = 1000, // broadcast frames/s admitted per port
parameter int MC_PPS_LIMIT = 5000, // multicast is usually capped higher
parameter int UU_PPS_LIMIT = 20000, // unknown unicast -- self-limiting
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic second_tick,
input logic flood_req,
input flood_cause_e cause,
input logic [PORT_BITS-1:0] ingress_port,
output logic flood_permit,
output logic [CNT_W-1:0] c_suppressed,
output logic [CNT_W-1:0] suppressed_by_port [N_PORTS],
output logic [PORT_BITS-1:0] worst_port,
output logic storm_active,
output flood_cause_e storm_cause
);
logic [CNT_W-1:0] bc_this_sec [N_PORTS];
logic [CNT_W-1:0] mc_this_sec [N_PORTS];
logic [CNT_W-1:0] uu_this_sec [N_PORTS];
// Per CAUSE, because the three have different costs and different
// remedies. A single combined cap would let a burst of self-limiting
// unknown unicast consume the allowance that broadcast -- the expensive
// one -- needs.
always_comb begin
flood_permit = 1'b0;
if (flood_req) begin
unique case (cause)
FC_BROADCAST:
flood_permit = (bc_this_sec[ingress_port] < CNT_W'(BC_PPS_LIMIT));
FC_MULTICAST:
flood_permit = (mc_this_sec[ingress_port] < CNT_W'(MC_PPS_LIMIT));
FC_UNKNOWN_UNICAST, FC_UNRESOLVED:
flood_permit = (uu_this_sec[ingress_port] < CNT_W'(UU_PPS_LIMIT));
default:
flood_permit = 1'b1;
endcase
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
for (int p = 0; p < N_PORTS; p++) begin
bc_this_sec[p] <= '0; mc_this_sec[p] <= '0; uu_this_sec[p] <= '0;
suppressed_by_port[p] <= '0;
end
c_suppressed <= '0;
worst_port <= '0;
storm_active <= 1'b0;
storm_cause <= FC_NONE;
end else begin
if (flood_req) begin
if (flood_permit) begin
unique case (cause)
FC_BROADCAST: bc_this_sec[ingress_port] <= bc_this_sec[ingress_port] + 1'b1;
FC_MULTICAST: mc_this_sec[ingress_port] <= mc_this_sec[ingress_port] + 1'b1;
FC_UNKNOWN_UNICAST,
FC_UNRESOLVED: uu_this_sec[ingress_port] <= uu_this_sec[ingress_port] + 1'b1;
default: ;
endcase
end else begin
if (!(&c_suppressed)) c_suppressed <= c_suppressed + 1'b1;
suppressed_by_port[ingress_port] <=
suppressed_by_port[ingress_port] + 1'b1;
// Record WHICH cause is storming. "Storm control engaged" with
// no cause is not actionable; broadcast and multicast storms
// have entirely different investigations.
storm_cause <= cause;
end
end
if (second_tick) begin
automatic logic [CNT_W-1:0] hi = '0;
automatic logic [PORT_BITS-1:0] hp = '0;
for (int p = 0; p < N_PORTS; p++) begin
if (suppressed_by_port[p] > hi) begin
hi = suppressed_by_port[p];
hp = PORT_BITS'(p);
end
bc_this_sec[p] <= '0;
mc_this_sec[p] <= '0;
uu_this_sec[p] <= '0;
end
worst_port <= hp;
storm_active <= (hi != '0);
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that the cap is per cause and per port, and both qualifiers are load-bearing. Per cause, because unknown-unicast flooding is self-limiting and cheap at the receiver while broadcast is permanent and expensive — a combined budget lets the harmless one consume the allowance the harmful one needs. Per port, because a shared budget lets one storming port suppress every other port's legitimate broadcast, which would break ARP for the whole switch in order to protect it.
And it teaches that suppression is a discard, not a queue. A suppressed broadcast is gone. There is no retry, no backpressure and no notification — the sender does not know, the intended receivers do not know, and the only record is suppressed_by_port. That is the correct behaviour and it is worth stating plainly, because the alternative — queueing broadcasts until the budget refreshes — delays ARP replies past their usefulness while still delivering the storm.
Deliberately simplified: hard per-second counters with a sharp edge at the limit. Production storm control uses a token bucket so that a legitimate burst — a rack powering on and every host ARPing at once — passes while the long-term average is held.
Production implication: storm_cause is the field that makes the feature usable. A switch that reports "storm control engaged on port 7" has told an operator almost nothing: a broadcast storm points at a loop or a misbehaving host, a multicast storm points at a video source or a discovery protocol, and an unknown-unicast storm points at Chapter 12.2 §12's table-filling attack. Same feature, same counter, three unrelated investigations.
12. What a Storm-Control Setting Actually Delivers
Storm control is configured as a percentage of line rate per port, and Section 10's limit is expressed in frames per second at the receiver. Convert between them and the settings real switches offer are revealed for what they are.
On a 24-port gigabit switch, a station receives broadcast from 23 other ports. With each port capped at a percentage of its line rate:
| Cap per port | Frames/s admitted per port | Station receives | Host CPU at 5 µs/frame |
|---|---|---|---|
| 10% | 148 810 | 3 422 619 /s | 1711% — 17 cores |
| 5% | 74 405 | 1 711 310 /s | 856% — 8.6 cores |
| 1% | 14 881 | 342 262 /s | 171% — 1.7 cores |
| 0.5% | 7 440 | 171 131 /s | 86% of a core |
| 0.1% | 1 488 | 34 226 /s | 17% of a core |
Every commonly-offered setting still permits a station to lose a substantial fraction of a core to broadcast. At the typical default of 1%, a fully "protected" switch still allows every attached host to spend 1.7 cores discarding frames it did not want.
Now solve the other direction. To hold a station at Section 10's 1000 frames per second budget, each of 23 ports must be capped at 1000 ÷ 23 = 43.5 frames per second — which is 0.00292% of line rate, roughly 34 times finer than a 0.1% setting and far below the granularity most switches expose.
The conclusion is uncomfortable and worth stating directly: storm control is a catastrophe limiter, not a design tool. It converts a network that has completely collapsed into one that is merely badly degraded. It does not, and at the available granularity cannot, hold a broadcast domain inside the budget that Section 10 derived.
Which is the argument for segmentation, and it is an arithmetic argument rather than a stylistic one. The only variable in (N − 1) × B that a designer actually controls is N — and reducing N is what Chapter 13.1 is about.
13. RTL 6 — Multicast: The Case With No Local Fix
Multicast is the only flood type where the switch is doing something demonstrably wasteful and demonstrably correct, and where no information available to it could do better.
// -----------------------------------------------------------------------
// multicast_flood_model -- what a basic switch knows about a group
// address, which is nothing, and what it would need to know to do better.
//
// The module deliberately computes the WASTE it is producing without
// being able to avoid it. Every quantity here is derivable from the
// switch's own ports; the one quantity that would fix the problem --
// which ports contain members -- is not derivable from anything the
// switch can observe.
// -----------------------------------------------------------------------
module multicast_flood_model
import flood_pkg::*;
#(
parameter int N_PORTS = 24,
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic mc_flood,
input logic [ADDR_W-1:0] mc_group,
input logic [7:0] fanout,
// ORACLE ONLY -- not available to any real switch at this layer. Wired
// from the testbench so the waste can be quantified in simulation.
input logic [N_PORTS-1:0] oracle_member_mask,
output logic [CNT_W-1:0] c_mc_floods,
output logic [CNT_W-1:0] c_copies_sent,
output logic [CNT_W-1:0] c_copies_wanted,
output logic [CNT_W-1:0] c_copies_wasted,
output logic [15:0] waste_pct,
output logic [ADDR_W-1:0] worst_group,
output logic membership_would_help
);
logic [CNT_W-1:0] worst_waste_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_mc_floods <= '0;
c_copies_sent <= '0;
c_copies_wanted <= '0;
c_copies_wasted <= '0;
waste_pct <= '0;
worst_group <= '0;
worst_waste_q <= '0;
membership_would_help <= 1'b0;
end else if (mc_flood) begin
automatic logic [CNT_W-1:0] wanted;
automatic logic [CNT_W-1:0] wasted;
wanted = CNT_W'($countones(oracle_member_mask));
wasted = CNT_W'(fanout) - wanted;
if (!(&c_mc_floods)) c_mc_floods <= c_mc_floods + 1'b1;
c_copies_sent <= c_copies_sent + CNT_W'(fanout);
c_copies_wanted <= c_copies_wanted + wanted;
c_copies_wasted <= c_copies_wasted + wasted;
// The share of emitted copies that no station wanted. On a group
// with three members on a 24-port switch this is 20 of 23 copies,
// or 87%.
waste_pct <= (fanout == 8'd0) ? 16'd0
: 16'((wasted * CNT_W'(100)) / CNT_W'(fanout));
if (wasted > worst_waste_q) begin
worst_waste_q <= wasted;
worst_group <= mc_group;
end
// Membership knowledge helps only when the group has FEWER members
// than the flood has destinations. A group every station joined
// costs nothing to flood, and no mechanism could improve it.
membership_would_help <= (wanted < CNT_W'(fanout));
end
end
endmoduleClassification: partly model — oracle_member_mask has no source in real hardware and exists only to quantify, in simulation, a waste the switch cannot measure.
What it teaches: that the group's membership is the one input that would fix multicast flooding, and it is precisely the input a basic switch has no way to obtain. Chapter 12.2 §4's first eligibility rule forbids learning a group address, and the reason is not conservatism — nothing ever transmits from a group address, so no frame anywhere carries evidence of membership. The learning mechanism that populates the unicast table has no analogue here, and the absence is structural.
And it teaches the shape of the waste. A group with 3 members on a 24-port switch produces a flood of 23 copies of which 20 are unwanted — 87%. The 20 unwanted copies are discarded by the receivers' hash filters, mostly for free — but Section 7's table showed that "mostly" degrades sharply: a station that has joined 64 groups passes 63.5% of unwanted multicast to its CPU.
Deliberately simplified: the oracle mask is supplied per flood. A production study derives it from a group-membership protocol's state, and the switch feature that uses it — snooping a membership protocol to build a per-group egress mask — lives above the layer this track covers, which is why this chapter can quantify the problem and not solve it.
Production implication: waste_pct is the number that justifies the feature that fixes this, and it is the number an engineer needs before deciding whether to enable it. A network whose multicast groups have most stations as members gains nothing from membership snooping — membership_would_help is low, and the flood was already close to optimal. A network with many small groups is emitting 85–95% waste, and the same feature is transformative. The switch can compute everything in that sentence except the one term that makes it actionable.
14. Correct and Wasteful at the Same Time
Hold two statements together, because the tension between them is the honest position and most explanations pick one.
Flooding a multicast frame is correct. The address names a set. The switch does not know which ports lead to members. Delivering to every port guarantees that every member receives it, and any narrower behaviour risks failing to deliver to a member — which is the one error that would make multicast unusable.
Flooding a multicast frame is wasteful. On a group with three members and twenty-four ports, twenty of twenty-three copies are unwanted, consuming egress bandwidth, receiver bandwidth, and — as the receiver's filter degrades with group count — receiver CPU.
Both are true, and the resolution is not a better switch. It is more information, and the information does not exist at this layer.
| can the switch obtain it | why not | |
|---|---|---|
| which ports lead to a unicast station | yes | its frames carry its source address — Chapter 12.2 |
| which ports lead to a multicast group's members | no | nothing transmits from a group address |
| which ports want a group | no | wanting is a receiver-side state with no wire representation |
The middle row is the structural fact. Chapter 12.2's entire mechanism rests on the observation that a frame's source address proves where its sender is. A group address is never a source, so the mechanism has nothing to work with — not because it was designed badly, but because there is no frame to learn from.
The bottom row is why the fix must come from above. Membership is something a station decides, and the only way a switch could know is if the station said so — which requires a protocol, and a protocol is exactly what this layer does not have.
How much is wasted, by group size
The waste is (fanout − members) ÷ fanout, and it is worst exactly where multicast is most useful.
| Members of the group | Copies emitted | Wasted | Waste |
|---|---|---|---|
| 1 | 23 | 22 | 95.7% |
| 2 | 23 | 21 | 91.3% |
| 3 | 23 | 20 | 87.0% |
| 6 | 23 | 17 | 73.9% |
| 12 | 23 | 11 | 47.8% |
| 18 | 23 | 5 | 21.7% |
| 23 — everyone | 23 | 0 | 0% |
The bottom row is the case where flooding is already optimal, and it is why membership_would_help exists: a group every station joined costs nothing to flood, and no mechanism could improve on what the switch is already doing.
The top rows are the ones that matter, and they are the common case. A group with one or two members — a video stream to a pair of displays, a control protocol between two controllers, a clock distribution to a handful of devices — wastes over 90% of the copies it generates. Multicast exists precisely so that a sender can reach a small set without unicasting to each, and at this layer the small set is exactly where the mechanism is most wasteful.
Which sharpens Section 13's membership_would_help into an operational rule: the value of membership snooping is waste_pct, it is highest for small groups, and a network with many small multicast groups is the one where enabling it changes the numbers.
15. RTL 7 — The Flood-to-Forward Ratio
One number characterises whether a switch is behaving like a switch, and it is the ratio between the two things it does.
// -----------------------------------------------------------------------
// flood_ratio_monitor -- the health metric for a learning switch.
//
// Chapter 12.2 Section 18 introduced the insert-to-refresh ratio for the
// learning side. This is its forwarding-side counterpart, and the two
// together localise almost every switching complaint before a packet is
// captured.
// -----------------------------------------------------------------------
module flood_ratio_monitor
import flood_pkg::*;
#(
parameter int CNT_W = 32,
parameter int WINDOW = 1_000_000
)(
input logic clk,
input logic rst_n,
input logic decision_valid,
input logic was_forward,
input logic was_flood,
input flood_cause_e cause,
output logic window_valid,
output logic [15:0] flood_pct,
output logic [15:0] bcast_share_pct, // of floods, how many broadcast
output logic hub_like, // flooding dominates
output logic learning_suspect, // unknown unicast dominates
output logic traffic_suspect // broadcast dominates
);
logic [CNT_W-1:0] n_fwd, n_flood, n_uu, n_bc, n_total;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_fwd <= '0; n_flood <= '0; n_uu <= '0; n_bc <= '0; n_total <= '0;
window_valid <= 1'b0;
flood_pct <= '0;
bcast_share_pct <= '0;
hub_like <= 1'b0;
learning_suspect <= 1'b0;
traffic_suspect <= 1'b0;
end else begin
window_valid <= 1'b0;
if (decision_valid) begin
n_total <= n_total + 1'b1;
if (was_forward) n_fwd <= n_fwd + 1'b1;
if (was_flood) n_flood <= n_flood + 1'b1;
if (was_flood && (cause == FC_UNKNOWN_UNICAST)) n_uu <= n_uu + 1'b1;
if (was_flood && (cause == FC_BROADCAST)) n_bc <= n_bc + 1'b1;
end
if (n_total >= CNT_W'(WINDOW)) begin
flood_pct <= 16'((n_flood * CNT_W'(100)) / n_total);
bcast_share_pct <= (n_flood == '0) ? 16'd0
: 16'((n_bc * CNT_W'(100)) / n_flood);
// A switch flooding a quarter of its decisions is spending most
// of its egress bandwidth on replicas -- Section 3's arithmetic.
hub_like <= (n_flood > (n_total >> 2));
// WHICH cause dominates decides which chapter's diagnosis
// applies. Unknown unicast is a learning-side condition;
// broadcast is a statement about the attached stations.
learning_suspect <= (n_uu > (n_flood >> 1)) &&
(n_flood > (n_total >> 4));
traffic_suspect <= (n_bc > (n_flood >> 1)) &&
(n_flood > (n_total >> 4));
n_fwd <= '0; n_flood <= '0; n_uu <= '0; n_bc <= '0; n_total <= '0;
window_valid <= 1'b1;
end
end
end
endmoduleClassification: synthesizable.
What it teaches: that the ratio's composition matters more than its value. A flood rate of 30% means very different things depending on whether the floods are unknown unicast or broadcast. learning_suspect and traffic_suspect are mutually exclusive diagnoses reached from the same headline number, and a monitor that reported only flood_pct would send an operator to Chapter 12.2's table-tuning remedies half the time it should send them to Section 12's rate limiter.
And it teaches the calibration. In a stable network the floods are broadcast, the occasional multicast, and the first frame to a station the switch has not heard from — a low single-digit percentage. Above a quarter, Section 3's arithmetic means most of the switch's egress bandwidth is replicas.
Deliberately simplified: a single global window over all ports. Production monitoring keeps the ratio per ingress port, because a domain-wide broadcast problem and one misbehaving host look identical in the aggregate and completely different per port.
Production implication: hub_like is deliberately named for what it means rather than for what it measures. A switch flooding more than a quarter of its decisions has given up most of Chapter 12.1's 48× advantage and is approaching the behaviour of the device switching replaced. That is the sentence an operator needs, and it is more useful than a percentage they have to calibrate for themselves.
16. RTL 8 — Conformance for a One-to-Many Operation
Every other operation in a switch produces at most one frame. A flood produces N−1, and the accounting that proves it correct is different in kind.
// -----------------------------------------------------------------------
// flood_conformance_monitor -- checks a one-to-many operation.
//
// Chapter 12.3's monitor could reconcile frames in against decisions out,
// one for one. Here one frame in produces many frames out, so the
// accounting is against the MASK: every set bit produced exactly one
// emission, every clear bit produced none, and the count balances.
// -----------------------------------------------------------------------
module flood_conformance_monitor
import flood_pkg::*;
#(
parameter int N_PORTS = 24,
parameter int PORT_BITS = 5,
parameter int CNT_W = 32
)(
input logic clk,
input logic rst_n,
input logic flood_start,
input logic [N_PORTS-1:0] flood_mask,
input logic [PORT_BITS-1:0] ingress_port,
input logic [N_PORTS-1:0] forwarding_mask,
input logic emit_accepted,
input logic [PORT_BITS-1:0] emit_port,
input logic release_buffer,
output logic [CNT_W-1:0] v_ingress_copy, // a copy to the ingress port
output logic [CNT_W-1:0] v_blocked_copy, // a copy to a blocked port
output logic [CNT_W-1:0] v_duplicate_copy, // two copies to one port
output logic [CNT_W-1:0] v_missing_copy, // released with bits pending
output logic [CNT_W-1:0] v_extra_copy, // a copy not in the mask
output logic [CNT_W-1:0] c_floods_checked,
output logic conformant
);
logic [N_PORTS-1:0] expect_q; // bits still owed
logic [N_PORTS-1:0] seen_q; // bits already emitted
logic active_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
expect_q <= '0;
seen_q <= '0;
active_q <= 1'b0;
v_ingress_copy <= '0;
v_blocked_copy <= '0;
v_duplicate_copy <= '0;
v_missing_copy <= '0;
v_extra_copy <= '0;
c_floods_checked <= '0;
end else begin
if (flood_start) begin
expect_q <= flood_mask;
seen_q <= '0;
active_q <= 1'b1;
if (!(&c_floods_checked)) c_floods_checked <= c_floods_checked + 1'b1;
// THE MASK ITSELF, checked before a single copy is emitted.
// Chapter 12.3's P10 and P11 restated where the replication
// happens rather than where the mask was built.
if (flood_mask[ingress_port])
if (!(&v_ingress_copy)) v_ingress_copy <= v_ingress_copy + 1'b1;
if ((flood_mask & ~forwarding_mask) != '0)
if (!(&v_blocked_copy)) v_blocked_copy <= v_blocked_copy + 1'b1;
end
if (emit_accepted && active_q) begin
// A SECOND copy to a port that already had one. Chapter 12.1's
// P10, and the invariant a careless replicator breaks first.
if (seen_q[emit_port])
if (!(&v_duplicate_copy)) v_duplicate_copy <= v_duplicate_copy + 1'b1;
// A copy to a port the mask never named.
if (!expect_q[emit_port])
if (!(&v_extra_copy)) v_extra_copy <= v_extra_copy + 1'b1;
seen_q[emit_port] <= 1'b1;
expect_q[emit_port] <= 1'b0;
end
// THE COMPLETENESS CHECK. The buffer was released while bits were
// still owed -- one or more stations silently did not get the
// frame, and nothing else in the design would ever notice.
if (release_buffer) begin
if ((expect_q & ~(N_PORTS'(1) << emit_port)) != '0)
if (!(&v_missing_copy)) v_missing_copy <= v_missing_copy + 1'b1;
active_q <= 1'b0;
expect_q <= '0;
end
end
end
assign conformant = (v_ingress_copy == '0) &&
(v_blocked_copy == '0) &&
(v_duplicate_copy == '0) &&
(v_missing_copy == '0) &&
(v_extra_copy == '0);
endmoduleClassification: synthesizable, and intended to stay in silicon.
What it teaches: that a one-to-many operation needs a per-destination ledger, not a counter. Chapter 12.3 §14 could reconcile frames in against decisions out one for one. Here the obligation is a set, and the only way to prove it was discharged is to track which bits were satisfied — a count of emissions would not detect two copies to port 3 and none to port 7.
And v_missing_copy is the check worth building even if nothing else here is. A dropped flood copy is the most silent failure in a switch: the sender does not know, the intended receiver does not know it should have received anything, no error counter moves, and — as Section 14 established — Ethernet provides no return path by which any of them could find out. The only place it can be caught is here, at the moment the buffer is released with bits still owed.
Deliberately simplified: one flood tracked at a time, matching Section 5's replicator. A production monitor keeps a small table of in-flight floods keyed by buffer handle, since several floods share the buffer concurrently.
Production implication: conformant here means the flood reached exactly the set of ports the mask named, once each. It does not mean the flood was useful — Section 13 showed 87% of a small group's copies are unwanted — and it does not mean any station received anything. The narrow claim is the only one available, and Section 17 is about why the broader one cannot be written at all.
17. Properties Worth Asserting, and One Worth Refusing
Every property here is about the copies the switch emitted. Not one is about what happened to them, and that boundary is this chapter's rejected class.
Classification
// P1. Broadcast is classified BEFORE the group test. FF:FF:FF:FF:FF:FF
// has the I/G bit set and would otherwise count as multicast.
property p_broadcast_before_group;
@(posedge clk) disable iff (!rst_n)
(flood_valid && (dst == {ADDR_W{1'b1}})) |-> (cause == FC_BROADCAST);
endproperty
a_bcast_first: assert property (p_broadcast_before_group);
// P2. A group destination that is not broadcast is multicast.
property p_group_is_multicast;
@(posedge clk) disable iff (!rst_n)
(flood_valid && dst[40] && (dst != {ADDR_W{1'b1}}))
|-> (cause == FC_MULTICAST);
endproperty
a_group_multicast: assert property (p_group_is_multicast);
// P3. An individual destination flooding means the lookup did not answer.
property p_individual_flood_is_a_miss;
@(posedge clk) disable iff (!rst_n)
(flood_valid && !dst[40])
|-> (cause inside {FC_UNKNOWN_UNICAST, FC_UNRESOLVED});
endproperty
a_individual_miss: assert property (p_individual_flood_is_a_miss);
// P4. Multicast and broadcast are flagged as PERMANENT -- they will not
// stop when a station transmits, because no station transmits from them.
property p_permanent_flood_flagged;
@(posedge clk) disable iff (!rst_n)
(flood_valid && (cause inside {FC_MULTICAST, FC_BROADCAST}))
|-> permanent_flood;
endproperty
a_permanent_flagged: assert property (p_permanent_flood_flagged);
// P5. Exactly one cause counter moves per flood.
property p_one_cause_counted;
@(posedge clk) disable iff (!rst_n)
flood_valid |=> ($changed(c_unknown_unicast) + $changed(c_multicast) +
$changed(c_broadcast) + $changed(c_unresolved)) == 1;
endproperty
a_one_cause: assert property (p_one_cause_counted);Replication
// P6. THE COMPLETENESS PROPERTY. Every set bit in the mask produces
// exactly one emission before the buffer is released.
property p_every_masked_port_served;
@(posedge clk) disable iff (!rst_n)
release_buffer |-> (expect_q == '0);
endproperty
a_all_ports_served: assert property (p_every_masked_port_served);
// P7. NEVER twice to one port -- Chapter 12.1's P10, at the point where
// replication actually happens.
property p_no_duplicate_copy;
@(posedge clk) disable iff (!rst_n)
(emit_accepted && active_q) |-> !seen_q[emit_port];
endproperty
a_no_dup_copy: assert property (p_no_duplicate_copy);
// P8. NEVER to the ingress port.
property p_no_copy_to_ingress;
@(posedge clk) disable iff (!rst_n)
flood_start |-> !flood_mask[ingress_port];
endproperty
a_no_ingress_copy: assert property (p_no_copy_to_ingress);
// P9. NEVER to a port that may not forward -- Chapter 12.3's P11.
property p_no_copy_to_blocked;
@(posedge clk) disable iff (!rst_n)
flood_start |-> ((flood_mask & ~forwarding_mask) == '0);
endproperty
a_no_blocked_copy: assert property (p_no_copy_to_blocked);
// P10. The pending bit clears on ACCEPTANCE, not on issue. Clearing on
// issue counts a copy the egress refused as emitted, and the lost copy
// is a station that silently did not get the frame.
property p_clear_on_accept_only;
@(posedge clk) disable iff (!rst_n)
(emit_valid && !emit_ready) |=> $stable(pending_q);
endproperty
a_clear_on_accept: assert property (p_clear_on_accept_only);
// P11. A new flood is not accepted while one is draining -- accepting it
// would silently discard the remaining copies of the first.
property p_no_overlapping_floods;
@(posedge clk) disable iff (!rst_n)
(flood_start && busy) |=> $stable(pending_q);
endproperty
a_no_overlap: assert property (p_no_overlapping_floods);
// P12. The frame is stored ONCE. The handle emitted with every copy is
// the same handle.
property p_single_stored_copy;
@(posedge clk) disable iff (!rst_n)
(emit_valid && busy) |-> (emit_handle == handle_q);
endproperty
a_one_buffer: assert property (p_single_stored_copy);
// P13. The buffer is released exactly once, on the last copy.
property p_release_once_on_last;
@(posedge clk) disable iff (!rst_n)
release_buffer |-> $past(emit_valid && emit_ready &&
($countones(pending_q) == 1));
endproperty
a_release_last: assert property (p_release_once_on_last);Accounting
// P14. Emitted copies equal the sum of fanouts. A shortfall is a copy
// that vanished, and nothing else in the system would report it.
property p_copies_match_fanout;
@(posedge clk) disable iff (!rst_n)
window_valid |-> (c_copies_out >= c_frames_in);
endproperty
a_copies_ge_frames: assert property (p_copies_match_fanout);
// P15. Amplification is at least 100 -- a switch cannot emit fewer bits
// than it received unless it is discarding, which is counted elsewhere.
property p_amplification_at_least_unity;
@(posedge clk) disable iff (!rst_n)
window_valid |-> (amplification_x100 >= 16'd100);
endproperty
a_amp_ge_unity: assert property (p_amplification_at_least_unity);
// P16. The fanout never exceeds N-1.
property p_fanout_bounded;
@(posedge clk) disable iff (!rst_n)
flood_start |-> (8'($countones(flood_mask)) <= 8'(N_PORTS - 1));
endproperty
a_fanout_bounded: assert property (p_fanout_bounded);
// P17. The flood share of egress bits is attributed, so that a high
// amplification can be traced to flooding rather than guessed at.
property p_flood_share_computed;
@(posedge clk) disable iff (!rst_n)
window_valid |-> (flood_share_pct <= 16'd100);
endproperty
a_flood_share: assert property (p_flood_share_computed);Rate limiting
// P18. The cap is PER CAUSE. A burst of self-limiting unknown unicast
// must not consume the allowance broadcast needs.
property p_per_cause_budget;
@(posedge clk) disable iff (!rst_n)
(flood_req && (cause == FC_BROADCAST) && !flood_permit)
|-> (bc_this_sec[ingress_port] >= CNT_W'(BC_PPS_LIMIT));
endproperty
a_per_cause: assert property (p_per_cause_budget);
// P19. The cap is PER PORT. One storming port must not suppress another
// port's legitimate broadcast.
property p_per_port_budget;
@(posedge clk) disable iff (!rst_n)
(flood_req && !flood_permit)
|-> (suppressed_by_port[ingress_port] != $past(suppressed_by_port[ingress_port]));
endproperty
a_per_port: assert property (p_per_port_budget);
// P20. A suppressed flood is DISCARDED, never queued. A delayed ARP
// reply is worse than an absent one.
property p_suppressed_not_queued;
@(posedge clk) disable iff (!rst_n)
(flood_req && !flood_permit) |=> !flood_start;
endproperty
a_suppress_discards: assert property (p_suppressed_not_queued);
// P21. A storm names its CAUSE. "Storm control engaged" without a cause
// is three unrelated investigations collapsed into one alert.
property p_storm_names_cause;
@(posedge clk) disable iff (!rst_n)
storm_active |-> (storm_cause != FC_NONE);
endproperty
a_storm_cause: assert property (p_storm_names_cause);
// P22. A storm names its PORT.
property p_storm_names_port;
@(posedge clk) disable iff (!rst_n)
storm_active |-> (suppressed_by_port[worst_port] != '0);
endproperty
a_storm_port: assert property (p_storm_names_port);Diagnosis
// P23. The flood ratio's COMPOSITION is reported, not only its value --
// the same percentage means different things for different causes.
property p_ratio_composition_reported;
@(posedge clk) disable iff (!rst_n)
window_valid |-> !(learning_suspect && traffic_suspect);
endproperty
a_diagnosis_exclusive: assert property (p_ratio_composition_reported);
// P24. hub_like implies the flood share is above a quarter.
property p_hub_like_definition;
@(posedge clk) disable iff (!rst_n)
(window_valid && hub_like) |-> (flood_pct > 16'd25);
endproperty
a_hub_like_def: assert property (p_hub_like_definition);
// P25. Multicast waste is computed but NEVER acted on -- the oracle is
// a measurement aid and must not reach the forwarding path.
property p_oracle_does_not_gate_forwarding;
@(posedge clk) disable iff (!rst_n)
mc_flood |-> (fanout == $past(fanout_from_mask));
endproperty
a_oracle_advisory: assert property (p_oracle_does_not_gate_forwarding);
// P26. Conformance is the conjunction of the five ledger violations, and
// it means the mask was discharged -- never that anybody received
// anything.
property p_conformant_definition;
@(posedge clk) disable iff (!rst_n)
conformant |-> ((v_ingress_copy == '0) && (v_blocked_copy == '0) &&
(v_duplicate_copy == '0) && (v_missing_copy == '0) &&
(v_extra_copy == '0));
endproperty
a_conformant_def: assert property (p_conformant_definition);
// P27. Every flood is checked, so a flood that bypassed the monitor is
// itself detectable.
property p_every_flood_checked;
@(posedge clk) disable iff (!rst_n)
flood_start |=> (c_floods_checked > $past(c_floods_checked));
endproperty
a_all_floods_checked: assert property (p_every_flood_checked);
// P28. The frame emitted on every port is byte-identical to the frame
// received. A flood replicates; it does not rewrite.
property p_copies_are_identical;
@(posedge clk) disable iff (!rst_n)
(emit_accepted && active_q) |-> (emit_handle == handle_q);
endproperty
a_copies_identical: assert property (p_copies_are_identical);
// P29. Broadcast reaches the CPU unconditionally in the receiver model.
// There is no configuration, filter or card that changes this, and the
// property exists to make the absence of an alternative explicit.
property p_broadcast_always_to_cpu;
@(posedge clk) disable iff (!rst_n)
(rx_valid && (rx_dst == {ADDR_W{1'b1}})) |-> (fate == RX_TO_CPU);
endproperty
a_bcast_to_cpu: assert property (p_broadcast_always_to_cpu);
// P30. A unicast frame not addressed to this station NEVER reaches the
// CPU -- the exact 48-bit compare is why unknown-unicast flooding costs
// bandwidth and nothing else.
property p_foreign_unicast_never_to_cpu;
@(posedge clk) disable iff (!rst_n)
(rx_valid && !rx_dst[40] && (rx_dst != my_address)) |-> !to_cpu;
endproperty
a_foreign_unicast_dropped: assert property (p_foreign_unicast_never_to_cpu);18. Verification Scenarios
Sixty-three scenarios. The ledger scenarios have no acceptable failure; the cost scenarios have expected values that include large amounts of waste.
Classification
| # | Scenario | Expected |
|---|---|---|
| 1 | Destination FF:FF:FF:FF:FF:FF | FC_BROADCAST — not multicast |
| 2 | Destination 01:00:5E:00:00:01 | FC_MULTICAST |
| 3 | Destination 01:00:00:00:00:00 | FC_MULTICAST — the group bit alone |
| 4 | Destination FE:FF:FF:FF:FF:FF | individual — I/G clear, so a miss, not multicast |
| 5 | Individual destination, lookup missed | FC_UNKNOWN_UNICAST |
| 6 | Individual destination, lookup timed out | FC_UNRESOLVED — Chapter 12.3's deadline |
| 7 | Any flood | exactly one cause counter moves |
| 8 | Broadcast or multicast | permanent_flood asserts |
| 9 | Unknown unicast | permanent_flood low — it will stop |
| 10 | Station transmits after an unknown-unicast flood | the next frame to it forwards, flood ends |
Replication ledger
| # | Scenario | Expected |
|---|---|---|
| 11 | Flood mask with 23 bits, 24-port switch | exactly 23 emissions, one per port |
| 12 | Any flood | no copy to the ingress port |
| 13 | Flood with 8 of 24 ports forwarding | 7 emissions; last_fanout = 7 |
| 14 | Flood with only the ingress port forwarding | zero emissions, mask empty |
| 15 | Egress applies backpressure mid-drain | the pending bit stays set; the copy is retried |
| 16 | Egress backpressure held for 100 cycles | no copy lost, busy stays high |
| 17 | Second flood_start while draining | ignored — the first flood's copies are not discarded |
| 18 | Any flood | emit_handle identical on every copy — one stored frame |
| 19 | Last copy accepted | release_buffer asserts exactly once |
| 20 | Buffer released with bits pending | v_missing_copy increments |
| 21 | Two copies emitted to one port | v_duplicate_copy increments |
| 22 | A copy to a port not in the mask | v_extra_copy increments |
| 23 | Mask containing the ingress bit | v_ingress_copy increments at flood_start |
| 24 | Mask containing a non-forwarding port | v_blocked_copy increments |
| 25 | 1000 floods, healthy design | conformant high throughout |
Amplification and cost
| # | Scenario | Expected |
|---|---|---|
| 26 | Pure unicast forwarding, no floods | amplification_x100 = 100 |
| 27 | One port flooding at line rate, 24-port switch | 23 Gb/s egress demand — 47.9% of aggregate |
| 28 | Same, minimum-length frames | 34.23 M emissions/s — 95.9% of the frame budget |
| 29 | 8-port switch, same conditions | 7 copies, 43.8% of aggregate |
| 30 | 48-port switch, same conditions | 47 copies, 49.0% — the share converges on one half |
| 31 | 50% floods by frame count | amplification_high asserts |
| 32 | flood_share_pct under mixed traffic | accounts for the excess above 100 |
Receiver model
| # | Scenario | Expected |
|---|---|---|
| 33 | Unknown-unicast copy at a non-addressee | RX_DROP_EXACT — zero host cost |
| 34 | Unicast copy at its addressee | RX_WANTED |
| 35 | Broadcast copy at any station | RX_TO_CPU — always, no exceptions |
| 36 | Multicast, group not in an enabled bucket | RX_DROP_HASH |
| 37 | Multicast, group hashing to an enabled bucket | RX_TO_CPU — a false accept |
| 38 | Station with 1 joined group, 64 buckets | ~1.6% of unwanted groups reach the CPU |
| 39 | Station with 16 joined groups | ~22.3% |
| 40 | Station with 256 joined groups | ~98.2% — the filter has effectively stopped working |
| 41 | 1.4881 M broadcasts/s at 5 µs each | 744% of a core — 7.4 cores per station |
Rate limiting
| # | Scenario | Expected |
|---|---|---|
| 42 | Broadcast at 2× the cap on port 3 | suppressed, worst_port = 3, storm_cause = FC_BROADCAST |
| 43 | Broadcast at the cap exactly | all permitted |
| 44 | Broadcast storm on port 3, ARP on port 7 | port 7 unaffected — per-port budget |
| 45 | Unknown-unicast burst while broadcast is near its cap | broadcast still permitted — per-cause budgets |
| 46 | A suppressed broadcast | discarded, never queued or retried |
| 47 | Storm ends | storm_active deasserts on the next second tick |
| 48 | Cap at 1% of line rate, 24 ports | a station still receives 342 262/s — 1.7 cores |
| 49 | Cap needed for a 1000/s station budget | 43.5 pps per port — 0.00292% of line rate |
Multicast waste and diagnosis
| # | Scenario | Expected |
|---|---|---|
| 50 | Group with 3 members, 24-port switch | 23 copies, 20 wasted — waste_pct = 87 |
| 51 | Group with 23 members | waste_pct = 0, membership_would_help low |
| 52 | Floods at 30%, mostly unknown unicast | learning_suspect — Chapter 12.2's remedies apply |
| 53 | Floods at 30%, mostly broadcast | traffic_suspect — a statement about the stations |
| 54 | Floods above 25% of decisions | hub_like asserts |
| 55 | Domain of 92 stations across 4 switches | each receives 455 broadcasts/s; the uplink carries all 460 |
| 56 | 100 000 broadcasts/s of 64-octet frames | link utilisation 6.7%, hosts at 0.5 cores |
| 57 | Same rate with 1518-octet frames | link utilisation 123% — impossible, so the storm is small frames |
| 58 | amplification_x100 on a pure-unicast run | exactly 100 |
| 59 | A flood on a switch where every port is blocked but the ingress | mask empty, zero emissions, release_buffer still asserts |
| 60 | Storm control disabled, line-rate broadcast | hub_like, amplification_high, and Section 8's host collapse |
| 61 | Multicast group with 1 member on a 24-port switch | waste_pct = 95.7 |
| 62 | Multicast group with 12 members | waste_pct = 47.8, membership_would_help high |
| 63 | Every station joined the group | waste_pct = 0, membership_would_help low |
19. Debugging Flooding
Every row produces valid links, zero frame errors, and a switch forwarding correctly. The third column is what separates them, and the first two columns are frequently confused with each other.
| Symptom | Likely cause | The observable that decides it |
|---|---|---|
| Everything slow for everyone at once | broadcast storm | c_broadcast rate, storm_active, storm_cause = FC_BROADCAST |
| Everything slow, hosts also unresponsive | broadcast storm, hosts CPU-bound | Section 8's arithmetic — the hosts are the casualty, not the network |
| Flood rate high, table not full | many unlearned stations | learning_suspect, and Chapter 12.2 §18's remedies |
Flood rate high, c_broadcast dominant | the stations are chatty | traffic_suspect — no switch change will help |
Flood rate high, c_unresolved non-zero | the lookup engine is over budget | Chapter 12.3 §11 — not a flooding problem at all |
| Egress bytes far exceed ingress bytes | replication, which is expected | amplification_x100 and flood_share_pct say how much and why |
| One host misses some multicast, others fine | its hash filter is saturated | its joined-group count against Section 7's table |
| One host has high CPU, network is fine | broadcast reaching it, correctly | c_broadcast — the switch is working; the domain is too big |
| A multicast group's members stop receiving | a group source address was learned | Chapter 12.2 §5 — LE_GROUP_SRC, and the lookup now hits |
| Storm control engaged, no idea why | the alert lacks a cause | storm_cause and worst_port — three unrelated investigations |
| ARP intermittently failing after enabling storm control | the cap is below legitimate broadcast | suppressed_by_port non-zero on a port with no storm |
| One station never receives floods | a copy is being lost in the drain | v_missing_copy — Section 18's directed test |
| Link utilisation normal, every host at high CPU | broadcast storm of minimum-length frames | c_broadcast per port — utilisation reads 6.7% at 0.5 cores per host |
| Broadcast load rises when a switch is added | the domain grew, not the traffic | N is topological — Section 10's uplink row |
| A flood emits nothing at all | every port except the ingress is blocked | last_fanout = 0 with release_buffer still asserting — correct |
20. Common Misconceptions
1 — "Flooding is a failure."
The wrong model: a switch that floods has failed to do its job, and the flood rate should be driven toward zero.
What it costs: it produces Chapter 12.2's catalogue of bad remedies — hold entries longer, refresh on lookup, never expire — every one of which trades wasted bandwidth for silent loss. It also misreads broadcast entirely, since broadcast flooding is not a failure to know anything.
The corrected model: flooding is the switch declining to guess, and it is correct under all five of Chapter 12.3 §2's reasons for a miss. The flood is also the mechanism that ends the flood — it reaches the station, the station replies, and the reply teaches the switch. A high flood rate is a cost to understand, not an error to eliminate.
2 — "A broadcast storm is a bandwidth problem."
The wrong model: too many frames, the links saturate, everything slows down.
What it costs: the diagnosis goes to utilisation graphs, which read 6.7% on a gigabit link at a storm rate that is already costing every host half a core. And the remedy goes to capacity — a faster link, a bigger switch — neither of which touches the actual constraint, since the cost is per frame at the receiver's CPU and a 10 Gb/s link does not make an interrupt handler faster.
The corrected model: a broadcast storm is a CPU problem at every station simultaneously. At 1.4881 M frames/s and 5 µs each it is 7.4 cores per host, which is why management agents, monitoring and remote access fail before the network does — and why the instruments an operator would reach for are among the first casualties.
3 — "A bigger MAC table would reduce flooding."
The wrong model: floods come from table misses, so more table means fewer floods.
What it costs: money, and no improvement. The table affects exactly one of the three causes — and it is the self-limiting one that ends on the station's next frame anyway. Multicast can never be in the table (Chapter 12.2 §4 forbids learning a group address, because nothing transmits from one) and broadcast never consults it (Chapter 12.3 §10 skips the lookup entirely).
The corrected model: check c_unknown_unicast against c_broadcast and c_multicast before buying anything. A bigger table helps only if the first dominates, and if it does, Chapter 12.2 §12's rate limiter and ageing interval are cheaper places to look first.
4 — "Unknown-unicast flooding and broadcast flooding cost the same."
The wrong model: both produce N−1 copies, so both cost N−1 copies.
What it costs: the two are conflated in a single "flood" counter, and an operator cannot tell a transient learning condition from a permanent load on every host. Inside the switch they genuinely are identical — same mask, same replication, same egress bandwidth — which is why the mistake is so easy to make.
The corrected model: they diverge completely at the receiver. An unknown-unicast copy dies in a 48-bit hardware comparator at zero host cost. A broadcast copy always reaches the CPU. Same bandwidth, and a difference of several orders of magnitude in the cost that actually limits the network.
5 — "Storm control solves broadcast."
The wrong model: enable storm control at the default and the domain is protected.
What it costs: false confidence. Section 12's arithmetic: at the common 1% default on a 24-port switch, a station still receives 342 262 broadcasts per second — 1.7 cores. And the cap that would actually meet a 1000/s budget is 43.5 frames per second per port, 0.00292% of line rate, roughly 34 times finer than a 0.1% setting and below the granularity most switches expose.
The corrected model: storm control is a catastrophe limiter. It turns a collapsed network into a badly degraded one and cannot hold a domain inside its budget. The design tool is segmentation — reducing N in (N − 1) × B — which discards nothing and is Chapter 13.1's subject.
6 — "Multicast flooding is a switch defect."
The wrong model: the switch should know which ports have members and send only there.
What it costs: engineering effort aimed at the wrong layer, and — if anyone succeeds in making the switch guess — a switch that sometimes fails to deliver to a member, which is the single error multicast cannot tolerate.
The corrected model: the switch flooding a multicast frame is simultaneously wasteful and correct, and the missing input is structural rather than an oversight. Chapter 12.2's learning works because a frame's source address proves where its sender is; a group address is never a source, so there is no frame to learn from and no evidence to gather. Membership is a receiver-side decision with no wire representation at this layer, and the mechanism that fixes it necessarily lives above it.
21. Interview Reasoning
Q1 — "One port on a 24-port gigabit switch is flooding at line rate. Quantify what that does to the switch."
Reason through it. Each flooded frame is replicated to 23 ports. In bits: 1 Gb/s in becomes 23 Gb/s of egress demand — 47.9% of the switch's 48 Gb/s aggregate, from one port. In frames, at minimum length: 1.4881 M × 23 = 34.23 M emissions per second, against Chapter 12.1 §12's total budget of 35.71 Mpps — 95.9% of the switch's entire frame-handling capacity. The strong answer adds that a bigger switch does not help, because the amplification scales with the port count: (N−1) ÷ 2N converges on one half regardless of N, so a 48-port switch suffers the same proportional damage and inflicts it on twice as many stations.
Q2 — "Why does a broadcast storm take down hosts before it takes down the network?"
Reason through it. Because the two costs scale differently and land in different places. Bandwidth degrades gradually — Section 3's amplification takes up to half the aggregate, so other traffic slows but continues. Host CPU collapses abruptly, because a network card cannot filter broadcast: the address means everyone, and discarding it would break ARP and DHCP. Every broadcast reaches every host's CPU, at roughly 5 µs of interrupt and stack traversal each, so 100 000 frames per second is half a core and line rate is 7.4 cores. The strong answer notes the operational consequence: management agents, monitoring and remote sessions fail first, so the operator loses their instruments before they lose connectivity — and a utilisation graph reads 6.7% throughout, because 100 000 minimum-length frames is only 67 Mb/s.
Q3 — "Derive the maximum number of stations in a broadcast domain."
Reason through it. Every station receives every other station's broadcasts, so with N stations each emitting B per second, each receives (N − 1) × B. Set a per-station CPU budget in frames per second and solve: N ≤ budget ÷ B + 1. At a typical B = 5/s and a budget of 1000/s, N ≤ 201 — which is exactly the "few hundred stations per domain" rule of thumb, derived rather than recited. The strong answer names what the limit does not depend on: not the switch's capacity, not the link speed, not the table size — because broadcast never consults the table and the constraint is interrupt rate at the receiver. The only lever is N, which is why segmentation and not capacity is the remedy.
Q4 — "Your switch floods a multicast frame to 23 ports when the group has 3 members. Is that a bug?"
Reason through it. No — it is simultaneously wasteful and correct, and the resolution is not a better switch. The switch has no way to know which ports lead to members. Chapter 12.2's learning works because a frame's source address proves where its sender is; nothing ever transmits from a group address, so there is no frame carrying evidence of membership and the learning mechanism has nothing to work with. The strong answer quantifies the waste — 20 of 23 copies, 87% — and then states the asymmetry that resolves the tension: the cost of being wastefully right is bounded bandwidth; the cost of being efficiently wrong is a member that silently stops receiving, which is the one error multicast cannot tolerate. The fix requires membership information, which is a receiver-side state with no wire representation at this layer, so it necessarily comes from a protocol above it.
Q5 — "Why can't you write an assertion that a flooded copy was useful?"
Reason through it. Because Ethernet has no return path. A frame is transmitted and the transmitter learns nothing — no acknowledgement, no receiver report, no completion, no negative acknowledgement. Chapter 1.2's collision detection was the last mechanism by which a transmitter learned anything about its own transmission's fate, and Chapter 1.5 removed it. So the property's subject is unobservable by construction of the protocol, not by an omission in the design — it would remain unobservable on perfect silicon. The strong answer names what to assert instead: the ledger, which proves every masked port got exactly one copy and none was lost in the drain, and the local arithmetic — amplification, flood share, cause composition, rate limits — all computed from the switch's own ports. And it names the honest way to get what the property wanted: a measurement campaign correlating captures against known membership, which is not an assertion.
Q6 — "A monitoring system reports a gigabit link at 6.7% utilisation. Every host on that segment is at 50% CPU. Reconcile these."
Reason through it. The two are consistent, and the reconciliation is that broadcast costs are per frame while utilisation is per bit. 6.7% of a gigabit link, if the frames are minimum-length, is 0.067 × 1e9 ÷ 672 = about 100 000 frames per second — and at roughly 5 µs of interrupt and stack traversal each, that is 0.5 of a core on every station in the domain. The strong answer names why no filter helps: a network card cannot discard broadcast, because the address means everyone and discarding it would break ARP and DHCP, so every one of those 100 000 frames reaches every host's CPU. It then names the instrument that would have shown it — broadcast frames per second per port, a counter incremented on one address comparison — and observes that it is absent from most switch statistics pages precisely because every other port metric is a volume.
22. Understanding Check
23. What's Next
This chapter priced the flood. It has said nothing about how often one happens, and that is a property of the structure Chapter 12.3 §11 called the only scarce resource in the decision.
Every unknown-unicast flood in this chapter began as a table miss, and the table's capacity, its collision behaviour and its ageing sweep decide how many misses a network produces. Chapter 12.5 — The MAC Table builds it: CAM against hashed set-associative, what a hash collision costs, why a table can refuse an insert while it is 50% empty, and where Chapter 12.3's 28 ns deadline is actually met or missed.
Then Chapter 12.6 — Store-and-Forward against Cut-Through returns to Chapter 12.1 §10's choice with the full six-gate decision in view, because a cut-through switch must run every gate before the frame has finished arriving — and one of the six needs the last four octets.
And Chapter 13.1 — Why VLANs Exist takes up the conclusion Section 12 reached and could not act on. The only variable in (N − 1) × B a designer controls is N, and reducing it is what segmentation does — at a cost this chapter has not yet counted.
Continue learning
Related tutorials
- Related topic
Unicast, Multicast and Broadcast
A group address is not looked up, it is tested — and the test is one-sided. Hardware can say definitely-not-subscribed cheaply and can never say definitely-subscribed, so the filter's job is to be cheap and to be wrong only in the direction that costs work rather than the direction that loses frames.
- Related topic
Address Filtering in Hardware
The receive filter is not a predicate but a priority-ordered set of accept reasons that overlap by design — and a station that cannot say which reason took a frame cannot explain the traffic it is receiving.
- Related topic
The Shared-Medium Problem
Why several independent transmitters on one medium is a distributed timing problem, not a formatting problem. Propagation delay makes every station's view of the medium stale, so two locally correct decisions can still collide — and that is the constraint the Ethernet MAC was built around.
- Related topic
CSMA/CD, Collision Domains and Slot Time
Slot time is the parameter the whole half-duplex MAC hangs on: it bounds medium acquisition, bounds a collision fragment, and is the retransmission quantum. Deriving it from round-trip propagation plus jam is what fixes Ethernet's minimum frame size — a timing constant wearing a frame-format costume.
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.
