FIFO depth from burst arithmetic

Size a FIFO from the burst, the consumer's rate and its stall, check the arithmetic against the words a bench counts, and see why no depth fixes a rate mismatch.

Lesson
R5.3
Time
30 min
Runs on
Your browser
Check
5 questions

A producer wrote bursts of 64 words, one per cycle, into a consumer that took one word every two cycles. The FIFO between them was 16 deep, sized "to be safe". On the first burst it refused the 32nd word, and 17 words were lost.

The 16-deep FIFO fills during the burstCycles 26 to 34 of the burst, where cycle 0 brings the first word. used is the count register after each edge.
012345678clkwrite_enread_enused14151615161516fulloverflow
  1. Cycle 0

    Cycle 26: two words arrive for every one that leaves, so used climbs by one every two cycles.

  2. Cycle 4

    Cycle 30: the 31st word makes 16. The FIFO is full.

  3. Cycle 5

    Cycle 31: the 32nd word arrives while a word leaves. The FIFO decides from the count before the edge, so it takes the read and refuses the write.

  4. Cycle 8

    From cycle 31 every second word is lost, 17 in all, and overflow stays 1 until reset.

Measured with a probe of the lesson's design at DEPTH 16.

Arrivals minus departures

A FIFO absorbs the difference between how fast words arrive and how fast they leave. The words it holds at any moment are the arrivals so far minus the departures so far. The depth must cover the largest value that difference reaches.

Two rates and a buffer between themArrows carry words. The accept logic decides both transfers of a cycle from the count before the edge.
Two rates and a buffer between themArrows carry words. The accept logic decides both transfers of a cycle from the count before the edge. component Producer B words, one percycle queue FIFO DEPTH slots component Consumer waits S, then one perC cycles logic Accept logic full and empty beforethe edge writereadcount
  1. Burst

    For B cycles a word arrives every cycle. The consumer takes one every C cycles, so the count climbs by B - B / C over the burst.

  2. Peak

    The count peaks when the last word arrives. After that the FIFO only drains.

  3. Last word

    If the FIFO is full before the edge where the last word arrives, the accept logic refuses it, even when a word leaves at that edge.

Read the diagram as text
  • Producer: B words, one per cycle
  • FIFO: DEPTH slots
  • Consumer: waits S, then one per C cycles
  • Accept logic: full and empty before the edge
  • Producer → FIFO: write
  • FIFO → Consumer: read
  • FIFO → Accept logic: count

The depth covers the peak of arrivals minus departures, plus what the accept rule needs.

Take a burst of B words at one per cycle, and a consumer that reads one word every C cycles. During the B cycles of the burst, the consumer takes B / C words, so the FIFO peaks at B - B / C. For the hook: 64 - 32 = 32 words.

A burst of 16 into a half-rate consumerCycles 0 to 23 of the burst at DEPTH 16. The ramp climbs while words arrive and drains after the burst ends.
01234567891011121314151617181920212223clkwrite_enread_enused123456787654
  1. Cycle 1

    Cycle 1: a word leaves for the first time. Every two cycles, two arrive and one leaves.

  2. Cycle 15

    Cycle 15: the last of 16 words arrives while the eighth word leaves. The peak is 8, which is 16 - 16 / 2.

  3. Cycle 23

    Cycle 23: with no arrivals, the FIFO drains one word every two cycles. It is empty at cycle 31.

Measured with a probe of the lesson's design at DEPTH 16. No word is lost.

A stall of S cycles before the consumer starts leaves fewer reads inside the burst: (B - S) / C of them. The peak becomes B - (B - S) / C. With C = 2, a stall of 8 cycles costs 4 words of depth, not 8.

One more word than the peak

The arithmetic gives the peak, with (B - S) / C rounded down. On this FIFO the depth to set is one more whenever the last word of the burst arrives in a cycle with a read. Every pattern on this page does that. Look at cycle 15 of the ramp: the last word arrives in the same cycle a word leaves. If the FIFO were full before that edge, it would refuse the arriving word. When the last word arrives in a cycle with no read, the peak itself is enough.

Both decisions use the count before the edge, the rule of R5.2 and of the FIFO the SystemVerilog track verifies. So a slot freed by a read is free only after the edge, one cycle late for a word arriving at the same edge.

Burst BRead every CStall SB - (B - S) / CMost held, measuredSmallest DEPTH with no loss
6420323233
1640121213
3228202021
820445

Measured on the lab bench: most held at a DEPTH with no loss, and the depth found by trying it and the depth below it. One depth less loses exactly one word in each row.

What depth cannot fix

The formula assumes the burst ends and the FIFO drains before the next one. Over a long run, if words arrive faster than they leave, the backlog grows by the difference every burst. No finite depth holds it.

Bursts of 8 words, one everyArrive per burstLeave per burstHeld at the end of bursts 1, 2, ... 10
16 cycles884 every time
14 cycles874, 5, 6, ... 13: one more per burst
12 cycles864, 6, 8, ... 22: two more per burst

Measured with a probe of the lesson's design at DEPTH 64, consumer reading every second cycle, ten bursts.

