AMBA AXI · Module 15
FIFO-Based Buffering
Use FIFOs to decouple producer and consumer rates on AXI — absorbing bursts, smoothing rate mismatches, and buffering across latency that a two-entry skid can't. The ready/valid FIFO wrapper, depth sizing for burst and rate-mismatch, sync vs async FIFOs, and where FIFOs belong on the five channels.
A skid buffer holds two beats — enough to register a handshake, not enough to decouple two sides that run at different rates or in bursts. When a producer emits a burst faster than the consumer drains it, or the consumer pauses for many cycles, you need real depth: a FIFO. Wrapped in a VALID/READY interface, a FIFO is a drop-in buffer that absorbs bursts, smooths rate mismatches, and bridges latency, all while preserving order and the handshake contract. This chapter builds the ready/valid FIFO wrapper, derives how to size its depth for burst absorption and rate mismatch, distinguishes synchronous from asynchronous (clock-crossing) FIFOs, and places FIFOs correctly on the AXI channels — the last building-block primitive before we assemble reusable RTL templates.
1. FIFO vs. Skid: When Two Slots Aren't Enough
A skid buffer and a FIFO both expose the same VALID/READY interface and both preserve order — the difference is depth and purpose. A skid buffer's two slots exist to register the handshake for timing; it cannot absorb a burst or a long stall. A FIFO has N slots sized for buffering: it lets the producer keep running while the consumer is busy (and vice-versa), decoupling their instantaneous rates as long as the FIFO doesn't fill or empty.
2. The Ready/Valid FIFO Wrapper
A FIFO becomes an AXI-friendly buffer by mapping its full/empty/push/pop flags onto the handshake: accept upstream when not full (s_ready = !full, push on s_valid && s_ready), present downstream when not empty (m_valid = !empty, pop on m_valid && m_ready). Order is intrinsic to a FIFO, so the channel's ordering is preserved automatically.
module axi_fifo #(parameter int W = 32, parameter int DEPTH = 16) (
input logic clk, rstn,
input logic [W-1:0] s_data, input logic s_valid, output logic s_ready,
output logic [W-1:0] m_data, output logic m_valid, input logic m_ready
);
localparam int AW = $clog2(DEPTH);
logic [W-1:0] mem [DEPTH];
logic [AW:0] wptr, rptr; // extra MSB to tell full from empty
wire full = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0]);
wire empty = (wptr == rptr);
assign s_ready = !full;
assign m_valid = !empty;
assign m_data = mem[rptr[AW-1:0]];
always_ff @(posedge clk) begin
if (!rstn) begin wptr <= '0; rptr <= '0; end
else begin
if (s_valid && s_ready) begin mem[wptr[AW-1:0]] <= s_data; wptr <= wptr + 1'b1; end
if (m_valid && m_ready) rptr <= rptr + 1'b1;
end
end
endmoduleThis wrapper drops into any AXI channel exactly where a skid buffer would — same ports — but with depth DEPTH instead of 2. (For full throughput at speed you may still wrap a skid buffer around the FIFO's output, since a simple FIFO read can have a combinational empty → m_valid path; production FIFOs register the output.)
3. Sizing the Depth
Depth is the design decision a FIFO forces. Too shallow and it fills (back-pressuring the producer, defeating the point); too deep and it wastes area and adds latency. Three sizing drivers:
- Burst absorption. To swallow a burst of
Bbeats while the consumer is stalled, you needDEPTH ≥ B(plus margin). E.g. buffering one AXI burst of up to 256 beats needs depth ≥ 256 if the consumer may be fully stalled across it. - Rate mismatch over a window. If the producer averages
r_pbeats/cycle and the consumerr_c < r_pover a bursty window of lengthT, the FIFO must hold the overflow(r_p − r_c)·T. Sustainedr_p > r_ccannot be fixed by any finite FIFO — it only smooths transient mismatches. - Latency / round-trip coverage. To keep a pipeline full across a consumer that responds with latency
L(e.g. a credit return or a memory round-trip), the FIFO must hold ≥Lbeats so the producer never starves waiting — the same idea as covering outstanding depth (Chapter 8.x).
4. Synchronous vs. Asynchronous FIFOs, and Where They Go on AXI
A synchronous FIFO has one clock for both ports — the common case for buffering within a clock domain. An asynchronous FIFO has separate write and read clocks and is the canonical safe clock-domain crossing for a data stream (Chapter 14.2): Gray-coded pointers cross the boundary through synchronizers so the full/empty comparison is metastability-safe. On AXI, FIFOs appear per channel as needed:
- Data FIFOs on
WandRto buffer burst payloads (decouple the data mover from memory rate). - Address/command FIFOs on
AW/ARto queue outstanding requests (more in flight without stalling). - Response FIFOs on
B/Rso responses don't back-pressure the data path. - Async FIFOs on any channel that crosses a clock domain — the standard AXI CDC bridge buffers each channel through its own async FIFO.
5. Common Misconceptions
6. Debugging Insight
7. Verification Insight
8. Interview Questions
9. Summary
A FIFO is the buffering primitive that a two-slot skid buffer can't be: wrapped in a VALID/READY interface (s_ready = !full, m_valid = !empty, push/pop on the handshakes), it decouples producer and consumer rates, absorbing bursts and smoothing transient mismatches while preserving order. Its defining design choice is depth, sized to the max of three drivers — burst absorption (≥ burst beats the consumer may stall across), transient rate mismatch (≥ rate-gap × window), and latency coverage (≥ round-trip beats) — with the hard caveat that no FIFO fixes a sustained rate deficit. Distinguishing full from empty needs the extra-MSB pointer trick (or a counter), and getting it wrong causes overflow/underflow.
Synchronous FIFOs buffer within a clock domain; asynchronous FIFOs (Gray-coded pointers through synchronizers) are the safe clock-domain crossing — the standard AXI CDC bridge places one async FIFO per channel. On AXI, FIFOs sit wherever decoupling is needed: data FIFOs on W/R, command FIFOs on AW/AR (deepening outstanding), response FIFOs on B/R (avoiding response backpressure). The system-level hazard is undersizing into deadlock when a FIFO sits in a dependency loop — so response-FIFO depth ties to maximum outstanding count. Verification asserts no overflow/underflow, conservation/ordering, and full/empty correctness, with CDC-aware checks for async FIFOs. With skid buffers, pipelines, and FIFOs in hand, we can now assemble them into reusable, parameterised AXI RTL templates.
10. What Comes Next
You now have the full set of flow-control building blocks; next we package them for reuse:
- 15.8 — Reusable AXI RTL Templates (coming next) — drop-in, parameterised AXI RTL building blocks (slaves, register banks, skid buffers, FIFOs, width/protocol adapters) assembled into a reusable library.
Previous: 15.6 — Ready/Valid Pipelining. Related: 15.5 — The Skid Buffer for the two-slot timing primitive, 14.2 — Asynchronous Bridges for async-FIFO clock crossing, and 8.5 — Outstanding in the Interconnect for the outstanding-depth sizing that drives command/response FIFO depth.