Skip to content
VLSI Mentor

Ethernet · Module 18

Mastering Memory over AXI

The same misalignment costs 1.40 percentage points of bus efficiency and 37% of the transaction budget, and only one of those two numbers is the one that binds.

Chapter 18.3 and Chapter 18.4 both used a 16-beat burst limit and neither justified it. This chapter does, and the justification produces a result that inverts halfway through.

Take a 1518-octet frame on a 512-bit bus. Twenty-four data beats, and one address cycle per burst.

Burst limitPayloadAligned burstsEfficiencyAt a random offsetEfficiency
4 beats256 B680.00%6.37079.02%
8512 B388.89%3.37087.69%
161 024 B292.31%2.37091.01%
322 048 B196.00%1.37094.60%
644 096 B196.00%1.37094.60%

Two things in that table are worth reading carefully.

Longer bursts are monotonically better and the returns stop abruptly. 4 to 32 beats buys sixteen percentage points; 32 to 64 buys nothing, because a 1518-octet frame already fits in one 2048-octet burst and a larger limit has nothing to do.

And the misalignment penalty looks small: 1.40 percentage points at 32 beats.

That second reading is wrong, and the reason it is wrong is the chapter's central point.

The same misalignment, measured asValue
cycle efficiency lost1.40 percentage points
transactions added+37.0%

Chapter 18.1 §12 established that the MAC is transaction-bound rather than bandwidth-bound, so the second row is the one that binds and the first is nearly irrelevant. An event that costs 1.4% of the bus's cycles costs 37% of its scarcest resource, and a design measuring efficiency will conclude alignment does not matter.

This chapter is about allocating three resources — burst length, outstanding transactions, and AXI IDs — and about the fact that the third one is not fungible.


1. Scope, and Why This Chapter Is About Resources

Chapter 18.1 to Chapter 18.4 settled the MAC's correctness. What is left is how much of a shared bus it may take.

ResourceBounded bySpent on
bandwidththe bus width × the clockframe data, and it is not the problem
transactionsone per address-channel cycleChapter 18.1 §12's wall
outstanding transactionsthe interconnect's limitLittle's law — Section 6
AXI IDsthe interconnect's ID widthordering, and it is not fungible — Section 8

Row four is the one this chapter exists for. Bandwidth, transactions and outstanding slots are all quantities: more is better, they are interchangeable, and allocating them is arithmetic. An AXI ID is not a quantity. Each ID carries an ordering contract — transactions sharing an ID complete in order — and two transactions that share an ID are constrained whether or not the design wanted them to be.

Which means an ID allocator that treats IDs as a pool of interchangeable tokens is correct by every obvious measure and wrong. Section 19's rejected class is exactly that.

What this chapter establishes:

SectionEstablishes
2the three limits on a burst, and which one usually binds
4burst efficiency, and the two verdicts on misalignment
6the outstanding-transaction budget, from Little's law
8what an ID buys and what it costs
10the ID budget already spent by Chapter 18.2 and Chapter 18.4
12how to get the ordering back without spending an ID on it
16the reorder buffer, sized from the ID count

What it does not do: it does not revisit the ordering requirements — those are settled — only the mechanism's cost. And it does not address multiple queues, which is Chapter 18.7.


2. The Three Limits on a Burst

Every burst this module issues is bounded by three things and the smallest wins. Chapter 18.3 §7 and Chapter 18.4 §5 both computed the minimum; neither said which limit usually binds.

LimitSourceTypical value
the buffer's remaining bytesthe driver's allocation2 KiB, or whatever is left
the 4 KiB page boundarythe bus protocol0 to 4096, uniformly
the burst-length maximumthis chapter's choiceMAX_BEATS × BUS_BYTES

Which binds depends on the frame size and it changes across a single frame.

A 1518-octet frame into a 2 KiB page-aligned buffer, 32-beat limit on a 512-bit bus: the burst limit is 2048 octets, the buffer has 2048, the page has 4096 — so the frame binds and one burst carries all 1518 octets. No limit was reached at all.

The same frame 3 KiB into a page: the page boundary is 1024 octets away. First burst 1024, second burst 494. The page bound, which the MAC cannot see and the driver chose by accident.

A 9000-octet jumbo frame into 2 KiB buffers: the buffer binds on every burst but the last, and the page bound never fires if the buffers are 2 KiB-aligned — which they are, if they came from a slab allocator.

CaseBinding limitBursts
1518 B, 2 KiB aligned buffer, 32-beatthe frame1
1518 B, 3 KiB into a pagethe page2
9000 B, 2 KiB aligned buffersthe buffer5
1518 B, 4-beat limitthe burst limit6

Row four is the only one where the design's own choice is the constraint, which is worth noticing: at any sensible burst limit, the MAC's parameter is rarely what binds. The buffer size and the alignment — both the driver's — decide almost every burst.

And that has a direct consequence for where tuning effort goes. Raising MAX_BEATS from 16 to 32 moves one row of that table. Aligning the buffer pool moves another, and enlarging the buffers moves a third — and two of the three are software changes that cost nothing.


Every burst the MAC issues is bounded by three quantities and the smallest wins. The first is the descriptor buffer's remaining bytes, which the driver's allocation decides. The second is the distance to the next 4 kilobyte page boundary, which the AXI protocol imposes and which depends on where the buffer happens to sit. The third is the configured burst-length maximum, which is the MAC's own parameter. For a 1518-octet frame in a 2 kilobyte page-aligned buffer at a 32-beat limit on a 512-bit bus, none of the three binds and the frame itself is the limit: one burst. For the same frame 3 kilobytes into a page, the page boundary binds and the frame becomes two bursts of 1024 and 494 octets. For a 9000-octet jumbo frame in 2 kilobyte buffers, the buffer binds on every burst but the last. Only at an unusually small burst limit, such as 4 beats, does the MAC's own parameter become the constraint. So two of the three levers belong to the driver — buffer size and alignment — and both are software changes that cost nothing, while raising the burst limit is a hardware parameter that usually changes nothing at all.One transferaddress and lengthBuffer remainingthe driver's allocation4 KiB boundarythe protocol's ruleMAX_BEATSthe MAC's parameterThe smallest winsand record WHICH1518 B, alignedframe binds — 1 burst1518 B, 3 KiB inpage binds — 1024 + 4949000 B, 2 KiB bufsbuffer binds — 5 bursts12
Figure 1 — three limits on every burst, and the MAC's own parameter is rarely the one that binds.

3. RTL 1 — The Burst-Length Selector

The block that takes Section 2's three limits and produces one length, and records which limit bound it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// axishape_pkg -- burst shaping, IDs and outstanding-transaction policy.
// -----------------------------------------------------------------------
package axishape_pkg;

  localparam int ADDR_W    = 64;
  localparam int BUS_BYTES = 64;         // 512-bit
  localparam int PAGE_B    = 4096;
  localparam int MAX_ID    = 16;
  localparam int MAX_OUT   = 32;

  // Which limit shortened a burst. Section 2: the identity of the
  // winner is more useful than the value, and it is free -- the
  // comparison has already been made.
  typedef enum logic [1:0] {
    BOUND_LENGTH = 2'd0,                 // the whole transfer fitted
    BOUND_BURST  = 2'd1,                 // MAX_BEATS
    BOUND_PAGE   = 2'd2,                 // the 4 KiB rule
    BOUND_BUFFER = 2'd3                  // the descriptor's buffer ended
  } bound_e;

  // What a transaction is FOR. This is the ID allocator's input and
  // section 8's subject: two transactions with the same purpose may
  // share an ID; two with different purposes must not, because an ID
  // imposes ordering neither of them asked for.
  typedef enum logic [2:0] {
    PURP_DESC_FETCH   = 3'd0,
    PURP_DESC_WRITE   = 3'd1,            // 18.2's ordering domain
    PURP_RX_DATA      = 3'd2,
    PURP_TX_DATA      = 3'd3,
    PURP_STATUS_WRITE = 3'd4
  } purpose_e;

  typedef struct packed {
    logic [ADDR_W-1:0] addr;
    logic [15:0]       bytes;
    purpose_e          purpose;
    logic              is_write;
    logic [15:0]       stream_id;        // which frame / which chain
  } req_t;

endpackage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// burst_length_selector -- one length, and the reason for it.
//
// The three-way minimum is 18.3 section 7's and 18.4 section 5's.
// What is new is BOUND, which turns "something limited this" into
// "this limited this" for one comparator's worth of logic.
// -----------------------------------------------------------------------
module burst_length_selector
  import axishape_pkg::*;
