Skip to content
VLSI Mentor

Ethernet · Module 21

DMA Failures

Eight descriptor-path failures sorted into three classes by one existing counter, and a consumer stall priced at 148.8 frames per microsecond beyond the buffer's 2.621.

Chapter 20.5 §12 proved that no sequence of frames can overflow a correctly sized receive FIFO: the arrival rate is the line rate and Chapter 19.5 §4's drain rate exceeds it. The consumer has to stop. This is the chapter where that stop is the subject rather than the stimulus.

Value
descriptor-path failures enumerated8
classes Chapter 19.6 §5's outstanding count sorts them into3
receive FIFO capacity, in wire time2.621 µs
Chapter 19.6 §8's design stall budget2.002 µs
the margin between them0.620 µs
frames lost per microsecond beyond it, minimum size148.8
maximum size8.13

The last three rows are the pricing the direction of this chapter asked for, and they turn a vague symptom into a schedule.

The stallBeyond the FIFO byMinimum-size frames lost
a DDR4 refresh — 350 nsnothing0
Chapter 19.6 §8's budget — 2.000 µsnothing0
the FIFO's capacity — 2.621 µsnothing, exactly0
a 10 µs interconnect stall7.379 µs1 098
a 1 ms scheduling gap997.4 µs148 419

A DRAM refresh is free and a driver that misses a millisecond costs a hundred and fifty thousand frames, and everything between the two is linear in the overshoot.

And one counter sorts the eight failures into three classes before any of that arithmetic is needed.

Outstanding countMeansFailures
falls to zeronothing is being requested4
pinned at its maximumresponses are not being consumed1
normalthe transfer works and something else is wrong3

1. Scope, and a Fault With No Frame Behind It

Scope: the descriptor path's failure modes, what each does to Chapter 19.6 §5's outstanding count, and what a consumer stall costs in frames.

Not in scope: the interface's design. Chapter 19.6 built the mechanism — the request shaper, the outstanding tracker, the (O − 1)-burst reorder buffer, the write commit tracker — and this chapter reads its instruments as evidence. Nor the drop's visibility: Chapter 21.6 established that a DMA drop is a class G drop, silent in every error counter, and named the two free ratios that reach it.

Start from what makes this chapter different from every other one in Module 21.

Every other chapter's faultThis one's
reachable from the wireyesNO
Chapter 20.5 §10's categoryan injectionneither of its two
what produces ita frame, or a design faultthe memory system stopping
what a generator can do about itproduce itnothing at all

Row one is Chapter 20.5 §12's proof and it is worth restating exactly. The receive FIFO's arrival rate is bounded by the line rate — that is what a line rate is — and Chapter 19.5 §4's system-side drain is 128 Gb/s against 100. So with the consumer running, the buffer empties, whatever the frames look like. Minimum size back to back, jumbo back to back, any mixture: the arrival rate is the same in every case and it is below the drain rate.

The consumer must stop. Nothing on the wire can make it stop, and that is why this fault has no stimulus and needs a chapter of its own.

Which gives the chapter its two halves.

HalfQuestion
Sections 2 to 9why did the consumer stop — eight failures, three classes
Sections 10 to 13what did the stop cost — 148.8 frames per microsecond

And one structural note about where the evidence lives. Every instrument in this chapter is on the memory sideChapter 19.6 §5's outstanding count, §13's commit tracker, the descriptor ring's own state — and none of it is a frame counter. Chapter 21.6's selectivity index sees the consequence; this chapter's instruments see the cause, and they are in a different register block behind a different driver.


2. The Descriptor Ring, as a Failure Space

A descriptor ring is a circular array whose entries each carry a buffer pointer, a length field and an ownership bit, and everything that goes wrong with it goes wrong with one of those three. Four failures are about ownership. The ring is exhausted, meaning no descriptor is owned by hardware. The ownership bit is read stale, so the controller sees its own earlier write rather than the driver's refill. The completion is never signalled, so the driver never reaps and the ring exhausts for a different reason. And the head and tail pointers diverge, so the ring appears full when it is not. Two failures are about ordering: the descriptor is written before the frame data has landed in memory, and the descriptor is committed once per burst rather than once per frame, which leaves the tail of a multi burst frame stale. One is about length: the posted buffer is smaller than the arriving frame. And the eighth is not a descriptor failure at all but the transmit read path's reorder buffer being undersized, which is in the list because its symptom appears on the receive path four links away. From the controller's side, an exhausted ring and a missing completion are indistinguishable, because the controller has no visibility into whether its interrupt was taken.Ownership4 failuresOrdering2 failuresLength1 failureRing exhaustednothing hw-ownedNo completionindistinguishable fromitDescriptor beforedata18.3's class 76Commit per burststale tailsReorder buffersmallthe 8th — not the ringSymptom on the RXpath4 links away12
Figure 1 — eight failures across three fields, and one of them is not a descriptor problem at all.

A descriptor ring is a circular array of entries, each with a buffer pointer, a length field and an ownership bit. Everything that goes wrong with it goes wrong with one of those three.

Owned by hardwareOwned by software
an empty descriptorready for the MAC to fillwaiting for the driver to refill
a filled descriptorwaiting for the driver to reap
the transitionthe driver sets itthe MAC sets it

Two parties, one bit, and a ring that both walk in the same direction. The eight failures are what happens when that arrangement breaks, and they group by which of the three fields is at fault.

#FailureField
1the ring is exhausted — no descriptor owned by hardwareownership
2the ownership bit is read staleownership
3the descriptor is written before the frame data landsordering
4the descriptor is committed per burst, not per frameordering
5head and tail pointers divergeposition
6the buffer is smaller than the framelength
7the completion is never signalled — no interruptownership, indirectly
8the reorder buffer is undersized and the read channel stallsnone — it is Chapter 19.6 §19's

Failure 8 is in the list for a reason that Section 17 is about, and it is the one that produces the chapter's most counter-intuitive diagnosis: it is a fault on the transmit read path whose symptom is loss on the receive path.

Three of the eight deserve a sentence now because they are the ones that look alike.

Failures 1 and 7 are indistinguishable from the MAC's side. A ring with nothing owned by hardware is a ring with nothing owned by hardware, whether the driver is slow, descheduled, or never learned there was work to do. The completion mechanism is in the host and the MAC has no visibility into whether its interrupt was taken.

Failures 3 and 4 both produce a descriptor the driver believes but should not. Chapter 18.3's class 76 named the general shape — custody is not visibility — and Chapter 19.6 §13's commit tracker exists to get it right: a frame is several bursts, and the descriptor may be marked done only when the last of them has returned its write response. Marking it when the last was issued, or when the first responded, are failures 3 and 4.

Failure 6 is Chapter 21.6's G_DMA_SIZE and it is the only member of this list that chapter's free selectivity index can name. Everything else here is invisible to a frame-side instrument, which is why the outstanding count is the axis this chapter uses.

And the two chapters' instruments are complementary rather than overlapping, which is worth laying out once.

FailureChapter 21.6's free ratios saythis chapter's outstanding count says
1 — ring exhaustedflat, clustered — class Czero
2 — stale ownershipnothing — no frames are lostnormal
3 — descriptor earlynothingnormal
4 — commit per burstnothingnormal
5 — pointers divergedflat, clustered — class Czero
6 — buffer too smallSELECTIVE — namedzero
7 — no completionflat, clustered — class Czero
8 — reorder bufferflat, clustered — class CPINNED — named

Rows two, three and four are the group Chapter 21.6 cannot see at all, because it reasons about frames that did not arrive and these deliver every frame. Rows six and eight are named by one instrument each and neither names both.

Named by
failure 6the selectivity index — a frame-side ratio
failure 8the outstanding count — a memory-side register
failures 1, 5, 7neither, and they are class C's three
failures 2, 3, 4neither, and no counter anywhere

Two instruments in two register blocks behind two drivers, and together they name two of the eight. The four in rows three and four of that table are the chapter's real subject, and Sections 7 to 9 are what is left to say about them.


3. RTL 1 — The DMA Diagnostic Package and the Ownership Model

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// dmadiag_pkg -- eight failures, one counter, and the three readings it
// takes.
//
// The package's claim: Chapter 19.6 Section 5's outstanding count is a
// three-valued diagnostic. Zero means nothing is being asked for;
// pinned means nothing is being answered; normal means the transfer
// works and the fault is in ownership or ordering.
// ---------------------------------------------------------------------
package dmadiag_pkg;

  typedef enum logic [3:0] {
    F_RING_EMPTY   = 4'd0,   // no descriptor owned by hardware
    F_OWN_STALE    = 4'd1,   // the ownership bit was read stale
    F_DESC_EARLY   = 4'd2,   // descriptor written before the data landed
    F_COMMIT_BURST = 4'd3,   // committed per burst, not per frame
    F_PTR_DIVERGE  = 4'd4,   // head and tail disagree
    F_BUF_SMALL    = 4'd5,   // Chapter 21.6's G_DMA_SIZE
    F_NO_COMPLETION= 4'd6,   // the interrupt never arrived
    F_REORDER_SMALL= 4'd7,   // Chapter 19.6 Section 19's 7 KiB
    F_NONE         = 4'd15
  } dfail_e;

  typedef logic [7:0] fmask_t;
  localparam fmask_t ALL_F = 8'hFF;

  // Section 4's three readings of the outstanding count.
  typedef enum logic [1:0] {
    O_ZERO   = 2'd0,   // nothing is being requested
    O_PINNED = 2'd1,   // requests issued, responses not consumed
    O_NORMAL = 2'd2,   // Little's law is satisfied
    O_UNKNOWN= 2'd3
  } ocount_e;

  localparam fmask_t CLASS_ZERO   = 8'b0111_0001;  // ring, ptr, buf, completion
  localparam fmask_t CLASS_PINNED = 8'b1000_0000;  // the reorder buffer
  localparam fmask_t CLASS_NORMAL = 8'b0000_1110;  // stale own, early, per-burst

  // Chapter 19.5 Section 3's receive FIFO, in beats and in wire time.
  localparam int FIFO_BEATS      = 512;
  localparam int BEAT_BITS       = 512;
  localparam int LINE_GBPS       = 100;
  // 512 x 512 / 100e9 = 2.621 us, in nanoseconds.
  localparam int FIFO_NS         = 2621;
  // Chapter 19.6 Section 8's design budget: 391 beats.
  localparam int BUDGET_NS       = 2002;
  localparam int MARGIN_NS       = FIFO_NS - BUDGET_NS;   // 619

  // Section 12: frames lost per nanosecond of overshoot, x1000.
  localparam int MIN_FRAMES_PER_US_X10 = 1488;   // 148.8
  localparam int MAX_FRAMES_PER_US_X10 = 81;     //   8.1

  function automatic int popcount8(fmask_t m);
    int n = 0;
    for (int i = 0; i < 8; i++) if (m[i]) n++;
    return n;
  endfunction

endpackage

Classification: a package whose one structural claim is that a single counter is three-valued rather than numeric.

What it teaches: that the outstanding count's value is almost useless and its regime is decisive. Chapter 19.6 §4's Little's-law arithmetic says the count should be R × L / burst — 13 bursts at 1 µs of memory latency with 1 KiB bursts — but knowing it is 13 rather than 11 tells a diagnosis nothing. Knowing it is zero or pinned at the limit eliminates four failures or seven.

And it teaches that the three class masks are disjoint and exhaust the eight. CLASS_ZERO has four members, CLASS_PINNED one, CLASS_NORMAL three; their union is ALL_F and their pairwise intersections are empty. That is a genuine partition, unlike Chapter 21.6's two axes which had to be intersected — because the outstanding count is a single quantity with three mutually exclusive regimes.

Deliberately simplified: FIFO_NS and BUDGET_NS are computed constants in a package where they should be derived from FIFO_BEATS, BEAT_BITS and LINE_GBPS — and a design at a different rate gets the wrong numbers silently, which is Chapter 20.6 §6's problem arriving again. MIN_FRAMES_PER_US_X10 hardcodes the minimum frame size. And the eight failures are treated as mutually exclusive, which Section 18's row six is about.

