Ethernet · Module 21
The Ethernet Error Taxonomy
Seven of the twelve reachable frame shapes belong to more than one error class, so exclusivity comes from a priority rule — and the two obvious rules disagree on exactly half the space.
An error class is a name for a set of frames. The useful question about a taxonomy is whether those sets overlap — and in Ethernet they do, on seven of the twelve reachable frame shapes.
| Count | |
|---|---|
| reachable frame shapes | 12 |
| shapes satisfying two or more error predicates | 7 — 58.3% |
| shapes with no name in the standard | 1 |
| shapes where the two obvious classification orders disagree | 6 — exactly half |
| error classes that are frame properties | 6 |
| error classes that are not | 6 |
Row four is the chapter's finding. The standard classifies size first and error second; the implementation everybody writes classifies error first and size second. Both are reasonable, both are self-consistent, and they disagree on half the reachable shapes — every one of which is a runt or a giant that also carries an error.
Which is precisely the traffic Chapter 20.5 §6 and Chapter 19.7 §7 spend their whole arithmetic on.
Row three is the smaller and stranger result. A frame that is not a whole number of octets and whose check sequence nonetheless passes is reachable, is not a good frame, and has no class in RMON at all — it is not an alignment error, because that requires the check to fail.
1. Scope, and Twelve Classes in Two Halves
Scope: every error class an Ethernet receive path can report, what each one physically is, and what decides which class a frame that qualifies for several ends up in.
Not in scope: localising a fault. Chapter 21.1 is the method and this chapter is the table it reads. Nor the individual diagnoses — Chapter 21.3 takes the first class and separates its causes.
The twelve classes, in two halves, and the halves are not symmetric.
| Class | Is it a property of the frame? | Required by RMON? | |
|---|---|---|---|
| 1 | FCS error | yes | yes |
| 2 | alignment error | yes | yes |
| 3 | undersize | yes | yes |
| 4 | fragment | yes | yes |
| 5 | oversize | yes | yes |
| 6 | jabber | yes | yes |
| 7 | symbol error | no — a PHY event | no |
| 8 | filtered | no — a table lookup | no |
| 9 | VLAN discard | no — port configuration | no |
| 10 | buffer drop | no — a system state | no |
| 11 | mid-frame truncation | no — a system state | no |
| 12 | descriptor error | no — a host state | no |
Six and six, and the line between them is exactly the line the RMON standard draws. Classes 1 to 6 are predicates over the octets of a frame — a receiver holding a frame can evaluate every one of them with no other information. Classes 7 to 12 need something else: a PHY's internal state, a filter table, a port's VLAN configuration, a buffer's occupancy, a descriptor ring.
And that is why Chapter 21.1 §4's class G holds five members. The five sites it could not separate — the filter, VLAN membership, the FIFO, the DMA and the driver — are exactly the sites whose error classes are numbers 8 to 12 of this table, and none of those has a required counter, because none of them is a property of a frame.
| Chapter 21.1's view | This chapter's view | |
|---|---|---|
| class G exists because | five sites share a counter signature | five classes are not frame properties |
| the fix is | four optional counters | the same four, named as classes |
Two chapters, one finding, arrived at from opposite directions — and the agreement is the evidence that the taxonomy is drawn in the right place.
2. The Predicates, and Why They Are Not a Partition
The first six classes are defined by three predicates over a received frame.
| Predicate | Definition | From |
|---|---|---|
A | the frame is not a whole number of octets | Chapter 7.3 §3 |
B | the frame check sequence does not match | Chapter 7.3 §3 |
S | the wire length: in range, below 64, or above the MTU | Chapter 5.1 |
S has three values and the other two have two, so there are twelve reachable combinations — and "reachable" is the right word: every one of the twelve can be produced on a wire. Chapter 20.5 §2's five injection actions reach all of them.
Now the observation the whole chapter rests on.
| Shapes | Share | |
|---|---|---|
| total reachable | 12 | 100% |
| satisfying exactly one predicate | 4 | 33.3% |
| satisfying two or more | 7 | 58.3% |
| satisfying none — a good frame | 1 | 8.3% |
Seven of twelve reachable frames qualify for more than one error class at once. A 40-octet frame with a bad check sequence is short and failing; a 40-octet frame that is also not whole octets is short, failing and misaligned. The classes overlap on more than half the space.
Chapter 7.3 §3 resolves this in one sentence — a frame is counted once — and the sentence does not say which one.
| The standard provides | What it does not provide | |
|---|---|---|
| the predicates | precise, normative | — |
| the exclusivity | "counted once" | — |
| the tie-break | implied by the class definitions | never stated as a rule |
So the taxonomy is a partition only after a priority rule is applied, and the priority rule has to be reconstructed from how RMON words the definitions of fragments and jabbers. Section 4 reconstructs it, Section 6 argues it is the right one, and Section 7 implements the other one to show what it costs.
The error classes are not a partition of the frame space. They are a partition of the frame space quotiented by a priority rule — and the rule is the part nobody writes down.
3. RTL 1 — The Taxonomy Package and the Predicate Extractor
// ---------------------------------------------------------------------
// taxon_pkg -- the twelve classes, the three predicates, and the two
// priority orders.
//
// The package's whole design argument is that the PREDICATES and the
// CLASSES are different types. A frame has predicates; a counter has a
// class; and the function between them is a priority rule that the
// standard implies and never states.
// ---------------------------------------------------------------------
package taxon_pkg;
// Section 1's twelve. The first six are frame properties; the last
// six need state the frame does not carry.
typedef enum logic [3:0] {
CLS_GOOD = 4'd0,
CLS_FCS = 4'd1,
CLS_ALIGN = 4'd2,
CLS_UNDERSIZE = 4'd3,
CLS_FRAGMENT = 4'd4,
CLS_OVERSIZE = 4'd5,
CLS_JABBER = 4'd6,
CLS_SYMBOL = 4'd7,
CLS_FILTERED = 4'd8,
CLS_VLAN = 4'd9,
CLS_DROP = 4'd10,
CLS_TRUNCATED = 4'd11,
CLS_DESCRIPTOR= 4'd12,
CLS_UNNAMED = 4'd15 // Section 8 -- reachable and nameless
} err_class_e;
typedef enum logic [1:0] {
SZ_IN_RANGE = 2'd0,
SZ_SHORT = 2'd1, // below 64 octets
SZ_LONG = 2'd2 // above the MTU
} size_class_e;
// A frame's three predicates. Nothing here is a class.
typedef struct packed {
logic not_whole_octets; // A
logic fcs_mismatch; // B
size_class_e size; // S
} predicates_t;
// Section 4: the two orders. The standard's is size-major.
typedef enum logic {
ORDER_SIZE_MAJOR = 1'b0,
ORDER_ERROR_MAJOR = 1'b1
} priority_order_e;
// Section 10: which classes a frame's own octets can decide.
localparam logic [12:0] FRAME_DECIDABLE = 13'b0000000_1111111;
function automatic logic is_frame_property(err_class_e c);
return (c <= CLS_JABBER);
endfunction
// Section 2: how many of A, B and S-not-in-range a frame satisfies.
function automatic int n_predicates(predicates_t p);
return int'(p.not_whole_octets) + int'(p.fcs_mismatch)
+ int'(p.size != SZ_IN_RANGE);
endfunction
endpackageClassification: a package whose central claim is a type distinction — predicates_t is not err_class_e.
What it teaches: that a frame does not have an error class; it has predicates, and a class is assigned. Writing those as one type — an enum a comparator drives directly — makes the priority rule invisible and unarguable, which is how two implementations end up disagreeing about a runt with a bad check sequence without either one containing a line anybody would call wrong.
And it teaches that CLS_UNNAMED has to exist. Section 8's shape — not whole octets, check sequence passes, length in range — is reachable and has no class in RMON. A classifier whose enum has no slot for it will put it somewhere, and wherever it puts it will be wrong.
Deliberately simplified: size_class_e has three values where Chapter 19.7 §2's histogram has seven, so this package cannot express "oversize but within the jumbo limit." The MTU is not a parameter here. FRAME_DECIDABLE is a literal where it should follow from is_frame_property. And there is no representation of a frame that is simultaneously filtered and malformed, which is real and which Section 11 argues is a different kind of overlap.
Production implication: the enum's numbering is load-bearing. is_frame_property is a single comparison because classes 0 to 6 are contiguous, and that one comparison is what Chapter 21.1 §4's class G reduces to. An enum reordered for readability turns a comparator into a lookup table — and more importantly turns a fact about the taxonomy into a coincidence about the encoding.
// ---------------------------------------------------------------------
// predicate_extractor -- evaluate A, B and S over a received frame.
// It assigns NO class. That separation is the point.
// ---------------------------------------------------------------------
module predicate_extractor
import taxon_pkg::*;
#(
parameter int MTU_OCTETS = 1518
)(
input logic clk,
input logic rst_n,
input logic frame_end,
input logic [13:0] wire_len, // octets, including the FCS
input logic [2:0] trailing_bits, // 0 to 7 -- Chapter 7.3 Section 7
input logic fcs_ok,
output predicates_t pred,
output logic pred_valid,
output logic [3:0] n_pred,
output logic [31:0] c_frames,
output logic [31:0] c_multi_predicate
);
always_comb begin
pred.not_whole_octets = (trailing_bits != 3'd0);
pred.fcs_mismatch = !fcs_ok;
pred.size = (wire_len < 14'd64) ? SZ_SHORT
: (wire_len > 14'(MTU_OCTETS)) ? SZ_LONG
: SZ_IN_RANGE;
n_pred = 4'(n_predicates(pred));
pred_valid = frame_end;
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_frames <= '0; c_multi_predicate <= '0;
end else if (frame_end) begin
c_frames <= c_frames + 32'd1;
// Section 2: 58.3% of reachable shapes land here, and every one
// of them needs a priority rule to become a class.
if (n_pred > 4'd1) c_multi_predicate <= c_multi_predicate + 32'd1;
end
end
endmoduleClassification: a pure predicate evaluator, and the only block in the chapter that touches a frame.
What it teaches: that trailing_bits is where the alignment predicate actually comes from, and that it is an interface signal rather than a computation. Chapter 7.3 §7's detector carries a bit count through the receive pipeline — Chapter 20.5 §19 costed it at 48 shipped flops — and a MAC without it cannot evaluate predicate A at all, so two of the six frame classes are unreachable for it.
And it teaches that c_multi_predicate is the number that tells a team whether the priority rule matters on their traffic. On conformant traffic it is zero and the two orders of Section 4 agree everywhere. On the error-injected traffic of Chapter 20.5 it is the majority of frames, and there the two orders disagree on half.
Deliberately simplified: MTU_OCTETS is a parameter with no VLAN awareness, so a double-tagged frame at 1 526 octets is classified SZ_LONG on a port that should permit it — Chapter 13.2's problem appearing as a misclassification. wire_len includes the check sequence, which is the right convention for RMON and the opposite of Chapter 19.4's pre-FCS length. And fcs_ok is taken as given where Section 17 argues that its own correctness is one of the twelve classes.
Production implication: the block deliberately has no err_class_e port, and a reviewer should check that before anything else. A predicate extractor that emits a class has made the priority decision inside a block whose name says it does not — and that is exactly where the disagreement in Section 4 hides in real designs: not in a classifier anybody reviews, but in a comparator chain somebody wrote in the order the conditions occurred to them.
4. Twelve Reachable Frame Shapes, and Six Disagreements
Enumerate all twelve and classify each one twice: the standard's way and the obvious way.
A | B | Size | RMON — size-major | Error-major | Agree? |
|---|---|---|---|---|---|
| 0 | 0 | in range | good | good | yes |
| 0 | 0 | short | undersize | undersize | yes |
| 0 | 0 | long | oversize | oversize | yes |
| 0 | 1 | in range | FCS error | FCS error | yes |
| 0 | 1 | short | fragment | FCS error | NO |
| 0 | 1 | long | jabber | FCS error | NO |
| 1 | 0 | in range | no name | no name | yes |
| 1 | 0 | short | fragment | undersize | NO |
| 1 | 0 | long | jabber | oversize | NO |
| 1 | 1 | in range | alignment | alignment | yes |
| 1 | 1 | short | fragment | alignment | NO |
| 1 | 1 | long | jabber | alignment | NO |
Six of twelve — exactly half — and the six have a shape.
| Rows that disagree | |
|---|---|
| all six are | short or long |
| all six carry | at least one error predicate |
| none of the six is | a frame that is only one thing |
The two orders agree on every frame that qualifies for exactly one class and disagree on every frame that is out of range and broken. Which is a clean statement and is worth reading twice: the disagreement is not at the margins, it is the whole overlap.
RMON's rule, reconstructed from the wording of its own definitions:
fragmentsare packets shorter than 64 octets with a bad check sequence or a bad alignment.jabbersare packets longer than the maximum with a bad check sequence or a bad alignment.
That "or" is the priority rule. It folds rows 5, 8 and 11 into fragments and rows 6, 9 and 12 into jabbers, because the size test is applied first and the error type only decides whether the frame is undersize or fragment. An implementation that tests A && B before testing the size reaches alignment first and never gets to the size test, which is rows 11 and 12.
And the consequence, in the counters a debugger reads:
| Traffic | Size-major reports | Error-major reports |
|---|---|---|
| 1 000 runts with bad FCS | fragments = 1 000 | fcs_errors = 1 000 |
| 1 000 dribbled runts | fragments = 1 000 | alignment = 1 000 |
| 1 000 dribbled giants | jabbers = 1 000 | alignment = 1 000 |
Row two is the one that matters for Chapter 21.1's method. The same physical fault moves a different counter on two conformant-looking implementations — and Chapter 21.1 §2's site table is keyed on which counter moves. A method built on one order gives a wrong answer on hardware built to the other, silently, and the only evidence is that the fragment counter is zero when it should not be.
And it is worth saying exactly how the method goes wrong, because the failure is specific rather than general.
Chapter 21.1 §2's table says c_crc_errors moves for five sites: the channel, the PHY lanes, the xMII, the parser and the check engine. That row is written for a size-major classifier. On an error-major one, every short or long broken frame also moves c_crc_errors or c_alignment_errors — so the counter's site set silently widens.
| Counter | Sites, size-major | Sites, error-major |
|---|---|---|
c_crc_errors | 5 | 5, and it also absorbs the fragments |
c_alignment_errors | 3 | 3, and it also absorbs dribbled runts |
c_fragments | 1, the medium | 0 — the counter is dead |
c_jabbers | 1, the far end | 0 — the counter is dead |
Two of the twelve counters become permanently zero, and a permanently zero counter is read as evidence. Chapter 21.1 §10 showed that a zero eliminates the sites its counter moves for — so on an error-major chip, c_fragments at zero "eliminates" the cut-cable site on every single investigation, forever, and the site is never a candidate.
| The cost, stated exactly | |
|---|---|
| what the method believes | the medium is not cutting frames |
| why | c_fragments is zero |
| why it is zero | the classifier can never assign it |
| how often this is checked | never, on any platform |
That is the chapter's most consequential finding and it is a two-register test to rule out — Section 16's fragments_never_move, or one injected frame.
5. RTL 2 — The Size-Major Classifier
// ---------------------------------------------------------------------
// size_major_classifier -- the standard's order, written so the
// priority is visible as a structure rather than as the order of some
// if-else arms.
//
// Size decides the FAMILY; the error predicates decide WHICH member of
// that family. Writing it as two steps is the whole difference from
// Section 7's version.
// ---------------------------------------------------------------------
module size_major_classifier
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
input logic pred_valid,
input predicates_t pred,
output err_class_e cls,
output logic cls_valid,
output logic priority_applied, // more than one class qualified
output logic [31:0] c_class [13]
);
logic broken;
// STEP ONE: is the frame broken at all? Both error predicates fold
// into one bit here, which is exactly what makes this size-major.
assign broken = pred.not_whole_octets || pred.fcs_mismatch;
// STEP TWO: the size chooses the family.
always_comb begin
case (pred.size)
SZ_SHORT: cls = broken ? CLS_FRAGMENT : CLS_UNDERSIZE;
SZ_LONG: cls = broken ? CLS_JABBER : CLS_OVERSIZE;
default: cls = pred.fcs_mismatch
? (pred.not_whole_octets ? CLS_ALIGN : CLS_FCS)
: (pred.not_whole_octets ? CLS_UNNAMED : CLS_GOOD);
endcase
// Section 2: the frame qualified for more than one class and a
// rule chose. Reporting THAT is what makes the rule auditable.
priority_applied = (n_predicates(pred) > 1);
end
assign cls_valid = pred_valid;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) for (int i = 0; i < 13; i++) c_class[i] <= '0;
else if (pred_valid && cls != CLS_UNNAMED)
c_class[cls] <= c_class[cls] + 32'd1;
end
endmoduleClassification: a two-step classifier whose first step is a disjunction, and the disjunction is the standard's rule.
What it teaches: that broken is where the priority lives. Folding A and B into one bit before looking at the size is size-major classification — it discards the distinction between an alignment error and a check-sequence error precisely for the frames whose size already put them out of range. RMON's "bad FCS or bad alignment" in the definitions of fragments and jabbers is that ||, and it is the only place the standard's priority is expressible as one operator.
And it teaches that priority_applied belongs in the design rather than in a comment. A frame that qualified for one class needed no rule; a frame that qualified for three had a rule applied to it, and a counter of how often that happened is the difference between a classifier whose behaviour can be audited and one whose behaviour must be inferred from its counters.
Deliberately simplified: c_class skips CLS_UNNAMED entirely, so Section 8's shape is silently uncounted — which is what real hardware does and is the reason nobody knows how often it occurs. The default arm handles SZ_IN_RANGE and any illegal encoding identically. And the counters are a flat array of 13 × 32 bits with no shadow bank, so Chapter 19.7 §15's common reading instant is unavailable.
Production implication: priority_applied against c_frames is the number that decides whether an implementation's classification order is observable on a given link. Zero means the traffic never produced a multi-predicate frame and the two orders of Section 4 are indistinguishable here. Anything else means the link is exercising the rule, and the counters a debugger reads depend on which rule this chip implements — which is not in any datasheet.
6. Why the Standard Classifies Size First
The size-major rule looks arbitrary until you ask what each class is for. Then it is the only sensible order.
| Class | The operator is being told | The action |
|---|---|---|
| FCS error | something is corrupting frames | look at the channel and the logic |
| alignment error | something is corrupting frames at bit granularity | look at the PHY and the interface |
| undersize | a partner is emitting illegal frames | look at the far end's transmitter |
| fragment | a collision or a truncation happened | look for a collision domain or a cut |
| oversize | an MTU mismatch | look at configuration |
| jabber | a partner is stuck transmitting | isolate the port |
The six actions divide into two groups and the division is by size, not by error. fragment and jabber are operational categories — they mean a device is misbehaving or the medium was cut — and they are actionable without knowing whether the check sequence or the alignment failed. FCS error and alignment error are diagnostic categories about a link that is otherwise working.
So the rule is: if the frame's size already says the link is broken, the error type adds nothing.
| Size-major | Error-major | |
|---|---|---|
| a 40-octet frame with a bad FCS | fragment — a cut or a collision | fcs_error — a marginal channel |
| the operator's action | look for a physical break | look for a marginal channel |
| which is right? | a 40-octet frame is not a marginal channel symptom | — |
Row three is the argument. A marginal channel corrupts frames; it does not shorten them to 40 octets, because the length is determined by the transmitter and by the framing, not by the bits the channel damaged. A 40-octet frame with a bad check sequence is a frame that was cut — a collision in a shared-medium deployment, a truncation, a partner that stopped transmitting. Reporting it as an FCS error sends the operator to the wrong place, and that is the whole reason the standard folds it into fragments.
And the same argument, in the other direction, explains the one shape nobody named.
| Reasoning | |
|---|---|
| a short frame with any error is a fragment | the size is the diagnosis |
| an in-range frame with both errors is an alignment error | the finer error is the diagnosis |
an in-range frame with A and no B | neither rule applies — Section 8 |
The taxonomy is coherent on eleven of twelve shapes and silent on the twelfth, which is a better record than most standards manage and is still one shape more than a classifier can leave undefined.
And the argument generalises into a rule worth carrying to any taxonomy, not just this one.
When two classes overlap, the one whose action is coarser should win — because a coarse action is correct whenever a fine one would have been, and the reverse is not true.
| Coarse | Fine | |
|---|---|---|
fragment | look for a physical break | — |
fcs_error | — | look for a marginal channel |
| if the frame really was cut | correct | wrong |
| if the channel really was marginal | finds it eventually | correct |
Row three and row four are asymmetric and that asymmetry is the whole argument. An operator sent to look for a physical break on a marginally-noisy link will find the noise while looking; an operator sent to look for a marginal channel on a cut cable will measure a channel that is not the problem. Classifying to the coarser action is the error-tolerant choice, and the standard made it.
7. RTL 3 — The Error-Major Classifier, For Contrast
// ---------------------------------------------------------------------
// error_major_classifier -- the version everybody writes, shown here
// to make the disagreement concrete rather than hypothetical.
//
// Nothing in this block is careless. Every arm is correct in
// isolation. The ORDER is the whole defect, and the order is the one
// the conditions occur to you in.
// ---------------------------------------------------------------------
module error_major_classifier
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
input logic pred_valid,
input predicates_t pred,
output err_class_e cls,
output logic cls_valid
);
// The natural order: check the most specific error first, then the
// less specific one, then the size. Every one of these tests is
// exactly Chapter 7.3's definition. The result is wrong on six of
// the twelve shapes.
always_comb begin
if (pred.not_whole_octets && pred.fcs_mismatch) cls = CLS_ALIGN;
else if (pred.fcs_mismatch) cls = CLS_FCS;
else if (pred.size == SZ_SHORT) cls = CLS_UNDERSIZE;
else if (pred.size == SZ_LONG) cls = CLS_OVERSIZE;
else if (pred.not_whole_octets) cls = CLS_UNNAMED;
else cls = CLS_GOOD;
end
assign cls_valid = pred_valid;
endmoduleClassification: a correct-looking priority chain, included as a control.
What it teaches: that the defect is not in any line. Every arm implements a normative definition faithfully: an alignment error is a frame that is not whole octets and whose check fails; an FCS error is a frame whose check fails. The block is six correct statements in the wrong order, and no line-level review finds it — which is why Section 13's comparator exists and Section 20's property does not.
And it teaches how the order arises. The conditions are written in decreasing specificity, which is the standard advice for a priority chain and produces error-major classification every time. Size is the least specific test, so it goes last; and going last is exactly what makes fragments and jabbers unreachable.
Deliberately simplified: the block is the contrast, so it has no counters and no priority_applied output. CLS_FRAGMENT and CLS_JABBER are never assigned by it at all, which is the finding stated as an unreachable enum value — a linting tool would flag it, and in a real design the two values are reached by a different code path and the unreachability is hidden.
Production implication: the tell is a counter that is permanently zero. A MAC whose fragments counter has never moved in a year of operation is either on a perfect link or is classifying error-major — and the second is far more likely, because Chapter 20.5 §6 showed runts are 0.08% of the coverage cross and are also the commonest symptom of a genuinely broken partner. The check is one injected frame: send a 40-octet frame with a stomped check sequence and see which counter moves.
8. The One Shape With No Name
Row seven of Section 4's table: not a whole number of octets, check sequence passes, length in range. It is reachable and RMON has no class for it.
| Value | |
|---|---|
predicate A | true — trailing bits are non-zero |
predicate B | false — the check sequence matched |
| size | in range |
| RMON class | none |
| 802.3 class | none — alignment error requires B |
The definition is explicit and the exclusion is deliberate. Chapter 7.3 §3 defines an alignment error as a frame that is both not an integral number of octets and failing its check sequence. A frame satisfying only the first is not an alignment error by the letter of the definition — and there is no other class it could be.
How does it arise? Three ways, and two of them are real.
| Cause | Real? | Rate |
|---|---|---|
| chance — the check passes over the octet prefix | yes, and rare | about 1 in 4.29 billion |
| a transmitter that appends a valid FCS to a misaligned frame | yes | whatever the fault's rate is |
| a receiver whose bit count is wrong | yes | a design fault |
Row one is the birthday-free version and the number is exact. Chapter 19.4's check value is 32 bits, so a randomly damaged frame passes its check with probability 2^-32 — one in 4 294 967 296. At 100 Gb/s and minimum size, 148.9 million frames per second, a link running entirely corrupted traffic produces one every 28.8 seconds. On realistic error rates it is a once-a-year event and it is not zero.
Row three is the one that matters, because it is a design fault that presents as nothing. A receiver whose trailing-bit count is stuck non-zero reports predicate A on every frame. With the check sequence passing — because the frames are fine — every frame lands in the unnamed shape. Size-major classification sends them to CLS_UNNAMED and the counter array does not count it; error-major sends them to CLS_UNNAMED too. So:
| What the operator sees | |
|---|---|
frames_in | moving normally |
| every error counter | zero |
frames_out | short by every frame |
| Chapter 21.1's mask | class G — five candidates |
A stuck bit-count register in the parser presents as Chapter 21.1 §4's class G, which is the five sites past the MAC — and the fault is at site 5, which class G does not contain. The method converges on a set that does not include the answer, and the reason is one unnamed shape in a taxonomy.
An unclassifiable frame is not counted, and an uncounted frame is invisible to every method built on counters. The one shape the taxonomy does not name is the one shape a debugger cannot see.
The fix is a counter, and it costs about 99 flops — Chapter 19.7 §19's per-counter figure. c_unclassified is not in any standard, it would have caught this fault in one register read, and it is the thirteenth entry Section 12's table argues for.
9. RTL 4 — The Symbol-Error Bridge
// ---------------------------------------------------------------------
// symbol_error_bridge -- class 7, and the only class in the taxonomy
// that is reported by a different chip.
//
// A symbol error is a PCS event: a code group the line coding cannot
// decode. It has no frame, no length and no check sequence, and it
// reaches the MAC as a control character in the middle of a frame or
// not at all. The bridge's job is to relate it to a frame WITHOUT
// pretending it is a property of one.
// ---------------------------------------------------------------------
module symbol_error_bridge
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
// From the PHY, over the xMII control encoding.
input logic rx_error, // Chapter 4.2's RX_ER
input logic in_frame,
// From the PHY's own registers, over MDIO -- Chapter 4.5.
input logic mdio_valid,
input logic [15:0] phy_symbol_errors,
output logic [31:0] c_symbol_in_frame,
output logic [31:0] c_symbol_in_gap,
output logic [15:0] phy_count_shadow,
output logic counts_disagree,
output logic phy_unreadable
);
logic [15:0] last_phy;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_symbol_in_frame <= '0; c_symbol_in_gap <= '0;
last_phy <= '0; phy_count_shadow <= '0;
counts_disagree <= 1'b0; phy_unreadable <= 1'b1;
end else begin
// A symbol error INSIDE a frame corrupts it and will also show
// up as an FCS or alignment error. One physical event, two
// classes -- Section 10's overlap, across a chip boundary.
if (rx_error && in_frame) c_symbol_in_frame <= c_symbol_in_frame + 32'd1;
// A symbol error in the gap corrupts nothing and is invisible
// to every frame-based class. It is still a failing channel.
if (rx_error && !in_frame) c_symbol_in_gap <= c_symbol_in_gap + 32'd1;
if (mdio_valid) begin
phy_unreadable <= 1'b0;
phy_count_shadow <= phy_symbol_errors;
last_phy <= phy_symbol_errors;
// The MAC sees RX_ER; the PHY counts internally. They should
// agree, and a disagreement is an xMII problem rather than a
// channel one -- Chapter 21.1's sites 2 and 3, separated.
if ((phy_symbol_errors - last_phy) !=
16'(c_symbol_in_frame + c_symbol_in_gap))
counts_disagree <= 1'b1;
end
end
end
endmoduleClassification: a cross-chip reconciliation, and the only block here whose two inputs come from different silicon.
What it teaches: that a symbol error is not a frame class and relating it to one is the bridge's entire difficulty. A corrupted code group inside a frame will also fail the check sequence, so one physical event produces a symbol error and an FCS error — the same frame counted in two places, which is not double counting because the two counters are in two chips.
And it teaches why c_symbol_in_gap is the more interesting of the two counters. A symbol error between frames corrupts nothing — there is no frame to damage — so it is invisible to every one of the six frame classes. A channel degrading steadily produces gap symbol errors long before it produces frame errors, which makes this the earliest available warning in the whole taxonomy and the one no RMON counter carries.
Deliberately simplified: counts_disagree subtracts 16-bit values with no wrap handling, so a PHY counter that wraps between MDIO reads produces a false disagreement. The comparison assumes the two counts cover the same window, which MDIO's millisecond-scale access makes untrue. And phy_unreadable starts asserted and clears on the first valid read, so a PHY that is never polled reports as unreadable forever — which is correct and is the common configuration.
Production implication: counts_disagree is the one observation that separates Chapter 21.1 §4's class B. The PHY says it saw N bad code groups; the MAC says it saw M assertions of RX_ER. Agreement means the channel is damaging symbols and the interface is faithfully reporting them — site 1 or 2. Disagreement means the interface between them is losing or inventing events — site 3. Two sites, one comparison, and it needs an MDIO transaction that most monitoring systems do not make.
10. The Six Classes That Are Not Frame Properties
Classes 7 to 12 need information the frame does not carry, and the six are not alike. Three are about the frame's destination, two about the system's state, and one about a different chip.
| Class | Needs | Decided by | Is the frame damaged? |
|---|---|---|---|
| symbol error | the PHY's decoder state | another chip | maybe — Section 9 |
| filtered | the address table | configuration | no |
| VLAN discard | port membership | configuration | no |
| buffer drop | FIFO occupancy | the system's timing | no |
| mid-frame truncation | FIFO occupancy mid-frame | the system's timing | yes — by the receiver |
| descriptor error | the host's ring state | software | no |
Row five is the odd one and it is worth naming why. A mid-frame truncation is the only class in the whole taxonomy where the receiver damages the frame. Chapter 19.5 §14's overflow guard cuts a frame that was perfect on the wire — so downstream of the FIFO it looks exactly like a fragment, and the distinction between "the partner sent a broken frame" and "we broke a good frame" is a counter in the MAC and nothing else.
| A fragment | A truncation | |
|---|---|---|
| damaged by | the far end or the medium | this receiver |
| looks like, downstream | short, bad check | short, bad check |
| the action | investigate the partner or the cable | investigate the memory system |
| the distinguishing evidence | — | c_fifo_drops — optional |
Four of the six do not damage anything, which is Chapter 21.1 §4's finding in this chapter's vocabulary: they are legal discards, no standard requires a counter for them, and the five sites that produce them are indistinguishable without one.
And the six split differently again on a question a debugger actually asks — can I reproduce it?
| Class | Reproducible from the wire? |
|---|---|
| symbol error | only with a channel impairment |
| filtered | yes — one wrong destination address |
| VLAN discard | yes — one non-member tag |
| buffer drop | NO — Chapter 20.5 §12 |
| mid-frame truncation | NO — the same proof |
| descriptor error | NO — a host action |
And one more split, because it decides whether a class can appear in a switch's statistics at all.
Four of the six are per-port facts and two are per-system.
| Class | Scope | Consequence |
|---|---|---|
| symbol error | per port, per lane | a lane-level counter is finer than the class |
| filtered | per port | countable per port |
| VLAN discard | per port, per VLAN | a per-port counter aggregates the VLANs away |
| buffer drop | per port on an input-buffered switch; per system on a shared-memory one | the counter may not be attributable |
| truncation | the same | the same |
| descriptor error | per queue, per host | not a port property at all |
Rows four and five are why buffer-drop counters are so often absent or aggregated. On a shared-memory switch the buffer that overflowed does not belong to a port; it belongs to the fabric, and attributing the drop to the ingress port is a design choice rather than a fact. A counter that cannot be attributed is a counter nobody builds — which is the mechanism behind Chapter 21.1 §4's class G being unresolvable on exactly the hardware where frames most often go missing.
Three of the six cannot be produced from the wire at any rate, in any pattern, with any frame sizes. Chapter 20.5 §12 proved it for the buffer: the arrival rate is the line rate and Chapter 19.5 §4's drain rate exceeds it, so the FIFO empties whatever arrives. The consumer has to stop — which is a memory-system action — and that is why Chapter 21.1 §11's probe cannot prove those counters exist.
11. RTL 5 — The Non-Error Discriminator
// ---------------------------------------------------------------------
// nonerror_discriminator -- classes 8 to 12, which a frame cannot
// decide about itself.
//
// The block exists to make one distinction loud: a frame that was
// DISCARDED and a frame that was DAMAGED are different events, and
// only one of them is an error. Conflating them is how a filtered
// frame ends up in a drop counter and a real fault stays hidden.
// ---------------------------------------------------------------------
module nonerror_discriminator
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_end,
input predicates_t pred,
// The five pieces of state a frame does not carry.
input logic addr_miss, // Chapter 7.4's filter
input logic vlan_nonmember, // Chapter 13.2's port mode
input logic fifo_full, // Chapter 19.5 Section 14
input logic truncated, // the same, mid-frame
input logic no_descriptor, // Chapter 19.6
output err_class_e cls,
output logic cls_valid,
output logic is_legal_discard,
output logic damaged_by_us,
output logic [31:0] c_legal_discards,
output logic [31:0] c_self_inflicted
);
// Order matters here too, and it is the ORDER OF THE PIPELINE --
// a frame meets the filter before the FIFO and the FIFO before the
// descriptor ring. Chapter 21.1 Section 17: the earliest fault
// masks every later one.
always_comb begin
if (addr_miss) cls = CLS_FILTERED;
else if (vlan_nonmember) cls = CLS_VLAN;
else if (truncated) cls = CLS_TRUNCATED;
else if (fifo_full) cls = CLS_DROP;
else if (no_descriptor) cls = CLS_DESCRIPTOR;
else cls = CLS_GOOD;
// Four of the five discard an intact frame on a rule. One of them
// cuts a frame that was perfect. That is the distinction.
is_legal_discard = (cls == CLS_FILTERED) || (cls == CLS_VLAN) ||
(cls == CLS_DROP) || (cls == CLS_DESCRIPTOR);
damaged_by_us = (cls == CLS_TRUNCATED);
end
assign cls_valid = frame_end;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_legal_discards <= '0; c_self_inflicted <= '0;
end else if (frame_end) begin
if (is_legal_discard) c_legal_discards <= c_legal_discards + 32'd1;
if (damaged_by_us) c_self_inflicted <= c_self_inflicted + 32'd1;
end
end
endmoduleClassification: a second priority chain, whose order is a physical fact rather than a standard's wording.
What it teaches: that this chain's order is not a choice. Section 5's order was a rule somebody wrote; this one is the order the frame meets the blocks in. A frame rejected by Chapter 7.4's filter never reaches the FIFO, so addr_miss genuinely does take precedence — and Chapter 21.1 §17's masking result is this if-else chain seen from the outside.
And it teaches that damaged_by_us deserves a counter of its own. Four of the five classes discard an intact frame; CLS_TRUNCATED cuts a frame that arrived perfectly. Downstream of the FIFO the two are indistinguishable — both are short frames with bad check sequences — so c_self_inflicted is the only evidence that separates "our partner is broken" from "our memory system is slow."
Deliberately simplified: the five state inputs are assumed to be aligned to frame_end, which truncated cannot be: a truncation is decided mid-frame and the frame's end never arrives. fifo_full and truncated are separate inputs where Chapter 19.5 §14's guard derives both from one condition. And a frame can be filtered and malformed — the chain reports CLS_FILTERED and loses the malformation, which is correct for a discard counter and wrong for a channel diagnosis.
Production implication: the last simplification is a real design decision and it should be made deliberately. A malformed frame addressed to another port is discarded twice over, and which counter moves decides what a monitoring system concludes about the link. Counting it as filtered hides a channel fault; counting it as an FCS error inflates the error rate with traffic that was never ours. Chapter 7.3 §3's rule settles it — validity is checked before the address, so the error class wins — and an implementation that filters first will under-report channel errors in exact proportion to how much foreign traffic the port sees.
12. The Taxonomy, Complete
Thirteen classes — the twelve of Section 1 plus the one Section 8 found — with what each physically is, what causes it, and which Chapter 21.1 sites it points at.
| # | Class | Physically | Points at sites |
|---|---|---|---|
| 1 | FCS error | the octets changed between transmitter and check | 1, 2, 3, 5, 6 |
| 2 | alignment error | the frame ended part-way through an octet | 1, 2, 3 |
| 3 | undersize | a conformant-looking frame below 64 octets | the far end's transmitter |
| 4 | fragment | a frame that was cut, or a collision remnant | 1, the medium, a partner |
| 5 | oversize | a frame above this port's MTU | configuration, 5, 7 |
| 6 | jabber | a partner transmitting continuously and badly | the far end |
| 7 | symbol error | a code group the line coding cannot decode | 1, 2, 3 |
| 8 | filtered | not addressed here | 8 — and it is not a fault |
| 9 | VLAN discard | this port is not in that VLAN | 9 — not a fault |
| 10 | buffer drop | the consumer stalled past the FIFO's depth | 10, 11, 12 |
| 11 | mid-frame truncation | the same, mid-frame | 10, 11, 12 |
| 12 | descriptor error | no buffer was posted | 11, 12 |
| 13 | unclassified | Section 8's shape | 5 — and nothing counts it |
Three rows deserve a sentence because they are routinely misread.
Row 3 — undersize is not an error in the frame. A 40-octet frame with a valid check sequence is a well-formed frame of an illegal length. Nothing corrupted it; a transmitter emitted it. Chapter 5.6's padding rule exists precisely to make this impossible, so an undersize frame means a transmitter that is not padding — a software MAC, a malformed injection, or a partner that is broken in a very specific way.
Row 4 — a fragment is a historical name for two different things. In a shared-medium deployment it is a collision remnant, which is what the name comes from. On a switched full-duplex link there are no collisions, so a fragment there is a frame that was cut — by a cable that parted mid-frame, by a partner that stopped transmitting, or by Chapter 19.5 §14's truncation at the far end's receiver being looped back. Same counter, two eras, and one of the two causes no longer exists.
Row 6 — jabber is the one class whose action is to disconnect. A partner transmitting continuously denies the medium to everything else in a shared domain and floods a buffer in a switched one. The standard's response is isolation rather than diagnosis, which makes it the only class in the table where the correct first action is not to investigate.
And the taxonomy, arranged by what a counter reading tells you about where to look:
| Counter moving | Look at | Certainty |
|---|---|---|
| alignment errors only | the PHY or the xMII | 3 sites |
| FCS errors only | the check engine — nothing else moved | 1 site |
| FCS and alignment | the channel or the PHY | 3 sites |
| fragments | a cut, a collision, or a truncation | several, and one is local |
| jabbers | the far end | 1 site, effectively |
| undersize with no errors | the far end's transmitter | 1 site |
| oversize with no errors | an MTU mismatch | configuration |
| nothing | classes 8 to 12 — Chapter 21.1's class G | 5 sites |
And the same table, read backwards — from a suspected site to the counter that would confirm it — because that is how an investigation that has a hypothesis actually proceeds.
| Suspected site | Confirming evidence | Disconfirming evidence |
|---|---|---|
| the channel | FCS and alignment both climbing | alignment at zero |
| the PHY's lanes | the PHY's own symbol counter agrees | counts_disagree |
| the xMII | counts_disagree — Section 9 | the two counts agree |
| the parser's offsets | undersize and oversize together, frames_in healthy | frames_in short |
| the check engine | FCS alone, everything else zero | any alignment error |
| the validity bounds | undersize and oversize on conformant sizes | the sizes are genuinely illegal |
| a partner not padding | undersize with valid check sequences | the check sequences fail |
| an MTU mismatch | oversize clustered at 1 522 or 1 526 | the sizes are spread |
| a cut cable | fragments — if the classifier can assign them | Section 4's error-major case |
| our own FIFO | c_self_inflicted — if implemented | it is not implemented |
Rows four and five are the pair worth memorising because they are adjacent and opposite. Both present as errors on frames the wire delivered intact; the parser moves the size counters and the check engine moves only the check-sequence one — and frames_in separates them from the three physical sites in one read.
Rows nine and ten are the two whose confirming evidence may not exist. A cut cable's fragments are unassignable on an error-major chip, and our own truncation needs a counter no standard requires — so two of the ten hypotheses in this table cannot be confirmed on a default platform, which is Section 19's argument in the vocabulary of an investigation rather than of a budget.
Row two is the reading everybody gets wrong and it is the most precise row in the table. FCS errors climbing with alignment errors at zero, on a link whose frames_in is healthy, is not a channel symptom: a damaged channel corrupts bits and bit corruption does not preserve octet boundaries reliably, so a real channel fault moves both counters. FCS errors alone, on frames the port counted correctly, points at the check engine itself — Chapter 19.4 §14's equivalence checker — and that is Chapter 21.3's whole subject.
13. RTL 6 — The Classification Comparator
// ---------------------------------------------------------------------
// classification_comparator -- run both orders on every frame and
// count where they differ.
//
// This is the only way to find out which order a piece of silicon
// implements, because no datasheet states it. In a testbench it is a
// checker; against real hardware it is a one-frame experiment.
// ---------------------------------------------------------------------
module classification_comparator
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
input logic cls_valid,
input predicates_t pred,
input err_class_e cls_size_major,
input err_class_e cls_error_major,
input err_class_e cls_observed, // what the DUT reported
output logic orders_differ,
output logic [31:0] c_differ,
output logic [31:0] c_agree,
output priority_order_e inferred_order,
output logic order_known,
output logic dut_matches_neither
);
logic [31:0] votes_size, votes_error;
assign orders_differ = (cls_size_major != cls_error_major);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
c_differ <= '0; c_agree <= '0;
votes_size <= '0; votes_error <= '0;
dut_matches_neither <= 1'b0;
end else if (cls_valid) begin
if (orders_differ) begin
c_differ <= c_differ + 32'd1;
// Only frames where the two orders DISAGREE carry information
// about which order the DUT implements. The other 6 of 12
// shapes are votes for both.
if (cls_observed == cls_size_major) votes_size <= votes_size + 32'd1;
else if (cls_observed == cls_error_major) votes_error <= votes_error + 32'd1;
else dut_matches_neither <= 1'b1;
end else begin
c_agree <= c_agree + 32'd1;
end
end
end
assign inferred_order = (votes_size >= votes_error)
? ORDER_SIZE_MAJOR : ORDER_ERROR_MAJOR;
// One discriminating frame is enough. Section 21's directed test
// sends exactly one.
assign order_known = ((votes_size + votes_error) > 32'd0);
endmoduleClassification: an inference block — it determines a fact about a design by watching it.
What it teaches: that only six of the twelve shapes carry any information about the classification order, and a test that never produces one cannot determine which order the hardware implements, however many frames it sends. Conformant traffic is entirely in the agreeing half; so is every frame with exactly one error. The discriminating frames are short-or-long and broken — which is Chapter 20.5's injector and nothing else.
And it teaches that dut_matches_neither is a real outcome rather than a defensive default. A third order exists — error-major but with alignment last, which some implementations use — and it agrees with neither of the two modelled here on rows 8 and 9. A comparator that assumed two possibilities would silently record a vote for whichever it matched by accident.
Deliberately simplified: the vote is a simple majority with no tie-break beyond the >=, so a design with zero discriminating frames reports ORDER_SIZE_MAJOR with no evidence — order_known exists to say so and nothing enforces reading it. The block compares three classifications where a real one compares a class against a set of counters, because hardware does not report a class; it increments a counter. And c_agree counts frames that carry no information, which is 6 of 12 shapes and almost 100% of real traffic.
Production implication: this block is the reason Chapter 21.1's method needs a per-platform fact recorded beside the counter inventory. The site table in Chapter 21.1 §2 is keyed on which counter moves, and rows 5, 8 and 11 of Section 4 move a different counter on the two orders. One injected frame — 40 octets, check sequence stomped — settles it permanently, and the answer belongs in the same one-minute datasheet check that Chapter 21.1 §12's step 0 already performs.
14. What a Taxonomy Must Never Do
Six prohibitions. Three are about the classes and three are about the counters, and all six produce a number that looks like a measurement.
| Never | Because | |
|---|---|---|
| 1 | treat the classes as disjoint predicates | 7 of 12 shapes satisfy two or more |
| 2 | leave a reachable shape unclassified | Section 8 — an uncounted frame is invisible |
| 3 | assume the priority order without testing it | Section 13 — no datasheet states it |
| 4 | count a discard and a damage in one counter | Section 11 — different actions |
| 5 | count a truncation as a fragment | one is the partner's fault and one is ours |
| 6 | report a symbol error as a frame class | Section 9 — the gap errors have no frame |
Row two is the one with the highest cost-to-effort ratio in the chapter. One counter — c_unclassified, about 99 flops — turns a silent stuck-bit-count fault from a class-G mystery into a one-read diagnosis. No standard requires it; nothing prevents it; and it is the thirteenth row of Section 12's table because this chapter added it.
Row five is the one that misdirects an entire team. A mid-frame truncation and a fragment are byte-identical downstream of the FIFO — short, bad check sequence — and the actions are opposite: investigate the partner, or investigate our own memory system. Chapter 19.6 §22's likely_buffer_not_cable exists for exactly this and is optional.
And the two that look like pedantry and are not:
| Why it is a prohibition | |
|---|---|
| row one | a $onehot over predicates fails on 58.3% of reachable shapes |
| row six | a gap symbol error is the earliest channel warning available |
Both mistake a property of the counting for a property of the frame, which is what all six have in common and is exactly the shape of Section 20's rejected property.
And three things a taxonomy must do, stated positively, because six prohibitions are hard to act on.
| Always | Costs | |
|---|---|---|
| 1 | make the priority rule a visible structure, not an if-else order | nothing — Section 5 against Section 7 |
| 2 | count every reachable shape, including the unnamed one | ~99 flops |
| 3 | report priority_applied beside the class counts | one bit |
Row one is free and is the one that would have prevented every disagreement in this chapter. Section 5's classifier folds A and B into broken and then switches on the size; Section 7's chain tests conditions in decreasing specificity. They differ by one intermediate signal, and the one with the intermediate signal cannot be written in the wrong order without somebody noticing, because broken has a name and a meaning and a reviewer can ask what it is for.
Row three is the one that makes the whole taxonomy auditable from outside. A frame that qualified for one class needed no rule; a frame that qualified for three had a rule applied to it. A counter of how often that happened turns "which order does this chip use" from a question requiring injected frames into a question answerable from a status page — and it is one bit and one counter.
A taxonomy is not a list of names. It is a list of names plus a rule for the overlap, and only the first half ever gets written down.
15. RTL 7 — Taxonomy Telemetry
// ---------------------------------------------------------------------
// taxon_telemetry -- what the class counts mean, which is a different
// question from what they are.
//
// Three groups: SHAPE says what kind of traffic this is; ORDER says
// whether the classification rule is observable here; and GAPS says
// what the taxonomy could not name.
// ---------------------------------------------------------------------
module taxon_telemetry
import taxon_pkg::*;
(
input logic clk,
input logic rst_n,
input logic [31:0] c_frames,
input logic [31:0] c_class [13],
input logic [31:0] c_multi_predicate,
input logic [31:0] c_unclassified,
input logic [31:0] c_differ,
input logic [31:0] c_legal_discards,
input logic [31:0] c_self_inflicted,
input logic order_known,
input priority_order_e inferred_order,
// Shape.
output logic [15:0] error_rate_ppm,
output logic [15:0] multi_predicate_pct,
output logic [2:0] dominant_class,
// Order.
output logic order_observable,
output logic order_is_size_major,
// Gaps.
output logic [15:0] unclassified_ppm,
output logic taxonomy_incomplete,
output logic discards_exceed_errors
);
logic [31:0] total_errors;
always_comb begin
total_errors = c_class[CLS_FCS] + c_class[CLS_ALIGN]
+ c_class[CLS_UNDERSIZE]+ c_class[CLS_FRAGMENT]
+ c_class[CLS_OVERSIZE] + c_class[CLS_JABBER];
error_rate_ppm = (c_frames == 32'd0) ? 16'd0
: 16'((total_errors * 32'd1000000) / c_frames);
multi_predicate_pct = (total_errors == 32'd0) ? 16'd0
: 16'((c_multi_predicate * 32'd100) / total_errors);
// Section 4: the order is only observable on frames where the two
// rules disagree, and those are short-or-long AND broken.
order_observable = (c_differ > 32'd0) && order_known;
order_is_size_major = (inferred_order == ORDER_SIZE_MAJOR);
unclassified_ppm = (c_frames == 32'd0) ? 16'd0
: 16'((c_unclassified * 32'd1000000) / c_frames);
// Section 8: a shape the standard does not name, occurring at a
// rate far above the 1-in-4.29-billion chance figure, is a design
// fault -- almost always a stuck trailing-bit count.
taxonomy_incomplete = (c_unclassified > 32'd1000);
// Section 10: four of the six non-frame classes are not errors,
// so a link discarding far more than it errors is CONFIGURED
// that way rather than broken.
discards_exceed_errors = (c_legal_discards > (total_errors << 4));
end
// Which frame class dominates. It is the fastest summary there is.
always_comb begin
dominant_class = 3'd0;
for (int i = 1; i <= 6; i++)
if (c_class[i] > c_class[dominant_class]) dominant_class = 3'(i);
end
endmoduleClassification: a reporting block whose three groups answer three different people's questions.
What it teaches: that order_observable has to be reported beside every class count. If it is false, the link produced no frame on which the two classification rules differ — so the counts are the same under either rule and a method built on one of them is safe here. If it is true, the counts depend on which rule this chip implements, and Chapter 21.1 §2's site table has to be selected accordingly.
And it teaches that discards_exceed_errors is a good reading. Four of the six non-frame classes are legal discards — a port on a busy segment filters most of what it hears — so a link discarding sixteen times more than it errors is working exactly as configured. Reporting discards and errors in one "drops" number, which many systems do, makes a healthy port look like a failing one and hides a failing one inside a healthy count.
Deliberately simplified: total_errors sums six counters that the standard already guarantees are disjoint, so the sum is safe only if this design's classifier is one of Section 4's two — a third order could double-count. dominant_class is a linear scan with no tie-break. discards_exceed_errors uses a shift by four as "sixteen times", which is a threshold nobody derived. And unclassified_ppm is against all frames where Section 8's chance rate is against corrupted ones.
Production implication: unclassified_ppm is the output that turns Section 8's invisible fault into a one-read diagnosis. Chance produces one unnamed frame in 4.29 billion corrupted ones, so any sustained rate above a handful per billion is a design fault and is almost always a trailing-bit count stuck non-zero. The counter does not exist in any standard, costs about 99 flops, and is the difference between a fault that presents as Chapter 21.1's class G and one that names its own site.
16. RTL 8 — The Taxonomy Conformance Monitor
// ---------------------------------------------------------------------
// taxon_conformance_monitor -- six verdicts, and the first one fires
// on a design that is perfectly conformant to a different reading of
// the same standard.
// ---------------------------------------------------------------------
module taxon_conformance_monitor
import taxon_pkg::*;
#(
parameter priority_order_e EXPECTED_ORDER = ORDER_SIZE_MAJOR
)(
input logic clk,
input logic rst_n,
input logic cls_valid,
input predicates_t pred,
input err_class_e cls,
input logic order_known,
input priority_order_e inferred_order,
input logic [31:0] c_unclassified,
input logic [31:0] c_class [13],
input logic [31:0] c_frames,
input logic dut_matches_neither,
output logic order_unexpected,
output logic order_untested,
output logic unnamed_shape_seen,
output logic fragments_never_move,
output logic classes_overlap,
output logic third_order_detected,
output logic taxonomy_sound
);
// Section 4: not a fault, and it changes which counter every fault
// in Chapter 21.1's table moves.
assign order_unexpected = order_known && (inferred_order != EXPECTED_ORDER);
// A run that never produced a discriminating frame has not tested
// the thing this monitor exists to test.
assign order_untested = !order_known && (c_frames > 32'd100000);
assign unnamed_shape_seen = (c_unclassified > 32'd0);
// Section 7: an error-major classifier can never reach these two,
// so a permanently-zero pair is the tell.
assign fragments_never_move = (c_frames > 32'd1000000) &&
(c_class[CLS_FRAGMENT] == 32'd0) &&
(c_class[CLS_JABBER] == 32'd0);
// The counters must be disjoint even though the predicates are not.
assign classes_overlap = cls_valid && (n_predicates(pred) > 1) &&
(cls == CLS_GOOD);
assign third_order_detected = dut_matches_neither;
assign taxonomy_sound = !unnamed_shape_seen && !classes_overlap &&
!third_order_detected && !order_untested;
always_ff @(posedge clk) begin
if (rst_n && order_unexpected)
$display("[taxon] classifier is %s; Chapter 21.1's site table must match",
inferred_order == ORDER_SIZE_MAJOR ? "size-major" : "error-major");
end
endmoduleClassification: an auditor whose loudest verdict reports a legitimate design choice.
What it teaches: that order_unexpected is deliberately not a term of taxonomy_sound. A size-major and an error-major classifier are both defensible readings of Chapter 7.3 §3, and only one of them is RMON's. Calling the other unsound would be wrong; saying nothing would let a debugging method silently use the wrong table. So it is a loud report and not a failure — the same split Chapter 21.1 §16 made for instrumentation_limited.
And it teaches that fragments_never_move is the cheapest possible detection of an error-major classifier. Section 7 showed that chain never assigns CLS_FRAGMENT or CLS_JABBER at all — so on any link that has seen a million frames and some errors, both counters being exactly zero is close to proof. No injection, no comparator, no MDIO: two register reads.
Deliberately simplified: classes_overlap checks only the one impossible case — multiple predicates classified as CLS_GOOD — where a full check would need the counters, not the class. order_untested's threshold of 100 000 frames is a guess at how long before a discriminating frame should have appeared, and on a healthy link the honest answer is never. And EXPECTED_ORDER is a parameter whose default encodes this chapter's conclusion, which is a position rather than a fact.
Production implication: fragments_never_move belongs in a fleet inventory rather than in a debug session. Two register reads per port, once, and the result is a per-platform fact that never changes: this chip classifies size-major, that one error-major, and Chapter 21.1 §2's table must be selected to match. A monitoring system that records it alongside the optional-counter inventory has captured everything a method needs to know about a port before anybody is paged.
17. What Each Class Physically Means
A class name is a compressed causal claim, and six of the thirteen names are older than the technology they now describe.
| Class | The name claims | Still true? |
|---|---|---|
| FCS error | the check sequence failed | yes — it is a definition |
| alignment error | the frame is misaligned to octets | yes |
| undersize | too short | yes |
| fragment | a collision remnant | NO — no collisions on a switched link |
| oversize | too long | yes |
| jabber | a station jabbering on the medium | partly — no shared medium |
| symbol error | an undecodable code group | yes |
| filtered | not for us | yes |
| buffer drop | we ran out of room | yes |
| truncation | we cut a frame | yes, and the name hides who |
And two names that are precise and are still routinely misread, which is a different failure from an obsolete name.
| Name | Precise meaning | Common misreading |
|---|---|---|
| alignment error | not whole octets and the check failed | "the frame was misaligned somehow" |
| oversize | longer than this port's MTU | "longer than Ethernet allows" |
The second is the one that costs time. There is no universal Ethernet maximum: 1 518 untagged, 1 522 with one tag, 1 526 with two, and 9 018 on a jumbo-configured port. oversize is therefore a statement about configuration, not about conformance — and the frames it counts are very often perfectly legal frames on a port that was told a smaller number.
Rows four and six are the two whose names are archaeology. Both come from the shared-medium era: a fragment was what a collision left on the wire, and a jabber was a station that would not stop transmitting. On a modern point-to-point full-duplex link neither cause exists, and the counters are still there counting something else entirely.
fragments counted, 1985 | fragments counted, today | |
|---|---|---|
| cause | a collision | a cut cable, a stopped partner, a far-end truncation |
| normal rate | non-zero — collisions were expected | zero |
| what non-zero means | the segment is busy | something is physically wrong |
The change in meaning is total and the counter's name did not move with it. A monitoring system with a fragment threshold inherited from the 1990s will tolerate a rate that today indicates a parting cable — and an engineer who reads "fragment" as "collision" will look for a collision domain that has not existed on that link since it was installed.
And one name hides an important distinction rather than an obsolete one.
"Truncation" does not say who truncated. Chapter 19.5 §14's overflow guard cuts frames this receiver could not store; a cut cable produces frames the medium cut. Downstream they are identical — short, bad check sequence, reported as fragments — and the actions are opposite. Chapter 19.6 §22's likely_buffer_not_cable bit exists to separate them, it is optional, and Section 14's row five is the prohibition it enforces.
Six of the thirteen classes are precise. Two describe a mechanism that no longer exists. One hides the agent. And one has no name at all.
Now the physical layer, class by class, because "what it physically means" is a different answer at every rate and the classes do not say so.
| Class | At 1 Gb/s over copper | At 100 Gb/s over fibre |
|---|---|---|
| symbol error | a 4B/5B or 8B/10B code group that does not decode | a 64B/66B block with a bad sync header, or an FEC block beyond correction |
| alignment error | a nibble lost on the interface, or a PHY that padded | a lane deskew failure — Chapter 3.4 |
| FCS error | noise on a pair, or a crosstalk event | almost always a lane or an interface fault, not the fibre |
| fragment | a cut, or a legacy collision | a cut, or a far-end truncation |
| jabber | a stuck transmitter | a stuck transmitter, and rarer |
Row three is the one whose meaning inverts across the table and the reason is Chapter 3.7's forward error correction. A 100 Gb/s optical link corrects the channel's errors before the MAC ever sees them — so by the time a frame reaches the check sequence with damage, the damage happened after correction, which means inside the receiver. The same counter name points outward at 1 Gb/s and inward at 100.
| What a rising FCS count suggests | |
|---|---|
| 1 Gb/s copper, no FEC | the channel — look outward |
| 10 Gb/s, no FEC | the channel or the interface |
| 25 Gb/s and above, with FEC | past the FEC — look inward |
And that is not a subtlety; it is the difference between replacing a cable and reading Chapter 19.4's residue logic. A rising uncorrected-error rate on a FEC-protected link should have shown up in the FEC's own counters first — and those are in the PHY, over MDIO, which is the same place Section 9's symbol counters live and the same reason nobody reads them.
One more physical note, about the class nobody expects to be physical.
filtered is not physical and VLAN discard is not physical, but buffer drop and truncation are timing — and timing is as physical as a cable. Chapter 19.5 §14's overflow happens when a memory system stalls longer than the buffer's depth in time: 2.621 µs at 100 Gb/s for a 512-beat FIFO. A DRAM refresh, a PCIe completion stall, a noisy neighbour on the interconnect — all of them produce a class that looks like a frame error and is a memory-system event.
18. What the Taxonomy Assumes
Nine assumptions. Four are about the standard, three about the design and two about the traffic — and the one most often false is about the MTU.
| Assumption | From | If false | |
|---|---|---|---|
| 1 | the classes are counted disjointly | Chapter 7.3 §3 | a frame is counted twice; every rate inflates |
| 2 | the priority order is size-major | RMON's wording | Section 4 — six shapes move a different counter |
| 3 | the MTU is what this port was told | configuration | the oversize boundary moves; VLAN tags move it too |
| 4 | trailing bits are available | Chapter 7.3 §7 | predicate A is unevaluable; two classes vanish |
| 5 | the check sequence result is correct | Chapter 19.4 | class 1 is about the checker, not the frame |
| 6 | validity is checked before the address | Chapter 7.3 §3 | errors on foreign traffic are under-reported |
| 7 | a truncation is distinguishable from a fragment | an optional counter | Section 14's row five |
| 8 | a symbol error inside a frame also fails the check | line coding | the two counters stop agreeing |
| 9 | the traffic exercises the overlap | nothing at all | the order is untestable — Section 16 |
Row three is the assumption that is false most often and it is false by configuration rather than by fault. The oversize boundary is 1 518 octets untagged, 1 522 with one VLAN tag and 1 526 with two — Chapter 13.2's stacking — so a port configured for untagged frames classifies every single-tagged frame at 1 519 to 1 522 as oversize.
| Port configuration | The MTU boundary | A 1 522-octet tagged frame is |
|---|---|---|
| untagged | 1 518 | oversize |
| one tag permitted | 1 522 | good |
| two tags permitted | 1 526 | good |
Four octets of configuration, and a conformant frame becomes an error class. The symptom is characteristic and is worth knowing: oversize climbing with no check-sequence errors, on frames of exactly one or two sizes — which is not damage at all and is Chapter 21.1's "configuration" row of Section 12's table.
Row five is the one that inverts the whole taxonomy and it is Chapter 21.3's subject. Every class in the table is defined against the check sequence's verdict. If the checker itself is wrong, class 1 is not a statement about the frame — it is a statement about Chapter 19.4's residue comparison, and the frames were perfect. Section 12's row two is the tell: FCS errors alone, alignment at zero, frames_in healthy.
Row nine is the assumption nothing supports and it is why Section 16 has an order_untested verdict. The six discriminating shapes are short-or-long and broken, which conformant traffic never produces — so a link can run for a year without ever revealing which classification order its MAC implements. The test is Chapter 20.5's injector and one frame.
And three things deliberately not assumed:
| Not assumed | Why not |
|---|---|
| that a class implies a site | Chapter 21.1 §2 — FCS errors are five sites |
| that a discard is an error | four of the six non-frame classes are not |
| that the taxonomy is complete | Section 8 — one reachable shape has no name |
Row three is this chapter's only disagreement with the standard, and it is a small one: the standard is complete on the eleven shapes it names and silent on the twelfth, which is not an error in the standard and is an error in any classifier built literally from it.
And there is a tenth assumption that is not in the table because it is about a person rather than about a design.
Every class name in Section 12 is read by somebody as a causal claim, and four of the thirteen names make a claim that is wrong or incomplete on a modern link.
| Name | What it is read as | What it now means |
|---|---|---|
| fragment | a collision | a cut frame, from three causes |
| jabber | a station hogging a shared medium | a stuck transmitter at the far end |
| truncation | something cut the frame | we cut the frame |
| undersize | the frame was damaged | a transmitter did not pad |
The assumption is that the reader knows all four, and nothing in a counter's name or a MIB's description conveys any of it. A monitoring system inherits the names, a threshold inherits the 1990s, and an engineer inherits the reading — which is why Section 17 exists as a section rather than as a footnote.
| If the assumption is false | |
|---|---|
| fragment misread | a search for a collision domain that does not exist |
| truncation misread | the partner is blamed for our memory system |
| undersize misread | the channel is blamed for a transmitter |
All three send an investigation outward when the fault is inward or the reverse, which is the most expensive class of error in Module 21 and the only one no counter can fix.
19. The Cost, Accounted
A taxonomy is mostly comparators, so the cost is in the counters rather than in the logic.
| Block | Flops | Nature |
|---|---|---|
taxon_pkg | 0 | types and two tables |
predicate_extractor | ~64 | two counters |
size_major_classifier — 13 × 32 | ~416 | the class counters |
error_major_classifier | 0 | combinational, and a control |
symbol_error_bridge | ~112 | two counters and a shadow |
nonerror_discriminator | ~64 | two counters |
classification_comparator | ~129 | four counters and a flag |
taxon_telemetry | 0 | combinational |
taxon_conformance_monitor | 0 | combinational |
| total | ~785 flops |
Five of the nine blocks are pure combinational logic, which is what a taxonomy is: a set of comparisons. That is the structural point of the accounting and it is worth pausing on: the classification itself is free. Section 4's six disagreements, Section 8's unnamed shape and Section 13's order inference all cost zero flip-flops — they are questions about how comparators are arranged, not about how much state a design carries.
| Flops | Share of this chapter | |
|---|---|---|
| comparators and priority chains | 0 | 0% |
| counters | 785 | 100% |
Every decision in this chapter is free and every observation costs about 99 flops, which is a useful way to read the whole of Module 21: getting the taxonomy right is an argument, and being able to see it is a budget. All 785 flops are counters, and 416 of them are the class counts that Chapter 19.7 already builds — so the chapter's genuine addition to a MAC is about 369 flops.
And the addition breaks down into three decisions, each of which can be taken separately.
| Addition | Flops | Buys |
|---|---|---|
c_unclassified | ~99 | Section 8's invisible fault becomes one read |
c_self_inflicted | ~99 | Section 11 — our truncation vs their fragment |
c_symbol_in_gap | ~99 | Section 9 — the earliest channel warning there is |
| the comparator's four | ~129 | which order this chip implements |
| all four | ~426 | 3.0% of Chapter 19.7 §19's datapath |
Three per cent, and every one of the four answers a question that is currently unanswerable from outside the chip. Compare the module's other numbers:
| Flops | Share of the 14 166-flop datapath | |
|---|---|---|
| Chapter 21.1 §19's four optional counters | ~297 | 2.1% |
| this chapter's four | ~426 | 3.0% |
| both | ~723 | 5.1% |
| Chapter 21.1 §8's twelve boundary counters | ~1 188 | 8.4% |
Five point one per cent buys the difference between Chapter 21.1's class of five and a named site, plus the three distinctions this chapter found were missing. Thirteen point five per cent buys all of that plus a four-observation bisection, which is the information-theoretic floor.
And the comparison that frames all of it:
| Flops | Was it built? | |
|---|---|---|
| Chapter 19.4's correction barrels | 5 397 XOR terms | yes — correctness |
| Chapter 19.7's shadow bank | 992 | yes — a common reading instant |
| every counter in Module 21 | ~1 911 at most | no |
Module 21's entire wish list is smaller than Chapter 19.7's shadow bank plus a fifth of Chapter 19.4's barrels, and none of it is built — because correctness is a requirement and diagnosability is a preference. That is not an argument; it is the observation the two chapters of this module keep arriving at from different directions.
20. Properties Worth Asserting, and One Worth Refusing
Thirty-three properties and eight covers, in four groups: the predicates, the classification, the counters, and the classes that are not frame properties.
Group one — the predicates.
// A, B and S are facts about octets. Nothing here mentions a class.
p_pred_from_bits: assert property (@(posedge clk) disable iff (!rst_n)
pred_valid |-> (pred.not_whole_octets == (trailing_bits != 3'd0)));
p_pred_fcs: assert property (@(posedge clk) disable iff (!rst_n)
pred_valid |-> (pred.fcs_mismatch == !fcs_ok));
p_size_exclusive: assert property (@(posedge clk) disable iff (!rst_n)
pred_valid |-> !((pred.size == SZ_SHORT) && (pred.size == SZ_LONG)));
p_size_boundaries: assert property (@(posedge clk) disable iff (!rst_n)
(pred_valid && wire_len == 14'd64) |-> (pred.size == SZ_IN_RANGE));
p_mtu_boundary: assert property (@(posedge clk) disable iff (!rst_n)
(pred_valid && wire_len == 14'(MTU_OCTETS)) |->
(pred.size == SZ_IN_RANGE));
p_npred_range: assert property (@(posedge clk) disable iff (!rst_n)
pred_valid |-> (n_pred <= 4'd3));
p_multi_counted: assert property (@(posedge clk) disable iff (!rst_n)
(pred_valid && n_pred > 4'd1) |=>
(c_multi_predicate == $past(c_multi_predicate) + 32'd1));
p_extractor_pure: assert property (@(posedge clk) disable iff (!rst_n)
($stable(wire_len) && $stable(trailing_bits) && $stable(fcs_ok))
|-> $stable(pred));Group two — the classification. Every property here names an order; none of them asserts that the classes are disjoint as predicates, which is Section 20's refused property.
p_short_is_family: assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && pred.size == SZ_SHORT) |->
((cls == CLS_UNDERSIZE) || (cls == CLS_FRAGMENT)));
p_long_is_family: assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && pred.size == SZ_LONG) |->
((cls == CLS_OVERSIZE) || (cls == CLS_JABBER)));
p_fragment_broken: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_FRAGMENT) |->
(pred.not_whole_octets || pred.fcs_mismatch));
p_undersize_clean: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_UNDERSIZE) |->
(!pred.not_whole_octets && !pred.fcs_mismatch));
p_align_needs_both: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_ALIGN) |->
(pred.not_whole_octets && pred.fcs_mismatch));
p_unnamed_shape: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_UNNAMED) |->
(pred.not_whole_octets && !pred.fcs_mismatch &&
(pred.size == SZ_IN_RANGE)));
p_good_is_clean: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_GOOD) |-> (n_predicates(pred) == 0));
p_priority_flagged: assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && n_predicates(pred) > 1) |-> priority_applied);
p_orders_agree_1: assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && n_predicates(pred) <= 1) |->
(cls_size_major == cls_error_major));Group three — the counters.
p_one_counter: assert property (@(posedge clk) disable iff (!rst_n)
cls_valid |=> ($countones(counters_moved) <= 1));
p_counter_matches: assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && cls != CLS_UNNAMED) |=>
(c_class[$past(cls)] == $past(c_class[$past(cls)]) + 32'd1));
p_unnamed_uncounted:assert property (@(posedge clk) disable iff (!rst_n)
(cls_valid && cls == CLS_UNNAMED) |=> $stable(c_class));
p_sum_le_frames: assert property (@(posedge clk) disable iff (!rst_n)
(total_errors + c_class[CLS_GOOD]) <= c_frames);
p_rate_bounded: assert property (@(posedge clk) disable iff (!rst_n)
error_rate_ppm <= 16'd1000000);
p_order_needs_diff: assert property (@(posedge clk) disable iff (!rst_n)
order_known |-> (c_differ > 32'd0));
p_errmajor_no_frag: assert property (@(posedge clk) disable iff (!rst_n)
(inferred_order == ORDER_ERROR_MAJOR && order_known) |->
(c_class[CLS_FRAGMENT] == 32'd0));
p_agree_no_info: assert property (@(posedge clk) disable iff (!rst_n)
!orders_differ |=> ($stable(votes_size) && $stable(votes_error)));Group four — the classes that are not frame properties.
p_discard_not_error:assert property (@(posedge clk) disable iff (!rst_n)
is_legal_discard |-> !damaged_by_us);
p_trunc_is_damage: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_TRUNCATED) |-> damaged_by_us);
p_filter_first: assert property (@(posedge clk) disable iff (!rst_n)
(frame_end && addr_miss) |-> (cls == CLS_FILTERED));
p_symbol_no_frame: assert property (@(posedge clk) disable iff (!rst_n)
(rx_error && !in_frame) |=>
(c_symbol_in_gap == $past(c_symbol_in_gap) + 32'd1));
p_symbol_in_frame: assert property (@(posedge clk) disable iff (!rst_n)
(rx_error && in_frame) |-> ##[1:$] (pred_valid && pred.fcs_mismatch));
p_phy_read_needed: assert property (@(posedge clk) disable iff (!rst_n)
!mdio_valid |-> phy_unreadable);
p_wire_cannot_drop: assert property (@(posedge clk) disable iff (!rst_n)
(cls == CLS_DROP) |-> consumer_stalled);
p_class_partition: assert property (@(posedge clk) disable iff (!rst_n)
cls_valid |-> (is_frame_property(cls) != (cls >= CLS_SYMBOL)));
p_sound_excludes: assert property (@(posedge clk) disable iff (!rst_n)
taxonomy_sound |-> (!classes_overlap && !third_order_detected));And eight covers, because six of these are shapes a reviewer needs to have seen produced.
c_all_twelve: cover property (@(posedge clk) shapes_seen == 12'hFFF);
c_unnamed_shape: cover property (@(posedge clk) cls == CLS_UNNAMED);
c_fragment: cover property (@(posedge clk) cls == CLS_FRAGMENT);
c_jabber: cover property (@(posedge clk) cls == CLS_JABBER);
c_orders_differ: cover property (@(posedge clk) orders_differ);
c_gap_symbol: cover property (@(posedge clk) rx_error && !in_frame);
c_truncation: cover property (@(posedge clk) damaged_by_us);
c_three_preds: cover property (@(posedge clk) n_pred == 4'd3);21. Verification Scenarios
Fifty-eight scenarios for a classifier, plus a five-run directed test that determines a fact about silicon nobody documents.
The predicates — 10 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 1 | a 64-octet frame, good FCS, whole octets | no predicate; CLS_GOOD |
| 2 | exactly 64 octets | SZ_IN_RANGE — the boundary is inclusive |
| 3 | 63 octets | SZ_SHORT |
| 4 | exactly 1 518 octets | SZ_IN_RANGE |
| 5 | 1 519 octets | SZ_LONG |
| 6 | 1 522 octets, one VLAN tag, untagged port | SZ_LONG — Section 18's row three |
| 7 | the same on a tag-permitting port | SZ_IN_RANGE |
| 8 | 3 trailing bits | not_whole_octets |
| 9 | a 40-octet frame, 3 trailing bits, bad FCS | n_pred = 3 |
| 10 | the same, re-presented | pred is stable — the extractor is pure |
The twelve shapes — 12 scenarios.
| # | Scenario | Size-major expects |
|---|---|---|
| 11 | A=0 B=0 in range | good |
| 12 | A=0 B=0 short | undersize |
| 13 | A=0 B=0 long | oversize |
| 14 | A=0 B=1 in range | FCS error |
| 15 | A=0 B=1 short | fragment — error-major says FCS |
| 16 | A=0 B=1 long | jabber — error-major says FCS |
| 17 | A=1 B=0 in range | CLS_UNNAMED |
| 18 | A=1 B=0 short | fragment — error-major says undersize |
| 19 | A=1 B=0 long | jabber — error-major says oversize |
| 20 | A=1 B=1 in range | alignment |
| 21 | A=1 B=1 short | fragment — error-major says alignment |
| 22 | A=1 B=1 long | jabber — error-major says alignment |
The order inference — 9 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 23 | 1 000 conformant frames | order_known stays low |
| 24 | 1 000 in-range FCS errors | still low — shape 14 agrees |
| 25 | one frame of shape 15 | order_known asserts |
| 26 | a size-major DUT on shape 15 | votes_size = 1 |
| 27 | an error-major DUT on shape 15 | votes_error = 1 |
| 28 | a DUT reporting CLS_ALIGN on shape 18 | dut_matches_neither |
| 29 | 1 000 000 frames, fragments = 0 | fragments_never_move |
| 30 | the same with one shape-15 frame injected | the flag clears or the order is proven |
| 31 | order_untested after 100 000 clean frames | asserts |
The unnamed shape — 8 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 32 | shape 17 once | c_unclassified = 1; no class counter moves |
| 33 | 1 001 of them | taxonomy_incomplete |
| 34 | a stuck trailing-bit register | every frame lands in shape 17 |
| 35 | the RMON counters during that | all zero |
| 36 | frames_in during that | healthy |
| 37 | frames_out during that | short by every frame |
| 38 | Chapter 21.1's mask on that evidence | class G — and the fault is at site 5 |
| 39 | c_unclassified read once | the diagnosis, in one register |
Symbol errors and the non-frame classes — 10 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 40 | RX_ER during a frame | c_symbol_in_frame; the FCS also fails |
| 41 | RX_ER in the gap | c_symbol_in_gap; no frame class moves |
| 42 | a degrading channel, early | gap errors only — the earliest warning |
| 43 | MDIO never polled | phy_unreadable stays asserted |
| 44 | PHY count exceeds the MAC's | counts_disagree — site 3 |
| 45 | PHY count matches | sites 1 or 2 |
| 46 | a filtered frame that is also malformed | Chapter 7.3 §3 — the error class wins |
| 47 | the same on a filter-first design | channel errors under-reported |
| 48 | a mid-frame truncation | damaged_by_us; looks like a fragment |
| 49 | a fragment from the wire | is_legal_discard low, damaged_by_us low |
Counters and telemetry — 9 scenarios.
| # | Scenario | Expected |
|---|---|---|
| 50 | one frame, one class | exactly one counter increments |
| 51 | a shape-21 frame | fragments only — not alignment too |
| 52 | priority_applied on shape 21 | asserts |
| 53 | priority_applied on shape 14 | asserts — two predicates |
| 54 | priority_applied on shape 12 | low — one predicate |
| 55 | 16× more discards than errors | discards_exceed_errors — and it is healthy |
| 56 | c_unclassified at 1 per 4.29 billion | chance — Section 8's row one |
| 57 | c_unclassified at 1 per 1 000 | a design fault |
| 58 | error_rate_ppm with every frame bad | 1 000 000 |
And the directed test, because random stimulus will not produce it.
The case: determine which classification order a piece of silicon implements, using one frame.
Constrained-random traffic will not do this in any run of any length, because the six discriminating shapes are short-or-long and carrying an error, and a generator constrained to legal sizes produces none of them — Chapter 20.1 §5's allow_illegal is clear by default. The case needs Chapter 20.5's injector and a deliberate choice of length.
| Run | The frame | What it establishes |
|---|---|---|
| A | 64 octets, good | the baseline — no counter moves |
| B | 64 octets, FCS stomped | fcs_errors — both orders agree |
| C | 40 octets, good FCS | undersize — both orders agree |
| D | 40 octets, FCS stomped | THE TEST — fragment or FCS error |
| E | 40 octets, 3 trailing bits, FCS stomped | fragment or alignment |
The oracle is four-part and runs D and E are the only two that carry information.
| Part | A | B | C | D | E |
|---|---|---|---|---|---|
| size-major class | good | FCS | undersize | fragment | fragment |
| error-major class | good | FCS | undersize | FCS | alignment |
| do they differ? | no | no | no | YES | YES |
| frames needed | 1 | 1 | 1 | 1 | 1 |
Row four is the test's whole economy: five frames, and two of them settle a fact no datasheet states. Runs A, B and C exist as controls — they prove the classifier is working at all, which matters because a silent classifier and an error-major one look the same on run D if you only look at fragments.
Run E is the one that separates the three possible orders rather than two. A size-major design reports fragment; an error-major design reports alignment; a design that checks size before alignment but after the check sequence reports fragment on D and alignment on E, which is Section 13's dut_matches_neither and is a real implementation pattern.
And the result belongs in the same one-minute record as Chapter 21.1 §12's step 0. Two facts per platform — which optional counters exist, and which classification order the MAC uses — and every subsequent investigation on every port of that platform is faster for it.
One more property of this test is worth naming: it is the only experiment in Module 21 whose oracle is entirely a priori.
| Needs | |
|---|---|
| the frames | five, generated by Chapter 20.5's injector |
| the expected results | Section 4's table — computed, not measured |
| a reference design | none |
| a fault to be present | none |
Row four is what makes it runnable at commissioning rather than during an outage. Nothing has to be broken; the five frames are sent into a perfectly healthy port and the counters answer a question about the silicon. Chapter 21.1 §11's counter probe has exactly the same character — four thousand frames into a healthy port — and the two together are about five seconds of traffic that permanently settle both of a platform's unknowns.
Which suggests the commissioning procedure this module has been building towards without saying so:
| Step | Traffic | Settles |
|---|---|---|
| 1 | 1 000 misaddressed frames | is c_filtered implemented? |
| 2 | 1 000 non-member VLAN frames | is c_vlan_discards implemented? |
| 3 | a stalled consumer | is c_fifo_drops implemented? |
| 4 | a driver with no descriptors | is c_desc_errors implemented? |
| 5 | 40 octets, FCS stomped | size-major or error-major? |
| 6 | 40 octets, dribbled, stomped | or a third order? |
| 7 | a dribbled in-range frame | is c_unclassified implemented? |
Seven steps, a few thousand frames, and the output is two lines in a platform inventory — which is what every investigation on every port of that platform will otherwise spend an hour rediscovering, badly.
22. Debugging a Classifier
Six complaints.
Complaint 1 — "the fragments counter has never moved."
| Check | If yes | Meaning |
|---|---|---|
| has the link seen errors at all? | yes | so the counter should have moved |
fragments_never_move set? | yes | Section 16's two-register test |
| inject a 40-octet stomped frame | fcs_errors moves instead | error-major — Section 7 |
| is that a bug? | no | it is a different reading of the standard |
And one more reading of the same evidence that is not a classification-order finding at all.
| Check | If yes | Meaning |
|---|---|---|
| has the link seen any error? | no | the counter is correct to be zero |
| is this a full-duplex point-to-point link? | yes | collisions are impossible, so one cause is gone |
is c_self_inflicted implemented? | no | our own truncations would show as fragments |
| have frames ever gone missing? | no | then nothing has been cut |
On a healthy modern link the fragment counter should be zero and stay zero for the life of the port, which is exactly why a permanently-zero reading is not by itself evidence of an error-major classifier. Section 16's fragments_never_move requires a million frames and some errors before it fires, and that qualification is the difference between a verdict and a guess.
Row four is the part that surprises people. An error-major classifier is not non-conformant; it implements every normative definition correctly and applies them in a different order. The consequence is not a defect report — it is a note in the platform record, because Chapter 21.1 §2's site table has to match.
Complaint 2 — "oversize errors on a link with no damage."
| Check | If yes | Meaning |
|---|---|---|
| are the FCS counters zero? | yes | nothing is corrupting anything |
| are the frames one or two sizes? | yes | not random damage |
| are those sizes 1 519 to 1 526? | yes | VLAN tags — Section 18's row three |
| is the port configured for tags? | no | the answer, and it is configuration |
Four octets per tag and a conformant frame becomes an error class. The characteristic signature is oversize climbing with every other counter at zero and the offending frames clustered at exactly 1 522 or 1 526 — which no amount of cable replacement will improve.
Complaint 3 — "all the counters are zero and frames are disappearing."
| Check | If yes | Meaning |
|---|---|---|
frames_in healthy? | yes | the port is receiving |
c_unclassified implemented? | no | then you cannot see Section 8's shape |
| if implemented, is it climbing? | yes | a stuck trailing-bit count — site 5 |
| Chapter 21.1's mask says? | class G | five sites, and none of them is site 5 |
Row four is the failure that motivates the whole of Section 8. The method converges on a candidate set that does not contain the answer, and it does so correctly — because an unclassified frame increments nothing and a method built on counters cannot see it. One counter, 99 flops, and the mystery is a register read.
Complaint 4 — "our error rate doubled after a firmware update."
| Check | If yes | Meaning |
|---|---|---|
| did the traffic change? | no | so the classification did |
| is any single counter unchanged? | frames_in | the same frames arrived |
| did fragments fall as FCS rose? | yes | the order changed — not the link |
| or did two counters both rise? | yes | a frame is being counted twice |
Rows three and four are two completely different findings from the same symptom. A reclassification moves frames between counters and leaves the total fixed; a double count raises the total, which violates Chapter 7.3 §3 and is a real defect. The discriminator is whether total_errors against c_frames moved, and it is one subtraction.
Complaint 5 — "the PHY says symbol errors and the MAC says nothing."
| Check | If yes | Meaning |
|---|---|---|
| are the symbol errors in the gap? | yes | no frame to damage — Section 9 |
c_symbol_in_gap implemented? | usually not | so the MAC genuinely sees nothing |
| is the channel degrading? | probably | this is the earliest warning available |
| wait for frame errors? | they will come | and the link will already be failing |
Gap symbol errors are the only leading indicator in the entire taxonomy and almost nothing counts them. A channel degrades for hours or days before its errors start landing inside frames, because a frame occupies a minority of the wire time at low utilisation — and every frame-based class is blind for that whole period.
Complaint 6 — "we see fragments on a full-duplex point-to-point link."
| Check | If yes | Meaning |
|---|---|---|
| are there collisions? | no | the name's original cause is impossible |
c_self_inflicted implemented? | no | then our own truncation is invisible |
| is the far end's buffer overflowing? | possibly | and it looks identical from here |
| is a cable parting mid-frame? | possibly | also identical |
Fragments on a modern link are never collisions and the counter's name says they are. Three causes remain — our truncation, the far end's truncation looped back, and a physical cut — and only the first is distinguishable, by a counter that is optional. Section 17's table is the archaeology; this is what it costs in practice.
Complaint 7 — "two switches on the same link report different error counts."
| Check | If yes | Meaning |
|---|---|---|
| are the totals equal? | yes | the same frames; only the classification differs |
| do the distributions differ? | yes | different classification orders — Section 4 |
| are the totals different? | yes | different MTUs, or one is filtering first |
| does one report fragments and the other not? | yes | one is size-major and one is error-major |
Rows one and two together are the diagnosis and they are checkable in one subtraction. Two conformant MACs on the ends of one cable see identical frames — so their total_errors must agree to within the skew of the window. A matching total with a different distribution is two classification orders; a different total is a different MTU, a different filter order, or a genuinely asymmetric link, which is a real and much rarer thing.
Complaint 8 — "the error rate improved when the neighbouring port got busier."
| Check | If yes | Meaning |
|---|---|---|
| is the port promiscuous or on a shared segment? | yes | it hears foreign traffic |
| does the design filter before validating? | yes | Section 18's row six |
did c_filtered rise with the "improvement"? | yes | the answer |
| did the physical link change? | no | nothing improved |
A filter-first classifier under-reports channel errors in exact proportion to how much foreign traffic the port sees, because a malformed frame addressed elsewhere is counted as filtered rather than as damaged. So the measured error rate falls when the neighbours get busier, which reads as an improvement and is an artefact of a priority order. Chapter 7.3 §3 says validity is checked first, and an implementation that does otherwise is not obviously wrong and is measurably misleading.
And the three symptoms this chapter is systematically blamed for:
| Symptom | Blamed on | Usually is |
|---|---|---|
| a counter that never moves | the design | a different classification order |
| oversize with no damage | the link | a VLAN tag and a port's MTU |
| frames vanishing with no class | the method | Section 8's unnamed shape |
23. Misconceptions
Misconception 1 — "the error classes are mutually exclusive."
The wrong model: Chapter 7.3 §3 says a frame is counted once, so a frame belongs to exactly one class.
What it costs: it makes the priority rule invisible. Seven of the twelve reachable frame shapes satisfy two or more error predicates — a 40-octet frame with a stomped check sequence is short and failing — so exclusivity is manufactured by a tie-break, not found in the frame. A team that believes the classes are intrinsically disjoint never asks which rule its hardware uses, and Section 4 says the two candidate rules disagree on half the space.
The corrected model: the classes are a partition of the frame space quotiented by a priority rule. The counters are disjoint; the predicates are not. Section 20's rejected property is this misconception written in SVA.
Misconception 2 — "a fragment means a collision."
The wrong model: the name is literal, so a non-zero fragment count means a collision domain.
What it costs: there are no collisions on a full-duplex point-to-point link, so an engineer looks for a cause that has not existed on that link since it was installed. Meanwhile the three causes that do exist — a cable parting mid-frame, the far end's buffer truncating, our own Chapter 19.5 §14 overflow looped back — go unexamined, and one of the three is local.
The corrected model: fragments today counts cut frames, from three causes, only one of which is distinguishable and only with an optional counter. Section 17's table names the other obsolete class too: jabber, which no longer means what its name says either.
Misconception 3 — "an undersize frame was damaged."
The wrong model: it is in the error counters, so something broke it.
What it costs: it sends an investigation to the channel. An undersize frame has a valid check sequence — nothing corrupted it — it is a well-formed frame of an illegal length, which means a transmitter emitted it that way. Chapter 5.6's padding rule exists to make it impossible, so an undersize frame is a partner that is not padding.
The corrected model: three of the six frame classes are about damage — FCS, alignment, fragment — and three are about a transmitter or a configuration — undersize, oversize, jabber. They take entirely different first actions, and Section 12's table says which is which.
Misconception 4 — "the standard defines everything."
The wrong model: the class definitions are normative and exhaustive, so a literal implementation is correct.
What it costs: one reachable shape has no class — not whole octets, check sequence passing, in range — and a literal classifier must put it somewhere or drop it. Dropping it is what real hardware does, and that makes a stuck trailing-bit count present as Chapter 21.1's class G: five candidates, none of which is the actual site.
The corrected model: the taxonomy is complete on eleven shapes and silent on the twelfth. A classifier needs a thirteenth class and a counter for it — c_unclassified, 99 flops — and the standard will not ask for it.
Misconception 5 — "symbol errors and FCS errors are the same fault reported twice."
The wrong model: a symbol error corrupts a frame, which then fails its check, so the two counters are redundant.
What it costs: it discards the most useful counter in the taxonomy. A symbol error in the interframe gap corrupts nothing — there is no frame — so it moves no frame-based class and is invisible to every RMON counter. A degrading channel produces those first, for hours, before any frame error appears, because frames occupy a minority of the wire time at ordinary utilisation.
The corrected model: in-frame symbol errors are largely redundant with FCS errors; gap symbol errors are a leading indicator with no substitute. Section 9's two counters are deliberately separate, and c_symbol_in_gap is the one worth wiring.
Misconception 6 — "our error rate went up, so the link got worse."
The wrong model: the error counters are a measurement of the link.
What it costs: they are a measurement of the link as classified by this chip, with this MTU, on this port's VLAN configuration. A firmware change that alters the classification order moves frames between counters with the total fixed; a VLAN change moves conformant frames into oversize; and neither is a change in the link. Section 22's fourth complaint is a doubled rate whose cause was a reclassification.
The corrected model: compare total_errors against c_frames, which is invariant under reclassification, before comparing any individual class. If the total is stable and the distribution moved, the classifier changed; if the total moved, the link did.
24. Interview Questions
Question 1 — "A 40-octet frame arrives with a bad check sequence. Which counter increments?"
What the answer should establish: that it depends on the implementation and the standard's answer is fragments. RMON defines fragments as short packets with a bad check sequence or a bad alignment, which is a size-major rule. A strong answer names the alternative: an error-major chain tests A && B and then B before it tests the size, reaches fcs_errors, and never assigns fragments at all. The strongest answer gives the test: send that frame and see, because no datasheet states the order.
Question 2 — "Are the Ethernet error classes mutually exclusive?"
What the answer should establish: the counters are; the predicates are not. Seven of the twelve reachable frame shapes satisfy two or more error predicates, so exclusivity is produced by a tie-break rather than found in the frame. A strong answer states what that means for verification: a $onehot0 over the predicates fails on 58.3% of the space on a correct design, and moving it to the class makes it vacuous.
Question 3 — "Is there a frame the taxonomy cannot name?"
What the answer should establish: yes — not a whole number of octets, check sequence passing, length in range. An alignment error requires the check to fail, so this is not one; nothing else fits. A strong answer says why it matters: a stuck trailing-bit count puts every frame there, every error counter reads zero, and the fault presents as the five sites past the MAC — none of which is the parser. The strongest answer prices the fix at one counter.
Question 4 — "What is the difference between a fragment and a truncation?"
What the answer should establish: who cut the frame. A fragment was cut by the far end or the medium; a truncation was cut by this receiver, when Chapter 19.5 §14's overflow guard ran out of buffer mid-frame. Downstream they are identical — short, bad check sequence — and the actions are opposite: investigate the partner, or investigate our own memory system. A strong answer names the separating evidence and notes that it is optional.
Question 5 — "Your oversize counter is climbing and nothing else is. What is it?"
What the answer should establish: almost certainly a VLAN tag against an untagged port's MTU. 1 518 becomes 1 522 with one tag and 1 526 with two; a port configured for untagged frames classifies tagged conformant frames as oversize. A strong answer gives the signature: no check-sequence errors, and the offending frames clustered at exactly one or two sizes — random damage does not produce a size histogram with two spikes in it.
Question 6 — "Which error counter would you add to a MAC if you could add one?"
What the answer should establish: a counter for the unnamed shape, or one for gap symbol errors, and the candidate should be argued rather than named. The unnamed-shape counter turns an invisible parser fault into one register read. The gap symbol counter is the only leading indicator in the taxonomy — it moves for hours before any frame error does. A strong answer costs both at about 99 flops each, notes that all of Module 21's wish list is about 1 900 flops against a 14 166-flop datapath, and observes that none of it is built because correctness is a requirement and diagnosability is a preference.
25. Questions and Answers
26. What's Next
The taxonomy is the table; the rest of Module 21 works through it one class at a time.
| Chapter | Takes | Because |
|---|---|---|
| Chapter 21.3 | class 1 | it is 0.98 bits and five sites |
| Chapter 21.4 | the link that never comes up | a fault space with no frames in it |
| Chapter 21.5 | negotiation | Chapter 11.4's asymmetric symptoms |
| Chapter 21.6 | classes 8 to 12 | the five this chapter could not separate |
Chapter 21.3 is next and it inherits this chapter's most precise row. Section 12's table says FCS errors climbing with alignment errors at zero, on frames the port counted correctly, points at the check engine rather than at the channel — because a damaged channel does not reliably preserve octet boundaries, so a real channel fault moves both counters. Separating Chapter 19.4's residue comparison from a marginal connector is the difference between a logic fix and a cable, and both are cheap once you know which.
Chapter 21.6 is where the five unresolvable sites finally get separated, and this chapter has already named the price: four optional counters that no standard requires, because four of the six non-frame classes are legal discards and a standard counts errors.
And the series is now ninety-five classes long. Chapter 20.2 §8's six groups have taken three extensions in five chapters — Chapter 20.4's 91, Chapter 20.6's 93 and Chapter 21.1's 94 — and class 95 belongs with none of them. What 93, 94 and 95 share is that the property is true of one object and is bound to a different one: a width at a seam, a search against its sensors, a classification's output against its input. That is a coherent eighth group and it now has three members, which is enough to name it: a property bound to the wrong side of a function.
Continue learning
Related tutorials
- Related topic
A Method for Debugging Ethernet
The receive path has twelve fault sites and the RMON-required counters separate them into seven classes, one holding five — so the method's ceiling is knowable from a datasheet.
- Related topic
Packet Drops
Five sites, one signature, and two ratios over counters that already exist take them to three classes — plus the finding that two of the four optional counters are nearly redundant.
- Related topic
CRC Errors
A channel fault's error rate is proportional to frame length and every logic fault's is flat, so the ratio is 23.72 or 1.00 — measurable on counters a MAC already computes.
- Related topic
Link Failures
A down link has no frames, so every instrument in Module 21 is unavailable at once; the replacement space has fourteen sites and MDIO separates nine classes of them.
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.