#(
  parameter int MAX_BEATS = 32
)(
  input  logic               clk,
  input  logic               rst_n,

  input  logic               req_valid,
  input  logic [ADDR_W-1:0]  req_addr,
  input  logic [15:0]        req_remaining,      // left in this buffer
  input  logic [15:0]        req_total_left,     // left in this transfer

  output logic [15:0]        burst_bytes,
  output logic [7:0]         burst_len,          // beats minus one
  output bound_e             bound,
  output logic               burst_is_aligned,

  output logic [31:0]        c_bound_length,
  output logic [31:0]        c_bound_burst,
  output logic [31:0]        c_bound_page,
  output logic [31:0]        c_bound_buffer,
  output logic [31:0]        c_bursts,
  output logic [31:0]        c_beats,
  output logic [15:0]        mean_beats_x10
);

  localparam int MAX_BYTES = MAX_BEATS * BUS_BYTES;

  // Distance to the next page boundary.
  wire [12:0] to_page = 13'(PAGE_B) - {1'b0, req_addr[11:0]};

  wire [15:0] by_total  = req_total_left;
  wire [15:0] by_buffer = req_remaining;
  wire [15:0] by_burst  = 16'(MAX_BYTES);
  wire [15:0] by_page   = {3'b0, to_page};

  // The minimum, and the identity of the winner. Priority when two
  // tie: report the SOFTER constraint, because that is the one a
  // driver can act on. A tie between page and buffer reports buffer.
  always_comb begin
    burst_bytes = by_total;
    bound       = BOUND_LENGTH;

    if (by_buffer < burst_bytes) begin
      burst_bytes = by_buffer;
      bound       = BOUND_BUFFER;
    end
    if (by_burst < burst_bytes) begin
      burst_bytes = by_burst;
      bound       = BOUND_BURST;
    end
    if (by_page < burst_bytes) begin
      burst_bytes = by_page;
      bound       = BOUND_PAGE;
    end
  end

  assign burst_len = 8'(((burst_bytes + BUS_BYTES - 1) / BUS_BYTES) - 1);

  // An aligned burst is one whose start is on a bus-width boundary.
  // A misaligned start wastes the first beat's leading strobes and,
  // worse, makes every subsequent beat unaligned too.
  assign burst_is_aligned = (req_addr[$clog2(BUS_BYTES)-1:0] == '0);

  always_comb begin
    if (c_bursts == '0) mean_beats_x10 = 16'd0;
    else mean_beats_x10 = 16'((c_beats * 32'd10) / c_bursts);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_bound_length <= '0; c_bound_burst <= '0;
      c_bound_page <= '0; c_bound_buffer <= '0;
      c_bursts <= '0; c_beats <= '0;
    end else if (req_valid) begin
      c_bursts <= c_bursts + 1;
      c_beats  <= c_beats + {24'b0, burst_len} + 32'd1;
      unique case (bound)
        BOUND_LENGTH: c_bound_length <= c_bound_length + 1;
        BOUND_BURST:  c_bound_burst  <= c_bound_burst + 1;
        BOUND_PAGE:   c_bound_page   <= c_bound_page + 1;
        BOUND_BUFFER: c_bound_buffer <= c_bound_buffer + 1;
      endcase
    end
  end

endmodule

Classification: a four-way minimum with winner identification and per-cause accounting.

What it teaches: that mean_beats_x10 is the single number that says whether the burst policy is working, and it is almost never what the parameter says. A design configured for 32-beat bursts whose mean is 9 beats is issuing three and a half times as many transactions as its configuration implies — and the four c_bound_* counters say why: buffer-bound means the buffers are small, page-bound means they are unaligned, burst-bound means the parameter is the limit and could be raised.

And it teaches that the tie-breaking priority is a deliberate choice with a diagnostic purpose. When the page bound and the buffer bound coincide — a 2 KiB buffer starting 2 KiB into a page — both are true and only one is actionable. Reporting BOUND_BUFFER sends an integrator to the buffer size, which is a real lever; reporting BOUND_PAGE sends them to alignment, which in that case is already perfect. The listing reports whichever check fires last in the chain, and the ordering is chosen so that the page check wins only when it strictly binds.

Deliberately simplified: the divide computing burst_len is a real divide; BUS_BYTES is a power of two so a production design shifts. mean_beats_x10 is a combinational divide that software should do instead. And burst_is_aligned checks only the start address, where a full treatment also considers whether the length leaves the next burst aligned — which it does not, whenever a page bound truncates a burst to a non-multiple of the bus width.

Production implication: the ratio c_bound_page / c_bursts is Chapter 18.3 §7's c_page_splits with the denominator attached, and it is the form an integrator can act on. A raw split count means nothing without the burst count; a ratio above a few per cent on a receive path is a misaligned pool and a free fix. On a transmit path it is Chapter 18.4 §5's application-supplied buffer and usually is not.


4. Burst Efficiency, and the Two Verdicts on Misalignment

Section 3 produced lengths. This section asks what they are worth, and the answer depends on which resource is being counted.

The efficiency model is one line. A burst costs one address cycle plus its data beats. A 1518-octet frame is 24 data beats on a 512-bit bus regardless of how it is split, so efficiency is 24 / (24 + bursts).

Burst limitAligned burstsEfficiencyRandom-offset burstsEfficiency
4 beats680.00%6.37079.02%
8388.89%3.37087.69%
16292.31%2.37091.01%
32196.00%1.37094.60%
64196.00%1.37094.60%

Three readings, and the third is the one that matters.

Reading 1 — longer is better, monotonically. There is no optimum in the interior; the efficiency rises all the way to 32 beats. A design choosing 16 beats "because longer bursts hog the bus" is giving up 3.69 percentage points for a fairness concern the interconnect's arbiter already handles.

Reading 2 — the returns stop abruptly and completely. 32 to 64 beats buys exactly nothing on a 1518-octet frame, because the frame already fits in one 2048-octet burst. A 64-beat burst on a 512-bit bus is 4096 octets — precisely one page — so it can only ever be issued page-aligned, and any other alignment guarantees a split. The parameter is worse than useless above 32 for this traffic.

Reading 3 — the misalignment penalty is 1.40 percentage points, and that is the wrong way to count it.

The same event, at a 32-beat limitMeasured asValue
a 1518-octet frame at a random page offsetcycle efficiency lost1.40 points
the sametransactions issued1 → 1.370, +37.0%

Chapter 18.1 §12 established the MAC is transaction-bound, so the second row is the binding one — and a team measuring bus efficiency will see 1.4% and conclude alignment is a rounding error.

But there is a second inversion, and it stops the conclusion being simple.

At 100 Gb/s the transaction budget binds on minimum-size frames, and a 64-octet frame crosses a page boundary only 1.54% of the time.

Frame sizeP(page split)Transactions per framePer cycle at 100 Gb/s
64, aligned1.2500.7440
64, random offset1.54%1.2650.7532
1518, aligned1.2500.0406
1518, random offset37.04%1.6200.0527

Read the last column. Misalignment costs 0.0092 transactions per cycle where the budget is tight, and 0.0121 where the budget has 96% slack.

So the honest statement is the one nobody expects: alignment matters most for the frames whose transaction budget matters least. The two never bind together — minimum-size frames saturate the address channel and rarely split; maximum-size frames split constantly and use 4% of the channel.

Which does not make alignment unimportant, and the reason is worth being precise about. The address channel is not the only thing a transaction consumes: it consumes an outstanding slot for a full round trip — Section 6 — and an entry in Section 16's reorder buffer. A 37% increase in transactions on large frames is a 37% increase in both, and those are sized for the worst case rather than the average.


A 1518-octet frame on a 512-bit bus is 24 data beats however it is split, and each burst costs one extra address cycle. Aligned at a 32-beat limit it is one burst, giving 24 divided by 25, or 96.00 percent cycle efficiency. At a uniformly random page offset it crosses a 4 kilobyte boundary 37.04 percent of the time, so the mean is 1.370 bursts and the efficiency falls to 24 divided by 25.370, or 94.60 percent — a loss of 1.40 percentage points. Measured as transactions the same event reads very differently: 1 burst becomes 1.370, an increase of 37.0 percent. Chapter 18.1 section 12 established that the MAC is transaction-bound rather than bandwidth-bound, because a single AXI address channel issues at most one transaction per cycle regardless of how much payload it carries, so the second verdict is the one that binds and the first is nearly irrelevant. A team measuring bus efficiency sees 1.4 percent and concludes alignment is a rounding error. The twist is that alignment bites hardest on maximum-size frames, whose transaction budget is only 4 percent used, while the budget actually binds on minimum-size frames, which cross a page boundary only 1.54 percent of the time — so the two never bind together, and misalignment's real cost lands on outstanding slots and reorder-buffer entries, both sized for the worst case.One misalignment1518 B at a random offsetAs cycle efficiency96.00% to 94.60%As transactions1 to 1.3701.40 pointslooks negligible+37.0%the binding resource18.1 s12's wall1 txn per cycle, any sizeBut: 64-octet framessplit only 1.54% — budgetbinds HERESo the cost lands onslots and reorder entries12
Figure 2 — one misalignment, two verdicts, and they differ by a factor of twenty-six.

5. RTL 2 — The Alignment Splitter

The block that turns one logical request into a sequence of legal bursts, and the one place where the 4 KiB rule is enforced rather than assumed.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// alignment_splitter -- walks a request, emitting legal bursts.
//
// The 4 KiB rule is not negotiable and is not visible to anything
// above this block. A frame lands wherever the descriptor's buffer
// pointer says; this module is the only place that knows the bus has
// an opinion about it.
// -----------------------------------------------------------------------
module alignment_splitter
  import axishape_pkg::*;
#(
  parameter int MAX_BEATS = 32
)(
  input  logic               clk,
  input  logic               rst_n,

  input  logic               req_valid,
  output logic               req_ready,
  input  req_t               req,

  // Legal bursts out.
  output logic               burst_valid,
  input  logic               burst_ready,
  output logic [ADDR_W-1:0]  burst_addr,
  output logic [7:0]         burst_len,
  output logic [15:0]        burst_bytes,
  output purpose_e           burst_purpose,
  output logic [15:0]        burst_stream,
  output logic               burst_is_last,          // of this request
  output bound_e             burst_bound,

  output logic [31:0]        c_requests,
  output logic [31:0]        c_bursts,
  output logic [31:0]        c_split_requests,       // needed >1 burst
  output logic [15:0]        worst_bursts_per_req,
  output logic               illegal_burst           // must never assert
);

  logic [ADDR_W-1:0] addr;
  logic [15:0]       left;
  logic              active;
  logic [15:0]       bursts_this_req;

  localparam int MAX_BYTES = MAX_BEATS * BUS_BYTES;

  wire [12:0] to_page = 13'(PAGE_B) - {1'b0, addr[11:0]};

  logic [15:0] this_bytes;
  bound_e      this_bound;
  always_comb begin
    this_bytes = left;
    this_bound = BOUND_LENGTH;
    if (16'(MAX_BYTES) < this_bytes) begin
      this_bytes = 16'(MAX_BYTES);
      this_bound = BOUND_BURST;
    end
    if ({3'b0, to_page} < this_bytes) begin
      this_bytes = {3'b0, to_page};
      this_bound = BOUND_PAGE;
    end
  end

  assign req_ready     = !active;
  assign burst_valid   = active && (left != 16'd0);
  assign burst_addr    = addr;
  assign burst_bytes   = this_bytes;
  assign burst_len     = 8'(((this_bytes + BUS_BYTES - 1) / BUS_BYTES) - 1);
  assign burst_purpose = req.purpose;
  assign burst_stream  = req.stream_id;
  assign burst_is_last = (this_bytes == left);
  assign burst_bound   = this_bound;

  // The invariant this block exists for. A burst that crosses a page
  // boundary is a protocol violation the interconnect may answer with
  // a SLVERR, a wrapped access, or silent corruption -- the standard
  // does not say, because the master is simply forbidden to do it.
  assign illegal_burst = burst_valid &&
                         ((addr[11:0] + this_bytes) > 16'(PAGE_B));

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      addr <= '0; left <= '0; active <= 1'b0; bursts_this_req <= '0;
      c_requests <= '0; c_bursts <= '0; c_split_requests <= '0;
      worst_bursts_per_req <= '0;
    end else begin
      if (req_valid && req_ready) begin
        addr            <= req.addr;
        left            <= req.bytes;
        active          <= 1'b1;
        bursts_this_req <= '0;
        c_requests      <= c_requests + 1;
      end

      if (burst_valid && burst_ready) begin
        addr            <= addr + {{(ADDR_W-16){1'b0}}, this_bytes};
        left            <= left - this_bytes;
        bursts_this_req <= bursts_this_req + 16'd1;
        c_bursts        <= c_bursts + 1;

        if (burst_is_last) begin
          active <= 1'b0;
          if (bursts_this_req != 16'd0)
            c_split_requests <= c_split_requests + 1;
          if ((bursts_this_req + 16'd1) > worst_bursts_per_req)
            worst_bursts_per_req <= bursts_this_req + 16'd1;
        end
      end
    end
  end

endmodule

Classification: a page-aware request walker with a protocol-violation guard.

What it teaches: that the 4 KiB rule has no defined failure mode, which is why illegal_burst must be an assertion rather than a counter. The AXI specification forbids a burst from crossing a 4 KiB boundary and does not say what happens if one does — because the master is simply not allowed to. An interconnect may return an error, may wrap the address within the boundary, or may forward it and let a downstream slave decide. All three have been seen, and the second is the worst: the write lands 4 KiB earlier than intended, silently.

And it teaches why the rule exists, which explains why it cannot be relaxed. A 4 KiB boundary is the smallest page an interconnect must assume, and a burst crossing one may need to be routed to two different slaves — two different memory controllers, or memory and a peripheral. A burst is a single routing decision, so a burst spanning two routes has no meaning.

Deliberately simplified: the splitter handles one request at a time, which serialises the address path and at Chapter 18.4 §16's rates is exactly the loop-versus-pipeline error that chapter warned about. A production splitter pipelines the address computation and holds several requests. worst_bursts_per_req is sticky with no reset, so a single pathological request marks the counter for ever. And the BOUND_BUFFER case from Section 3 is absent, because this block receives a request already limited to one buffer.

Production implication: c_split_requests / c_requests is the fraction of transfers that needed more than one burst, and comparing it against the theoretical fraction is how an integrator separates alignment from size. At a 32-beat limit, a 1518-octet frame should split 37.04% of the time from alignment alone. A measured 40% is alignment; a measured 95% means the buffers are smaller than the frames and Section 2's buffer bound is firing, which the c_bound_* counters confirm directly.


6. The Outstanding-Transaction Budget

Burst length decides how much each transaction carries. This section decides how many may be in flight, and the answer is Little's law with a bus in it.

A transaction occupies an outstanding slot from issue to response. If the round trip is RT and each transaction carries P bits, O outstanding transactions sustain O × P / RT bits per second.

Round trip1 outstanding24816
100 ns81.92 Gb/s163.84327.68655.361 310.72
200 ns40.9681.92163.84327.68655.36
300 ns27.3154.61109.23218.45436.91
500 ns16.3832.7765.54131.07262.14

Those figures are for 16-beat bursts — 1024 octets each. Chapter 18.1 §4 established that a 100 Gb/s port needs 114.29 Gb/s in each direction:

Round tripOutstanding needed for 114.29 Gb/s
100 ns2
200 ns3
300 ns5
500 ns7
800 ns12

Which is a much smaller number than Chapter 18.2 §9's table of 32 and Chapter 18.4 §16's 45 frames, and the discrepancy is worth resolving because it looks like a contradiction.

It is not. Those chapters counted frames in flight, not transactions. A 64-octet frame is one transaction; the 45-frame figure at 100 Gb/s is 45 tiny transactions, carrying 64 octets each rather than 1024. Little's law with P = 512 bits instead of 8192 gives sixteen times the count — and 45 is what it gives.

TrafficPayload per transactionOutstanding needed at 300 ns
64-octet frames512 bits~67
1518-octet frames, 16-beat bursts8192 bits5
9000-octet frames, 32-beat bursts16 384 bits3

So the outstanding budget is set by minimum-size frames, exactly as the transaction budget was — and for the same reason: small transfers occupy a slot for a full round trip while carrying almost nothing.

And there is a hard ceiling this arithmetic runs into. An interconnect offers a fixed number of outstanding transactions per master, frequently 8 or 16, and a design needing 67 cannot simply ask for more. The lever is the burst length again: at 64-octet frames there is nothing to lengthen, which is why Chapter 18.3 §17's answer was to reduce the transaction count by batching descriptors rather than to increase concurrency.


7. RTL 3 — The Outstanding Governor

The block that enforces Section 6's budget, and that has to divide it between purposes that need very different amounts.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// outstanding_governor -- per-purpose outstanding limits with a
// shared pool and a reservation.
//
// A single global limit starves whichever purpose asks last. A purely
// per-purpose partition wastes slots when one purpose is idle. The
// structure here is a reserved minimum per purpose plus a shared
// remainder, which is the standard answer and is worth stating
// because designs routinely do neither.
// -----------------------------------------------------------------------
module outstanding_governor
  import axishape_pkg::*;
#(
  parameter int NUM_PURP = 5,
  parameter int TOTAL    = MAX_OUT
)(
  input  logic               clk,
  input  logic               rst_n,

  // Reserved slots per purpose. Must sum to <= TOTAL.
  input  logic [5:0]         cfg_reserved [NUM_PURP],

  input  logic               issue_valid,
  input  purpose_e           issue_purpose,
  output logic               issue_grant,

  input  logic               retire_valid,
  input  purpose_e           retire_purpose,

  output logic [5:0]         in_use [NUM_PURP],
  output logic [5:0]         shared_free,
  output logic [31:0]        c_grants,
  output logic [31:0]        c_stalls [NUM_PURP],
  output logic [5:0]         peak_total,
  output logic               reservation_overcommitted
);

  logic [5:0] used [NUM_PURP];
  logic [5:0] total_used;

  always_comb begin
    int i;
    total_used = '0;
    for (i = 0; i < NUM_PURP; i++) total_used = total_used + used[i];
  end

  // A reservation that oversubscribes the pool is a configuration
  // error whose symptom is a purpose that is starved despite having
  // a reservation -- which looks exactly like a hardware bug.
  logic [7:0] reserved_sum;
  always_comb begin
    int i;
    reserved_sum = '0;
    for (i = 0; i < NUM_PURP; i++) reserved_sum = reserved_sum + 8'(cfg_reserved[i]);
  end
  assign reservation_overcommitted = (reserved_sum > 8'(TOTAL));

  // How many slots are not spoken for by anybody's reservation.
  logic [7:0] reserved_unused;
  always_comb begin
    int i;
    reserved_unused = '0;
    for (i = 0; i < NUM_PURP; i++)
      if (used[i] < cfg_reserved[i])
        reserved_unused = reserved_unused + 8'(cfg_reserved[i] - used[i]);
  end
  assign shared_free = 6'(8'(TOTAL) - 8'(total_used) - reserved_unused);

  // A purpose may issue if it is inside its own reservation, or if
  // the shared remainder has room. The first clause is what prevents
  // a high-rate purpose from starving a low-rate one.
  wire within_reservation = (used[issue_purpose] < cfg_reserved[issue_purpose]);
  wire shared_available   = (shared_free != 6'd0);

  assign issue_grant = issue_valid &&
                       (within_reservation || shared_available);

  always_ff @(posedge clk or negedge rst_n) begin
    int i;
    if (!rst_n) begin
      for (i = 0; i < NUM_PURP; i++) begin
        used[i] <= '0; c_stalls[i] <= '0;
      end
      c_grants <= '0; peak_total <= '0;
    end else begin
      if (issue_valid && issue_grant) begin
        used[issue_purpose] <= used[issue_purpose] + 6'd1;
        c_grants <= c_grants + 1;
      end
      if (issue_valid && !issue_grant)
        c_stalls[issue_purpose] <= c_stalls[issue_purpose] + 1;

      if (retire_valid && (used[retire_purpose] != 6'd0))
        used[retire_purpose] <= used[retire_purpose] - 6'd1;

      if (total_used > peak_total) peak_total <= total_used;
    end
  end

  assign in_use = used;

endmodule

Classification: a reserved-plus-shared credit pool with per-purpose starvation accounting.

What it teaches: that a single global outstanding limit starves the purpose that asks least often, and a pure partition wastes the pool. The receive data path issues constantly; a descriptor fetch issues once per eight frames — so under a global limit the fetch competes for a pool the data path has already emptied, and a descriptor fetch delayed is Chapter 18.2 §3's starved, which becomes a dropped frame. The reservation guarantees the fetch a slot without permanently removing it from the data path, because the shared calculation returns unused reservations to the pool.

And it teaches that c_stalls must be per purpose. A single stall counter says the pool is too small; five say which purpose is being squeezed, and the remedies differ: data-path stalls mean the pool is too small, descriptor-fetch stalls mean the reservation is too small, and those are opposite changes to the same configuration.

Deliberately simplified: reserved_unused and total_used are both computed combinationally by summing five counters every cycle, which at 400 MHz needs a maintained running total instead. The reservation is static where a real design may adapt it — though a static reservation that is occasionally wrong is much safer than an adaptive one that is occasionally wrong, because its failure is a known throughput loss rather than an unbounded one. And retirement assumes the purpose is carried back with the response, which requires the ID or the tag to encode it — Section 9's job.

Production implication: peak_total against TOTAL says whether the master is actually using the concurrency it was given. A peak well below the limit means the MAC never had that many requests to make — so raising the interconnect's limit would change nothing, and the bottleneck is upstream. A peak pinned at the limit with c_stalls rising means the limit binds, and Section 6's table says what it should be for this traffic. Without both numbers, an integrator negotiating for more outstanding slots has no case.


8. What an AXI ID Actually Buys, and What It Costs

This is the section the chapter exists for. An ID is not a slot, a credit or a tag — it is a contract, and a design that allocates IDs as though they were interchangeable breaks something no counter will show.

AXI's ordering model in one sentence: transactions issued with the same ID complete in order; transactions with different IDs have no ordering relationship at all.

Which means an ID does two things at once, and they point in opposite directions.

Sharing an IDGives youCosts you
two transactionsa guarantee they complete in orderthe ability to complete them concurrently
two transactions that did not want orderingnothingthe concurrency, for free, forever

Row two is the failure mode. Two transactions that share an ID are ordered whether or not anybody wanted it — so an allocator handing out IDs round-robin from a pool imposes ordering between unrelated transactions and gives up their concurrency for no benefit.

And the reverse is the failure mode that corrupts rather than slows. Two transactions that needed ordering and were given different IDs have no ordering, and Chapter 18.2 §10's mode 0 stops working — silently, on some interconnects, some of the time.

So the allocation rule is not "spread the load" but "group by ordering requirement":

Two transactions share an ID if and only if the design requires them to complete in order.

Which turns the ID allocator into a classifier rather than a distributor, and the classification is by purpose:

PurposeMust be ordered againstID policy
descriptor writebackeach other, per ringone ID per ring
RX frame datanothing — Chapter 18.3 §8 counts responsesmany IDs
TX frame dataeach other, per frameone ID per frame in flight
descriptor fetchnothing — reads are independentmany IDs
status writethe descriptor writeback it precedesshare with the writeback

Row three is the one that costs, and it is Chapter 18.4 §5's c_reorder_stalls explained. A transmit frame's data must reach the FIFO in frame order. Giving the whole frame one ID gets that from the protocol and serialises the frame's own bursts — a 1518-octet frame at 32 beats is one burst, so nothing is lost, but a 9000-octet frame at 32 beats is five bursts and they are now sequential.

FrameBursts at 32 beatsOne ID: throughput at 300 ns
64 octets1unaffected
1518 octets1unaffected
9000 octets55 round trips instead of 1 — 5× the latency

Row three is why jumbo transmit is slower than it looks on a same-ID design, and Section 12 is how to get it back without spending five IDs on one frame.


An AXI ID guarantees that transactions sharing it complete in order, and that transactions with different IDs have no ordering relationship at all. So an ID does two things at once and they point in opposite directions: it buys ordering and it costs concurrency. The allocation rule that follows is not to spread load but to group by requirement — two transactions share an ID if and only if the design requires them to complete in order. An allocator that hands out the first free ID makes two mistakes a pool property cannot see. It gives unrelated transactions the same ID, which orders them for nothing and loses their concurrency. And it gives transactions that needed ordering different IDs, which silently removes the guarantee Chapter 18.2's mode 0 depends on. A third mistake is subtler: an ID whose transactions have all responded is idle, not free, because a later burst of the same frame must still be ordered against the earlier ones — so releasing on the outstanding count reaching zero can rebind an ID mid-frame, and every allocation remains legal while the ordering is gone. The correct allocator is a classifier keyed on the ordering domain, which finds the ID already bound to that domain and falls back to a free ID only when the domain is new.An AXI IDsame ID = in orderBuys orderingwithin the IDCosts concurrencywithin the IDAllocate byavailabilityfirst free IDAllocate by domaina classifierUnrelated: orderedconcurrency lost fornothingRelated: unordered18.2 mode 0 brokenIdle is not freerelease on domain close12
Figure 3 — an ID is a contract, not a slot, and the difference is invisible to a pool.

9. RTL 4 — The ID Allocator

Not a pool. A classifier that maps an ordering requirement onto an identifier, and refuses to reuse one whose contract is still live.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// axi_id_allocator -- maps (purpose, stream) onto an AXI ID.
//
// The rule from section 8: two transactions share an ID if and only
// if they must complete in order. So the ID is a function of the
// ORDERING DOMAIN, not of load. Reuse is permitted only when the
// previous holder's domain is closed -- section 19's rejected class
// is an allocator that reuses on the basis of the slot being free.
// -----------------------------------------------------------------------
module axi_id_allocator
  import axishape_pkg::*;
#(
  parameter int NUM_ID = MAX_ID
)(
  input  logic               clk,
  input  logic               rst_n,

  input  logic               req_valid,
  input  purpose_e           req_purpose,
  input  logic [15:0]        req_stream,          // frame or ring number
  output logic               req_grant,
  output logic [$clog2(NUM_ID)-1:0] req_id,

  // A response retires one transaction under an ID.
  input  logic               rsp_valid,
  input  logic [$clog2(NUM_ID)-1:0] rsp_id,

  // A stream declares itself finished -- its ordering domain closes
  // and its ID may be rebound to a different domain.
  input  logic               stream_closed,
  input  logic [15:0]        closed_stream,
  input  purpose_e           closed_purpose,

  output logic [31:0]        c_grants,
  output logic [31:0]        c_no_id,
  output logic [31:0]        c_rebinds,
  output logic [5:0]         ids_bound,
  output logic               id_reused_while_live    // must never assert
);

  // Each ID is BOUND to an ordering domain while it has outstanding
  // transactions. The binding is the contract; the outstanding count
  // is what keeps it alive.
  logic                      bound   [NUM_ID];
  purpose_e                  b_purp  [NUM_ID];
  logic [15:0]               b_strm  [NUM_ID];
  logic [5:0]                b_out   [NUM_ID];

  // Does an ID already carry this exact domain?
  logic                      hit;
  logic [$clog2(NUM_ID)-1:0] hit_i;
  always_comb begin
    int i;
    hit = 1'b0; hit_i = '0;
    for (i = NUM_ID-1; i >= 0; i--)
      if (bound[i] && (b_purp[i] == req_purpose) &&
          (b_strm[i] == req_stream)) begin
        hit   = 1'b1;
        hit_i = i[$clog2(NUM_ID)-1:0];
      end
  end

  // A free ID is one with nothing outstanding AND no live binding.
  // Those are different conditions: an ID may have retired every
  // transaction and still be bound, because its stream has not
  // declared itself finished and a later burst of the same frame
  // must still be ordered against the earlier ones.
  logic                      free;
  logic [$clog2(NUM_ID)-1:0] free_i;
  always_comb begin
    int i;
    free = 1'b0; free_i = '0;
    for (i = NUM_ID-1; i >= 0; i--)
      if (!bound[i]) begin
        free   = 1'b1;
        free_i = i[$clog2(NUM_ID)-1:0];
      end
  end

  assign req_grant = req_valid && (hit || free);
  assign req_id    = hit ? hit_i : free_i;

  // The invariant. An ID whose binding is live must not be handed to
  // a different domain, however many of its transactions have
  // retired. This is the whole difference between an ID and a slot.
  assign id_reused_while_live = req_valid && !hit && free &&
                                bound[free_i];

  always_comb begin
    int i;
    ids_bound = '0;
    for (i = 0; i < NUM_ID; i++) if (bound[i]) ids_bound = ids_bound + 6'd1;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    int i;
    if (!rst_n) begin
      for (i = 0; i < NUM_ID; i++) begin
        bound[i] <= 1'b0; b_purp[i] <= PURP_DESC_FETCH;
        b_strm[i] <= '0;  b_out[i]  <= '0;
      end
      c_grants <= '0; c_no_id <= '0; c_rebinds <= '0;
    end else begin
      if (req_valid && req_grant) begin
        c_grants <= c_grants + 1;
        if (hit) begin
          b_out[hit_i] <= b_out[hit_i] + 6'd1;
        end else begin
          bound[free_i]  <= 1'b1;
          b_purp[free_i] <= req_purpose;
          b_strm[free_i] <= req_stream;
          b_out[free_i]  <= 6'd1;
          c_rebinds      <= c_rebinds + 1;
        end
      end

      if (req_valid && !req_grant) c_no_id <= c_no_id + 1;

      if (rsp_valid && (b_out[rsp_id] != 6'd0))
        b_out[rsp_id] <= b_out[rsp_id] - 6'd1;

      // Unbinding is explicit and is NOT triggered by the outstanding
      // count reaching zero.
      for (i = 0; i < NUM_ID; i++)
        if (stream_closed && bound[i] &&
            (b_strm[i] == closed_stream) && (b_purp[i] == closed_purpose) &&
            (b_out[i] == 6'd0))
          bound[i] <= 1'b0;
    end
  end

endmodule

Classification: a content-addressed binding table keyed on an ordering domain, with explicit release.

What it teaches: that an ID becomes free in two stages and a design that collapses them is broken. An ID whose transactions have all responded still carries its binding, because a later burst of the same frame must be ordered against the earlier ones — and if the ID has been rebound to another frame in the meantime, that ordering is gone. The outstanding count reaching zero means the ID is idle; only stream_closed means it is free.

And it teaches that c_no_id is a throughput limit with a completely different cause from c_stalls in Section 7. Both stall a request. Running out of outstanding slots means the memory system is the limit; running out of IDs means too many independent ordering domains are open at once — which at Chapter 18.4 §16's 45 transmit chains in flight is 45 domains against 16 IDs, and the resolution is Section 12's.

Deliberately simplified: the hit and free searches are combinational over sixteen entries and evaluated every cycle, which is a large amount of logic in the address path; a production allocator keeps a small CAM and a free list. The unbind loop writes bound[i] for all sixteen entries in one cycle, which is a sixteen-way comparison plus a write. And there is no ageing: a stream that is never closed holds its ID for ever, which a production design bounds with a timeout.

Production implication: ids_bound against NUM_ID is the number that decides whether an interconnect's ID width is adequate, and it is a negotiation an integrator usually loses for lack of evidence. An interconnect offering 4-bit IDs gives sixteen; a design needing more must either reduce its ordering domains or accept c_no_id stalls. The counter converts "we would like more IDs" into "we exhaust sixteen for 14% of requests", which is an argument.


10. The ID Budget, Already Partly Spent

Before this chapter allocates IDs, two earlier chapters have already committed some. This section adds them up, and the total does not fit.

Chapter 18.2 §10 offered three barrier strategies and mode 0 was "give both writes the same AXI ID." That is a commitment: the descriptor writebacks for a ring must all share one ID, or the ordering that mode depends on is gone.

Chapter 18.4 §5 requires a transmit frame's data to reach the FIFO in frame order, and the cheapest way to get that is one ID per frame — which at Section 6's 45 frames in flight is 45 IDs.

Add it up for a single-queue port:

ConsumerIDs neededWhy
RX descriptor writeback1Chapter 18.2 §10 mode 0
TX descriptor writeback1the same, other ring
RX descriptor fetch1 — reads are unorderedany ID will do
TX descriptor fetch1the same
RX frame data1 — Chapter 18.3 §8 counts responsesno ordering needed
TX frame dataone per frame in flight — 45frame order into the FIFO
total50against an interconnect's 16

The transmit data row is the whole problem and it is 90% of the requirement.

Three ways out, and they are not equally good.

OptionCost
fewer transmit frames in flightChapter 18.4 §16: throughput falls proportionally
share one ID across all transmit datathe frames serialise — 5 round trips for a jumbo
stop needing frame order from the protocolSection 12 — a reorder buffer

Row one is a real option and it is the one most designs take without realising. A design with 16 IDs and one per transmit frame has 16 frames in flight, which at 300 ns and 6.72 ns per minimum-size frame is 16/45 = 36% of line rateand nothing reports it as a limit, because c_no_id is a counter most designs do not have.

Row two is worse than it looks for jumbo frames and free for ordinary ones, because a 1518-octet frame at a 32-beat limit is one burst and cannot be reordered with itself.

Row three is the right answer and Section 12 develops it. The key realisation is that frame order is needed at the FIFO, not on the bus — so a small buffer that restores order locally buys back every ID the transmit path was spending, and the buffer's size is Section 16's subject.


11. RTL 5 — The Ordering-Domain Tracker

The block that knows which transactions must be ordered against which, independently of how the bus is told about it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// ordering_domain_tracker -- the design's own model of what must be
// ordered, kept separately from the ID assignment.
//
// The separation is the point. Section 9 maps domains onto IDs when
// there are enough IDs; section 12 restores order locally when there
// are not. Both need the same underlying fact -- which transactions
// belong to which domain -- and that fact is this block.
// -----------------------------------------------------------------------
module ordering_domain_tracker
  import axishape_pkg::*;
#(
  parameter int NUM_DOMAIN = 64
)(
  input  logic               clk,
  input  logic               rst_n,

  input  logic               open_valid,
  input  purpose_e           open_purpose,
  input  logic [15:0]        open_stream,
  output logic               open_grant,
  output logic [$clog2(NUM_DOMAIN)-1:0] open_domain,

  // A transaction joins a domain and takes a sequence number within
  // it. The sequence number is what section 12's reorder buffer
  // uses; the ID is what the bus uses. They are not the same thing.
  input  logic               join_valid,
  input  logic [$clog2(NUM_DOMAIN)-1:0] join_domain,
  output logic [7:0]         join_seq,
  output logic               join_grant,

  input  logic               retire_valid,
  input  logic [$clog2(NUM_DOMAIN)-1:0] retire_domain,

  input  logic               close_valid,
  input  logic [$clog2(NUM_DOMAIN)-1:0] close_domain,

  output logic [7:0]         domain_next_expect [NUM_DOMAIN],
  output logic [15:0]        domains_open,
  output logic [31:0]        c_opens,
  output logic [31:0]        c_no_domain,
  output logic [31:0]        c_joins,
  output logic               closed_with_outstanding    // must never assert
);

  logic                  d_open [NUM_DOMAIN];
  purpose_e              d_purp [NUM_DOMAIN];
  logic [15:0]           d_strm [NUM_DOMAIN];
  logic [7:0]            d_next [NUM_DOMAIN];       // next seq to allocate
  logic [7:0]            d_exp  [NUM_DOMAIN];       // next seq to deliver
  logic [5:0]            d_out  [NUM_DOMAIN];

  logic                  free;
  logic [$clog2(NUM_DOMAIN)-1:0] free_i;
  always_comb begin
    int i;
    free = 1'b0; free_i = '0;
    for (i = NUM_DOMAIN-1; i >= 0; i--)
      if (!d_open[i]) begin
        free   = 1'b1;
        free_i = i[$clog2(NUM_DOMAIN)-1:0];
      end
  end

  assign open_grant  = open_valid && free;
  assign open_domain = free_i;

  assign join_grant = join_valid && d_open[join_domain];
  assign join_seq   = d_next[join_domain];

  // Closing a domain with transactions still outstanding loses the
  // ordering the domain existed to express. It is the domain-level
  // form of section 9's id_reused_while_live.
  assign closed_with_outstanding = close_valid && d_open[close_domain] &&
                                   (d_out[close_domain] != 6'd0);

  always_comb begin
    int i;
    domains_open = '0;
    for (i = 0; i < NUM_DOMAIN; i++)
      if (d_open[i]) domains_open = domains_open + 16'd1;
  end

  always_ff @(posedge clk or negedge rst_n) begin
    int i;
    if (!rst_n) begin
      for (i = 0; i < NUM_DOMAIN; i++) begin
        d_open[i] <= 1'b0; d_purp[i] <= PURP_DESC_FETCH;
        d_strm[i] <= '0; d_next[i] <= '0; d_exp[i] <= '0; d_out[i] <= '0;
      end
      c_opens <= '0; c_no_domain <= '0; c_joins <= '0;
    end else begin
      if (open_valid && open_grant) begin
        d_open[free_i] <= 1'b1;
        d_purp[free_i] <= open_purpose;
        d_strm[free_i] <= open_stream;
        d_next[free_i] <= '0;
        d_exp[free_i]  <= '0;
        d_out[free_i]  <= '0;
        c_opens        <= c_opens + 1;
      end
      if (open_valid && !open_grant) c_no_domain <= c_no_domain + 1;

      if (join_valid && join_grant) begin
        d_next[join_domain] <= d_next[join_domain] + 8'd1;
        d_out[join_domain]  <= d_out[join_domain] + 6'd1;
        c_joins             <= c_joins + 1;
      end

      // Retiring advances the EXPECTED sequence, which is what the
      // reorder buffer compares against. Note it advances by one per
      // retirement regardless of which sequence retired -- section 12
      // holds anything early.
      if (retire_valid) begin
        d_exp[retire_domain] <= d_exp[retire_domain] + 8'd1;
        if (d_out[retire_domain] != 6'd0)
          d_out[retire_domain] <= d_out[retire_domain] - 6'd1;
      end

      if (close_valid && (d_out[close_domain] == 6'd0))
        d_open[close_domain] <= 1'b0;
    end
  end

  assign domain_next_expect = d_exp;

endmodule

Classification: a domain table carrying two sequence numbers — one for allocation, one for delivery.

What it teaches: that the design's ordering model and the bus's ordering mechanism are separate things, and separating them is what makes Section 12 possible. A design that expresses ordering only by ID assignment has no ordering model left when it runs out of IDs. This block keeps the model — domain, sequence number, next-expectedindependently of whether the bus is enforcing it, so the same information serves both the ID policy and the local reorder buffer.

And it teaches that two sequence numbers are needed rather than one. d_next is what the next transaction takes; d_exp is what the reorder buffer will deliver next. They differ by exactly the number of transactions in flight, and collapsing them into one counter makes it impossible to tell a transaction that arrived early from one that arrived on time.

Deliberately simplified: 64 domains with combinational search and a 64-entry parallel update is far more logic than a real design spends — a production tracker keeps a free list and indexes directly, since the domain number is known to the requester. The sequence numbers are 8-bit and wrap at 256, which bounds a domain to 256 transactions with no check. And domain_next_expect is exported as a 64-entry array, which is a wide bus to Section 12 rather than the single lookup a real design would use.

Production implication: c_no_domain and Section 9's c_no_id together are the diagnostic pair for Section 10's arithmetic. Domains exhausted means the design has more independent streams than it can trackChapter 18.4 §16's 45 chains against 64 domains is comfortable. IDs exhausted with domains available means the mapping is the limit, which is exactly the condition Section 12 removes, and the pair is how an integrator knows which of the two to spend area on.


12. Getting the Ordering Back Without Spending an ID

Section 10 ended with 50 IDs wanted and 16 available. This section is the way out, and it rests on one observation.

Frame order is needed at the FIFO. It is not needed on the bus.

Chapter 18.4 §5's gather engine must deliver octets into the transmit FIFO in frame order, because that is the order they go onto the wire. It does not follow that the bus must deliver them in that order — only that something between the bus and the FIFO must.

Where order is restoredCostIDs consumed
on the bus, by IDthe frames serialiseone per frame — 45
locally, by a reorder bufferthe buffer's SRAMone, for everything

And the buffer is small, because the reordering window is bounded by the number of outstanding transactions rather than by the number of frames.

OutstandingBurst sizeWorst-case buffer
216 beats — 1 KiB1.00 KiB
416 beats3.00 KiB
816 beats7.00 KiB
832 beats — 2 KiB14.00 KiB
1616 beats15.00 KiB

The rule is (O − 1) × burst payload: with O transactions in flight, at most O − 1 can have arrived out of order while the design waits for the one it wants. At eight outstanding and 16-beat bursts that is 7 KiB — against Chapter 18.1 §18's 32 KiB receive FIFO at 100 Gb/s, 21.9%.

So the trade is explicit: 7 KiB of SRAM buys back 44 AXI IDs, and with them Chapter 18.4 §16's full pipeline depth.

Two details make this work and both are easy to get wrong.

First, the buffer is per ordering domain, not global. A transmit frame's bursts must be ordered against each other, not against another frame's — so the reorder buffer is indexed by Section 11's domain and sequence number, and a design with one flat buffer reintroduces exactly the cross-frame ordering it was trying to avoid.

Second, the buffer must be sized for the worst case, not the average. Out-of-order responses are rare on a well-behaved interconnect — c_reorder_stalls on a typical port is near zero — and a buffer sized for the average is a buffer that overflows on the day the memory controller reorders. An overflow means a burst's data has nowhere to go and the bus must be stalled, which is safe but slow; a design that instead drops the data has silently corrupted a frame.

And the result closes Chapter 18.4's open question. That chapter's c_reorder_stalls counted responses arriving early with no buffer to hold them. With the buffer, the counter changes meaning: it becomes a measure of how much reordering the interconnect actually does, which is the input to deciding whether the buffer could be smaller.


A single-queue 100 gigabit port wants about fifty AXI IDs: one each for the receive and transmit descriptor writebacks, one each for the two descriptor fetch streams, one for receive frame data, and one per transmit frame in flight — which at Chapter 18.4's forty-five chains is forty-five, or ninety percent of the total. A typical interconnect offers sixteen. Three ways out exist. Reducing the number of transmit frames in flight cuts throughput proportionally: sixteen frames against the forty-five needed is thirty-six percent of line rate, and no counter reports it. Sharing one ID across all transmit data serialises a frame's own bursts, which costs nothing on a 1518-octet frame that fits in one burst and costs five round trips on a 9000-octet jumbo. The third way is to notice that frame order is needed at the transmit FIFO, not on the bus, and to restore it locally with a reorder buffer indexed by ordering domain and sequence number. Its size is a structural bound: with O transactions outstanding, at most O minus one can have arrived early while the design waits for the one it needs, so the buffer holds O minus one burst payloads — seven kilobytes at eight outstanding and 16-beat bursts, which is 3.7 percent of Chapter 18.1's receive FIFO and buys back forty-four IDs.50 IDs wanted45 of them TX frames16 offereda 4-bit ID fieldFewer frames inflight16/45 = 36% of line rateOne ID for all TXdata5 round trips per jumboReorder locallyorder is needed at theFIFO(O - 1) x bursta structural bound7.00 KiB8 outstanding, 16 beats44 IDs recoveredand full pipeline depth12
Figure 4 — seven kilobytes of SRAM in exchange for forty-four AXI IDs.

13. RTL 6 — The Efficiency Monitor

The block that measures Section 4's two verdicts separately, because a design that measures only one of them draws the wrong conclusion.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// efficiency_monitor -- bus efficiency AND transaction count, kept
// apart.
//
// Section 4: the same misalignment is 1.40 points of cycle efficiency
// and 37.0% of transactions. A monitor reporting one number reports
// whichever the designer happened to care about, and the two give
// opposite verdicts on whether alignment matters.
// -----------------------------------------------------------------------
module efficiency_monitor
  import axishape_pkg::*;
(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        addr_issued,
  input  logic        beat_transferred,
  input  logic        bus_idle,
  input  logic        stalled_no_slot,
  input  logic        stalled_no_id,
  input  logic        stalled_reorder,

  input  logic        req_started,
  input  logic [15:0] req_bytes,

  output logic [31:0] c_addr_cycles,
  output logic [31:0] c_data_cycles,
  output logic [31:0] c_idle_cycles,
  output logic [31:0] c_stall_slot,
  output logic [31:0] c_stall_id,
  output logic [31:0] c_stall_reorder,
  output logic [31:0] c_requests,
  output logic [31:0] c_request_octets,

  // Two independent verdicts.
  output logic [15:0] cycle_efficiency_pct,      // data / (data + addr)
  output logic [15:0] octets_per_txn,            // the transaction verdict
  output logic [15:0] bus_occupancy_pct
);

  wire [31:0] busy = c_addr_cycles + c_data_cycles;
  wire [31:0] total = busy + c_idle_cycles;

  always_comb begin
    // Verdict 1: of the cycles the bus spent on us, what fraction
    // moved data? This is what "efficiency" usually means and it is
    // insensitive to the thing that matters.
    if (busy == '0) cycle_efficiency_pct = 16'd0;
    else cycle_efficiency_pct = 16'((c_data_cycles * 32'd100) / busy);

    // Verdict 2: how much payload did each transaction carry? This
    // is the one that binds, because 18.1 section 12's limit is one
    // transaction per cycle regardless of its size.
    if (c_addr_cycles == '0) octets_per_txn = 16'd0;
    else octets_per_txn = 16'(c_request_octets / c_addr_cycles);

    if (total == '0) bus_occupancy_pct = 16'd0;
    else bus_occupancy_pct = 16'((busy * 32'd100) / total);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      c_addr_cycles <= '0; c_data_cycles <= '0; c_idle_cycles <= '0;
      c_stall_slot <= '0; c_stall_id <= '0; c_stall_reorder <= '0;
      c_requests <= '0; c_request_octets <= '0;
    end else begin
      if (addr_issued)      c_addr_cycles    <= c_addr_cycles + 1;
      if (beat_transferred) c_data_cycles    <= c_data_cycles + 1;
      if (bus_idle)         c_idle_cycles    <= c_idle_cycles + 1;
      if (stalled_no_slot)  c_stall_slot     <= c_stall_slot + 1;
      if (stalled_no_id)    c_stall_id       <= c_stall_id + 1;
      if (stalled_reorder)  c_stall_reorder  <= c_stall_reorder + 1;
      if (req_started) begin
        c_requests       <= c_requests + 1;
        c_request_octets <= c_request_octets + {16'b0, req_bytes};
      end
    end
  end

endmodule

Classification: a dual-verdict bus monitor with three independent stall causes.

What it teaches: that octets_per_txn is the number Chapter 18.1 §12's wall is actually about, and almost no bus monitor reports it. A conventional monitor reports utilisation and efficiency — both cycle-based — and both are insensitive to the transaction count, because a transaction's address cycle is one cycle whether it carries 64 octets or 2048. A port at 96% cycle efficiency and 64 octets per transaction is in serious trouble; one at 80% and 1024 octets per transaction is fine.

And it teaches that the three stall counters name three different owners. c_stall_slot is the memory system — Section 7's pool. c_stall_id is this chapter's allocation policy — Section 10's arithmetic. c_stall_reorder is the interconnect's behaviour — how much it reorders, which nobody controls. A single "stalled" counter cannot separate a memory problem from a design parameter from a third party's behaviour.

Deliberately simplified: three combinational divides, which software should do from the raw counters. bus_idle is taken as an input where a real monitor derives it from the handshakes. And the counters are global rather than per purpose, so a port whose descriptor traffic is efficient and whose data traffic is not reports an average that describes neither — which Section 14's telemetry splits.

Production implication: the pair cycle_efficiency_pct and octets_per_txn should be reported together and never separately, because Section 4's whole finding is that they disagree. An integration report quoting 94.6% bus efficiency on a port issuing 1.370 transactions where 1 would do is quoting the number that does not bind, and the design will be signed off as adequate and will not reach line rate on minimum-size frames.


14. RTL 7 — Shaping Telemetry

Per-purpose accounting, because Section 13's global numbers hide the thing that would fix them.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// axishape_telemetry -- the same measurements, split by purpose.
//
// A port's descriptor traffic and its frame data have completely
// different shapes: 16-octet single-beat transactions against
// 1024-octet bursts. Averaging them describes neither.
// -----------------------------------------------------------------------
module axishape_telemetry
  import axishape_pkg::*;
#(
  parameter int NUM_PURP = 5
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic        txn_valid,
  input  purpose_e    txn_purpose,
  input  logic [15:0] txn_bytes,
  input  logic [7:0]  txn_len,
  input  bound_e      txn_bound,
  input  logic        txn_split,

  input  logic        rsp_valid,
  input  purpose_e    rsp_purpose,
  input  logic [15:0] rsp_latency_cycles,

  output logic [31:0] c_txn      [NUM_PURP],
  output logic [31:0] c_octets   [NUM_PURP],
  output logic [31:0] c_beats    [NUM_PURP],
  output logic [31:0] c_splits   [NUM_PURP],
  output logic [31:0] c_lat_sum  [NUM_PURP],
  output logic [31:0] c_lat_n    [NUM_PURP],
  output logic [15:0] worst_lat  [NUM_PURP],
  output logic [31:0] c_bound    [4],
  output logic [15:0] octets_per_txn [NUM_PURP]
);

  always_comb begin
    int p;
    for (p = 0; p < NUM_PURP; p++)
      octets_per_txn[p] = (c_txn[p] == '0) ? 16'd0
                                           : 16'(c_octets[p] / c_txn[p]);
  end

  always_ff @(posedge clk or negedge rst_n) begin
    int p;
    if (!rst_n) begin
      for (p = 0; p < NUM_PURP; p++) begin
        c_txn[p] <= '0; c_octets[p] <= '0; c_beats[p] <= '0;
        c_splits[p] <= '0; c_lat_sum[p] <= '0; c_lat_n[p] <= '0;
        worst_lat[p] <= '0;
      end
      for (p = 0; p < 4; p++) c_bound[p] <= '0;
    end else begin
      if (txn_valid) begin
        c_txn[txn_purpose]    <= c_txn[txn_purpose] + 1;
        c_octets[txn_purpose] <= c_octets[txn_purpose] + {16'b0, txn_bytes};
        c_beats[txn_purpose]  <= c_beats[txn_purpose] + {24'b0, txn_len} + 32'd1;
        if (txn_split) c_splits[txn_purpose] <= c_splits[txn_purpose] + 1;
        c_bound[txn_bound] <= c_bound[txn_bound] + 1;
      end

      // Per-purpose latency. The descriptor fetch's latency is what
      // 18.2 section 3's prefetch window must cover; the data write's
      // is what 18.3 section 10's barrier waits for. They are
      // different numbers and both are needed.
      if (rsp_valid) begin
        c_lat_sum[rsp_purpose] <= c_lat_sum[rsp_purpose] +
                                  {16'b0, rsp_latency_cycles};
        c_lat_n[rsp_purpose]   <= c_lat_n[rsp_purpose] + 1;
        if (rsp_latency_cycles > worst_lat[rsp_purpose])
          worst_lat[rsp_purpose] <= rsp_latency_cycles;
      end
    end
  end

endmodule

Classification: a per-purpose transaction and latency accumulator.

What it teaches: that per-purpose latency is the measurement Module 18 has needed since Chapter 18.1 §17 and has been estimating instead. Three chapters have used "a 300 ns round trip" as a placeholder: Chapter 18.2 §9 sized an in-flight table with it, Chapter 18.3 §10 sized a barrier with it, Chapter 18.4 §16 derived a pipeline depth from it. worst_lat measures it, per purpose, on the real system — and the read latency and the write-response latency are frequently very different numbers.

And it teaches that splitting by purpose changes what the split counter means. A global c_splits mixes descriptor fetches — 16 octets, never split — with frame data. Per purpose, c_splits[PURP_RX_DATA] / c_txn[PURP_RX_DATA] is directly comparable against Section 4's theoretical 37.04%, which is what makes it actionable rather than merely nonzero.

Deliberately simplified: NUM_PURP divides evaluated combinationally; the latency measurement requires a timestamp carried with each transaction, which is a field this listing assumes exists and does not create. The accumulators wrap, and a windowed version reset on read is what a real design provides. And c_bound is indexed by a two-bit enum cast to an array index, which works and is not how a production design would write it.

Production implication: worst_lat[PURP_DESC_FETCH] is the number Chapter 18.2 §5's low-water mark should be set from, and it is measurable only here. A prefetch window refilled at a threshold chosen from a datasheet figure is a guess; one refilled at a threshold derived from the measured worst-case fetch latency is a design. The same argument applies to Chapter 18.4 §7's cut-through lead, which is computed from a worst-case stall figure an integrator supplies and this counter can check.


15. RTL 8 — The Shaping Conformance Monitor

The last block, and the first verdict in Module 18 that is about a policy rather than a fault.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// -----------------------------------------------------------------------
// axishape_conformance_monitor -- is the shaping policy working?
//
// Unlike the monitors in 18.1 to 18.4, most of these verdicts are
// not faults. They are a policy that could be better, and the
// distinction matters: a fault stops the port, a policy costs
// throughput nobody has attributed to it.
// -----------------------------------------------------------------------
module axishape_conformance_monitor
  import axishape_pkg::*;
#(
  parameter int NUM_PURP = 5,
  parameter int MAX_BEATS = 32
)(
  input  logic        clk,
  input  logic        rst_n,

  input  logic [31:0] c_bound [4],
  input  logic [31:0] c_txn   [NUM_PURP],
  input  logic [15:0] octets_per_txn [NUM_PURP],
  input  logic [15:0] mean_beats_x10,
  input  logic [31:0] c_stall_slot,
  input  logic [31:0] c_stall_id,
  input  logic [31:0] c_stall_reorder,
  input  logic [31:0] c_no_id,
  input  logic [31:0] c_no_domain,
  input  logic [5:0]  peak_total,
  input  logic [5:0]  ids_bound,
  input  logic [15:0] worst_lat [NUM_PURP],

  input  logic        illegal_burst,
  input  logic        id_reused_while_live,
  input  logic        closed_with_outstanding,
  input  logic        reservation_overcommitted,

  output logic        shaping_ok,
  output logic        protocol_violation,
  output logic        cfg_fault,
  output logic        bursts_too_short,
  output logic        alignment_dominating,
  output logic        id_starved,
  output logic        outstanding_starved,
  output logic        reorder_pressure,
  output logic        none_of_the_above
);

  // Faults. These stop the port or corrupt it.
  assign protocol_violation = illegal_burst | id_reused_while_live |
                              closed_with_outstanding;
  assign cfg_fault          = reservation_overcommitted;

  // Policy findings. These cost throughput nobody has attributed.
  assign bursts_too_short = (mean_beats_x10 < 16'(MAX_BEATS * 10 / 2));

  // More than a fifth of bursts shortened by the page rule means
  // alignment is the dominant limit -- section 4's 37.04% is what
  // pure misalignment looks like on maximum-size frames.
  assign alignment_dominating =
    ((c_bound[BOUND_PAGE] + c_bound[BOUND_BURST] +
      c_bound[BOUND_BUFFER] + c_bound[BOUND_LENGTH]) > 32'd1000) &&
    (c_bound[BOUND_PAGE] >
     ((c_bound[BOUND_PAGE] + c_bound[BOUND_BURST] +
       c_bound[BOUND_BUFFER] + c_bound[BOUND_LENGTH]) >> 3));

  assign id_starved          = (c_no_id > 32'd1000) ||
                               (c_stall_id > 32'd1000);
  assign outstanding_starved = (c_stall_slot > 32'd1000);
  assign reorder_pressure    = (c_stall_reorder > 32'd1000);

  assign shaping_ok = !protocol_violation && !cfg_fault;

  assign none_of_the_above = shaping_ok && !bursts_too_short &&
                             !alignment_dominating && !id_starved &&
                             !outstanding_starved && !reorder_pressure;

  // ---- properties -------------------------------------------------

  p_no_illegal_burst:
    assert property (@(posedge clk) disable iff (!rst_n)
      !illegal_burst)
    else $error("a burst crossed a 4 KiB boundary");

  p_no_live_id_reuse:
    assert property (@(posedge clk) disable iff (!rst_n)
      !id_reused_while_live)
    else $error("an ID was rebound while its ordering domain was live");

  p_violation_excludes_ok:
    assert property (@(posedge clk) disable iff (!rst_n)
      protocol_violation |-> !shaping_ok)
    else $error("shaping_ok asserted with a protocol violation");

  p_peak_within_pool:
    assert property (@(posedge clk) disable iff (!rst_n)
      peak_total <= MAX_OUT)
    else $error("outstanding transactions exceeded the pool");

  p_ids_bound_within_pool:
    assert property (@(posedge clk) disable iff (!rst_n)
      ids_bound <= MAX_ID)
    else $error("more IDs bound than exist");

endmodule

Classification: a verdict generator that separates protocol faults from policy findings.

What it teaches: that most of what this chapter measures is not a fault, and saying so explicitly changes how it is acted on. illegal_burst stops the port. bursts_too_short costs a few per cent of the bus and nobody notices for a year — and the two arriving through the same reporting channel with the same urgency is how a real fault gets lost among tuning suggestions.

And it teaches that alignment_dominating needs a denominator that sums all four bound causes. A page-split count alone is meaningless; a page-split count against the total burst count is Section 4's 37.04% made comparable. The threshold of one eighth is deliberately below that figure: a port at 12.5% page-bound is already worth looking at, and one at 37% is textbook misalignment.

Deliberately simplified: bursts_too_short fires at half the configured maximum, which is arbitrary and would be a register. The four-way sum of c_bound is recomputed twice combinationally in one expression. And there is no verdict for the interaction Section 4 identified — a port whose large frames are misaligned and whose small frames saturate the channel is fine on both counts separately and should be reported as such, which needs the per-purpose split from Section 14 rather than these globals.

Production implication: none_of_the_above for the fifth and last time in Module 18. Five monitors now cover the MAC's interfaces, its ring, both DMA directions and its bus shaping. A port on which all five assert has no known hardware or driver-contract problem, and the remaining explanations are the application, the network, or the offered load — which is where Chapter 18.1 §21's complaint 2 wanted to send an investigation and had no evidence to.


16. The Reorder Buffer, Sized

Section 12 asserted a rule. This section derives it and then argues about the worst case, because the worst case is what a buffer must be sized for and it is not the common one.

The rule: with O transactions outstanding in one ordering domain, at most O − 1 responses can have arrived out of order while the design waits for the one it needs.

The reasoning is short. The design is waiting for sequence k. Every other outstanding transaction has a sequence above k — sequences below k have already been delivered — so at most O − 1 of them can be sitting in the buffer. Each occupies one burst's payload.

Outstanding16-beat bursts (1 KiB)32-beat bursts (2 KiB)
21.00 KiB2.00 KiB
43.00 KiB6.00 KiB
87.00 KiB14.00 KiB
1615.00 KiB30.00 KiB

And the table shows why burst length and reorder buffer are the same decision. Doubling the burst length halves the transaction count — Section 4 — and doubles the reorder buffer. A design choosing 32-beat bursts and 8 outstanding is choosing 14 KiB of SRAM, which against Chapter 18.1 §18's 32 KiB receive FIFO is 44% and is emphatically not free.

Now the worst case, and this is where designs get it wrong.

On a well-behaved interconnect, out-of-order responses are rare. Chapter 18.4 §5's c_reorder_stalls on a typical port is near zero, because most interconnects return responses in issue order most of the time. So a buffer sized for the measured behaviour is nearly empty.

And a buffer sized for the measured behaviour overflows on the day the memory controller reorders, which happens when:

ConditionWhy responses reorder
two DRAM banksdifferent queue depths
a refreshone bank stalls, the other does not
a competing masterits traffic lands between ours
an interleaving switchtwo routes of different depth
a retryone transaction goes round twice

Every row is ordinary and every row is intermittent, which is Chapter 18.2 §19's class-75 environment all over again: a failure rate that is a property of the system rather than of the input.

So the buffer must be sized for O − 1 and the overflow behaviour must be safe.

On overflowConsequence
stall the read data channelsafe — the bus backpressures; throughput falls
drop the early datathe frame is corrupted, silently
deliver out of orderthe frame is corrupted, silently

Row one is the only acceptable answer, and it is available because AXI's read data channel has a ready signal. A design that cannot stall — because it has already accepted the beat — must have the buffer space, which is the argument for sizing at the worst case rather than handling the overflow.


17. What the Bus Assumes About the MAC

Chapter 18.1 §17 listed what the MAC assumes about the system. This chapter is the only one in Module 18 where the obligations run the other way.

#The bus requires the MAC toIf violatedDetectable?
1never cross a 4 KiB boundary in one burstundefined — error, wrap, or silenceyes — Section 5
2not exceed the agreed outstanding limitthe interconnect blocks or errorsyes — Section 7
3not exceed the agreed burst lengthrejectedyes
4keep AWVALID stable until AWREADYprotocol violationassertion
5not deadlock by holding a channelthe whole interconnect stopshard
6respect the ID width it was givenan ID nobody routesyes

Row five is the one with system-wide consequences and the least local visibility. A master that asserts AWVALID and waits for something that depends on the write data channel making progress — which depends on the write address being acceptedhas deadlocked the interconnect for every master on it. The MAC's own counters show nothing; the symptom is the whole SoC stopping.

And the rule that prevents it is a discipline rather than a check: never make the acceptance of one channel conditional on progress in another. Chapter 18.4 §5's gather engine is the block that could get this wrong — it issues a read address and consumes read data, and if the address issue waited for the FIFO to drain it would be conditioning one channel on another.

Which is why that listing gates ar_valid on !fifo_full rather than on the FIFO having room for the whole burst. The distinction is small and the consequence is not: a master that will always eventually accept its read data cannot deadlock the read channel; one that may refuse indefinitely can.

Now the reciprocal list, because this chapter has also established what the MAC needs from the bus:

#The MAC requires the bus toEstablished in
1complete same-ID transactions in orderChapter 18.2 §10's mode 0
2offer enough outstanding slotsSection 6 — 5 at 300 ns, or 67 on minimum-size frames
3offer enough IDsSection 10 — 50 wanted, 16 typical
4respond to writes at a point of visibilityChapter 18.3 §19's class 76
5not reorder beyond O − 1Section 16 — it cannot, structurally

Rows two and three are the ones an integration negotiation is actually about, and this chapter's contribution is that both now have numbers attached. "We need more outstanding transactions" is a request; "we stall on 14% of requests at eight outstanding and Little's law says we need five for 1518-octet frames and 67 for 64-octet ones" is an argument.

And row one is worth a closing note because it is the only requirement the MAC cannot verify. An interconnect that claims same-ID ordering and does not honour it produces Chapter 18.2 §20's run C — corrupted descriptors, intermittently, with every component behaving as documented. The only defence is Chapter 18.2 §11's mode 1, which depends on nothing, and the only reason not to use it is the round trip Section 6 has now shown how to pipeline away.


18. The Cost, Accounted

Eight blocks, and this chapter's cost is unusual: the logic is significant, the memory is significant, and the parameters are what really cost.

BlockApproximate costDominated by
burst_length_selector~200 flops + 4 comparatorsthe minimum
alignment_splitter~250 flopsthe address walk
outstanding_governor~300 flops + 5-way sumsthe combinational totals
axi_id_allocator~700 flops + a 16-entry CAMthe CAM
ordering_domain_tracker~2 200 flops64 domains × 34 bits
efficiency_monitor~300 flopscounters
axishape_telemetry~1 400 flops5 purposes × 7 counters
axishape_conformance_monitor~150 flopscomparators

About 5 500 flops, the largest of Module 18's five chapters — and two thirds of it is the domain tracker and the telemetry, both of which are bookkeeping rather than datapath.

The memory:

StructureSizeSet by
reorder buffer, 8 outstanding, 16-beat7.00 KiBSection 16
reorder buffer, 8 outstanding, 32-beat14.00 KiBthe burst-length choice
the ID CAM16 × 24 bits — 48 octetsnegligible

The second row is the one to carry forward, because §4 chose 32-beat bursts. Chapter 19.6 §19 shows that pairing the 16-beat figure with a 32-beat design is not a budget slip: 7 KiB holds 3.5 of this datapath's 2 KiB bursts, so the fourth simultaneous out-of-order response overflows it — and Section 16's "the overflow behaviour must be safe" then has no safe answer.

And the parameters, which is where the real cost is, because each is a negotiation with somebody outside the MAC team:

ParameterWantedTypically offeredCost of the gap
burst length32 beats16, often3.69 points of efficiency, 2× the transactions
outstanding5 to 678 or 16Section 6's throughput ceiling
AXI IDs5016Section 12's 7 KiB, or 36% of line rate

Row three is the one this chapter resolves and it resolves it by spending memory. Sixteen IDs and no reorder buffer is 36% of line rate; sixteen IDs and 7 KiB of reorder buffer is full rate. The 7 KiB buys 44 IDs.

Module 18's whole memory bill, now complete, for a 100 Gb/s port:

SizeOf a 4 MiB on-chip budget
receive FIFO, lossless to 100 m32 KiB0.78%
transmit FIFO~9 KiB0.22%
reorder buffer14 KiB0.34%
total on-chip~55 KiB1.34%
rings, both directions, padded512 KiB DRAM
receive buffers, 4096 × 2 KiB8 MiB DRAM

Just over one per cent of a chip's SRAM for one 100 Gb/s port's system interface, of which 58% is the receive FIFO — and at this rate three quarters of that is the memory-stall buffer rather than Chapter 14.2's headroom. At 1 Gb/s the proportions invert entirely and the headroom is 87% of a 1.9 KiB FIFO, which is Chapter 18.1 §18's crossover at about 9 Gb/s.


19. Properties Worth Asserting, and One Worth Refusing

This chapter's properties are mostly about resource limits, which makes the rejected one particularly easy to write: it is a resource property that is true of the resource and false of what the resource carries.

Burst shaping.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// No burst crosses a 4 KiB boundary. Non-negotiable.
p_burst_within_page:
  assert property (@(posedge clk) disable iff (!rst_n)
    (burst_valid && burst_ready) |->
      ((burst_addr[11:0] + burst_bytes) <= PAGE_B))
  else $error("a burst crossed a 4 KiB boundary");

// No burst exceeds the negotiated length.
p_burst_within_max:
  assert property (@(posedge clk) disable iff (!rst_n)
    (burst_valid && burst_ready) |-> (burst_len < MAX_BEATS))
  else $error("a burst exceeded the negotiated maximum length");

// The bound reported matches the length produced.
p_bound_matches_length:
  assert property (@(posedge clk) disable iff (!rst_n)
    (req_valid && (bound == BOUND_PAGE)) |->
      (burst_bytes == {3'b0, to_page}))
  else $error("a page-bound burst was not the page distance");

// A request is fully consumed before the next is accepted.
p_one_request_at_a_time:
  assert property (@(posedge clk) disable iff (!rst_n)
    active |-> !req_ready)
  else $error("a request was accepted while one was in progress");

// The remaining count only decreases.
p_left_monotonic:
  assert property (@(posedge clk) disable iff (!rst_n)
    active |=> (left <= $past(left)))
  else $error("the remaining byte count increased");

// The last burst of a request consumes exactly what is left.
p_last_burst_exact:
  assert property (@(posedge clk) disable iff (!rst_n)
    (burst_valid && burst_ready && burst_is_last) |->
      (burst_bytes == left))
  else $error("the final burst did not consume the remainder");

// Every request eventually completes.
p_request_completes:
  assert property (@(posedge clk) disable iff (!rst_n)
    (req_valid && req_ready) |-> ##[1:$] (burst_valid && burst_ready &&
                                          burst_is_last))
  else $error("a request never produced a final burst");

Outstanding transactions.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The pool is never exceeded.
p_pool_not_exceeded:
  assert property (@(posedge clk) disable iff (!rst_n)
    peak_total <= TOTAL)
  else $error("outstanding transactions exceeded the pool");

// A grant inside a reservation is always given.
p_reservation_honoured:
  assert property (@(posedge clk) disable iff (!rst_n)
    (issue_valid && (used[issue_purpose] < cfg_reserved[issue_purpose]))
      |-> issue_grant)
  else $error("a purpose was refused inside its own reservation");

// A retirement never underflows.
p_retire_no_underflow:
  assert property (@(posedge clk) disable iff (!rst_n)
    retire_valid |-> (used[retire_purpose] != 6'd0))
  else $error("a retirement arrived for a purpose with nothing outstanding");

// An overcommitted reservation is flagged, not silently clipped.
p_overcommit_flagged:
  assert property (@(posedge clk) disable iff (!rst_n)
    (reserved_sum > TOTAL) |-> reservation_overcommitted)
  else $error("an overcommitted reservation was not flagged");

// Every issued transaction eventually retires.
p_issue_retires:
  assert property (@(posedge clk) disable iff (!rst_n)
    (issue_valid && issue_grant) |-> ##[1:$] retire_valid)
  else $error("an issued transaction never retired");

ID allocation and ordering domains.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// An ID is never bound to two domains at once.
p_id_one_domain:
  assert property (@(posedge clk) disable iff (!rst_n)
    (req_valid && req_grant && !hit) |-> !bound[free_i])
  else $error("an ID was bound while already bound");

// The invariant signal never asserts.
p_no_live_reuse:
  assert property (@(posedge clk) disable iff (!rst_n)
    !id_reused_while_live)
  else $error("an ID was rebound while its ordering domain was live");

// Two requests in the same domain always get the same ID.
p_same_domain_same_id:
  assert property (@(posedge clk) disable iff (!rst_n)
    (req_valid && req_grant && hit) |-> (req_id == hit_i))
  else $error("a request in an open domain was given a different ID");

// A domain is only closed when nothing is outstanding.
p_close_requires_quiet:
  assert property (@(posedge clk) disable iff (!rst_n)
    !closed_with_outstanding)
  else $error("an ordering domain was closed with transactions in flight");

// Sequence numbers within a domain are consecutive.
p_seq_consecutive:
  assert property (@(posedge clk) disable iff (!rst_n)
    (join_valid && join_grant) |=>
      (d_next[$past(join_domain)] == $past(join_seq) + 8'd1))
  else $error("a domain's sequence numbers skipped");

// The expected sequence never passes the allocated sequence.
p_expect_le_next:
  assert property (@(posedge clk) disable iff (!rst_n)
    d_exp[0] <= d_next[0])
  else $error("more deliveries than allocations in a domain");

// An unbound ID has nothing outstanding.
p_unbound_is_quiet:
  assert property (@(posedge clk) disable iff (!rst_n)
    !bound[0] |-> (b_out[0] == 6'd0))
  else $error("an unbound ID had outstanding transactions");

Reorder buffer.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Data is delivered strictly in sequence within a domain.
p_delivery_in_sequence:
  assert property (@(posedge clk) disable iff (!rst_n)
    deliver_valid |-> (deliver_seq == domain_next_expect[deliver_domain]))
  else $error("data was delivered out of sequence");

// The buffer never holds more than O-1 entries per domain.
p_buffer_bounded:
  assert property (@(posedge clk) disable iff (!rst_n)
    buffer_occupancy <= (MAX_OUT - 1))
  else $error("the reorder buffer exceeded its derived bound");

// On overflow the read channel is stalled, never dropped.
p_overflow_stalls:
  assert property (@(posedge clk) disable iff (!rst_n)
    buffer_full |-> !r_ready)
  else $error("the reorder buffer accepted data it could not hold");

// Nothing is ever dropped.
p_no_drop:
  assert property (@(posedge clk) disable iff (!rst_n)
    (r_valid && r_ready) |-> (deliver_valid || buffer_accepted))
  else $error("accepted read data was neither delivered nor buffered");

Efficiency accounting.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Data cycles never exceed the beats issued.
p_data_le_beats:
  assert property (@(posedge clk) disable iff (!rst_n)
    c_data_cycles <= c_total_beats_issued)
  else $error("more data cycles than beats were issued");

// Every address cycle corresponds to a request.
p_addr_matches_txn:
  assert property (@(posedge clk) disable iff (!rst_n)
    addr_issued |=> (c_addr_cycles == $past(c_addr_cycles) + 1))
  else $error("an address cycle was not counted");

// The efficiency percentage is bounded.
p_efficiency_bounded:
  assert property (@(posedge clk) disable iff (!rst_n)
    cycle_efficiency_pct <= 16'd100)
  else $error("cycle efficiency exceeded 100%");

// Octets per transaction never exceeds the maximum burst payload.
p_octets_per_txn_bounded:
  assert property (@(posedge clk) disable iff (!rst_n)
    (c_addr_cycles > 32'd1000) |->
      (octets_per_txn <= 16'(MAX_BEATS * BUS_BYTES)))
  else $error("a transaction carried more than a maximum burst");

20. Verification Scenarios

Fifty-eight scenarios, plus a six-run directed test that needs an interconnect model that reorders responses.

Burst shaping — 11 scenarios.

#ScenarioExpected
11518 octets, 2 KiB aligned buffer, 32-beat1 burst, BOUND_LENGTH
2the same, 3 KiB into a page2 bursts, first 1024, second 494, BOUND_PAGE
39000 octets, 2 KiB buffers, 32-beat5 bursts, BOUND_BUFFER
41518 octets, 4-beat limit6 bursts, BOUND_BURST
5a transfer ending exactly on a page boundaryno split
6a transfer starting exactly on a page boundaryfull burst available
764 octets, any alignment inside a page1 burst
8a 4096-octet transfer at offset 12 bursts — 4095 then 1
9a zero-length requestno burst issued
1064-beat limit on a 512-bit bus4096-octet burst — one whole page
11the same, misalignedalways splits — the limit is useless

Outstanding transactions — 9 scenarios.

#ScenarioExpected
12issue up to TOTALall granted
13issue one beyondrefused, c_stalls
14a purpose inside its reservation, pool exhaustedGRANTED — the reservation
15a purpose outside its reservation, pool exhaustedrefused
16reservations summing above TOTALreservation_overcommitted
17all reservations idlethe full pool is shared
18a retirement for an idle purposeproperty fires
19peak recorded across a burst of trafficpeak_total
208 outstanding, 300 ns, 16-beat218.45 Gb/s sustained

ID allocation — 11 scenarios.

#ScenarioExpected
21a new domain, IDs freea fresh ID bound
22a second transaction in the same domainthe SAME ID
23a second domaina different ID
24all IDs bound, a new domainrefused, c_no_id
25an ID idle but bound, a new domainrefused — idle is not free
26the domain closes, then a new domainthe ID is rebound, c_rebinds
27a close with transactions outstandingclosed_with_outstanding
2816 domains on 16 IDsall bound, none refused
2945 transmit frames on 16 IDs29 refused — Section 10
30the same with Section 12's buffer1 ID, none refused
31a response for an unbound IDproperty fires

Ordering domains and reordering — 10 scenarios.

#ScenarioExpected
32open, join ×3, retire ×3, closeclean
33responses in orderdelivered immediately, no buffering
34response 2 before response 12 held, 1 delivered, then 2
35responses 2,3,4 before 1three held — O−1 at O=4
36O−1 held, one more arrivesthe read channel stalls
37a domain's sequence wrapping at 256the known limitation
38two domains reordering simultaneouslyeach buffered independently
39one flat buffer instead of per domaincross-domain order imposed — the bug
40a domain with one transactionnever buffered
4164 domains open, a 65thc_no_domain

Efficiency and verdicts — 9 scenarios.

#ScenarioExpected
421518 octets, 32-beat, aligned96.00% cycle efficiency, 1518 octets/txn
43the same, random offset94.60%, 1108 octets/txn
441518 octets, 4-beat80.00%, 253 octets/txn
45descriptor fetches only50.00% efficiency, 16 octets/txn
46mixed traffican average describing neither
47the same, per purposetwo distinct profiles
48mean beats below half the maximumbursts_too_short
49page-bound above an eighth of burstsalignment_dominating
50everything cleannone_of_the_above

Protocol violations — 8 scenarios.

#ScenarioExpected
51a burst forced across a page boundaryillegal_burst, property fires
52an ID rebound while liveid_reused_while_live
53a burst longer than negotiatedproperty fires
54outstanding beyond the poolproperty fires
55data delivered out of sequenceproperty fires
56the reorder buffer accepting data it cannot holdproperty fires
57an overcommitted reservationcfg_fault
58all of the above absentshaping_ok

The directed test — six runs random stimulus will not produce.

The failure this test targets is Section 19's rejected class made concrete, and it needs two things a conventional environment lacks.

First, an interconnect model that reorders responses across IDs — most return in issue order, which makes every ID policy look correct. Second, a sequence of allocations that causes an ID to be rebound while its domain is still live, which needs a domain whose transactions all respond before a later burst of the same domain is issued. That is a specific interleaving, not a random one.

Construct it. Six runs, one variable: the ID policy.

RunID policyInterconnectExpected
Aone ID per domain, released on closein-ordercorrect, and the test proves nothing
Bone ID per domain, released on closereorders across IDscorrect — each domain's bursts are same-ID
Cround-robin from a free listin-ordercorrect by accident — the interconnect ordered it
Dround-robin from a free listreorders across IDsCORRUPT — a frame's bursts arrive out of order
Ereleased when outstanding hits zeroreordersCORRUPT — the rebind case
Fone ID, everythingreorderscorrect and slow — 5 round trips per jumbo

Run C is the false pass and it is the reason the test exists. A round-robin allocator on an in-order interconnect is indistinguishable from a correct one, and the pool property of Section 19 passes in every run including D and E. A regression consisting of runs A and C reports the ID policy as verified.

Run E is the subtler of the two failures. The allocator is not round-robin — it binds by domain, correctly — and it releases an ID when its outstanding count reaches zero rather than when the domain closes. A transmit frame whose first burst responds before its second is issued has an idle ID, which is rebound, and the second burst goes out under a foreign ID with no ordering against the first.

The oracle, in four parts:

CheckRuns A, B, FRun CRuns D, E
frame contents at the FIFOcorrectcorrectout of order
id_reused_while_liveneverneverasserts in E; not in D
the pool propertypassespassesPASSES
c_reorder_stallsnon-zero in Bzerozero — nothing was held

Row three is the finding. The pool property passes in all six runs, including the two that corrupt frames — which is the rejected class demonstrated rather than argued.

And row two distinguishes the two failures, which matters because they have different fixes. Run E asserts id_reused_while_live and is caught by Section 9's invariant. Run D does not — the allocator never rebinds a live ID; it simply assigns unrelated IDs to related transactions — and the only thing that catches it is p_same_domain_same_id, which is a property about the classification rather than about the pool.


21. Debugging a Shaping Problem

Three complaints, and all three are throughput. This chapter has no failures that lose data — which makes it the hardest of Module 18's five to diagnose, because nothing goes wrong.

Complaint 1 — "the port runs at 40% and no counter reports an error."

CheckIf yesMeaning
c_no_id rising?ID exhaustionSection 10 — 45 domains on 16 IDs
c_stall_slot rising?outstanding exhaustionSection 6 — Little's law
c_stall_reorder rising?the buffer is too smallSection 16 — O−1 entries
mean_beats_x10 far below the maximum?bursts are being shortenedthe c_bound_* counters say why
all flat, rate still low?not this chapternone_of_the_above

Row one is the one nobody has a counter for, and it is the most common cause of a MAC that runs at a fixed fraction of line rate with everything apparently healthy. Sixteen IDs against forty-five transmit chains is 36% — a number that looks like a mysterious platform limitation and is arithmetic.

Complaint 2 — "efficiency is 95% and we still cannot reach line rate."

CheckIf yesMeaning
octets_per_txn small?transaction-bound, not bandwidth-boundSection 4's two verdicts
the traffic is minimum-size frames?Chapter 18.1 §12's wallbatching, not burst length
c_bound[BOUND_LENGTH] dominant?the transfers are simply smallnothing to lengthen
efficiency measured over descriptor traffic?16 octets in a 1-beat burst — 50%the average is meaningless

Row one is the diagnosis and it is a measurement problem rather than a design one. A bus monitor reporting cycle efficiency cannot see the transaction wall, because an address cycle is one cycle whether it carries 64 octets or 2048. The two numbers must be read together, and Section 13 exists to make that possible.

Complaint 3 — "frames are corrupted and every property passes."

CheckIf yesMeaning
id_reused_while_live ever?an ID was rebound mid-domainSection 20's run E
domains and IDs both available, still corrupt?the allocator is not classifyingSection 20's run D
does an in-order interconnect model hide it?confirms the ID policy is the causerun C's false pass
the reorder buffer per domain?a flat buffer imposes cross-domain orderSection 12

Row three is the test to run first because it is a one-line change to the environment. If the corruption disappears when the interconnect model is made in-order, the design is depending on an ordering the bus does not promise — which is the ID policy, and Section 19's rejected class.

And the two symptoms this chapter is systematically blamed for and is not the cause of:

SymptomBlamed onUsually is
a port at a fixed fraction of line rate"the interconnect is slow"ID or outstanding exhaustion — arithmetic
poor bus efficiency on a port that worksburst shapingdescriptor traffic averaged in — Section 14

22. Misconceptions

Misconception 1 — "the longest legal burst is the best burst."

The wrong model: longer bursts amortise the address cycle, so use the maximum the interconnect allows.

What it costs: a 64-beat limit on a 512-bit bus, which is exactly 4096 octets — one whole page — so any burst not starting page-aligned is guaranteed to split. And on 1518-octet frames it buys nothing over 32 beats, because the frame already fits in one.

The corrected model: efficiency rises monotonically to the point where the transfer fits in one burst and then stops completely. For 1518-octet frames on a 512-bit bus that point is 32 beats — 96.00% — and 64 beats is 96.00% with a guaranteed split on any misalignment. Section 4.

Misconception 2 — "misalignment costs 1.4% so it does not matter."

The wrong model: measure bus efficiency with and without alignment; the difference is small; move on.

What it costs: an alignment problem left unfixed on a port that is transaction-bound, where the same event costs 37.0% of the transactions rather than 1.4% of the cycles.

The corrected model: cycle efficiency and transaction count are two verdicts on the same event and they differ by a factor of twenty-six. Chapter 18.1 §12 established which one binds. And the twist: alignment matters most on large frames, whose transaction budget is 4% used — so the two rarely bind together, and the real cost of misalignment is in outstanding slots and reorder-buffer entries, which are sized for the worst case. Section 4.

Misconception 3 — "an AXI ID is a tag; any free one will do."

The wrong model: IDs are a pool; allocate the first free one; release it when its transactions retire.

What it costs: two failures a pool property cannot see. Unrelated transactions sharing an ID are ordered for nothing — concurrency lost. Related transactions with different IDs are unorderedChapter 18.2 §10's mode 0 silently broken.

The corrected model: an ID is an ordering contract. Two transactions share an ID if and only if the design requires them to complete in order. The allocator is a classifier keyed on the ordering domain, not a distributor keyed on availability. Sections 8, 9, 19.

Misconception 4 — "an ID with nothing outstanding is free."

The wrong model: the outstanding count reached zero; return the ID to the pool.

What it costs: a transmit frame whose first burst responds before its second is issued has its ID rebound to another frame, and the second burst goes out unordered against the first — a corrupted frame, intermittently, with the pool's invariant perfectly maintained.

The corrected model: an ID becomes idle when its transactions retire and free only when its ordering domain closes. The two are different states and collapsing them is Section 20's run E. Section 9.

Misconception 5 — "we need more AXI IDs."

The wrong model: the design wants 50 IDs, the interconnect offers 16, so negotiate for more.

What it costs: a negotiation that will not succeed — ID width is an interconnect-wide parameter affecting every master — and, while it fails, a port at 36% of line rate with no counter explaining why.

The corrected model: frame order is needed at the FIFO, not on the bus. A reorder buffer of (O − 1) burst payloads restores it locally — 7 KiB at eight outstanding and 16-beat bursts — and buys back 44 IDs. Sections 12, 16.

Misconception 6 — "out-of-order responses are rare, so size the buffer for the average."

The wrong model: measure c_reorder_stalls on a real system, see nearly zero, size the buffer accordingly.

What it costs: an overflow on the day two DRAM banks have different queue depths, or a refresh lands, or a competing master's traffic interleaves. And the overflow's handling decides whether that is a slowdown or a corruption.

The corrected model: size for O − 1, which is a structural bound rather than a statistical one, and stall the read data channel on overflow rather than dropping or reordering. The reordering rate is a property of the system's dynamics, not of the traffic — which is Chapter 18.2 §19's environment again. Section 16.


23. Interview Questions

Q1 — "What burst length should a 100 Gb/s Ethernet MAC use on a 512-bit AXI bus?"

Thirty-two beats, and the reasoning stops there for a specific reason. A 1518-octet frame is 24 data beats, so efficiency is 24 / (24 + bursts): 80.00% at 4 beats, 92.31% at 16, 96.00% at 32 — and 96.00% at 64, because the frame already fits in one 2048-octet burst. Sixty-four beats is 4096 octets, exactly one page, so it can only be issued page-aligned and any other alignment guarantees a split. The returns stop where the transfer fits, and the parameter becomes harmful beyond it.

Q2 — "How much does an unaligned buffer pool cost?"

Two answers that differ by a factor of twenty-six, and you have to say which one binds. At a 32-beat limit a 1518-octet frame at a random page offset splits 37.04% of the time: 1.40 percentage points of cycle efficiency, and 37.0% more transactions. Chapter 18.1 §12 established the MAC is transaction-bound, so the second is the one that matters — and the twist is that alignment bites hardest on large frames, whose transaction budget is 4% used. The real cost is in outstanding slots and reorder-buffer entries, both sized for the worst case.

Q3 — "What does an AXI ID buy you?"

Ordering, and nothing else — and it costs concurrency. Transactions sharing an ID complete in order; transactions with different IDs have no relationship. So an ID is a contract, not a slot, and the allocation rule is: two transactions share an ID if and only if the design requires them to complete in order. An allocator handing out the first free ID imposes ordering nobody asked for and withholds ordering somebody needed — and a pool property checking allocation and release passes throughout.

Q4 — "When is an AXI ID free?"

When its ordering domain closes, not when its outstanding count reaches zero. An ID whose transactions have all responded is idle: a later burst of the same frame must still be ordered against the earlier ones, and if the ID has been rebound in the meantime that ordering is gone. The failure is a corrupted frame, intermittently, with every allocation legal and the pool's invariant maintained.

Q5 — "Your interconnect offers 16 IDs and your design wants 50. What do you do?"

Add about 7 KiB of SRAM and stop wanting them. The 50 comes mostly from one ID per transmit frame in flight — Chapter 18.4 §16's 45 chains — and the ordering those IDs buy is needed at the FIFO, not on the bus. A reorder buffer of (O − 1) burst payloads restores frame order locally: at eight outstanding and 16-beat bursts, 7 KiB, which buys back 44 IDs. Without it the port runs at 16/45 — 36% of line rate — and no counter says why.

Q6 — "How many outstanding transactions does a 100 Gb/s port need?"

Little's law, and the answer depends entirely on the frame size. At 114.29 Gb/s and a 300 ns round trip: five, with 16-beat bursts carrying 1024 octets each. But minimum-size frames are one transaction of 64 octets, so the same rate needs about 67 — which no interconnect offers. That is why Chapter 18.3 §17's answer was to reduce the transaction count by batching descriptors rather than to increase concurrency: at 64 octets there is nothing to lengthen.


24. Understanding Check


25. What's Next

Module 18 has two chapters left, and this one has left them a resource each.

ChapterThe resourceLeft by
Chapter 18.6interrupts, and the latency they costChapter 18.3 §15's 4 132× term
Chapter 18.7the address channel, divided across queuesSection 4's 0.744 per cycle

Chapter 18.6 — Interrupts, Coalescing and Completion takes Chapter 18.3 §14's fixed count-and-timer coalescer and makes it adaptive. Chapter 18.1 §10 established that a fixed count with no timer stalls and a fixed timer wastes latency; the answer is a threshold that tracks the arrival rate, and the hard part is proving such a policy cannot stall — a feedback loop whose output gates its own input is exactly the structure that can.

And it has a debt to Chapter 16.1 that this chapter's machinery makes it possible to pay properly. Chapter 18.3 §15 priced fixed coalescing at 100 µs against a 24.2 ns clock — 4 132×. An adaptive policy's delay is smaller, and it is variable, which for a measurement may be worse than a large constant — because a constant is a bias a calibration removes and a variable is not.

Chapter 18.7 — Offload and Multi-Queue then divides what this chapter allocated. Section 4 left 0.744 transactions per cycle and 26% of the address channel; multi-queue spends the remainder by giving each queue its own context, and receive-side scaling decides which queue a frame goes to using Chapter 15.2's flow hash — the same hash, the same balls-in-bins arithmetic, and the same disappointing distribution.

It also has to build out an argument Chapter 18.3 §20's callout started and did not finish: checksum offload moves the last end-to-end check upstream of the DMA path, leaving it unprotected. This chapter has now added a reorder buffer to that path — one more place where a frame's octets can be rearranged with no check downstream — which makes the argument sharper rather than weaker.

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.