Depth also has costs. Measured at DEPTH 16, 33 and 64: 448, 1000 and 1666 cells, with 151, 292 and 541 flops. A deep FIFO adds latency too: in the hook's burst at DEPTH 33, the last word arrives at cycle 63 and leaves at cycle 127.

The lab: size the FIFO

The sizing brief.
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
write_eninput1as in R5.2
write_datainput[WIDTH-1:0]as in R5.2
read_eninput1as in R5.2
read_dataoutput[WIDTH-1:0]as in R5.2
read_validoutput1as in R5.2
fulloutput1as in R5.2
emptyoutput1as in R5.2
overflowoutput11 from the first edge where a write is offered while full, until reset
Throughput
a burst of B words, one per cycle; the consumer waits S cycles, then reads in the last cycle of every C
Parameters
DEPTH words, any value from 2 up, 16 shipped; WIDTH 8. Patterns: B 64, C 2, S 0; B 16, C 4, S 0; B 32, C 2, S 8
Corner cases
the last word arriving as a word leaves; a stall at the start; a DEPTH one below the need. Rule: at full, a write and a read together: the read is accepted, the write refused

Run the lab. At DEPTH 16 the first and third patterns lose words and report most held 16; the second fits, with a peak of 12. Work out the depth for each pattern, set DEPTH to the largest, and run again.

At DEPTH 33 every pattern fits, with peaks of 32, 12 and 20. Try 32: the first pattern loses one word. Synthesize at 16 and at 33: 448 cells, 151 flops, 13 levels of logic, then 1000 cells, 292 flops, 14 levels.

Asked in interviews
  1. How do you size a FIFO?

    Find the largest value of arrivals minus departures over the worst burst. Count a consumer stall in the departures: a stall of S cycles removes S / C reads from the burst, never S words. Add one word if the FIFO refuses a write at full in the cycle it is read.

  2. A producer bursts 64 words at line rate into a half-rate consumer: what depth?

    The consumer takes 32 during the burst, so the peak is 32. A FIFO that decides from the count before the edge needs 33.

  3. Why can no FIFO fix a rate mismatch?

    If words arrive faster than they leave over the long run, the backlog grows every burst. A FIFO only smooths bursts; the average rates must match or the producer must be slowed.

Try it

Each line is one pattern, after a reset. most held is the largest count the bench saw after any edge, and lost counts writes offered while full. A FIFO that reports most held equal to its DEPTH has been full, and may have lost words.

At DEPTH 33 the lines read most held 32, 12 and 20, with no loss. At DEPTH 32 the first line reads most held 32, lost 1.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: burst 64, read every 2, stall 0: most held 16, lost 17, overflow=1 burst 16, read every 4, stall 0: most held 12, lost 0, overflow=0 burst 32, read every 2, stall 8: most held 16, lost 5, overflow=1

Original design · 448 cells · 151 flip-flops · 0 latches · 13 logic levels. Synthesis uses Yosys; counts depend on the toolchain.