Production implication: the rate-dependence of the three timing constants is the one to parameterise before anything else. At 10 Gb/s a 512-beat FIFO at 64 bits per beat holds 4 096 octets, which is 3.277 µs of wire time rather than 2.621 — a 25% larger budget for the same buffer — and the frames-per-microsecond figure falls by a factor of ten. A diagnosis calibrated at 100 Gb/s over-states the cost of a stall by an order of magnitude at 10 Gb/s, which turns a real finding into a number nobody believes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// desc_ownership_model -- the ring's state as the MAC can see it, and
// the two transitions that are not observable from here.
//
// The MAC sets ownership to software. The driver sets it to hardware.
// The MAC can see its own transition and can only INFER the other,
// which is the asymmetry the whole chapter turns on.
// ---------------------------------------------------------------------
module desc_ownership_model
  import dmadiag_pkg::*;
#(
  parameter int RING_SIZE = 256
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        desc_fetched,
  input  logic        desc_owned_by_hw,   // the bit we read
  input  logic        desc_released,      // we set it to software
  input  logic [15:0] head_ptr,           // ours
  input  logic [15:0] tail_ptr,           // the driver's, if visible

  output logic [15:0] hw_owned_count,
  output logic [15:0] c_fetches,
  output logic [15:0] c_releases,
  output logic [15:0] c_empty_fetches,
  output logic        ring_exhausted,
  output logic        ptr_diverged,
  output logic        tail_visible
);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_fetches <= '0; c_releases <= '0; c_empty_fetches <= '0;
    end else begin
      if (desc_fetched) begin
        c_fetches <= c_fetches + 16'd1;
        // A fetch that found the descriptor owned by SOFTWARE is the
        // ring's exhaustion, observed one descriptor at a time.
        if (!desc_owned_by_hw) c_empty_fetches <= c_empty_fetches + 16'd1;
      end
      if (desc_released) c_releases <= c_releases + 16'd1;
    end
  end

  // What the MAC believes it owns. It cannot know what the driver has
  // refilled until it fetches and looks.
  assign hw_owned_count = (c_fetches >= c_releases)
                        ? (c_fetches - c_releases) : 16'd0;

  assign ring_exhausted = (c_empty_fetches > 16'd0);

  // The driver's tail pointer is visible only if the design mirrors it
  // into a register the MAC can read, which many do not.
  assign tail_visible = 1'b1;
  assign ptr_diverged = tail_visible &&
                        ((head_ptr - tail_ptr) > 16'(RING_SIZE));
endmodule

Classification: a state model that is deliberately incomplete, because the state it models is shared with a party it cannot observe.

What it teaches: that c_empty_fetches is the ring's exhaustion observed one descriptor at a time, and it is the only direct evidence of it the MAC has. The driver's refilling is invisible: the MAC learns a descriptor is available by fetching it and finding the ownership bit set. A fetch that finds it clear is the only positive signal the ring is empty — and a MAC that stops fetching when it is empty produces no such signal at all.

And it teaches that tail_visible is a design choice most designs get wrong. The driver's tail pointer — how far it has reaped — is in host memory and is not read by the MAC. Mirroring it into a readable register costs sixteen flops and turns failure 5 from an inference into a comparison, which Section 8's table shows is one of the three the outstanding count cannot separate.

Deliberately simplified: tail_visible is hardcoded to 1, which is the assumption rather than the common case. hw_owned_count is a difference of two counters that wrap independently. ptr_diverged compares a raw difference against the ring size where a circular comparison is needed. And the block cannot distinguish "the ring is empty" from "we stopped fetching" — Section 4's first ambiguity.

Production implication: the counter this block is missing is the one that separates failures 1 and 7, and it is in the host rather than in the MAC. A driver that records how long since it last refilled turns "the ring is empty" into "the ring is empty and the driver has not run in 3 ms", which is failure 7 rather than failure 1 — and it is a software counter, free, and almost never collected alongside the MAC's.


4. What Each Failure Does to the Outstanding Count

Chapter 19.6 §5's outstanding tracker counts requests issued minus responses returned. Under Chapter 19.6 §4's Little's law it should sit near R × L / burst.

Memory latencyOutstanding, 1 KiB burstsReorder buffer
400 ns54 KiB
1 µs1312 KiB
2 µs2524 KiB
5.5 µs6867 KiB

Now each of Section 2's eight failures, and what it does to that count.

#FailureOutstanding countWhy
1the ring is exhaustedfalls to zeronothing to write, so nothing is issued
2the ownership bit is read stalenormalthe MAC issues happily, into the wrong buffer
3descriptor written before datanormalboth are issued; only the order is wrong
4committed per burstnormalthe bursts are issued correctly
5pointers divergefalls to zerothe ring appears full and fetching stops
6the buffer is too smallfallsframes are dropped before any burst is issued
7no completion signalledfalls to zerothe driver never reaps, so the ring exhausts
8the reorder buffer is undersizedpinned at maximumRREADY is low; responses stop returning

Three regimes, and they are mutually exclusive.

ReadingFailuresCount
zero1, 5, 6, 74
pinned at maximum81
normal2, 3, 43

One counter, eight failures, three classes — and the counter already exists, because Chapter 19.6 §5 built it to enforce the outstanding limit rather than to diagnose anything.

The pinned reading is the most decisive and the least expected.

A count pinned at its maximum means requests are being issued and responses are not being consumed. There is exactly one mechanism in this list that does that, and it is a buffer on the transmit path.

Pinned at maximum means
the shaper is issuingyes — it is at the limit
the interconnect is respondingpossibly, and RREADY is low
the reorder buffer is fullChapter 19.6 §19's buffer_undersized
the receive path is losing framesand there is no signal connecting the two

Row four is Section 17's subject and Chapter 19.6 §22's first complaint: the buffer that overflowed is on the transmit read path, the frames lost are on the receive path, and the only thing coupling them is a shared RREADY that appears in neither path's interface.

And the zero reading's four members are what the rest of the chapter has to separate.

FailureDistinguishing evidenceWhere it lives
1 — ring exhaustedc_empty_fetches climbingthe MAC
5 — pointers divergedthe tail pointer, if mirroreda register most designs omit
6 — buffer too smallChapter 21.6's selectivity indexfour existing counters
7 — no completiontime since the driver last ranthe host, and not collected

Two of the four are named by evidence that already exists and two need something nobody builds — sixteen flops for a mirrored tail pointer, and a software timestamp. Section 19 prices both.


5. RTL 2 — The Outstanding-Count Classifier

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// outstanding_regime -- read Chapter 19.6 Section 5's counter as three
// values rather than as a number.
//
// The regime is what discriminates; the value is what Little's law
// predicts and neither confirms nor denies a fault. A count of 13 at
// 1 us of memory latency is exactly right and says nothing about
// ownership, ordering or the ring.
// ---------------------------------------------------------------------
module outstanding_regime
  import dmadiag_pkg::*;
#(
  parameter int MAX_OUTSTANDING = 32,
  parameter int WINDOW_CYCLES   = 4096
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [7:0]  outstanding,        // from Chapter 19.6 Section 5
  input  logic [15:0] expected_by_little,  // R x L / burst
  input  logic        rready,

  output ocount_e     regime,
  output fmask_t      candidates,
  output logic [7:0]  min_seen,
  output logic [7:0]  max_seen,
  output logic [15:0] c_at_zero,
  output logic [15:0] c_at_max,
  output logic        regime_stable
);
  logic [15:0] window;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      min_seen <= 8'hFF; max_seen <= 8'd0;
      c_at_zero <= '0; c_at_max <= '0; window <= '0;
    end else begin
      if (outstanding < min_seen) min_seen <= outstanding;
      if (outstanding > max_seen) max_seen <= outstanding;
      if (outstanding == 8'd0)                   c_at_zero <= c_at_zero + 16'd1;
      if (outstanding == 8'(MAX_OUTSTANDING))    c_at_max  <= c_at_max + 16'd1;
      window <= window + 16'd1;
    end
  end

  always_comb begin
    // The regime is about where the count SPENDS its time, not where
    // it is at any instant -- a healthy count touches zero between
    // bursts and touches the limit under load.
    if      (c_at_zero > ((window * 16'd3) / 16'd4)) regime = O_ZERO;
    else if (c_at_max  > ((window * 16'd3) / 16'd4)) regime = O_PINNED;
    else if (window > 16'd1000)                      regime = O_NORMAL;
    else                                             regime = O_UNKNOWN;

    case (regime)
      O_ZERO:   candidates = CLASS_ZERO;
      O_PINNED: candidates = CLASS_PINNED;
      O_NORMAL: candidates = CLASS_NORMAL;
      default:  candidates = ALL_F;
    endcase

    regime_stable = (window > 16'(WINDOW_CYCLES));
  end
endmodule

Classification: a three-way regime detector over an existing counter, and the thresholds are about occupancy in time rather than about instantaneous value.

What it teaches: that a healthy outstanding count visits both extremes and lives at neither. It touches zero between bursts and touches the limit under load — so an instantaneous reading of zero proves nothing and three quarters of a window at zero proves a great deal. The regime test is a time-occupancy test, which is the same shape as Chapter 21.6 §9's clustering probe and for the same reason: the interesting property is where a quantity spends its time.

And it teaches that expected_by_little is an input the block does not use, deliberately. Chapter 19.6 §4's Little's-law figure is what the count should be — 13 bursts at 1 µs and 1 KiB — and comparing the measured count against it separates nothing in Section 4's table. A count of 9 against an expected 13 is a slower interconnect, not a fault; a count of 0 is a fault whatever the expectation was.

Deliberately simplified: the three-quarters thresholds are judgements, and a burst-y workload can put a healthy count at zero for three quarters of a short window. window counts cycles and never resets, so the ratios are since reset — the windowing gap Chapter 21.6 §18 and Chapter 21.3 §3 both flagged. And rready is an input that is never read, where it would immediately confirm the pinned regime's cause.

Production implication: rready is the signal that turns the pinned regime from one class into a proof. A count pinned at the limit with RREADY continuously low is Chapter 19.6 §19's undersized reorder buffer with no ambiguity at all; the same count with RREADY high is an interconnect that has stopped responding, which is somebody else's fault entirely. One signal, already routed to the reorder sink, and it splits the pinned class's single member into two whose owners are different teams.


6. Three Classes From One Counter

The memory interface chapter built an outstanding tracker to enforce a limit, and read as a diagnostic it takes three values. Under Little's law the count should be the bandwidth latency product divided by the burst size: thirteen bursts at one microsecond of memory latency with one kilobyte bursts, sixty eight at five and a half microseconds. But the number between the extremes discriminates nothing, because a faster interconnect gives a lower count and no fault at all. What discriminates is the regime. A count that spends three quarters of a window at zero means nothing is being requested, which is four of the eight failures: an exhausted ring, diverged pointers, a buffer too small for the frames, or a completion that was never signalled. A count pinned at its maximum means requests are being issued and responses are not being consumed, which is exactly one failure, the undersized reorder buffer, and is therefore worth three bits on its own. And a normal count means the transfer is working and the fault is in ownership or ordering, which is the remaining three: a stale ownership read, a descriptor written before its data landed, and a commit taken per burst. Those three deliver every frame and no counter in the whole debugging module sees any of them.The outstandingcount19.6 Section 5Zeronothing requestedPinned at thelimitnothing answeredNormalLittle's law satisfied4 failuresring, ptr, buffer,completion1 failurethe reorder buffer — 3bits3 failuresownership and orderingAll framesdeliveredno counter sees them1.41 bitsexpectedfrom an existingregister12
Figure 2 — the outstanding count as a three-valued diagnostic: zero, pinned, or normal.

Section 4's table, read as a diagnosis.

RegimeCandidatesWhat it eliminates
zeroring exhausted, pointers diverged, buffer too small, no completionthe ordering failures and the reorder buffer
pinnedthe reorder buffereverything else — 7 of 8
normalstale ownership, descriptor early, per-burst commiteverything that stops the flow

One counter, eight failures, and the information content is worth computing because it is unusually good.

RegimeFailures it namesBits
zero4 of 81.00
pinned1 of 83.00
normal3 of 81.42
expected1.41

An expected 1.41 bits from a counter that already exists, against ceil(log₂ 8) = 3 bits to name a failure outright. Just under half the job, from one register read — and the pinned regime alone is worth three bits, because it has exactly one member.

And the three classes have completely different characters, which decides what to do next.

ClassCharacterNext step
zerothe flow stoppedwhy is nothing being asked for
pinnedthe flow is blockedRREADY, and then Chapter 19.6 §19
normalthe flow is fine and the data is wrongChapter 19.6 §13's commit test

Row three is the class that produces no loss at all and the worst bugs. Stale ownership, a descriptor written early, a per-burst commit — all three deliver every frame, the counters are healthy, the outstanding count is exactly what Little's law predicts, and the driver reads frames with stale tails. Chapter 19.6 §22's second complaint is that symptom and its test is one counter: c_frames_committed against the frame count.

Two of the three classes are about frames that did not arrive. The third is about frames that arrived wrong, and no drop counter anywhere will ever show it.

Which is the boundary between this chapter and Chapter 21.6.

Chapter 21.6this chapter's normal regime
frames deliveredshortcomplete
frames_inframes_deliverednon-zerozero
the faultsomething dropped themsomething delivered them wrong
the instrumentcountersthe data itself

Row four is why the normal regime is the hardest of the three. Its faults are invisible to every counter in Module 21the frames are all there — and the only evidence is that the contents are wrong, which is Chapter 20.3's scoreboard rather than a diagnostic counter.


7. RTL 3 — The Ownership-Transition Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// ownership_transition_monitor -- watch the one bit two parties write,
// and catch the transitions that should not happen.
//
// The MAC sets ownership to software. The driver sets it to hardware.
// Anything else -- a descriptor that becomes hardware-owned without the
// driver acting, or one the MAC writes twice -- is failure 2 or 5.
// ---------------------------------------------------------------------
module ownership_transition_monitor
  import dmadiag_pkg::*;
#(
  parameter int RING_SIZE = 256
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        desc_read,
  input  logic [15:0] desc_index,
  input  logic        own_bit_read,
  input  logic        we_released,

  output logic [15:0] c_hw_owned_reads,
  output logic [15:0] c_sw_owned_reads,
  output logic [15:0] c_reread_after_release,
  output logic        double_ownership,
  output logic        stale_read_suspected,
  output logic [15:0] c_releases
);
  logic released [RING_SIZE];

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_hw_owned_reads <= '0; c_sw_owned_reads <= '0;
      c_reread_after_release <= '0; c_releases <= '0;
      double_ownership <= 1'b0; stale_read_suspected <= 1'b0;
      for (int i = 0; i < RING_SIZE; i++) released[i] <= 1'b0;
    end else begin
      if (we_released) begin
        released[desc_index] <= 1'b1;
        c_releases <= c_releases + 16'd1;
      end
      if (desc_read) begin
        if (own_bit_read) c_hw_owned_reads <= c_hw_owned_reads + 16'd1;
        else              c_sw_owned_reads <= c_sw_owned_reads + 16'd1;

        // We released this descriptor to software and are now reading
        // it as hardware-owned again. Either the driver refilled it --
        // normal -- or we are seeing a stale copy of our own write.
        if (released[desc_index] && own_bit_read) begin
          c_reread_after_release <= c_reread_after_release + 16'd1;
          released[desc_index] <= 1'b0;
        end
      end
    end
  end

  // Chapter 18.2's class 75 in this chapter's vocabulary: the ownership
  // bit is a memory location and a memory system may reorder the writes
  // that reach it. A re-read that is TOO SOON after our own release is
  // the signature.
  always_comb begin
    stale_read_suspected = (c_reread_after_release > (c_releases >> 2));
    double_ownership     = 1'b0;   // needs the driver's view -- Section 8
  end
endmodule

Classification: a transition watcher over a bit that two parties write and only one of which this block can see.

What it teaches: that the MAC can detect a suspicious re-read and cannot detect a genuine double ownership. A descriptor the MAC released and then reads as hardware-owned again is either the driver having refilled it — which is the normal cycle — or a stale copy of the MAC's own write. The two are distinguished by how soon, and "soon" is a property of the memory system rather than of the ring.

And it teaches that double_ownership is wired to zero deliberately. Detecting that both parties believe they own a descriptor requires the driver's view, which is in host memory and is not read by the MAC. It is the same shape as Chapter 21.4's partner status: the decisive observation belongs to the other party, and a design that does not mirror it cannot make the observation at any price.

Deliberately simplified: released is a RING_SIZE-bit array of flops — 256 flops for a 256-entry ring, and a real ring is thousands. The quarter-of-releases threshold for stale_read_suspected is a judgement with no basis in the memory system's actual reordering window. And there is no timestamp, so "too soon" is not measured at all: the block counts re-reads and infers.

Production implication: the missing timestamp is what would make this block decisive rather than suggestive. Chapter 18.2 §19's class 75 is about a multi-word structure observed while the memory system may reorder the writes, and the defence there was ordering rather than detection. Here a single cycle counter between a release and its re-read — sixteen flops — turns "we re-read it soon after releasing" into "we re-read it 40 cycles after releasing, and the interconnect's write-to-read visibility is 200", which is a proof. The instrument is cheap and the number it needs comes from the interconnect's documentation.


8. The Ordering Failures the Count Cannot See

Section 6's normal regime holds three failures and they share a property: every frame is delivered and the outstanding count is exactly what Little's law predicts.

FailureFrames deliveredOutstandingWhat is wrong
stale ownership readall of themnormalinto a buffer the driver also owns
descriptor written before dataall of themnormalthe driver reads before the data lands
committed per burstall of themnormalthe tail of a multi-burst frame is stale

No counter in Module 21 sees any of the three, because every counter in Module 21 counts frames and all the frames are there.

What the three have in common is Chapter 18.3's class 76 — custody is not visibility — and Chapter 19.6 §13's commit tracker was built to get it right:

A frame is several bursts, and its descriptor may be marked done only when the last of them has returned its write response. Not when the last was issued, and not when the first responded.

Those three moments are the three failures.

When the descriptor is markedFailureSymptom
when the last burst returns its responsenone — correct
when the last burst is issuedfailure 3the driver may read before the write lands
when the first burst respondsfailure 4the tail of the frame is stale

And Chapter 19.6 §22's second complaint gives the test for the pair, in one counter.

Test
c_frames_committed against the frame countequal is correct; larger is per-burst committing
why it worksa multi-burst frame commits once, not once per burst
what it costsone comparison of two existing counters

A frame at 1 518 octets with 1 KiB bursts is two bursts, so a per-burst commit produces two commits per frame and c_frames_committed runs at roughly twice the frame count. The ratio is the diagnosis and the ratio's value is the mean bursts per frame, which is computable from the mean frame size and the burst size.

Mean frame sizeBursts per frame, 1 KiBc_frames_committed ratio if per-burst
64 octets11.00 — invisible
645.6 octets11.00 — invisible
1 518 octets22.00
9 000 octets99.00

Rows one and two are the blind case and they are the common one. A frame smaller than a burst is one burst, so committing per burst and committing per frame are the same thing — and the bug is invisible on any traffic whose frames are below the burst size. Chapter 19.6 §22's fourth row said the same thing from the other direction: slowing the interconnect makes the bug disappear, and this is the traffic-side version of the same conditionality.

Which is Chapter 21.5's structural zero appearing for the third time in the module.

ChapterThe fault is invisible when
Chapter 21.5 §6the frames are 64 octets — no late collision is possible
Chapter 21.6 §6the traffic is one size — the index cannot move
this chapterthe frames fit in one burst — one commit either way

Three different faults, three different mechanisms, and the same shape: a threshold in the frame size below which the evidence does not exist. In all three the threshold is a design constant — the slot time, the size variance, the burst length — and in all three a regression built from minimum-size frames finds nothing.


9. RTL 4 — The Commit-Completeness Checker

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// commit_completeness -- Chapter 19.6 Section 13's `closed` term, read
// as a diagnostic rather than enforced as a design rule.
//
// One counter against another: commits against frames. Equal is
// correct. A ratio near the mean bursts per frame is a per-burst
// commit, which is failure 4 and produces stale frame tails.
// ---------------------------------------------------------------------
module commit_completeness
  import dmadiag_pkg::*;
#(
  parameter int BURST_OCTETS = 1024
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [31:0] c_frames_written,
  input  logic [31:0] c_frames_committed,
  input  logic [31:0] c_bursts_issued,
  input  logic [15:0] mean_frame_octets,

  output logic [15:0] commit_ratio_x100,
  output logic [15:0] bursts_per_frame_x100,
  output logic        commits_per_burst,
  output logic        commits_correct,
  output logic        test_is_blind,
  output logic        ordering_suspect
);
  always_comb begin
    commit_ratio_x100 = (c_frames_written == 32'd0) ? 16'd0
      : 16'((c_frames_committed * 32'd100) / c_frames_written);

    bursts_per_frame_x100 = (c_frames_written == 32'd0) ? 16'd100
      : 16'((c_bursts_issued * 32'd100) / c_frames_written);

    // A ratio near 1.00 is correct. A ratio near the bursts-per-frame
    // figure is a commit per burst.
    commits_correct = (commit_ratio_x100 >= 16'd95) &&
                      (commit_ratio_x100 <= 16'd105);

    commits_per_burst = (bursts_per_frame_x100 > 16'd150) &&
                        (commit_ratio_x100 > (bursts_per_frame_x100 - 16'd30));

    // Section 8: a frame smaller than a burst is one burst, so the two
    // policies are indistinguishable. The test cannot fire.
    test_is_blind = (mean_frame_octets <= 16'(BURST_OCTETS));

    // Failure 3 -- descriptor before data -- does not change either
    // count. It changes the ORDER, and only Chapter 19.6 Section 12's
    // sequencing sees it.
    ordering_suspect = commits_correct && !test_is_blind;
  end
endmodule

Classification: a ratio test between two counters that a correct design keeps equal.

What it teaches: that test_is_blind covers most real traffic and that is the finding, not the caveat. A 1 KiB burst against a 645.6-octet mean frame means almost every frame is one burst, so committing per burst and per frame produce identical counts — and the bug that produces stale frame tails is completely invisible on ordinary traffic. It appears when the traffic turns to jumbo frames or when the burst size is reduced, which is a configuration change rather than a fault.

And it teaches that ordering_suspect is a residual rather than a detection. Failure 3 — the descriptor written before the data lands — changes neither counter: both the data bursts and the descriptor write are issued, both complete, and only their order is wrong. The commit ratio is exactly 1.00 and the frames are corrupt, so this block's output on failure 3 is "commits look correct", which is true and useless.

Deliberately simplified: commits_per_burst's tolerance of 30 hundredths is a judgement. bursts_per_frame_x100 is derived from a burst counter that may include descriptor fetches, which would inflate it. And ordering_suspect asserts on every healthy link with large frames, which makes it a poor name for what is really "this block has nothing to say."

Production implication: failure 3 is the one this chapter cannot reach with counters and it is the most damaging of the three. A descriptor marked done before the frame data has landed hands the driver a buffer whose contents are partly stale, and the corruption is silent, intermittent and rate-dependent — Chapter 19.6 §22's second complaint noted that slowing the interconnect makes it disappear. The instrument that catches it is not a counter at all: it is Chapter 19.6 §12's sequencing, enforced rather than observed — the design either orders the data before the descriptor or it does not, and a property is the right tool because the fault is a design decision rather than a runtime event.


10. Pricing the Consumer Stall

Chapter 20.5 §12 proved the consumer has to stop for a frame to be lost. This section is what each duration of stop costs.

The receive FIFO's capacity in wire time:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
512 beats × 512 bits / 100 Gb/s = 2.621 µs

Chapter 19.6 §8's design budget is 391 beats — 2.002 µs — and the margin between them is 0.620 µs.

Time
the FIFO's capacity2.621 µs
Chapter 19.6 §8's budgeted stall2.002 µs
the margin0.620 µs
what the margin coversthe pause round trip and the synchroniser reserve

Beyond 2.621 µs, frames are lost at the line rate.

Frame sizeFrames per microsecond at 100 Gb/s
64 octets148.8
1 518 octets8.13

So the price list:

The stallBeyond the FIFOMinimum-size framesMaximum-size
a DDR4 refresh, 350 ns000
Chapter 19.6 §8's 2.000 µs budget000
2.621 µs — exactly the FIFO000
10 µs7.379 µs1 09860
100 µs97.4 µs14 493792
1 ms997.4 µs148 4198 106

Four readings of that table.

First, a DRAM refresh is free with a factor of seven to spare. DDR4's tRFC is around 350 ns and the FIFO absorbs 2 621. A design that loses frames to refreshes has a buffer far below Chapter 19.5 §3's sizing, which is Chapter 19.6 §22's first complaint and is caught at elaboration by buffer_undersized.

Second, the margin is 0.620 µs and it is not spare capacity. Chapter 19.5 §3 sized the FIFO as a sum — the memory stall, the pause round trip, the synchroniser reserve — so the 0.620 µs beyond the stall budget is allocated to the other two terms. Spending it on a longer stall means a pause frame arrives too late.

Third, the relationship is linear and steep. Every microsecond beyond the FIFO costs 148.8 minimum-size frames, which at a typical 1 ms scheduling quantum is a hundred and fifty thousand.

Fourth, the maximum-size column is eighteen times smaller and that is not a mitigation. A link carrying large frames loses fewer frames and the same octets8.13 frames per microsecond at 1 518 octets is 12.3 KiB, and 148.8 at 64 octets is 9.5 KiB — so the byte loss is within 30% either way. The frame count differs by eighteen and the data loss barely differs at all.

Which gives the one framing of this cost that survives a change of rate or frame size.

A stall beyond the FIFO loses the wire. Every nanosecond past 2.621 µs is a nanosecond of line rate that arrived and had nowhere to go — 12.5 gigabytes per second, whatever it was carved into.

Lost per microsecond of overshoot
at 100 Gb/s12.5 KB
at 10 Gb/s1.25 KB
frames, at 64 octets148.8 or 14.88
frames, at 1 518 octets8.13 or 0.813

The octet figure is the rate and nothing else, which makes it the number to quote when the frame size is unknown — and the frame figure is what a driver's ring-refill logic actually has to keep up with, which is why both appear in Section 11's estimate.


11. RTL 5 — The Stall Budget Tracker

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// stall_budget_tracker -- measure the consumer's stalls against the
// budget Chapter 19.5 Section 3 allocated, and price the overshoot.
//
// The block's premise is Chapter 20.5 Section 12's proof: no frame can
// cause this. So the stall's duration is a property of the memory
// system and the loss is a deterministic function of it.
// ---------------------------------------------------------------------
module stall_budget_tracker
  import dmadiag_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,
  input  logic        tick_ns,

  input  logic        consumer_stalled,
  input  logic [15:0] mean_frame_octets,

  output logic [31:0] current_stall_ns,
  output logic [31:0] longest_stall_ns,
  output logic [31:0] c_stalls,
  output logic [31:0] c_stalls_over_budget,
  output logic [31:0] c_stalls_over_fifo,
  output logic [31:0] frames_lost_estimate,
  output logic        within_budget,
  output logic        within_fifo
);
  logic stalled_prev;

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      current_stall_ns <= '0; longest_stall_ns <= '0;
      c_stalls <= '0; c_stalls_over_budget <= '0; c_stalls_over_fifo <= '0;
      frames_lost_estimate <= '0; stalled_prev <= 1'b0;
    end else begin
      stalled_prev <= consumer_stalled;

      if (consumer_stalled && tick_ns)
        current_stall_ns <= current_stall_ns + 32'd1;

      if (!consumer_stalled && stalled_prev) begin
        c_stalls <= c_stalls + 32'd1;
        if (current_stall_ns > longest_stall_ns)
          longest_stall_ns <= current_stall_ns;
        if (current_stall_ns > 32'(BUDGET_NS))
          c_stalls_over_budget <= c_stalls_over_budget + 32'd1;
        if (current_stall_ns > 32'(FIFO_NS)) begin
          c_stalls_over_fifo <= c_stalls_over_fifo + 32'd1;
          // Section 10: 148.8 minimum-size frames per microsecond of
          // overshoot, scaled by the mean frame size.
          frames_lost_estimate <= frames_lost_estimate +
            ((current_stall_ns - 32'(FIFO_NS)) * 32'd1488) /
            (32'd10000 * 32'(mean_frame_octets) / 32'd64);
        end
        current_stall_ns <= '0;
      end
    end
  end

  assign within_budget = (longest_stall_ns <= 32'(BUDGET_NS));
  assign within_fifo   = (longest_stall_ns <= 32'(FIFO_NS));
endmodule

Classification: a duration histogram collapsed to three counters and a loss estimate.

What it teaches: that the three thresholds are three different statements and a design needs all three. within_budget says the stalls fit what Chapter 19.6 §8 planned for; within_fifo says they fit what the buffer can absorb — and between them is Chapter 19.5 §3's 0.620 µs of margin, which is allocated to the pause round trip and the synchroniser reserve rather than to stalls. A design living in that margin is losing nothing and has spent somebody else's budget.

And it teaches that longest_stall_ns matters more than c_stalls. A thousand stalls of 300 ns cost nothing; one stall of 10 µs costs 1 098 frames. The loss is a function of the maximum rather than of the mean or the count, which is the opposite of how a stall counter is usually read.

Deliberately simplified: tick_ns at nanosecond granularity is a 1 GHz reference the design does not have — a real implementation counts in its own clock and scales. The frames_lost_estimate arithmetic divides twice with integer truncation and will read zero for mean frame sizes above about 6 400 octets. And consumer_stalled is an input, so the block does not say what "stalled" means: Chapter 19.6 §11's arbiter, a deasserted RREADY, or a descriptor fetch that has not returned are three different conditions with the same consequence.

Production implication: that last ambiguity is worth resolving in the port list rather than in the documentation. Three stall sources produce identical FIFO behaviour and completely different fixes — an arbitration policy, an interconnect, a descriptor ring — and a single consumer_stalled bit merges them exactly as Chapter 21.2 §11 warned about merging discard reasons. Three bits instead of one, one per source, and the stall's duration histogram becomes attributable. It is the same three-bit argument Chapter 21.6 §13 made about drop reasons, in a different block.


12. 148.8 Frames per Microsecond, and Where the Budget Goes

The receive buffer chapter sized the first in first out buffer as a sum of three terms: three hundred and ninety one beats allowing for a two microsecond memory stall, one hundred and twenty three beats of pause round trip headroom, and four beats of synchroniser reserve, rounded to five hundred and twelve beats, which is two point six two one microseconds of wire time at one hundred gigabits per second. A consumer stall spends those allocations in order. A stall below two point zero zero two microseconds is the design point and costs nothing. A stall between two point zero zero two and two point six two one drops no frames at all and consumes part of the flow control headroom, so a pause frame sent at that moment arrives too late and the next event that would have been absorbed will not be: it is a failure with no symptom, invisible to every counter, one comparison away from visible. Beyond two point six two one microseconds frames are lost at the line rate, which is one hundred and forty eight point eight minimum size frames per microsecond or eight point one three maximum size ones. A ten microsecond interconnect stall is one thousand and ninety eight frames; a one millisecond scheduling gap is one hundred and forty eight thousand four hundred and nineteen. And a bigger buffer is not the answer beyond a few microseconds, because absorbing one millisecond at one hundred gigabits per second would need twelve and a half megabytes.512 beats = 2.621usa sum of three terms391 beats: thestall2.002 us — the designpoint123 beats: pause0.630 us of headroom4 beats:synchroniser0.020 us2.002 to 2.621 usno symptom at allBeyond 2.621 us148.8 frames per us10 us stall1 098 frames1 ms gap148 419 frames12
Figure 3 — three allocations, spent in order, and only the third overflow drops a frame.

Chapter 19.5 §3 sized the receive FIFO as a sum of three terms. This section is what each term is for and what spending it costs.

TermBeatsTimeWhat it covers
the memory stall allowance3912.002 µsChapter 19.6 §8's consumer stall
the pause round trip1230.630 µsChapter 14.2's headroom
the synchroniser reserve40.020 µsclock-domain crossing
rounded down to5122.621 µsthe FIFO

The three terms are additive and they are not interchangeable.

A stall of 2.4 µs loses no frames and consumes 0.4 µs of the pause round trip's headroom. Nothing is dropped and a pause frame sent at that moment arrives too late.

Which makes the budget's boundaries three different failures rather than one gradient.

Longest stallFrames lostWhat else is affected
below 2.002 µs0nothing — this is the design point
2.002 to 2.621 µs0the pause headroom, partly spent
above 2.621 µs148.8 per µseverything

Row two is the invisible failure and it is the one a stall counter reports as healthy. No frames are lost, every counter is clean, and the link's flow control has quietly lost its margin — so the next event that would have been absorbed is not.

And the loss rate itself is worth putting beside the things it is usually compared against.

Frames lost
a 10 µs stall1 098
a 1-in-10⁷ bit error rate for a second, at minimum sizeabout 15
Chapter 21.5's duplex mismatch at 10% load, per secondabout 13.6 million
a 1 ms scheduling gap148 419

Row one against row two is the comparison that reframes a stall. A ten-microsecond interconnect hiccup loses seventy times more frames than a second of a marginal channel — and it leaves no error counter moving at all, because Chapter 21.6 established that a FIFO drop is a class G drop. The loudest fault in Module 21 is silent and the quietest one is loud.

Row four is the number to put in a driver review. A millisecond is an ordinary scheduling quantum, an ordinary interrupt-coalescing interval and an ordinary NAPI budget; at 100 Gb/s it is a hundred and forty-eight thousand frames. The mitigation is not a bigger FIFO — a millisecond of FIFO at 100 Gb/s is 12.5 MBit is never stalling for a millisecond, which is a software property and is why Chapter 19.5 §3 budgeted 2 µs rather than 1 ms.

FIFO needed to absorb it
2.0 µs32 KiB — built
10 µs122 KiB
1 ms12.5 MB — not a buffer, a memory

The curve is linear and the answer is not a buffer. Chapter 19.5 §3's sizing is an argument that beyond a few microseconds the correct response is flow control rather than storage, and the pause round trip in the FIFO's own budget is that argument made into 123 beats.


13. RTL 6 — The Cross-Path Bridge

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// crosspath_bridge -- the block that exists because a transmit-path
// stall becomes a receive-path loss with no signal connecting them.
//
// Chapter 19.6 Section 22's first complaint: the buffer that overflowed
// is on the TRANSMIT read path and the frames lost are on the RECEIVE
// path. The only coupling is a shared RREADY, which appears in neither
// path's interface.
// ---------------------------------------------------------------------
module crosspath_bridge
  import dmadiag_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  // Transmit read path.
  input  logic        tx_reorder_full,
  input  logic        tx_rready,
  input  logic        buffer_undersized,   // Chapter 19.6 Section 19

  // Receive write path.
  input  logic        rx_fifo_full,
  input  logic        rx_frame_dropped,

  // The shared resource.
  input  logic        shared_interconnect,

  output logic [31:0] c_rx_drops_during_tx_stall,
  output logic [31:0] c_rx_drops_total,
  output logic [15:0] coincidence_ppt,
  output logic        cross_path_coupled,
  output logic        coupling_invisible_locally
);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_rx_drops_during_tx_stall <= '0; c_rx_drops_total <= '0;
    end else if (rx_frame_dropped) begin
      c_rx_drops_total <= c_rx_drops_total + 32'd1;
      // The correlation nobody looks for: a receive drop while the
      // TRANSMIT path's reorder buffer is full.
      if (tx_reorder_full || !tx_rready)
        c_rx_drops_during_tx_stall <= c_rx_drops_during_tx_stall + 32'd1;
    end
  end

  always_comb begin
    coincidence_ppt = (c_rx_drops_total == 32'd0) ? 16'd0
      : 16'((c_rx_drops_during_tx_stall * 32'd1000) / c_rx_drops_total);

    // Nearly every receive drop happening while the transmit reorder
    // buffer is full is not a coincidence.
    cross_path_coupled = shared_interconnect &&
                         (c_rx_drops_total > 32'd64) &&
                         (coincidence_ppt > 16'd800);

    // Section 20's rejected class: neither path's interface contains
    // the other's signals, so a property scoped to one path cannot
    // express this and a reviewer of one path cannot see it.
    coupling_invisible_locally = shared_interconnect;
  end
endmodule

Classification: a correlation between two paths that share nothing in their interfaces.

What it teaches: that coincidence_ppt is a number nobody computes because nobody thinks to. A receive drop and a transmit reorder buffer are in different blocks, owned by different engineers, reviewed in different documents — and eight hundred parts per thousand of receive drops happening while the transmit buffer is full is a proof of coupling that costs two counters and a division.

And it teaches that coupling_invisible_locally is a property of the design's structure rather than of its state. It is true whenever the two paths share an interconnect, which is always, and it means: no property written against the receive path's interface can express this fault, because the transmit path's signals are not in that interface. Section 20's rejected class is exactly that.

Deliberately simplified: the correlation is instantaneous rather than windowed, so a receive drop just after the transmit buffer drains is not counted. shared_interconnect is a parameter-like input describing the SoC's topology. And the block cannot distinguish "the transmit stall caused the receive drop" from "a third thing caused both" — which is the honest position, because a busy interconnect does cause both.

Production implication: the third-cause ambiguity is not a weakness of the measurement; it is the correct reading. Chapter 19.6 §22's first complaint traced it: an undersized transmit reorder buffer deasserts RREADY, which stalls the shared read channel, which delays the receive path's descriptor fetches, which fills the receive FIFO. The chain has four links and buffer_undersized at elaboration identifies it before any of them happens — which is why that chapter concluded an elaboration-time check is worth more than any runtime counter. This block is what to do when the elaboration check was not there.


14. What a DMA Diagnosis Must Never Do

Six prohibitions. Two are about the outstanding count, two about the stall, and two about where a symptom's cause lives.

NeverBecause
1read the outstanding count's value as a diagnosisSection 5 — the regime discriminates, the number does not
2conclude a fault from a count below Little's lawa slower interconnect gives a lower count and no fault
3report a stall's mean or countSection 11 — the loss is a function of the maximum
4treat the 0.620 µs margin as spareSection 12 — it is the pause round trip's
5look for a receive drop's cause on the receive pathSection 13 — the coupling is a shared interconnect
6conclude "the DMA is fine" from a healthy frame countSection 8 — three failures deliver every frame

Row five is the prohibition that this chapter exists to state. Chapter 19.6 §22's first complaint traced a four-link chain: an undersized transmit reorder buffer deasserts RREADY, the shared read channel stalls, the receive path's descriptor fetches are delayed, the receive FIFO fills. A team investigating receive drops does not look at the transmit reorder buffer and has no reason to — it is not in the receive path's interface, its document or its owner's remit.

Row six is the prohibition with the worst consequences. Stale ownership, a descriptor written early and a per-burst commit all deliver every frameframes_in equals frames_delivered, the outstanding count matches Little's law, every counter in Module 21 is clean — and the driver reads frames with stale tails. "No drops" and "the DMA is fine" are different statements and nothing in a counter distinguishes them.

And the two that look like statistical hygiene and are not:

Why it is a prohibition
row threea thousand 300 ns stalls cost nothing; one 10 µs stall costs 1 098 frames
row foura stall of 2.4 µs drops nothing and spends the flow-control headroom

Both report a number that is true and is about the wrong moment — which is what all six share: in this chapter the extremes matter and the averages do not.


15. RTL 7 — DMA Diagnostic Telemetry

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// dmadiag_telemetry -- three groups: the regime, the cost, and the
// coupling.
//
// The cost group is the one no other chapter in Module 21 has, because
// this is the only fault whose damage is a deterministic function of a
// measurable duration.
// ---------------------------------------------------------------------
module dmadiag_telemetry
  import dmadiag_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  ocount_e     regime,
  input  fmask_t      candidates,
  input  logic        regime_stable,
  input  logic [31:0] longest_stall_ns,
  input  logic [31:0] frames_lost_estimate,
  input  logic [15:0] commit_ratio_x100,
  input  logic        test_is_blind,
  input  logic [15:0] coincidence_ppt,
  input  logic        cross_path_coupled,
  input  logic        buffer_undersized,

  // Regime.
  output logic [2:0]  n_candidates,
  output logic [1:0]  regime_out,
  output logic        single_failure,

  // Cost.
  output logic [31:0] worst_stall_ns,
  output logic [31:0] overshoot_ns,
  output logic [31:0] frames_lost,
  output logic        headroom_spent,
  output logic        loss_occurring,

  // Coupling.
  output logic        look_at_transmit_path,
  output logic        elaboration_check_would_have_caught_it
);
  assign n_candidates = 3'(popcount8(candidates));
  assign regime_out   = regime[1:0];
  assign single_failure = (n_candidates == 3'd1) && regime_stable;

  assign worst_stall_ns = longest_stall_ns;
  assign frames_lost    = frames_lost_estimate;

  always_comb begin
    overshoot_ns = (longest_stall_ns > 32'(FIFO_NS))
                 ? (longest_stall_ns - 32'(FIFO_NS)) : 32'd0;

    // Section 12's row two: no frames lost and the pause round trip's
    // headroom partly consumed. The invisible failure.
    headroom_spent = (longest_stall_ns > 32'(BUDGET_NS)) &&
                     (longest_stall_ns <= 32'(FIFO_NS));

    loss_occurring = (overshoot_ns != 32'd0);

    // Section 13: the receive path's drops have a transmit-path cause.
    look_at_transmit_path = cross_path_coupled;

    // Chapter 19.6 Section 22: buffer_undersized fires at elaboration
    // and is worth more than every runtime counter in this block.
    elaboration_check_would_have_caught_it = buffer_undersized;
  end
endmodule

Classification: a reporting block whose middle group converts a duration into a frame count.

What it teaches: that headroom_spent names a failure with no symptom. A longest stall between 2.002 and 2.621 µs drops nothing — every counter is clean, the link is perfect — and Chapter 19.5 §3's pause round trip has lost part of its allowance. The next event that would have been absorbed will not be, and the only warning is a comparison between two constants and one measurement.

And it teaches that elaboration_check_would_have_caught_it is the most humbling output in Module 21. Chapter 19.6 §19's buffer_undersized is a compile-time comparison of a parameter against (O − 1) × burstit needs no traffic, no fault and no run — and when it is set, every counter in this block is describing a consequence of something that was decidable before the design was built.

Deliberately simplified: regime_out truncates a three-valued enum to two bits and loses O_UNKNOWN. frames_lost is passed through from an estimate with two integer divisions. And there is no best_possible — the ceiling output the previous three chapters all had — because this chapter's ceiling is not a property of the instrumentation but of whether the ordering failures are present, which cannot be known in advance.

Production implication: the missing ceiling is worth noting rather than adding. Chapter 21.1 §15, Chapter 21.4 §15 and Chapter 21.6 §15 all computed a ceiling from which counters exist — a platform fact, knowable at step zero. Here the three normal-regime failures are invisible to every counter regardless of which exist, so the ceiling depends on whether the fault is one of those three, which is exactly what is being diagnosed. The honest output is a warning: a healthy regime and healthy counters do not exonerate the DMA path.


16. RTL 8 — The DMA Diagnostic Conformance Monitor

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------
// dmadiag_conformance_monitor -- six verdicts, and the first one fires
// at elaboration on a design that will lose frames months later.
// ---------------------------------------------------------------------
module dmadiag_conformance_monitor
  import dmadiag_pkg::*;
#(
  parameter int REORDER_BYTES  = 12288,
  parameter int OUTSTANDING    = 13,
  parameter int BURST_BYTES    = 1024
)(
  input  logic        clk,
  input  logic        rst_n,

  input  ocount_e     regime,
  input  logic        regime_stable,
  input  logic [31:0] longest_stall_ns,
  input  logic        test_is_blind,
  input  logic [15:0] commit_ratio_x100,
  input  logic        tail_visible,
  input  logic        stale_read_suspected,
  input  logic [15:0] coincidence_ppt,

  output logic        buffer_undersized,
  output logic        headroom_spent,
  output logic        commit_test_blind,
  output logic        tail_not_mirrored,
  output logic        ordering_untested,
  output logic        regime_not_settled,
  output logic        diagnosis_sound
);
  // Chapter 19.6 Section 19, at elaboration: the reorder buffer must
  // hold (O - 1) bursts or the read channel will stall.
  localparam int REQUIRED_BYTES = (OUTSTANDING - 1) * BURST_BYTES;
  assign buffer_undersized = (REORDER_BYTES < REQUIRED_BYTES);

  // Section 12's invisible failure.
  assign headroom_spent = (longest_stall_ns > 32'(BUDGET_NS)) &&
                          (longest_stall_ns <= 32'(FIFO_NS));

  assign commit_test_blind = test_is_blind;

  // Section 3: sixteen flops would turn failure 5 from an inference
  // into a comparison.
  assign tail_not_mirrored = !tail_visible;

  // Section 8: failure 3 changes neither counter, so a clean commit
  // ratio is not evidence about ordering.
  assign ordering_untested = (commit_ratio_x100 >= 16'd95) &&
                             (commit_ratio_x100 <= 16'd105);

  assign regime_not_settled = !regime_stable;

  assign diagnosis_sound = !regime_not_settled && !commit_test_blind;

  always_ff @(posedge clk) begin
    if (rst_n && buffer_undersized)
      $display("[dmadiag] reorder buffer %0d B below the required %0d B -- this was decidable at elaboration",
               REORDER_BYTES, REQUIRED_BYTES);
  end
endmodule

Classification: an auditor whose first verdict is a parameter comparison and whose others are run-time.

What it teaches: that buffer_undersized is the only verdict in Module 21 that is decidable before the design exists. (O − 1) × burst against the reorder buffer's size is two parameters and a multiplicationChapter 19.6 §19 derived it and §22's first complaint showed its consequence — and a design that fails it will lose receive frames during DRAM refreshes, months later, for a reason four links away.

And it teaches that ordering_untested asserts on every healthy design, deliberately. A commit ratio near 1.00 is what correct commits look like and also what failure 3 looks like, because a descriptor written before the data still commits once per frame. The verdict says "this test had nothing to say about ordering", which is true on every run where the ratio is clean — and folding it into diagnosis_sound would make every sound diagnosis unsound.

Deliberately simplified: OUTSTANDING is a parameter where Chapter 19.6 §4 derives it from the bandwidth-latency product, so a design whose interconnect is slower than assumed passes the check and fails in the field. headroom_spent duplicates the telemetry's. And diagnosis_sound omits buffer_undersized, which is right: a design with an undersized buffer can still be correctly diagnosed — the diagnosis will simply be "the buffer is undersized".

Production implication: the OUTSTANDING parameter is the weak link and Chapter 19.6 §4 gives the fix. The required outstanding count is R × L / burst where L is the memory latency the interconnect actually delivers — 13 bursts at 1 µs, 68 at 5.5 µs — and a design parameterised on an assumed latency is a design whose reorder buffer is sized for a system it may not be integrated into. The check should take the latency as a parameter and the integration should supply it, which turns a silent field failure into a build error in somebody else's repository.


17. The Cross-Path Coupling, Measured

Chapter 19.6 §22's first complaint is the strangest diagnosis in the whole track, and it is worth following link by link.

LinkWhat happensWhich path
1the reorder buffer is below (O − 1) burststransmit read
2it fills, and RREADY deassertstransmit read
3the shared read channel stallsneither — the interconnect
4receive descriptor fetches are delayedreceive write
5the receive FIFO fills past 2.621 µsreceive
6frames are droppedreceive

Six links, two paths, and the only thing connecting them is link three — a shared channel that appears in neither path's interface.

Which makes the fault invisible to three different kinds of review.

ReviewerSeesMisses
the receive path's ownerframes dropped, FIFO fullwhy the fetches were slow
the transmit path's ownera full reorder bufferthat anything was dropped
the integratorboth, in different documentsthat they are the same event

And the correlation that proves it is two counters. Section 13's coincidence_pptreceive drops occurring while the transmit reorder buffer is full, as a share of all receive dropsis above 800 parts per thousand when the coupling is real and near the duty cycle of the transmit buffer's fullness when it is not.

CoupledCoincidental
receive drops during a transmit stallmost of themas often as the stall occurs
coincidence_ppt> 800≈ the stall's duty cycle
cost to measuretwo counters and a divisionthe same

The asymmetry in the two paths is what makes the coupling one-directional, and Chapter 19.6 §8 derived it.

Transmit pathReceive path
what a stall does firstthe transmit FIFO drains — 0.74 µsthe receive FIFO fills — 2.00 µs
what happens at the endthe wire idlesframes are dropped
recoverable?yes, completelyno
who can waitthe transmit path canthe receive path cannot

Time is only useful to a path that can spend it waiting. The transmit path has 0.74 µs and does not need it; the receive path has 2.00 µs and cannot use it, because its arrival rate does not depend on anything this design does.

So a shared stall costs the transmit path throughput and the receive path frames, and the receive path is the one that has no choice — Chapter 19.1 §11's "the receive path has no backpressure" stated as a consequence.

And the elaboration check dominates every runtime instrument here.

InstrumentWhen it firesWhat it costs
buffer_undersizedat elaborationtwo parameters and a multiply
coincidence_pptafter the dropstwo counters
the receive drop counterafter the dropsalready there
a capturehours laterhours

Row one is Chapter 19.6 §22's conclusion and it is the module's cleanest example of the general principle: a fault that is decidable from parameters should never be diagnosed from counters, and the cost difference is a multiplication against a field investigation.

And the coupling generalises past the reorder buffer, which is why Section 20 refuses a property rather than a design.

Shared resourceCouplesIn either path's interface?
the read channeltransmit reads and descriptor fetchesno
the write channelreceive writes and descriptor writebacksno
the outstanding-ID spaceevery path using the same AXI IDno — Chapter 19.6 §6
memory bandwidth itselfeverythingno

Four shared resources, none of them in a path's signal list, and every one of them can turn one path's pressure into another path's loss. Chapter 19.6 §6's ID space is the subtlest: two logically independent streams that share an AXI ID must be returned in order, so a slow response on one blocks the other — a coupling created by a four-bit field rather than by a buffer.


18. What the Diagnosis Assumes

Nine assumptions. Three are about the counter, three about the stall and three about the ring — and the first is false at every rate except the one it was written for.

AssumptionFromIf false
1the FIFO is 2.621 µs of wire timeChapter 19.5 §3 at 100 Gb/sat 10 Gb/s it is 3.277 µs and the loss rate is a tenth
2the outstanding count is visibleChapter 19.6 §5the whole three-class method is unavailable
3a healthy count visits zero and the limitburst-y traffica smooth workload never touches either
4the eight failures are mutually exclusiveconveniencetwo at once give a mixed regime
5consumer_stalled names one conditionthe port listSection 11 — three sources, one bit
6the driver's tail pointer is mirroredmost designs do notfailure 5 is an inference, not a comparison
7the frames are larger than a burstnothingSection 8 — the commit test is blind
8the memory latency matches the design's parameterintegrationthe reorder buffer is sized for another system
9the loss is a function of the longest stallChapter 19.5 §3back-to-back stalls compound before the FIFO refills

Row nine is the assumption this chapter's arithmetic quietly makes and it is the one that understates the loss. Section 10's table prices each stall independently, as if the FIFO were empty when it began. Two stalls a microsecond apart do not give the buffer time to drain, so the second one starts partly full and overshoots sooner:

Two 2.0 µs stalls
1 ms apartthe FIFO drains completely; no loss
1 µs apartthe second starts with 1.0 µs of occupancy
effective capacity for the second1.621 µs
frames lost56 at minimum size

Two stalls that are individually within budget lose frames when they are close together, and nothing in Section 11's tracker sees it — it measures each stall's duration and not the FIFO's occupancy when it began. The instrument that would is Chapter 19.5 §15's occupancy telemetry, which exists and is in a different block.

Row one is the rate-dependence and it cuts both ways. At 10 Gb/s a 512-beat FIFO at 64 bits per beat is 3.277 µs — a 25% larger budget — and 14.88 minimum-size frames per microsecond rather than 148.8. So the same stall is more survivable and ten times cheaper: a 10 µs stall costs 1 098 frames at 100 Gb/s and 100 at 10 Gb/s.

And three things deliberately not assumed:

Not assumedWhy not
that a healthy frame count exonerates the DMASection 8's three ordering failures
that a receive drop has a receive causeSection 17's six-link chain
that a stall within the FIFO is freeSection 12 — it spends the pause headroom

Row three is the one that is hardest to hold onto, because the instrument reports zero drops and the design is measurably worse off. Chapter 19.5 §3's FIFO is a sum of three allocations and a stall spends them in order — the memory budget first, then the pause round trip, then the synchroniser reserve — and only the third overflow drops a frame.

And one assumption the chapter shares with Chapter 21.6 and should not: that the eight failures occur one at a time.

Two failures at once
an exhausted ring and an undersized reorder bufferzero, then pinned, alternating
the regime classifier's readingwhichever it saw in the last window
the candidate maskwrong in both directions
what would detect ita regime transition counter

Row four is the instrument this chapter does not have and Section 21's run E shows why it matters. A memory system that stops responding pins the count while requests are outstanding and then drops it to zero when the ring exhauststwo regimes in one fault, in sequence — and the classifier reports whichever was last. A transition counter is about sixteen flops and it turns a sequence into evidence rather than into a race between two readings.


19. The Cost, Accounted

A DMA diagnosis is counters over an existing counter, plus one array that is a copy of the ring.

BlockFlopsNature
dmadiag_pkg0eight failures, three regimes, four constants
desc_ownership_model~64four counters
outstanding_regime~64two extrema and two occupancy counters
ownership_transition_monitor — 256-entry ring~320the released array, and it scales with the ring
commit_completeness0combinational
stall_budget_tracker~161durations and an estimate
crosspath_bridge~64two counters
dmadiag_telemetry0combinational
dmadiag_conformance_monitor0combinational
total~673 flops

One block is 48% of it and it scales with the ring. The ownership transition monitor's released array is one flop per descriptor — 256 for a small ring and four thousand for a 4 096-entry one — which makes it the only structure in Module 21 whose cost is set by a software data structure.

And the additions to a design are three, with very different value.

AdditionFlopsBuys
mirror the driver's tail pointer~16failure 5 becomes a comparison
three stall-source bits instead of one~3Section 11's attribution
a release-to-reread cycle counter~16failure 2 becomes a proof
all three~350.25% of the datapath

Thirty-five flops, and none of them is a counter in the usual sensea mirrored pointer, three source bits and a timestamp. Compare what the chapter's own machinery costs to observe the same things indirectly: 673 flops, and it still cannot separate failures 1 and 7 or detect failure 3 at all.

Thirty-five flops of representation beat 673 flops of inference, and that is the fourth time Module 21 has reached the same conclusion.

ChapterThe representation it wantedFlops
Chapter 21.2 §19c_unclassified~99
Chapter 21.3 §19the residue, logged per failing frame~0
Chapter 21.4 §19a latched gate-vector snapshot~5
Chapter 21.6 §13a three-bit drop reason~3
this chaptera mirrored pointer and three source bits~35

Five chapters, five tiny additions, about 142 flops between them — one per cent of the datapathand every one of them replaces a substantial inference apparatus with a read.

Module 21's running bill, seven chapters in:

ChapterFlops to addShare
Chapter 21.1 §19~2972.1%
Chapter 21.2 §19~4263.0%
Chapter 21.3 §19, logged~7925.6%
Chapter 21.4 §19~710.5%
Chapter 21.5 §19~4553.2%
Chapter 21.6 §1900.0%
this chapter~350.25%
total~2 07614.7%

And Chapter 21.6 §17's saving still applies, so the module's realistic bill is about 1 977 flops — 14.0% — for every diagnosis seven chapters have built.


20. Properties Worth Asserting, and One Worth Refusing

The natural property for a receive path reviewer asserts that a frame is dropped on the receive path only when the receive buffer is full, and anything else is a receive path bug. It holds. It is sound and complete for its scope. And it is why the fault takes two weeks. The chain has six links. The transmit read path's reorder buffer fills because it is below the outstanding count minus one times the burst size. Its ready signal deasserts. The shared read channel stalls. The receive path's descriptor fetches are delayed. The receive buffer fills past two point six two one microseconds. Frames are dropped. Links five and six are in the receive path's interface and links one to three are not: the transmit reorder buffer, the ready signal and the shared channel appear in no receive path signal list. So a property scoped to the receive path can express the last two steps of a six step chain and no more. Worse, it misdirects: it passes, the receive path is exonerated correctly, and the search continues within the receive path because that is where the symptom is. What can be asserted instead is the elaboration check, decidable from two parameters before the design exists; a progress property whose scope is the shared channel rather than either path; and a cover that the two paths were ever loaded at the same time.1. Reorder bufferfillstransmit read path2. RREADYdeassertstransmit read path3. Shared channelstallsneither path4. Fetchesdelayedreceive write path5. RX FIFO fillsin scope6. Frames droppedin scopeThe RX propertycovers 5 and 6 onlyAssert atelaborationtwo parameters12
Figure 4 — six links, two paths, and a property whose scope covers the last two.

Thirty-three properties and eight covers, in four groups: the regime, the ownership bit, the stall budget, and the coupling.

Group one — the outstanding count and its three regimes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Chapter 19.6 Section 5's counter, read as three mutually exclusive
// regimes rather than as a number.
p_regimes_exclusive:assert property (@(posedge clk) disable iff (!rst_n)
                      regime_stable |-> ($countones({regime == O_ZERO,
                                                     regime == O_PINNED,
                                                     regime == O_NORMAL}) == 1));

p_classes_partition:assert property (@(posedge clk) disable iff (!rst_n)
                      ((CLASS_ZERO | CLASS_PINNED | CLASS_NORMAL) == ALL_F) &&
                      ((CLASS_ZERO & CLASS_PINNED) == 8'd0) &&
                      ((CLASS_ZERO & CLASS_NORMAL) == 8'd0) &&
                      ((CLASS_PINNED & CLASS_NORMAL) == 8'd0));

p_zero_is_four:     assert property (@(posedge clk) disable iff (!rst_n)
                      (regime == O_ZERO) |-> (popcount8(candidates) == 4));

p_pinned_is_one:    assert property (@(posedge clk) disable iff (!rst_n)
                      (regime == O_PINNED) |-> (popcount8(candidates) == 1));

p_count_bounded:    assert property (@(posedge clk) disable iff (!rst_n)
                      outstanding <= 8'(MAX_OUTSTANDING));

p_pinned_needs_max: assert property (@(posedge clk) disable iff (!rst_n)
                      (regime == O_PINNED) |-> (max_seen == 8'(MAX_OUTSTANDING)));

p_regime_needs_win: assert property (@(posedge clk) disable iff (!rst_n)
                      (regime != O_UNKNOWN) |-> (window > 16'd1000));

p_little_not_used:  assert property (@(posedge clk) disable iff (!rst_n)
                      $stable(expected_by_little) |-> 1'b1);

Group two — the ownership bit, which two parties write.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p_release_sets_sw:  assert property (@(posedge clk) disable iff (!rst_n)
                      desc_released |=> released[$past(desc_index)]);

p_fetch_counts:     assert property (@(posedge clk) disable iff (!rst_n)
                      desc_fetched |=> (c_fetches == $past(c_fetches) + 16'd1));

p_empty_fetch:      assert property (@(posedge clk) disable iff (!rst_n)
                      (desc_fetched && !desc_owned_by_hw) |=>
                        (c_empty_fetches == $past(c_empty_fetches) + 16'd1));

p_exhausted_sticky: assert property (@(posedge clk) disable iff (!rst_n)
                      ring_exhausted |=> always ring_exhausted);

p_owned_nonneg:     assert property (@(posedge clk) disable iff (!rst_n)
                      (c_fetches >= c_releases) |->
                        (hw_owned_count == (c_fetches - c_releases)));

p_double_needs_far: assert property (@(posedge clk) disable iff (!rst_n)
                      !double_ownership);

p_reread_counted:   assert property (@(posedge clk) disable iff (!rst_n)
                      (desc_read && own_bit_read && released[desc_index]) |=>
                        (c_reread_after_release ==
                         $past(c_reread_after_release) + 16'd1));

p_stale_threshold:  assert property (@(posedge clk) disable iff (!rst_n)
                      stale_read_suspected |->
                        (c_reread_after_release > (c_releases >> 2)));

Group three — the stall budget, which is three constants and one measurement.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p_budget_lt_fifo:   assert property (@(posedge clk) disable iff (!rst_n)
                      (BUDGET_NS < FIFO_NS) && (MARGIN_NS == (FIFO_NS - BUDGET_NS)));

p_stall_monotone:   assert property (@(posedge clk) disable iff (!rst_n)
                      (consumer_stalled && tick_ns) |=>
                        (current_stall_ns > $past(current_stall_ns)));

p_longest_is_max:   assert property (@(posedge clk) disable iff (!rst_n)
                      longest_stall_ns >= current_stall_ns ||
                      consumer_stalled);

p_within_budget:    assert property (@(posedge clk) disable iff (!rst_n)
                      within_budget |-> within_fifo);

p_no_loss_in_fifo:  assert property (@(posedge clk) disable iff (!rst_n)
                      within_fifo |-> (frames_lost_estimate == 32'd0));

p_headroom_window:  assert property (@(posedge clk) disable iff (!rst_n)
                      headroom_spent |-> (!within_budget && within_fifo));

p_overshoot_zero:   assert property (@(posedge clk) disable iff (!rst_n)
                      within_fifo |-> (overshoot_ns == 32'd0));

p_loss_needs_over:  assert property (@(posedge clk) disable iff (!rst_n)
                      loss_occurring |-> (overshoot_ns > 32'd0));

p_elab_check:       assert property (@(posedge clk) disable iff (!rst_n)
                      buffer_undersized |-> (REORDER_BYTES < REQUIRED_BYTES));

Group four — the coupling, which is Section 20's subject.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p_coupling_needs_shared:assert property (@(posedge clk) disable iff (!rst_n)
                      cross_path_coupled |-> shared_interconnect);

p_coincidence_bounded:assert property (@(posedge clk) disable iff (!rst_n)
                      coincidence_ppt <= 16'd1000);

p_drops_counted:    assert property (@(posedge clk) disable iff (!rst_n)
                      rx_frame_dropped |=>
                        (c_rx_drops_total == $past(c_rx_drops_total) + 32'd1));

p_during_is_subset: assert property (@(posedge clk) disable iff (!rst_n)
                      c_rx_drops_during_tx_stall <= c_rx_drops_total);

p_invisible_flag:   assert property (@(posedge clk) disable iff (!rst_n)
                      shared_interconnect |-> coupling_invisible_locally);

p_look_at_tx:       assert property (@(posedge clk) disable iff (!rst_n)
                      cross_path_coupled |-> look_at_transmit_path);

p_commit_ratio_one: assert property (@(posedge clk) disable iff (!rst_n)
                      commits_correct |-> (commit_ratio_x100 >= 16'd95));

p_blind_below_burst:assert property (@(posedge clk) disable iff (!rst_n)
                      (mean_frame_octets <= 16'(BURST_OCTETS)) |-> test_is_blind);

p_sound_excludes:   assert property (@(posedge clk) disable iff (!rst_n)
                      diagnosis_sound |-> (!regime_not_settled && !commit_test_blind));

And eight covers, because six of these states need a fault no frame can produce.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
c_regime_zero:     cover property (@(posedge clk) regime == O_ZERO);
c_regime_pinned:   cover property (@(posedge clk) regime == O_PINNED);
c_headroom_spent:  cover property (@(posedge clk) headroom_spent);
c_loss:            cover property (@(posedge clk) loss_occurring);
c_coupled:         cover property (@(posedge clk) cross_path_coupled);
c_stale_read:      cover property (@(posedge clk) stale_read_suspected);
c_per_burst:       cover property (@(posedge clk) commits_per_burst);
c_empty_fetch:     cover property (@(posedge clk) desc_fetched && !desc_owned_by_hw);

21. Verification Scenarios

Fifty-eight scenarios, plus a five-run directed test whose stimulus is the memory system rather than the wire — because Chapter 20.5 §12 proved no frame can produce this fault.

The outstanding count's three regimes — 11 scenarios.

#ScenarioExpected
1steady traffic, 1 µs memory latency, 1 KiB burstscount near 13 — normal
2the same at 5.5 µs latencynear 68 — still normal
3the ring emptiedcount falls to zero
4RREADY held lowcount pinned at the limit
5a stale ownership readcount normal throughout
6a per-burst commitcount normal throughout
7the count at zero for 10 cyclesregime still normal — it visits zero
8at zero for three quarters of a windowregime O_ZERO
9the three class masks intersectedpairwise empty
10their unionall eight failures
11fewer than 1 000 cyclesO_UNKNOWN

The ring and ownership — 10 scenarios.

#ScenarioExpected
12a fetch finding the bit clearc_empty_fetches increments
13one such fetch, everring_exhausted sticky
14the MAC stops fetching when emptyno evidence at all — the blind case
15a release then an immediate re-read as hw-ownedc_reread_after_release
16a quarter of releases re-readstale_read_suspected
17the driver genuinely refillingthe same signature
18double_ownershipalways low — it needs the driver's view
19head minus tail exceeding the ring sizeptr_diverged, if the tail is mirrored
20the tail not mirroredtail_not_mirrored; failure 5 is an inference
21failures 1 and 7, from the MAC's sideidentical

The commit test — 9 scenarios.

#ScenarioExpected
221 518-octet frames, 1 KiB bursts, per-frame commitratio 1.00
23the same with a per-burst commitratio 2.00
24645.6-octet mean, per-burst commitratio 1.00 — blind
2564-octet framesblind
269 000-octet frames, per-burstratio 9.00
27test_is_blind on scenario 24asserts
28failure 3 — descriptor before dataratio 1.00; neither counter moves
29ordering_untested on itasserts, and says nothing useful
30slowing the interconnectfailure 3 disappears — Chapter 19.6 §22

The stall budget — 10 scenarios.

#ScenarioExpected
31a 350 ns stallwithin budget; nothing lost
32a 2.000 µs stallexactly the budget; nothing lost
33a 2.400 µs stallheadroom_spent; nothing lost
34a 2.621 µs stallexactly the FIFO; nothing lost
35a 2.622 µs stallloss begins
36a 10 µs stall1 098 minimum-size frames
37the same, maximum-size traffic60 frames, and about the same octets
38a 1 ms stall148 419 frames
39a thousand 300 ns stallsnothing lost; c_stalls = 1 000
40two 2.0 µs stalls, 1 µs apart56 frames — Section 18's row nine

The coupling — 9 scenarios.

#ScenarioExpected
41an undersized transmit reorder bufferbuffer_undersized at elaboration
42the same design, at run timereceive frames dropped
43coincidence_pptabove 800
44cross_path_coupledasserts
45a receive-path-only propertypasses throughout
46the receive path's reviewerexonerates it, correctly
47a shared-channel progress propertyfails — the right scope
48the transmit path idle throughout the runthe coupling cannot occur
49c_both_paths_busy on that runnever fires

Rates and blindness — 9 scenarios.

#ScenarioExpected
50the FIFO at 100 Gb/s2.621 µs
51512 beats of 64 bits at 10 Gb/s3.277 µs
52a 10 µs stall at 10 Gb/s100 frames, not 1 098
53the constants unparameteriseda tenfold over-estimate at 10 Gb/s
54a smooth workloadthe count never visits zero or the limit
55regime on that workloadO_NORMAL by default
56one consumer_stalled bit, three sourcesattribution impossible
57three source bitsthe stall histogram becomes attributable
58elaboration_check_would_have_caught_itthe humbling output

And the directed test, because random stimulus will not produce it.

The case: five memory-system behaviours, applied to a design whose wire-side stimulus is identical in every run.

Chapter 20.5 §10 divided injections into what a wire can produce and what needs a fault inside the design. This test needs neither. Every run sends the same conformant frames at the same rate; what varies is what the memory model does, and Chapter 20.5 §12 proved that no arrangement of frames can substitute for it.

RunThe memory modelDuration
Aresponds within 400 ns, always
Ba 350 ns refresh every 7.8 µswithin budget
Ca 2.4 µs stall once per millisecondwithin the FIFO
Da 10 µs stall once per millisecondbeyond it
Estops responding for 1 msfar beyond it

The oracle is four-part and none of it looks at a frame's contents.

PartABCDE
outstanding regimenormalnormalnormalnormalpinned, then zero
frames lost0001 098148 419
headroom_spentnonoYESyesyes
every error counterzerozerozerozerozero

Row four is the control and it is Chapter 21.6's finding restated: a DMA drop is a class G drop, so a hundred and forty-eight thousand lost frames move no error counter at all.

Run C is the chapter's most useful run and it is the one nobody writes. A 2.4 µs stall drops nothing — every counter is clean, the link is perfect — and Chapter 19.5 §3's pause round trip has lost 0.4 µs of its 0.63 µs allowance. The failure is invisible in every instrument except a comparison of the longest stall against two constants, and a regression that checks for dropped frames passes it.

Run E's regime transition is the other thing worth watching. The count pins while requests are outstanding and unanswered, then falls to zero when the ring exhausts because nothing further can be written — two of the three regimes in one fault, in sequence, which Section 5's classifier reports as whichever it saw last. A regime that has changed is itself evidence, and nothing in this chapter records the transition.


22. Debugging a DMA Path

Six complaints.

Complaint 1 — "frames are dropped during DRAM refreshes."

CheckIf yesMeaning
what is tRFC?about 350 nsthe FIFO absorbs 2 621
buffer_undersized?oftenChapter 19.6 §19, at elaboration
is the longest stall much larger than a refresh?yesthe refresh is not the stall
is the drop on the receive side?yesand the buffer is on the transmit side

A refresh has a factor of seven of margin, so a design losing frames to one has a buffer far below Chapter 19.5 §3's sizing — and the buffer at fault is usually Chapter 19.6 §19's transmit reorder buffer rather than the receive FIFO, which is Section 17's six-link chain.

Complaint 2 — "the outstanding count is 9 and the design says 13."

CheckIf yesMeaning
is the regime normal?yesthere is no fault here
what is the measured memory latency?700 nsLittle's law gives 9
is the design losing frames?nothen 9 is correct
is the reorder buffer sized for 13?yesand that is fine — it is oversized

The value is not a diagnosis. Chapter 19.6 §4's figure is R × L / burst and L is what the interconnect delivers, not what the design assumed — a faster interconnect gives a lower count and no fault at all. Only the regime matters, and normal is normal.

Complaint 3 — "the driver sees frames with stale tails."

CheckIf yesMeaning
is the frame multi-burst?yesthe commit test can see it
c_frames_committed against the frame countabout 2committing per burst
is the mean frame below the burst size?nootherwise the test is blind
does slowing the interconnect fix it?yesfailure 3 instead — the ordering

Rows two and four separate the two ordering failures. A commit ratio near the bursts-per-frame figure is failure 4; a ratio of exactly 1.00 that disappears when the interconnect slows is failure 3and Chapter 19.6 §22 noted that the second is reported as intermittent because it is rate-dependent.

Complaint 4 — "the ring is always empty and the driver says it is refilling."

CheckIf yesMeaning
c_empty_fetches climbing?yesthe MAC finds the bit clear
is the tail pointer mirrored?nofailure 5 cannot be ruled out
when did the driver last run?not collectedfailure 7 cannot be ruled out
how many candidates remain?two, of the fourand both need host-side evidence

Failures 1, 5 and 7 all produce an empty ring from the MAC's side, and two of the three are distinguished only by evidence in the host: a mirrored tail pointer — sixteen flops — and a software timestamp of the last refill. Neither is usually collected, and the combination is the second-commonest real DMA complaint after an undersized buffer.

Complaint 5 — "no frames are dropped but throughput is down."

CheckIf yesMeaning
is the regime pinned?yesresponses are not being consumed
is RREADY low?yesChapter 19.6 §19's reorder buffer
is the transmit FIFO draining?yes — 0.74 µsthe wire idles
is anything lost?noChapter 19.6 §8's asymmetry

This is the transmit-path half of the same stall and it costs throughput rather than frames. Chapter 19.6 §8 put it exactly: the transmit path drains in 0.74 µs and recovers completely; the receive path fills in 2.00 µs and loses framesand the path with less time is the one that can afford it.

Complaint 6 — "we fixed the reorder buffer and the receive drops stopped."

CheckIf yesMeaning
did anything change on the receive path?noSection 17's chain
was coincidence_ppt above 800 before?if it was measuredthe correlation was there
would buffer_undersized have fired?at elaborationbefore the design was built
how long did the investigation take?weeks, usuallyagainst a parameter check

Row three is the whole moral of the chapter. (O − 1) × burst against the reorder buffer's size is decidable from two parameters and a multiplication, and the alternative is a receive-path investigation that is correctly scoped, correctly executed and looking in the wrong place.

Complaint 7 — "the outstanding count looks perfect and frames are corrupt."

CheckIf yesMeaning
are all frames delivered?yesSection 8's normal regime
c_frames_committed against the frame count1.00either correct, or failure 3
is the mean frame above the burst size?nothe commit test is blind anyway
does the corruption move with the interconnect's speed?yesfailure 3 — the ordering

This is the hardest complaint in the chapter and the counters contribute almost nothing. Chapter 19.6 §12's sequencing is what prevents failure 3 — the design either orders the data before the descriptor or it does not — and the evidence is a scoreboard comparing delivered contents against what was sent, which is Chapter 20.3's instrument rather than a diagnostic counter.

Complaint 8 — "the stall counter says a thousand stalls and nothing is lost."

CheckIf yesMeaning
what is longest_stall_ns?300far inside the budget
what is the count?1 000and it is irrelevant
is anything lost?nocorrectly
what would change that?one stall of 2 622 nsthe maximum, not the count

A thousand short stalls and one long one are completely different states and a stall counter reports the first as worse. The loss is a function of the maximum, so the useful record is longest_stall_ns against two constants — and a design reporting a large count and a small maximum is a memory system that interrupts often and briefly, which is exactly what Chapter 19.5 §3's 391-beat allowance was sized for.

And the three symptoms this chapter is systematically blamed for:

SymptomBlamed onUsually is
receive drops during refreshesthe receive FIFOthe transmit reorder buffer
stale frame tailsthe interconnecta commit test missing its closed term
an empty descriptor ringthe MACthe driver, and there is no counter for it

23. Misconceptions

Misconception 1 — "a receive drop means the receive path is broken."

The wrong model: the symptom is on the receive path, so the fault is too.

What it costs: Chapter 19.6 §22's chain has six links and the first three are on the transmit read path and the interconnect. An undersized transmit reorder buffer deasserts RREADY, the shared channel stalls, the receive path's descriptor fetches are delayed, and the receive FIFO overflows. A receive-path investigation is correctly scoped, correctly executed, and looking at links five and six of a six-link chain.

The corrected model: two paths that share an interconnect are not independent, and the coupling appears in neither path's interface. The correlation is two counters — receive drops occurring while the transmit reorder buffer is full — and the elaboration check that prevents it entirely is two parameters and a multiplication.

Misconception 2 — "the outstanding count should equal Little's law's figure."

The wrong model: Chapter 19.6 §4 says 13 bursts at 1 µs, so a count of 9 is a fault.

What it costs: R × L / burst depends on the delivered memory latency, not on the assumed one. A faster interconnect gives a lower count and no fault at all, and chasing the difference finds nothing. Meanwhile the readings that do discriminate — zero, and pinned at the limit — are ignored because they look like extremes rather than measurements.

The corrected model: the count is a three-valued diagnostic. Zero eliminates four of the eight failures, pinned eliminates seven, and normal eliminates five — 1.41 bits on average from a register that already exists, and the numeric value between the extremes says nothing.

Misconception 3 — "no frames dropped means the DMA path is healthy."

The wrong model: the counters are clean, so the transfer is correct.

What it costs: three of the eight failures deliver every frame. A stale ownership read, a descriptor written before its data lands, a commit taken per burst — all three have frames_in equal to frames_delivered, an outstanding count that matches Little's law, and a driver reading frames with stale tails. No counter in Module 21 sees any of them.

The corrected model: "no drops" and "correct data" are different claims. The first is a counter; the second is Chapter 20.3's scoreboard, and the only diagnostic counter that touches it is c_frames_committed against the frame count — which is blind whenever the frames fit in one burst.

Misconception 4 — "a stall inside the FIFO's capacity is free."

The wrong model: nothing was dropped, so nothing was lost.

What it costs: Chapter 19.5 §3 sized the FIFO as a sum — 391 beats of memory stall, 123 of pause round trip, 4 of synchroniser reserve — and a stall spends them in order. A 2.4 µs stall drops nothing and consumes 0.4 µs of the flow-control headroom, so the next event that would have been absorbed is not.

The corrected model: three thresholds, three different statements. Below 2.002 µs is the design point; between there and 2.621 µs is headroom spent with no symptom; beyond it is 148.8 frames per microsecond. The middle band is the invisible failure and it is one comparison away from visible.

Misconception 5 — "count the stalls."

The wrong model: a stall counter measures how often the memory system misbehaves, so it measures the problem.

What it costs: the loss is a function of the longest stall and not of the count or the mean. A thousand 300 ns stalls cost exactly nothing; one 10 µs stall costs 1 098 frames. A design reporting a thousand stalls and one reporting one may be in completely different states, and the counter that distinguishes them is a maximum.

The corrected model: record the maximum and compare it against two constants. longest_stall_ns against 2 002 and against 2 621 is the whole diagnosis, and the frame cost follows by multiplication.

Misconception 6 — "bigger buffers would fix it."

The wrong model: frames are lost because the buffer filled, so make the buffer bigger.

What it costs: the arithmetic does not scale. Absorbing a 2 µs stall needs 32 KiB; a 10 µs stall needs 122 KiB; a 1 ms scheduling gap needs 12.5 MB — which is not a buffer, it is a memory. And Chapter 19.5 §3's sizing already allocated 123 beats to the pause round trip precisely because beyond a few microseconds the correct response is flow control rather than storage.

The corrected model: the answer beyond a few microseconds is not stalling for that long. A millisecond is an ordinary scheduling quantum and an ordinary coalescing interval; at 100 Gb/s it is a hundred and forty-eight thousand frames, and the fix is in the driver rather than in the RTL.


24. Interview Questions

Question 1 — "What is the only way to lose a frame in the receive FIFO?"

What the answer should establish: the consumer has to stop. The arrival rate is bounded by the line rate — that is what a line rate is — and Chapter 19.5 §4's drain rate is 128 Gb/s against 100, so the buffer empties whatever the frames look like. No sequence of frames overflows it. A strong answer names the consequence: this fault has no wire-side stimulus at all, which is why it needs a chapter rather than an injector.

Question 2 — "You read the outstanding count. What does it tell you?"

What the answer should establish: three things, depending on its regime, and nothing from its value. Zero means nothing is being requested — the ring, the pointers, the buffer size, a missing completion. Pinned at the limit means requests are issued and responses are not consumed, which is exactly one mechanism. Normal means the transfer works and the fault is in ownership or ordering. A strong answer prices it at 1.41 bits from a register that already exists.

Question 3 — "How much does a 10 µs memory stall cost?"

What the answer should establish: 7.379 µs beyond the FIFO's 2.621, at 148.8 minimum-size frames per microsecond — about 1 098 frames. A strong answer notes what is not lost: a DDR4 refresh at 350 ns has a factor of seven of margin, and a stall inside the FIFO drops nothing. The strongest answer adds the invisible band: between 2.002 and 2.621 µs nothing is dropped and the pause round trip's headroom is partly spent.

Question 4 — "Receive frames are being dropped. Where do you look?"

What the answer should establish: the transmit path, among other places. Chapter 19.6 §22's chain: an undersized transmit reorder buffer deasserts RREADY, the shared read channel stalls, receive descriptor fetches are delayed, the receive FIFO overflows. A strong answer gives the correlation — receive drops occurring while the transmit reorder buffer is full, two counters — and notes that a receive-path property passes correctly throughout.

Question 5 — "Which DMA failures deliver every frame?"

What the answer should establish: three of eight. A stale ownership read, a descriptor written before the data lands, and a commit taken per burst. All three have clean counters and an outstanding count matching Little's law, and all three hand the driver frames whose contents are wrong. A strong answer gives the one counter that touches themc_frames_committed against the frame count — and notes it is blind whenever the mean frame fits in one burst, which is most traffic at a 1 KiB burst size.

Question 6 — "What would you add to make this diagnosable?"

What the answer should establish: about thirty-five flops of representation, not more counters. A mirrored copy of the driver's tail pointer (16), three stall-source bits instead of one (3), and a release-to-re-read cycle counter (16). Against 673 flops of the inference machinery this chapter builds, which still cannot separate an exhausted ring from a missing completion. The strongest answer names the elaboration check first: (O − 1) × burst against the reorder buffer's size, decidable before the design exists.


25. Questions and Answers


26. What's Next

Module 21 has two chapters left, and this one has sharpened what the next one is about.

ChapterTakesWhat is left to observe
this chapterthe descriptor pathan outstanding count and a stall duration
Chapter 21.8a link that is slow and correctnothing that counts an event at all
Chapter 21.9the captureChapter 21.1 §12's step 6

Chapter 21.8 is the module's last inversion and this chapter has drawn its boundary. Section 6's normal regime holds three failures where the flow is healthy and the data is wrong; the next chapter's subject is the case where the flow is healthy, the data is right, and there is simply less of it than the rate implies. No drops, no errors, no stalls beyond budget — and a throughput number below the line rate.

The instruments change completely. Chapter 8.3's efficiency arithmetic replaces the counters: the framing overhead, the interframe gap, the frame-rate ceiling, and the distinction between throughput and goodput that this chapter's retransmission-free world never needed. And Chapter 19.6 §4's Little's law returns as a bound rather than as an expectation — a design with too few outstanding requests cannot reach the line rate and drops nothing at all while failing to.

And the series is now one hundred classes long. Class 100 is about a property scoped to a path in a design whose paths are coupled through something outside both — sound, complete, and structurally unable to name the cause. Together with Chapter 18.7 §19's class 80 — a property of every member of a partition concluded of the whole — it names a family about partitions that are not partitions, and Chapter 21.8 will decide whether a third member exists: a throughput budget is a sum over terms that are assumed independent, and the whole of that chapter is about which of them are not.


Continue learning

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.