Read included file: tb.sv
`timescale 1ns/1ps
module tb;
  logic clk = 0;
  always #5 clk = ~clk;                   // rising edges at 5, 15, 25, ...
  logic rst_n = 1;                        // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
  int edge_n = -1;
  always @(posedge clk) edge_n = edge_n + 1;
  logic       write_en = 0, read_en = 0;
  logic [7:0] write_data = 0, read_data;
  logic       read_valid, full, empty, overflow;
  // One traffic pattern: a burst of b words, one per cycle from cycle 0; the consumer waits s
  // cycles, then reads in the last cycle of every c. The bench counts what the FIFO holds.
  int held, most, lost;
  task automatic pattern(input int b, input int c, input int s);
    int t;
    bit wf, re_ok;
    rst_n = 0; #1 rst_n = 1;                 // empty the FIFO and clear overflow
    held = 0; most = 0; lost = 0; t = 0;
    while (t < b || held > 0) begin
      write_en = (t < b); write_data = 8'(t);
      read_en  = (t >= s) && ((t - s) % c == c - 1);
      wf = full; re_ok = read_en && !empty;   // the flags before the edge
      tick();
      if (write_en && wf) lost++;
      held = held + (write_en && !wf) - re_ok;
      if (held > most) most = held;
      t++;
    end
    write_en = 0; read_en = 0;
  endtask
  task automatic run(input int b, input int c, input int s);
    pattern(b, c, s);
    $display("burst %0d, read every %0d, stall %0d: most held %0d, lost %0d, overflow=%b",
             b, c, s, most, lost, overflow);
  endtask
  sync_fifo dut (.clk(clk), .rst_n(rst_n), .write_en(write_en), .write_data(write_data), .read_en(read_en),
                .read_data(read_data), .read_valid(read_valid), .full(full), .empty(empty), .overflow(overflow));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    
  endtask
  initial begin
    #1 rst_n = 0; tick(); tick(); rst_n = 1;   // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
    run(64, 2, 0);
    run(16, 4, 0);
    run(32, 2, 8);
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule

Check yourself

Predict the outputThe lab FIFO is built 4 deep. A burst of 8 words, one per cycle, meets a consumer that reads in every second cycle, from cycle 1. The bench counts words offered while full. What does it print?
// sync_fifo: the FIFO of R5.2 with a count register, so DEPTH may be any value from 2 up,
// and a sticky overflow flag: 1 from the first edge where a write is offered while full.
module sync_fifo #(parameter int DEPTH = 4, parameter int WIDTH = 8) (
  input  logic             clk,
  input  logic             rst_n,         // asynchronous, active-low
  input  logic             write_en,
  input  logic [WIDTH-1:0] write_data,
  input  logic             read_en,
  output logic [WIDTH-1:0] read_data,
  output logic             read_valid,
  output logic             full,
  output logic             empty,
  output logic             overflow       // a write was refused: a word was lost
);
  localparam int AW = $clog2(DEPTH);      // address bits
  localparam int CW = $clog2(DEPTH + 1);  // count bits: 0 to DEPTH

  logic [WIDTH-1:0] mem [DEPTH];
  logic [AW-1:0]    wptr_q, rptr_q;
  logic [CW-1:0]    used_q;               // words held

  assign empty = (used_q == '0);
  assign full  = (used_q == CW'(DEPTH));

  wire take_w = write_en && !full;
  wire take_r = read_en && !empty;

  // A pointer wraps from DEPTH - 1 to 0, so DEPTH need not be a power of two.
  function automatic logic [AW-1:0] next(input logic [AW-1:0] p);
    return (p == AW'(DEPTH - 1)) ? '0 : p + 1'b1;
  endfunction

  always_ff @(posedge clk)
    if (take_w) mem[wptr_q] <= write_data;

  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin
      wptr_q <= '0; rptr_q <= '0; used_q <= '0;
      read_valid <= 1'b0; read_data <= '0; overflow <= 1'b0;
    end else begin
      if (take_w) wptr_q <= next(wptr_q);
      if (take_r) begin
        rptr_q    <= next(rptr_q);
        read_data <= mem[rptr_q];
      end
      read_valid <= take_r;
      if (take_w && !take_r)      used_q <= used_q + 1'b1;
      else if (take_r && !take_w) used_q <= used_q - 1'b1;
      if (write_en && full) overflow <= 1'b1;
    end
endmodule
`timescale 1ns/1ps
module tb;
  logic clk = 0;
  always #5 clk = ~clk;                   // rising edges at 5, 15, 25, ...
  logic rst_n = 1;                        // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
  int edge_n = -1;
  always @(posedge clk) edge_n = edge_n + 1;
  logic       write_en = 0, read_en = 0;
  logic [7:0] write_data = 0, read_data;
  logic       read_valid, full, empty, overflow;
  // One traffic pattern: a burst of b words, one per cycle from cycle 0; the consumer waits s
  // cycles, then reads in the last cycle of every c. The bench counts what the FIFO holds.
  int held, most, lost;
  task automatic pattern(input int b, input int c, input int s);
    int t;
    bit wf, re_ok;
    rst_n = 0; #1 rst_n = 1;                 // empty the FIFO and clear overflow
    held = 0; most = 0; lost = 0; t = 0;
    while (t < b || held > 0) begin
      write_en = (t < b); write_data = 8'(t);
      read_en  = (t >= s) && ((t - s) % c == c - 1);
      wf = full; re_ok = read_en && !empty;   // the flags before the edge
      tick();
      if (write_en && wf) lost++;
      held = held + (write_en && !wf) - re_ok;
      if (held > most) most = held;
      t++;
    end
    write_en = 0; read_en = 0;
  endtask
  sync_fifo dut (.clk(clk), .rst_n(rst_n), .write_en(write_en), .write_data(write_data), .read_en(read_en),
                .read_data(read_data), .read_valid(read_valid), .full(full), .empty(empty), .overflow(overflow));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    
  endtask
  initial begin
    #1 rst_n = 0; tick(); tick(); rst_n = 1;   // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
    pattern(8, 2, 0);
    $display("lost %0d", lost);
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule
Spot the bugEach option is the DEPTH of the lab FIFO for the hook's pattern: a burst of 64 words into a consumer that reads every second cycle. The bench passes when no word is lost. Which depth fails?
ConceptBursts of 8 words arrive every 12 cycles. The consumer reads one word every 2 cycles. What depth does the FIFO need?
ConceptA burst of 32 words meets a consumer that reads every 2 cycles. If the consumer first waits 8 cycles, how does the peak change?
Read the waveformThis is the burst of 16 words into a half-rate consumer, cycles 12 to 19. What DEPTH does this pattern need on the lab FIFO?
The top of the ramp
01234567write_enread_enused7876

Values after the edges of cycles 12 to 19, left to right.

Remember

  • The words a FIFO holds are the arrivals so far minus the departures so far; the depth must cover the largest value over the worst burst.
  • A burst of B words at one per cycle into a consumer that waits S cycles, then reads every C, peaks at B - (B - S) / C.
  • When both accept decisions use the count before the edge, a full FIFO refuses a write in the cycle it is read, so set the depth one above the peak whenever the last word of the burst arrives in a read cycle.
  • If the long-run arrival rate exceeds the departure rate, the backlog grows every burst and no depth is enough.