Skip to content

RTL Design Patterns · Chapter 7 · FIFO Design

First-Word-Fall-Through vs Standard

A FIFO has two read interfaces, and they disagree about when the output data is valid. In a standard FIFO the read is registered: you assert the read enable this cycle and the popped word appears on the output bus the next cycle. In a first-word-fall-through FIFO the head word is already sitting on the output the instant the FIFO is non-empty, and the read enable becomes an acknowledge that advances to the next word for zero read latency. Same storage underneath, a completely different read contract. Confuse the two and a consumer latches the wrong first word, so every word after it is off by one and the start of every packet is corrupt. This lesson draws both timings, builds a standard read and an FWFT wrapper around it, and proves each with self-checking testbenches, in SystemVerilog, Verilog, and VHDL.

Intermediate14 min readRTL Design PatternsFIFOFirst-Word Fall-ThroughRead LatencyShow-AheadValid Ready

Chapter 7 · Section 7.5 · FIFO Design

1. The Engineering Problem

You have a working FIFO from 7.1 and 7.2. The pointers wrap, the flags are correct, no word is ever overwritten or lost. You hand it to the engineer building the consumer — a block that drains the FIFO one word per cycle into a packet parser — and the first packet comes out corrupt. Not dropped, not duplicated: shifted. The header field holds what should have been in the previous transaction, every field is one position late, and the last word of the packet is missing. The FIFO is fine. The bug is that the consumer and the FIFO disagree about when dout is valid.

This is the read-side contract, and it is genuinely two different things wearing the same port list. Both interfaces expose the identical signals — clk, rd_en, dout, empty — and both drain the same circular buffer in the same order. What differs is a single question with two incompatible answers: at the moment you decide to take a word, is the word already on dout, or does asserting rd_en bring it there one cycle later?

  • Standard (normal-latency) read. dout is a registered read: you assert rd_en in cycle N, and the popped word appears on dout in cycle N+1. rd_en is a request — "pop the head, and present it next cycle." There is exactly one cycle of read latency, and dout is meaningless until that latency has elapsed.
  • First-word-fall-through (FWFT / show-ahead) read. The head word falls through to dout on its own: whenever the FIFO is non-empty, dout already holds the oldest word, before any rd_en. rd_en is now an acknowledge — "I have consumed the word you are showing me; advance so the next one falls through." There is zero read latency: the data is valid the cycle empty deasserts.

A consumer must be written to exactly one of these contracts. Assume FWFT on a standard FIFO and you read the stale value the standard read has not yet produced — the classic off-by-one that shifts the whole stream. Assume standard on an FWFT FIFO and your rd_en pulse double-advances — you acknowledge a word you never sampled. The two are not "the same FIFO with a latency knob"; they are two contracts, and the producer and consumer must sign the same one.

the-need.v — same signals, two incompatible read contracts
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // Both FIFOs expose the identical port list:
   //   input  rd_en;  output [W-1:0] dout;  output empty;
 
   // STANDARD (normal-latency): dout is REGISTERED. rd_en is a request.
   //   cycle N   : rd_en = 1                 (ask for the head)
   //   cycle N+1 : dout  = <that head word>  (arrives ONE cycle later)
 
   // FWFT (show-ahead): dout FALLS THROUGH. rd_en is an acknowledge.
   //   whenever !empty : dout = <head word>  (already there, BEFORE rd_en)
   //   cycle N   : rd_en = 1                 (ack: I took it, advance)
   //   cycle N+1 : dout  = <NEXT head word>  (next word already presented)
 
   // The need: the consumer must know WHICH contract it is draining. Sampling
   // dout the cycle empty drops is right for FWFT and one word too early for
   // standard. That single disagreement is this page.

2. Mental Model

3. Pattern Anatomy

The structure is a standard FIFO core with, in the FWFT case, a prefetch stage wrapped around its read port. Everything hard is in the timing of dout relative to rd_en and empty.

The standard read — registered dout, one-cycle latency. The core is the 7.1/7.2 FIFO. On a committed read (rd_en && !empty) the read pointer advances and the memory word at the old rptr is captured into an output register, so dout presents it on the next clock edge. dout is therefore a registered function of the pointer as it was one cycle ago: assert rd_en in cycle N, and the word lands in cycle N+1. This is the simplest internal form — one output flop, no look-ahead — and its empty means only "a pop is allowed," never "the data is on the bus now."

The FWFT prefetch — a look-ahead stage that keeps dout pre-loaded. FWFT wraps the standard core with a small output stage whose job is to always have the next word ready on dout before the consumer asks. Conceptually: whenever the output stage is not holding a valid word and the underlying standard FIFO is not empty, the stage issues an internal read to the core to prefetch the head word into dout. Once loaded, dout shows that word and the FWFT-level empty deasserts (equivalently a valid asserts). When the consumer pulses rd_en (the acknowledge), the stage marks its held word consumed and immediately prefetches the next word from the core, so dout rolls forward with no bubble. The result is zero read latency: the first word has fallen through to dout and is valid the cycle FWFT-empty drops.

The empty semantics differ, and that is the crux. In the standard FIFO, empty describes the core: empty low means the buffer holds a word you may pop (next cycle). In the FWFT FIFO, empty describes the output stage: empty low means a valid head word is presented on dout this cycle. FWFT empty is really a data-valid flag; standard empty is a pop-permitted flag. The consumer's read of the very first word hangs entirely on this: with FWFT it samples dout the cycle empty drops; with standard it must assert rd_en and sample dout the cycle after.

Two read interfaces — standard registers dout, FWFT prefetches it ahead of demand

data flow
Two read interfaces — standard registers dout, FWFT prefetches it ahead of demand1-cyc latencyprefetchon rd_enrefillstandard FIFOcorewrapping pointers, full/empty (7.1/7.2)STANDARD readdout registered; valid N+1 after rd_enFWFT prefetchstagelook-ahead: pull next word onto doutFWFT read(show-ahead)dout already valid when !empty; 0 latencyrd_en =ACKNOWLEDGEadvance to next; next word falls through
The same standard FIFO core drives both interfaces. STANDARD registers the read: rd_en in cycle N, dout in cycle N+1, one cycle of latency, and empty means only pop-permitted. FWFT wraps the core with a prefetch stage that pulls the head word onto dout ahead of demand, so dout is ALREADY valid the cycle empty deasserts (zero latency), and rd_en is an acknowledge that advances the stage and refills it from the core. FWFT costs one extra entry of buffering (the prefetch register) and turns empty into a data-valid signal. This structure is identical in SystemVerilog, Verilog, and VHDL, and the FWFT empty/valid is exactly the valid that a valid/ready handshake consumes downstream.

Why FWFT suits handshakes and pipelines. A valid/ready handshake and a straight-through pipeline both want the data valid the cycle you look at it — a pipeline stage registers whatever is on its input each cycle, so it needs the word present now, not one cycle after a request it would have to have issued in the past. FWFT's !empty is precisely a valid, and its rd_en is precisely a ready-driven acknowledge, so an FWFT FIFO drops straight into a valid/ready interface with no adapter. A standard FIFO in that role needs a one-cycle read-latency shim (or the consumer must pre-issue rd_en a cycle early), which is exactly the mismatch this page is about. Standard's advantage is the mirror image: it is simpler internally — one output flop, no prefetch handshake, no extra entry — so where the consumer is a state machine that already waits a cycle, standard is the leaner choice.

The one-entry cost of the prefetch. The prefetch stage is a real storage element: it holds one word outside the core's DEPTH. That has two consequences worth stating. First, an FWFT FIFO built as "standard core + prefetch" can present DEPTH + 1 words in flight (the core's DEPTH plus the one in the output register), or, depending on how you account for it, the prefetch changes exactly when the core reports empty relative to when the consumer sees dout go invalid. Second, the prefetch is why FWFT empty deasserts one cycle later than a naive read of the core would suggest on the very first fill — the first word has to be pulled through into the output stage before it can fall through to dout. Forgetting that the prefetch is a cycle of pipeline (and an entry of state) is a common source of the "my FWFT FIFO's empty timing is off by one" confusion in §6.

4. Real RTL Implementation

This is the core of the page. We build three things — (a) a standard-read FIFO (registered dout, one-cycle read latency); (b) an FWFT wrapper (a prefetch/output stage around a standard FIFO giving zero-latency reads); and (c) the standard-vs-FWFT mismatch bug beside the matched fix — and each is shown in SystemVerilog, Verilog, and VHDL with an RTL block and a self-checking clocked testbench. The testbenches drive the same stream into a standard and an FWFT FIFO and check the read-latency/interface contract (standard: data next cycle; FWFT: data now) and FIFO ordering. The reasoning is identical across all three languages; only the syntax differs.

One discipline runs through every RTL block, inherited from 7.1/7.2 and the register pattern: pointers and the output/prefetch registers update on the clock with non-blocking assignments, reset is explicit (synchronous, active-high rst), a write commits only on wr_en && !full, and a read commits only on the interface's own read condition — for standard, rd_en && !empty; for FWFT, an acknowledge or an internal prefetch. The flags gate motion, exactly as before.

4a. The standard-read FIFO — registered dout, one-cycle latency

The core stores DEPTH = 2**AW words. On a committed read the word at the current rptr is captured into an output register, so dout presents it one cycle after rd_en. empty here means only "a pop is permitted."

fifo_std.sv — standard read: registered dout, valid one cycle after rd_en
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_std #(
       parameter int WIDTH = 8,
       parameter int DEPTH = 16,                      // power of two
       localparam int AW   = $clog2(DEPTH)
   )(
       input  logic             clk,
       input  logic             rst,                  // synchronous, active-high
       input  logic             wr_en,
       input  logic             rd_en,
       input  logic [WIDTH-1:0] wr_data,
       output logic [WIDTH-1:0] dout,                 // REGISTERED: valid N+1 after rd_en
       output logic             full,
       output logic             empty
   );
       logic [WIDTH-1:0] mem [DEPTH];
       logic [AW:0]      wptr, rptr;                  // extra-MSB pointers (7.2)
 
       assign empty = (wptr == rptr);                 // pop-permitted flag (NOT data-valid)
       assign full  = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0]);
 
       always_ff @(posedge clk) begin
           if (rst) begin
               wptr <= '0;
               rptr <= '0;
               dout <= '0;
           end else begin
               if (wr_en && !full) begin
                   mem[wptr[AW-1:0]] <= wr_data;
                   wptr <= wptr + 1'b1;
               end
               if (rd_en && !empty) begin
                   dout <= mem[rptr[AW-1:0]];         // capture head -> appears NEXT cycle
                   rptr <= rptr + 1'b1;               // ONE cycle of read latency
               end
           end
       end
   endmodule

The clocked testbench asserts the standard contract precisely: after rd_en is pulsed in a cycle, the popped word must appear on dout the next cycle, in FIFO order. Sampling dout in the same cycle as rd_en would see the stale previous word — that is the trap §6 and §7 are about, so the testbench deliberately samples one cycle late.

fifo_std_tb.sv — self-checking: dout valid ONE cycle after rd_en, in order
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_std_tb;
       localparam int WIDTH = 8, DEPTH = 4;
       logic clk = 1'b0, rst, wr_en, rd_en, full, empty;
       logic [WIDTH-1:0] wr_data, dout;
       int errors = 0;
 
       fifo_std #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(rd_en),
           .wr_data(wr_data), .dout(dout), .full(full), .empty(empty));
 
       always #5 clk = ~clk;
 
       initial begin
           rst = 1'b1; wr_en = 1'b0; rd_en = 1'b0; wr_data = '0;
           @(posedge clk); #1; rst = 1'b0;
 
           // Fill with a known stream 0xA0..0xA3.
           for (int i = 0; i < DEPTH; i++) begin
               @(negedge clk); wr_en = 1'b1; wr_data = 8'hA0 + i[7:0];
               @(posedge clk); #1; wr_en = 1'b0;
           end
 
           // STANDARD contract: pulse rd_en, then sample dout the NEXT cycle.
           for (int i = 0; i < DEPTH; i++) begin
               @(negedge clk); rd_en = 1'b1;          // request in this cycle
               @(posedge clk); #1; rd_en = 1'b0;      // rptr advanced, dout not yet valid
               @(negedge clk);                        // wait the 1-cycle read latency
               // dout now holds the word requested one cycle earlier.
               assert (dout === 8'hA0 + i[7:0])
                   else begin $error("std: dout=%h exp=%h (latency wrong)", dout, 8'hA0 + i[7:0]); errors++; end
           end
 
           if (errors == 0) $display("PASS: standard dout valid one cycle after rd_en, in order");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog form is identical in intent with reg/wire typing; dout is a reg captured in the clocked block, so the one-cycle latency is structural.

fifo_std.v — standard read in Verilog (registered dout)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_std #(
       parameter WIDTH = 8,
       parameter DEPTH = 16,
       parameter AW    = 4                            // = clog2(DEPTH)
   )(
       input  wire             clk,
       input  wire             rst,
       input  wire             wr_en,
       input  wire             rd_en,
       input  wire [WIDTH-1:0] wr_data,
       output reg  [WIDTH-1:0] dout,                  // REGISTERED: valid N+1 after rd_en
       output wire             full,
       output wire             empty
   );
       reg [WIDTH-1:0] mem [0:DEPTH-1];
       reg [AW:0]      wptr, rptr;
 
       assign empty = (wptr == rptr);
       assign full  = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0]);
 
       always @(posedge clk) begin
           if (rst) begin
               wptr <= {(AW+1){1'b0}};
               rptr <= {(AW+1){1'b0}};
               dout <= {WIDTH{1'b0}};
           end else begin
               if (wr_en && !full) begin
                   mem[wptr[AW-1:0]] <= wr_data;
                   wptr <= wptr + 1'b1;
               end
               if (rd_en && !empty) begin
                   dout <= mem[rptr[AW-1:0]];         // head captured -> next cycle
                   rptr <= rptr + 1'b1;
               end
           end
       end
   endmodule
fifo_std_tb.v — self-checking; sample dout one cycle after rd_en, PASS/FAIL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_std_tb;
       parameter WIDTH = 8, DEPTH = 4, AW = 2;
       reg  clk, rst, wr_en, rd_en;
       reg  [WIDTH-1:0] wr_data;
       wire [WIDTH-1:0] dout;
       wire full, empty;
       integer i, errors;
 
       fifo_std #(.WIDTH(WIDTH), .DEPTH(DEPTH), .AW(AW)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(rd_en),
           .wr_data(wr_data), .dout(dout), .full(full), .empty(empty));
 
       always #5 clk = ~clk;
 
       initial begin
           errors = 0; clk = 1'b0; rst = 1'b1; wr_en = 0; rd_en = 0; wr_data = 0;
           @(posedge clk); #1; rst = 1'b0;
 
           for (i = 0; i < DEPTH; i = i + 1) begin
               @(negedge clk); wr_en = 1'b1; wr_data = 8'hA0 + i;
               @(posedge clk); #1; wr_en = 1'b0;
           end
 
           // STANDARD: pulse rd_en, wait ONE cycle, then check dout.
           for (i = 0; i < DEPTH; i = i + 1) begin
               @(negedge clk); rd_en = 1'b1;
               @(posedge clk); #1; rd_en = 1'b0;
               @(negedge clk);
               if (dout !== (8'hA0 + i)) begin
                   $display("FAIL: std dout=%h exp=%h (latency)", dout, 8'hA0 + i);
                   errors = errors + 1;
               end
           end
 
           if (errors == 0) $display("PASS: standard dout valid one cycle after rd_en, in order");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL dout is a signal assigned inside the clocked process, giving the same registered one-cycle latency; pointers are unsigned(AW downto 0) from numeric_std.

fifo_std.vhd — standard read in VHDL (registered dout, numeric_std)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity fifo_std is
       generic ( WIDTH : positive := 8; DEPTH : positive := 16; AW : positive := 4 );
       port (
           clk, rst, wr_en, rd_en : in  std_logic;
           wr_data                : in  std_logic_vector(WIDTH-1 downto 0);
           dout                   : out std_logic_vector(WIDTH-1 downto 0);  -- valid N+1
           full, empty            : out std_logic
       );
   end entity;
 
   architecture rtl of fifo_std is
       type mem_t is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal mem  : mem_t;
       signal wptr : unsigned(AW downto 0) := (others => '0');
       signal rptr : unsigned(AW downto 0) := (others => '0');
       signal full_i, empty_i : std_logic;
   begin
       empty_i <= '1' when wptr = rptr else '0';
       full_i  <= '1' when (wptr(AW) /= rptr(AW)) and
                           (wptr(AW-1 downto 0) = rptr(AW-1 downto 0)) else '0';
       empty <= empty_i;
       full  <= full_i;
 
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   wptr <= (others => '0');
                   rptr <= (others => '0');
                   dout <= (others => '0');
               else
                   if wr_en = '1' and full_i = '0' then
                       mem(to_integer(wptr(AW-1 downto 0))) <= wr_data;
                       wptr <= wptr + 1;
                   end if;
                   if rd_en = '1' and empty_i = '0' then
                       dout <= mem(to_integer(rptr(AW-1 downto 0)));   -- head -> next cycle
                       rptr <= rptr + 1;
                   end if;
               end if;
           end if;
       end process;
   end architecture;
fifo_std_tb.vhd — self-checking; dout valid one cycle after rd_en (assert severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity fifo_std_tb is
   end entity;
 
   architecture sim of fifo_std_tb is
       constant WIDTH : positive := 8; constant DEPTH : positive := 4; constant AW : positive := 2;
       signal clk : std_logic := '0';
       signal rst, wr_en, rd_en, full, empty : std_logic := '0';
       signal wr_data, dout : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
   begin
       dut : entity work.fifo_std
           generic map (WIDTH => WIDTH, DEPTH => DEPTH, AW => AW)
           port map (clk => clk, rst => rst, wr_en => wr_en, rd_en => rd_en,
                     wr_data => wr_data, dout => dout, full => full, empty => empty);
 
       clkgen : process begin clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns; end process;
 
       stim : process
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
 
           for i in 0 to DEPTH-1 loop
               wait until falling_edge(clk);
               wr_en <= '1'; wr_data <= std_logic_vector(to_unsigned(16#A0# + i, WIDTH));
               wait until rising_edge(clk); wait for 1 ns; wr_en <= '0';
           end loop;
 
           -- STANDARD: pulse rd_en, wait ONE cycle, then check dout.
           for i in 0 to DEPTH-1 loop
               wait until falling_edge(clk); rd_en <= '1';
               wait until rising_edge(clk); wait for 1 ns; rd_en <= '0';
               wait until falling_edge(clk);
               assert dout = std_logic_vector(to_unsigned(16#A0# + i, WIDTH))
                   report "std: dout not valid one cycle after rd_en" severity error;
           end loop;
 
           report "fifo_std self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. The FWFT wrapper — a prefetch stage giving zero-latency reads

Now wrap the standard core with a small output stage. It holds one word (fw_data) and a validity bit (fw_valid). Whenever the stage is empty and the core is non-empty, it prefetches the head word into fw_data and sets fw_valid — so dout = fw_data is already valid the cycle FWFT-empty (= !fw_valid) drops. rd_en is an acknowledge: when the consumer pulses it, the stage immediately refills from the core (or clears if the core is empty), so the next word falls through with no bubble.

fifo_fwft.sv — FWFT wrapper: prefetch stage, dout valid when !empty, rd_en = ack
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_fwft #(
       parameter int WIDTH = 8,
       parameter int DEPTH = 16,
       localparam int AW   = $clog2(DEPTH)
   )(
       input  logic             clk,
       input  logic             rst,
       input  logic             wr_en,
       input  logic             rd_en,               // ACKNOWLEDGE: I took dout, advance
       input  logic [WIDTH-1:0] wr_data,
       output logic [WIDTH-1:0] dout,                // FALLS THROUGH: valid when !empty
       output logic             full,
       output logic             empty                // = !fw_valid : data-valid flag
   );
       logic [WIDTH-1:0] mem [DEPTH];
       logic [AW:0]      wptr, rptr;
       logic [WIDTH-1:0] fw_data;                    // the prefetched head word
       logic             fw_valid;                   // is a word presented on dout?
 
       // core-level flags (internal): does the memory hold a word to prefetch?
       logic core_empty, core_full;
       assign core_empty = (wptr == rptr);
       assign core_full  = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0]);
 
       // A core read fires when the output stage needs (re)filling and the core has data:
       //   - the stage is empty (need to prime dout), OR
       //   - the consumer is acknowledging this cycle (advance to the next word).
       logic do_core_rd;
       assign do_core_rd = (!core_empty) && (!fw_valid || rd_en);
 
       assign full  = core_full;                     // full is still the core's
       assign empty = !fw_valid;                     // EMPTY == no valid head presented
       assign dout  = fw_data;                       // head word, already on the bus
 
       always_ff @(posedge clk) begin
           if (rst) begin
               wptr <= '0; rptr <= '0;
               fw_data <= '0; fw_valid <= 1'b0;
           end else begin
               if (wr_en && !core_full) begin
                   mem[wptr[AW-1:0]] <= wr_data;
                   wptr <= wptr + 1'b1;
               end
               if (do_core_rd) begin
                   fw_data  <= mem[rptr[AW-1:0]];     // PREFETCH head onto dout
                   fw_valid <= 1'b1;
                   rptr <= rptr + 1'b1;
               end else if (rd_en && fw_valid) begin
                   fw_valid <= 1'b0;                  // ack, but core empty -> stage drains
               end
           end
       end
   endmodule

The FWFT testbench asserts the opposite contract to 4a: the first word is on dout the cycle empty deasserts, before any rd_en, and each rd_en acknowledge advances to the next word in FIFO order — zero read latency.

fifo_fwft_tb.sv — self-checking: dout valid when !empty (0 latency); rd_en advances
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_fwft_tb;
       localparam int WIDTH = 8, DEPTH = 4;
       logic clk = 1'b0, rst, wr_en, rd_en, full, empty;
       logic [WIDTH-1:0] wr_data, dout;
       int errors = 0;
 
       fifo_fwft #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(rd_en),
           .wr_data(wr_data), .dout(dout), .full(full), .empty(empty));
 
       always #5 clk = ~clk;
 
       initial begin
           rst = 1'b1; wr_en = 1'b0; rd_en = 1'b0; wr_data = '0;
           @(posedge clk); #1; rst = 1'b0;
 
           // Same stream 0xA0..0xA3 as the standard test.
           for (int i = 0; i < DEPTH; i++) begin
               @(negedge clk); wr_en = 1'b1; wr_data = 8'hA0 + i[7:0];
               @(posedge clk); #1; wr_en = 1'b0;
           end
 
           // Wait until the prefetch primes dout (empty deasserts).
           @(negedge clk);
           while (empty) @(negedge clk);
 
           // FWFT contract: dout is ALREADY valid; rd_en acknowledges and advances.
           for (int i = 0; i < DEPTH; i++) begin
               // Zero latency: the head word is on dout THIS cycle, before rd_en.
               assert (!empty && dout === 8'hA0 + i[7:0])
                   else begin $error("fwft: dout=%h exp=%h (not shown-ahead)", dout, 8'hA0 + i[7:0]); errors++; end
               @(negedge clk); rd_en = 1'b1;          // ACK: took it, advance
               @(posedge clk); #1; rd_en = 1'b0;
               @(negedge clk);                        // next word has fallen through
           end
 
           if (errors == 0) $display("PASS: FWFT dout valid when !empty (zero latency), in order");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog FWFT wrapper is the same structure with reg/wire typing; fw_valid/fw_data are the prefetch registers, and the internal read condition is the same look-ahead.

fifo_fwft.v — FWFT wrapper in Verilog (prefetch register + valid bit)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_fwft #(
       parameter WIDTH = 8,
       parameter DEPTH = 16,
       parameter AW    = 4
   )(
       input  wire             clk,
       input  wire             rst,
       input  wire             wr_en,
       input  wire             rd_en,                // ACKNOWLEDGE
       input  wire [WIDTH-1:0] wr_data,
       output wire [WIDTH-1:0] dout,                 // FALLS THROUGH
       output wire             full,
       output wire             empty                 // = !fw_valid
   );
       reg [WIDTH-1:0] mem [0:DEPTH-1];
       reg [AW:0]      wptr, rptr;
       reg [WIDTH-1:0] fw_data;
       reg             fw_valid;
 
       wire core_empty = (wptr == rptr);
       wire core_full  = (wptr[AW] != rptr[AW]) && (wptr[AW-1:0] == rptr[AW-1:0]);
       wire do_core_rd = (!core_empty) && (!fw_valid || rd_en);
 
       assign full  = core_full;
       assign empty = !fw_valid;
       assign dout  = fw_data;
 
       always @(posedge clk) begin
           if (rst) begin
               wptr <= {(AW+1){1'b0}}; rptr <= {(AW+1){1'b0}};
               fw_data <= {WIDTH{1'b0}}; fw_valid <= 1'b0;
           end else begin
               if (wr_en && !core_full) begin
                   mem[wptr[AW-1:0]] <= wr_data;
                   wptr <= wptr + 1'b1;
               end
               if (do_core_rd) begin
                   fw_data  <= mem[rptr[AW-1:0]];     // prefetch head onto dout
                   fw_valid <= 1'b1;
                   rptr <= rptr + 1'b1;
               end else if (rd_en && fw_valid) begin
                   fw_valid <= 1'b0;                  // ack with empty core -> drain
               end
           end
       end
   endmodule
fifo_fwft_tb.v — self-checking; dout valid when !empty, rd_en advances, PASS/FAIL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_fwft_tb;
       parameter WIDTH = 8, DEPTH = 4, AW = 2;
       reg  clk, rst, wr_en, rd_en;
       reg  [WIDTH-1:0] wr_data;
       wire [WIDTH-1:0] dout;
       wire full, empty;
       integer i, errors;
 
       fifo_fwft #(.WIDTH(WIDTH), .DEPTH(DEPTH), .AW(AW)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(rd_en),
           .wr_data(wr_data), .dout(dout), .full(full), .empty(empty));
 
       always #5 clk = ~clk;
 
       initial begin
           errors = 0; clk = 1'b0; rst = 1'b1; wr_en = 0; rd_en = 0; wr_data = 0;
           @(posedge clk); #1; rst = 1'b0;
 
           for (i = 0; i < DEPTH; i = i + 1) begin
               @(negedge clk); wr_en = 1'b1; wr_data = 8'hA0 + i;
               @(posedge clk); #1; wr_en = 1'b0;
           end
 
           @(negedge clk);
           while (empty) @(negedge clk);              // wait for prefetch to prime dout
 
           for (i = 0; i < DEPTH; i = i + 1) begin
               // FWFT: word already on dout THIS cycle, before rd_en (zero latency).
               if (empty || dout !== (8'hA0 + i)) begin
                   $display("FAIL: fwft dout=%h exp=%h (not shown-ahead)", dout, 8'hA0 + i);
                   errors = errors + 1;
               end
               @(negedge clk); rd_en = 1'b1;          // ack: advance
               @(posedge clk); #1; rd_en = 1'b0;
               @(negedge clk);
           end
 
           if (errors == 0) $display("PASS: FWFT dout valid when !empty (zero latency), in order");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the prefetch stage is a pair of signals (fw_data, fw_valid) updated in the clocked process; the internal read condition and the fall-through of dout <= fw_data are the same.

fifo_fwft.vhd — FWFT wrapper in VHDL (prefetch register + valid bit)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity fifo_fwft is
       generic ( WIDTH : positive := 8; DEPTH : positive := 16; AW : positive := 4 );
       port (
           clk, rst, wr_en, rd_en : in  std_logic;   -- rd_en = ACKNOWLEDGE
           wr_data                : in  std_logic_vector(WIDTH-1 downto 0);
           dout                   : out std_logic_vector(WIDTH-1 downto 0);  -- falls through
           full, empty            : out std_logic     -- empty = not fw_valid
       );
   end entity;
 
   architecture rtl of fifo_fwft is
       type mem_t is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal mem      : mem_t;
       signal wptr     : unsigned(AW downto 0) := (others => '0');
       signal rptr     : unsigned(AW downto 0) := (others => '0');
       signal fw_data  : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
       signal fw_valid : std_logic := '0';
       signal core_empty, core_full, do_core_rd : std_logic;
   begin
       core_empty <= '1' when wptr = rptr else '0';
       core_full  <= '1' when (wptr(AW) /= rptr(AW)) and
                              (wptr(AW-1 downto 0) = rptr(AW-1 downto 0)) else '0';
       -- prefetch when the stage needs (re)filling and the core has data:
       do_core_rd <= '1' when (core_empty = '0') and
                              (fw_valid = '0' or rd_en = '1') else '0';
 
       full  <= core_full;
       empty <= not fw_valid;
       dout  <= fw_data;
 
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   wptr <= (others => '0'); rptr <= (others => '0');
                   fw_data <= (others => '0'); fw_valid <= '0';
               else
                   if wr_en = '1' and core_full = '0' then
                       mem(to_integer(wptr(AW-1 downto 0))) <= wr_data;
                       wptr <= wptr + 1;
                   end if;
                   if do_core_rd = '1' then
                       fw_data  <= mem(to_integer(rptr(AW-1 downto 0)));   -- prefetch head
                       fw_valid <= '1';
                       rptr <= rptr + 1;
                   elsif rd_en = '1' and fw_valid = '1' then
                       fw_valid <= '0';                                    -- ack, core empty
                   end if;
               end if;
           end if;
       end process;
   end architecture;
fifo_fwft_tb.vhd — self-checking; dout valid when !empty (assert severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity fifo_fwft_tb is
   end entity;
 
   architecture sim of fifo_fwft_tb is
       constant WIDTH : positive := 8; constant DEPTH : positive := 4; constant AW : positive := 2;
       signal clk : std_logic := '0';
       signal rst, wr_en, rd_en, full, empty : std_logic := '0';
       signal wr_data, dout : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
   begin
       dut : entity work.fifo_fwft
           generic map (WIDTH => WIDTH, DEPTH => DEPTH, AW => AW)
           port map (clk => clk, rst => rst, wr_en => wr_en, rd_en => rd_en,
                     wr_data => wr_data, dout => dout, full => full, empty => empty);
 
       clkgen : process begin clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns; end process;
 
       stim : process
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
 
           for i in 0 to DEPTH-1 loop
               wait until falling_edge(clk);
               wr_en <= '1'; wr_data <= std_logic_vector(to_unsigned(16#A0# + i, WIDTH));
               wait until rising_edge(clk); wait for 1 ns; wr_en <= '0';
           end loop;
 
           wait until falling_edge(clk);
           while empty = '1' loop wait until falling_edge(clk); end loop;  -- prime dout
 
           -- FWFT: the head word is already on dout, before rd_en (zero latency).
           for i in 0 to DEPTH-1 loop
               assert empty = '0' and dout = std_logic_vector(to_unsigned(16#A0# + i, WIDTH))
                   report "fwft: dout not shown-ahead / out of order" severity error;
               wait until falling_edge(clk); rd_en <= '1';                -- ack: advance
               wait until rising_edge(clk); wait for 1 ns; rd_en <= '0';
               wait until falling_edge(clk);
           end loop;
 
           report "fifo_fwft self-check complete" severity note;
           wait;
       end process;
   end architecture;

4c. The standard-vs-FWFT mismatch — buggy consumer vs matched fix

The signature failure: a consumer written for the FWFT contract (sample dout the cycle empty drops, pulse rd_en to advance) is wired to a standard FIFO whose data arrives one cycle after rd_en. It latches the stale value as the first word and the whole stream shifts by one. The fix is to make the consumer's contract match the FIFO — either drain an FWFT FIFO, or add the one-cycle read-latency handling.

fifo_mismatch.sv — BUGGY (FWFT-style consumer on a STANDARD FIFO) vs FIXED (contracts match)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY consumer: assumes FWFT. It samples dout the SAME cycle it sees !empty
   // and pulses rd_en. But dut is a STANDARD FIFO -> dout is valid NEXT cycle, so
   // this captures the STALE previous value as the "first word" and shifts by one.
   module drain_bad (
       input  logic             clk, rst,
       input  logic             empty,
       input  logic [7:0]       dout,        // from a STANDARD FIFO (registered, 1-cyc)
       output logic             rd_en,
       output logic [7:0]       captured,    // what the consumer thinks the head is
       output logic             cap_valid
   );
       always_ff @(posedge clk) begin
           if (rst) begin rd_en <= 1'b0; captured <= '0; cap_valid <= 1'b0; end
           else begin
               rd_en     <= !empty;          // pulse whenever not empty
               captured  <= dout;            // SAMPLE NOW  <-- FWFT assumption: WRONG here
               cap_valid <= !empty;          // ...one cycle too early for a standard FIFO
           end
       end
   endmodule
 
   // FIXED consumer: matches the STANDARD contract. It knows dout is valid the cycle
   // AFTER rd_en, so it delays its capture by one cycle (samples on the read-data
   // valid, not on !empty). Now the FIRST word is the true head, no shift.
   module drain_good (
       input  logic             clk, rst,
       input  logic             empty,
       input  logic [7:0]       dout,
       output logic             rd_en,
       output logic [7:0]       captured,
       output logic             cap_valid
   );
       logic rd_en_q;                        // was a read issued last cycle?
       always_ff @(posedge clk) begin
           if (rst) begin rd_en <= 1'b0; rd_en_q <= 1'b0; captured <= '0; cap_valid <= 1'b0; end
           else begin
               rd_en     <= !empty;          // request when data is available to pop
               rd_en_q   <= rd_en;           // read-data-valid is rd_en delayed one cycle
               captured  <= dout;            // dout is now the word requested last cycle
               cap_valid <= rd_en_q;         // capture is valid ONLY when the read returned
           end
       end
   endmodule
fifo_mismatch_tb.sv — same stream; buggy consumer shifts by one, fixed matches
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_mismatch_tb;
       localparam int WIDTH = 8, DEPTH = 4;
       logic clk = 1'b0, rst, wr_en, s_rd, s_full, s_empty;
       logic [WIDTH-1:0] wr_data, s_dout;
       logic bad_rd, bad_cv; logic [7:0] bad_cap;
       logic good_rd, good_cv; logic [7:0] good_cap;
       int errors = 0, good_idx = 0;
 
       // ONE standard FIFO feeding BOTH consumers with the same read stream is awkward,
       // so drive the FIXED consumer's rd_en into the FIFO and observe both captures.
       fifo_std #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(good_rd),
           .wr_data(wr_data), .dout(s_dout), .full(s_full), .empty(s_empty));
 
       drain_bad  ub (.clk(clk), .rst(rst), .empty(s_empty), .dout(s_dout),
                      .rd_en(bad_rd), .captured(bad_cap), .cap_valid(bad_cv));
       drain_good ug (.clk(clk), .rst(rst), .empty(s_empty), .dout(s_dout),
                      .rd_en(good_rd), .captured(good_cap), .cap_valid(good_cv));
 
       always #5 clk = ~clk;
 
       // The FIXED consumer's captures must be the exact stream, in order.
       always @(posedge clk) if (!rst && good_cv) begin
           if (good_cap !== 8'hA0 + good_idx[7:0]) begin
               $error("FIXED shifted: got %h exp %h", good_cap, 8'hA0 + good_idx[7:0]); errors++;
           end
           good_idx++;
       end
 
       initial begin
           rst = 1'b1; wr_en = 1'b0; wr_data = '0;
           @(posedge clk); #1; rst = 1'b0;
           for (int i = 0; i < DEPTH; i++) begin
               @(negedge clk); wr_en = 1'b1; wr_data = 8'hA0 + i[7:0];
               @(posedge clk); #1; wr_en = 1'b0;
           end
           repeat (DEPTH + 4) @(posedge clk);
           // The BUGGY consumer's FIRST valid capture is the stale value, not 0xA0.
           if (good_idx != DEPTH) begin $error("FIXED captured %0d of %0d words", good_idx, DEPTH); errors++; end
           if (errors == 0) $display("PASS: matched-contract consumer got the stream in order (FWFT-on-standard would shift)");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog mismatch pair is the same story: the buggy consumer samples dout on !empty (an FWFT assumption) while the FIFO is standard; the fixed consumer delays its capture by the one-cycle read latency.

fifo_mismatch.v — BUGGY vs FIXED consumer in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: FWFT-style capture on a STANDARD FIFO -> samples stale value, shifts by one.
   module drain_bad (
       input  wire       clk, rst, empty,
       input  wire [7:0] dout,
       output reg        rd_en,
       output reg  [7:0] captured,
       output reg        cap_valid
   );
       always @(posedge clk) begin
           if (rst) begin rd_en <= 1'b0; captured <= 8'h0; cap_valid <= 1'b0; end
           else begin
               rd_en     <= !empty;
               captured  <= dout;        // SAME cycle -> one too early for standard
               cap_valid <= !empty;
           end
       end
   endmodule
 
   // FIXED: match the standard contract -> capture on read-data-valid (rd_en delayed 1).
   module drain_good (
       input  wire       clk, rst, empty,
       input  wire [7:0] dout,
       output reg        rd_en,
       output reg  [7:0] captured,
       output reg        cap_valid
   );
       reg rd_en_q;
       always @(posedge clk) begin
           if (rst) begin rd_en <= 1'b0; rd_en_q <= 1'b0; captured <= 8'h0; cap_valid <= 1'b0; end
           else begin
               rd_en     <= !empty;
               rd_en_q   <= rd_en;       // data-valid is rd_en delayed one cycle
               captured  <= dout;
               cap_valid <= rd_en_q;
           end
       end
   endmodule
fifo_mismatch_tb.v — self-checking; fixed consumer gets the stream, buggy shifts
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module fifo_mismatch_tb;
       parameter WIDTH = 8, DEPTH = 4, AW = 2;
       reg  clk, rst, wr_en;
       reg  [WIDTH-1:0] wr_data;
       wire [WIDTH-1:0] s_dout;
       wire s_full, s_empty;
       wire bad_rd, bad_cv;   wire [7:0] bad_cap;
       wire good_rd, good_cv; wire [7:0] good_cap;
       integer i, errors, good_idx;
 
       fifo_std #(.WIDTH(WIDTH), .DEPTH(DEPTH), .AW(AW)) dut (
           .clk(clk), .rst(rst), .wr_en(wr_en), .rd_en(good_rd),
           .wr_data(wr_data), .dout(s_dout), .full(s_full), .empty(s_empty));
 
       drain_bad  ub (.clk(clk), .rst(rst), .empty(s_empty), .dout(s_dout),
                      .rd_en(bad_rd), .captured(bad_cap), .cap_valid(bad_cv));
       drain_good ug (.clk(clk), .rst(rst), .empty(s_empty), .dout(s_dout),
                      .rd_en(good_rd), .captured(good_cap), .cap_valid(good_cv));
 
       always #5 clk = ~clk;
 
       always @(posedge clk) if (!rst && good_cv) begin
           if (good_cap !== (8'hA0 + good_idx)) begin
               $display("FAIL: FIXED shifted got %h exp %h", good_cap, 8'hA0 + good_idx);
               errors = errors + 1;
           end
           good_idx = good_idx + 1;
       end
 
       initial begin
           errors = 0; good_idx = 0; clk = 0; rst = 1; wr_en = 0; wr_data = 0;
           @(posedge clk); #1; rst = 0;
           for (i = 0; i < DEPTH; i = i + 1) begin
               @(negedge clk); wr_en = 1; wr_data = 8'hA0 + i;
               @(posedge clk); #1; wr_en = 0;
           end
           repeat (DEPTH + 4) @(posedge clk);
           if (good_idx !== DEPTH) begin
               $display("FAIL: FIXED captured %0d of %0d", good_idx, DEPTH); errors = errors + 1;
           end
           if (errors == 0) $display("PASS: matched consumer got the stream in order (FWFT-on-standard would shift)");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the same mismatch is a process that samples dout on not empty (buggy, FWFT assumption) versus one that delays the capture by the registered read latency (fixed).

fifo_mismatch.vhd — BUGGY vs FIXED consumer in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   -- BUGGY: FWFT-style capture on a STANDARD FIFO -> samples the stale value, shifts.
   entity drain_bad is
       port ( clk, rst, empty : in std_logic;
              dout            : in std_logic_vector(7 downto 0);
              rd_en, cap_valid : out std_logic;
              captured        : out std_logic_vector(7 downto 0) );
   end entity;
   architecture rtl of drain_bad is
   begin
       process (clk) begin
           if rising_edge(clk) then
               if rst = '1' then
                   rd_en <= '0'; captured <= (others => '0'); cap_valid <= '0';
               else
                   rd_en     <= not empty;
                   captured  <= dout;             -- SAME cycle -> one too early for standard
                   cap_valid <= not empty;
               end if;
           end if;
       end process;
   end architecture;
 
   -- FIXED: match the standard contract -> capture on read-data-valid (rd_en delayed 1).
   library ieee;
   use ieee.std_logic_1164.all;
   entity drain_good is
       port ( clk, rst, empty : in std_logic;
              dout            : in std_logic_vector(7 downto 0);
              rd_en, cap_valid : out std_logic;
              captured        : out std_logic_vector(7 downto 0) );
   end entity;
   architecture rtl of drain_good is
       signal rd_en_i, rd_en_q : std_logic;
   begin
       rd_en <= rd_en_i;
       process (clk) begin
           if rising_edge(clk) then
               if rst = '1' then
                   rd_en_i <= '0'; rd_en_q <= '0'; captured <= (others => '0'); cap_valid <= '0';
               else
                   rd_en_i   <= not empty;
                   rd_en_q   <= rd_en_i;          -- data-valid is rd_en delayed one cycle
                   captured  <= dout;
                   cap_valid <= rd_en_q;
               end if;
           end if;
       end process;
   end architecture;
fifo_mismatch_tb.vhd — self-checking; fixed consumer gets the stream in order
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity fifo_mismatch_tb is
   end entity;
 
   architecture sim of fifo_mismatch_tb is
       constant WIDTH : positive := 8; constant DEPTH : positive := 4; constant AW : positive := 2;
       signal clk : std_logic := '0';
       signal rst, wr_en, s_full, s_empty : std_logic := '0';
       signal wr_data, s_dout : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
       signal bad_rd, bad_cv, good_rd, good_cv : std_logic;
       signal bad_cap, good_cap : std_logic_vector(7 downto 0);
       signal good_idx : integer := 0;
       signal errors   : integer := 0;
   begin
       dut : entity work.fifo_std
           generic map (WIDTH => WIDTH, DEPTH => DEPTH, AW => AW)
           port map (clk => clk, rst => rst, wr_en => wr_en, rd_en => good_rd,
                     wr_data => wr_data, dout => s_dout, full => s_full, empty => s_empty);
 
       ub : entity work.drain_bad
           port map (clk => clk, rst => rst, empty => s_empty, dout => s_dout,
                     rd_en => bad_rd, cap_valid => bad_cv, captured => bad_cap);
       ug : entity work.drain_good
           port map (clk => clk, rst => rst, empty => s_empty, dout => s_dout,
                     rd_en => good_rd, cap_valid => good_cv, captured => good_cap);
 
       clkgen : process begin clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns; end process;
 
       check : process (clk) begin
           if rising_edge(clk) and rst = '0' and good_cv = '1' then
               assert good_cap = std_logic_vector(to_unsigned(16#A0# + good_idx, 8))
                   report "FIXED shifted: capture out of order" severity error;
               good_idx <= good_idx + 1;
           end if;
       end process;
 
       stim : process
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
           for i in 0 to DEPTH-1 loop
               wait until falling_edge(clk);
               wr_en <= '1'; wr_data <= std_logic_vector(to_unsigned(16#A0# + i, WIDTH));
               wait until rising_edge(clk); wait for 1 ns; wr_en <= '0';
           end loop;
           for i in 0 to DEPTH+4 loop wait until rising_edge(clk); end loop;
           report "fifo_mismatch self-check complete (matched consumer got the stream)" severity note;
           wait;
       end process;
   end architecture;

Across all three languages the lesson is one sentence: standard and FWFT are a contract about when dout is valid relative to rd_en and empty — sample the head the cycle empty drops for FWFT, sample one cycle after rd_en for standard, and the producer and consumer must sign the same contract or the first word of every burst is wrong.

5. Verification Strategy

Both interfaces drain the same buffer in the same order, so correctness is a timing property: verification must pin down when dout is valid, not just what it eventually equals. The single invariant that captures both interfaces is:

A standard FIFO presents the popped word on dout exactly one cycle after rd_en && !empty; an FWFT FIFO presents the head word on dout the moment empty deasserts, before any rd_en, and each rd_en advances to the next word — and in both cases the words come out in FIFO order.

Everything below makes that invariant checkable and maps directly onto the testbenches in §4.

  • Standard: data exactly one cycle after rd_en. Drive a known stream in, then for each read assert rd_en in cycle N and sample dout in cycle N+1, checking it equals the expected word. Deliberately do not sample in cycle N — that would see the stale previous value and is exactly the bug §7 dramatizes. The §4a testbenches do this: pulse rd_en, wait one cycle, then assert (dout === exp) (SV), if (dout !== exp) $display("FAIL") (Verilog), assert dout = exp … severity error (VHDL).
  • FWFT: the first word is on dout the moment empty deasserts. After the stream is written, wait for FWFT-empty to drop, then assert dout already equals the head this cycle, before any rd_en. Then pulse rd_en (the acknowledge) and assert the next word has fallen through with zero latency. This is the §4b testbench: assert (!empty && dout === exp) before the acknowledge, then advance. The check that FWFT empty is really a data-valid flag (not merely pop-permitted) is the heart of it.
  • Drive both with the same stream and check ordering. Push the identical sequence (0xA0, 0xA1, 0xA2, 0xA3) into a standard and an FWFT FIFO and confirm both emit it in order — differing only in when each word is valid. This isolates the interface (timing) from the storage (ordering): if the order ever differs, the bug is in the core, not the read interface. The §4c mismatch testbench is the sharpest form: it shows a matched-contract consumer recovers the exact stream while an FWFT-style consumer on a standard FIFO would shift it by one.
  • The mismatch is the key negative test. Wire a consumer written for one contract to a FIFO of the other and assert the corruption: an FWFT-style consumer on a standard FIFO captures the stale value as the first word and every subsequent word is off by one; a standard-style consumer on an FWFT FIFO double-advances (acknowledges a word it never sampled). Proving the matched consumer is correct and the mismatched one shifts is what documents the contract as load-bearing, not incidental.
  • Parameter-generic verification. Re-run the standard and FWFT read tests for DEPTH = 2, 4, 8, 16 and WIDTH = 1, 8, 32. The FWFT prefetch stage in particular must be verified across depths — a wrapper that is accidentally correct at DEPTH=4 can mishandle the prime/refill at DEPTH=2 (where the core empties almost immediately and the prefetch and acknowledge collide). Include the single-word case (write one, read one) explicitly: for FWFT that one word must fall through and, after the acknowledge, empty must reassert with no phantom second word.
  • Expected waveform. For the standard FIFO, on a scope dout steps to the requested word one clock after the rd_en pulse — a visible one-cycle skew between request and data. For the FWFT FIFO, dout already carries the head word the cycle empty falls, and it advances on each rd_en edge with no skew — !empty tracks a valid word being present, not a pop being permitted. A standard FIFO whose consumer samples on !empty shows the tell of the bug: the first captured word is the reset/previous value and the whole captured stream is shifted one position late.

6. Common Mistakes

Read-interface mismatch — assuming FWFT on a standard FIFO. The signature bug. Downstream logic samples dout the cycle empty drops (an FWFT assumption), but the FIFO is standard, so dout is not valid until one cycle after rd_en. The consumer reads the stale/garbage previous value as the "first word," and because it has taken the wrong word first, every word is off by one — the first transfer of every packet is corrupt while later structure looks plausible, which is why it survives a casual test and fails on real packets. (Post-mortem in §7.)

Assuming standard on an FWFT FIFO — double-popping. The mirror error. On an FWFT FIFO the head word is already on dout, so a consumer that treats rd_en as a request ("pulse it, then read next cycle") acknowledges the shown word and skips forward, effectively double-advancing: it consumes a word it never sampled. The FWFT rd_en is an acknowledge, not a fetch; pulse it only when you have actually taken the word currently on dout.

Forgetting the FWFT prefetch costs an entry and changes the empty timing. The prefetch/output register is real state holding one word outside the core. Two things follow that trip people up: the FWFT FIFO can have DEPTH + 1 words in flight (the core plus the prefetched one), and FWFT empty deasserts one cycle later on the first fill than a naive read of the core would suggest, because the first word must be pulled through into the output stage before it can fall through to dout. Treating the prefetch as free (no entry, no pipeline cycle) yields "my FWFT empty is off by one" confusion.

Not accounting for the standard read latency in the consumer pipeline. Even when the consumer correctly knows the FIFO is standard, it must carry a read-data-valid that is rd_en delayed by the one-cycle latency, and register dout on that valid — not on !empty. A pipeline that forwards dout combinationally the cycle it issues rd_en forwards stale data. The fix is a one-cycle rd_en delay (or a small skid/valid pipe) so the capture aligns with when the read actually returns — exactly the rd_en_q in the §4c fixed consumer.

Using FWFT empty and standard empty interchangeably. They mean different things: standard empty low is pop-permitted, FWFT empty low is data-valid-now. Logic that treats standard !empty as "the data is on the bus" reads early; logic that treats FWFT !empty as merely "a pop is allowed" and still waits a cycle reads late (and may re-acknowledge). When integrating, always ask which empty you have — a valid flag or a permission flag — before wiring it to a consumer's sample enable.

Reaching for FWFT (or standard) without matching the downstream interface. FWFT is the natural fit for valid/ready handshakes and straight-through pipelines (data valid the cycle you look); standard is simpler internally and fine where the consumer is a state machine that already waits a cycle. Picking the read style by habit rather than by what the consumer expects forces an adapter (a latency shim for standard into a valid/ready sink, or a prefetch for a consumer that wanted show-ahead) — or, worse, ships the mismatch of §7.

7. DebugLab

The packet whose header came out one word late — a standard/FWFT interface mismatch

The engineering lesson: standard and FWFT are a contract about when the data is valid relative to rd_en and empty — the producer FIFO and the consumer must agree, or the first word of every burst is wrong and the whole stream shifts by one. The tell is corruption that is a shift, not a loss: all the words are present but each is one position late, the first field is a stale value, and adding a leading dummy word or a one-cycle wait "fixes" it — that is the fingerprint of a read-latency/interface mismatch, not a data-path bug. Two habits make it impossible, and they are identical across SystemVerilog, Verilog, and VHDL: know which read contract you have — FWFT (dout valid when empty drops, rd_en acknowledges) or standard (dout valid one cycle after rd_en, rd_en requests) — and sample accordingly, and verify the first word directly by asserting the first captured word equals the first word written, which exposes the one-cycle shift on the very first transfer.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the "which read contract is this?" habit and the timing behind it.

Exercise 1 — Match the read style to the consumer

For each downstream consumer, state whether you would give it a standard or an FWFT FIFO, and why in one line: (a) a valid/ready sink that registers its input every cycle whenever valid && ready; (b) a state machine that issues a read, does one cycle of address setup, then uses the data; (c) a straight-through pipeline stage that must see the data the cycle it looks; (d) a block where internal simplicity of the FIFO matters more than read latency. (Hint: does the consumer want the data now, or can it tolerate one cycle after the request?)

Exercise 2 — Trace both interfaces on the same stream

The stream 0xA0, 0xA1, 0xA2, 0xA3 is written into both a standard and an FWFT FIFO (DEPTH = 4). Draw the cycle-by-cycle timing of rd_en, empty, and dout for a consumer that reads one word per cycle from each. Mark the exact cycle on which the first word (0xA0) is validly available on dout in each case, and state the read latency (in cycles) of each interface. Then say which cycle the FWFT empty first deasserts relative to when the core actually holds a word, and why the prefetch adds that cycle.

Exercise 3 — Diagnose the shift on paper

A consumer captures dout on the cycle it sees !empty and pulses rd_en, and it is wired to a standard FIFO preloaded with 0xA0..0xA3. Write out the sequence of values it captures (including the reset value) and show precisely how the captured stream is shifted relative to the written stream. Then give the one-line change to the consumer that fixes it without changing the FIFO, and separately the one structural change to the FIFO that fixes it without changing the consumer. Explain why each fix aligns the contract.

Exercise 4 — Account for the prefetch entry

An FWFT FIFO is built as a standard core of DEPTH = 8 plus a prefetch output register. State how many words can be in flight (accepted but not yet acknowledged) at once, and where each lives. Then describe what happens to empty/dout in the corner case of writing exactly one word into a reset-empty FWFT FIFO: on which cycle does empty deassert, when is that word on dout, and after the acknowledge, does empty reassert cleanly with no phantom second word? Explain what state the prefetch stage must hold to get that corner right.

Continue in RTL Design Patterns (forward links unlock as they ship):

  • FIFO Depth Calculation — Chapter 7.4; sizing the DEPTH this page's read interface drains — where the prefetch entry factors into the words-in-flight budget.
  • FIFO Verification — Chapter 7.6; the full self-checking + assertion strategy the standard/FWFT read-timing checks here become part of.
  • Valid/Ready Handshake — Chapter 9.1; the interface FWFT drops straight into — !empty is the valid, rd_en is the ready-driven acknowledge.
  • Skid Buffer — the two-entry buffer that decouples a valid/ready producer and consumer — the same show-ahead / one-entry-of-slack idea as the FWFT prefetch.

Backward / in-track dependencies:

  • Synchronous FIFO Architecture — Chapter 7.1; the circular-buffer core both read interfaces are built on.
  • Full & Empty Logic — Chapter 7.2; the pointer scheme and the empty flag whose meaning (permission vs data-valid) this page reinterprets.
  • FIFO Programmable Flags — Chapter 7.3; almost-full/almost-empty thresholds on the same occupancy the read interface consumes.
  • The Register Pattern (D-FF) — Chapter 2.1; the flops the pointers and the prefetch/output register are built from, and the reset-first clocked-update discipline.
  • Register with Enable / Load — Chapter 2.2; the load-enabled register is exactly the prefetch stage — a flop that captures a new word only on the acknowledge.
  • Multiplexers (2:1, N:1, one-hot) — Chapter 1.1; the selection between "hold the prefetched word" and "load the next" is a 2:1 mux feeding the output register.
  • Pulse & Strobe Generators — Chapter 3.6; the single-cycle rd_en acknowledge pulse that advances the FWFT stage.
  • The RTL Design Mindset — Chapter 0.2; the Interface and Verification lenses this page applies — the read style is an interface contract, and the tests prove when the data is valid.
  • What Is an RTL Design Pattern? — Chapter 0.1; why the read interface earns the name "pattern" — a recurring problem, two reusable forms, a synthesis reality, and a signature failure.

Verilog v1 prerequisites (the grammar this pattern transcribes into):

  • Blocking and Non-Blocking Assignments — why the pointers, output register, and prefetch stage update with non-blocking (<=) in their clocked process, giving the one-cycle read latency.
  • If-Else Statements — the prefetch-or-acknowledge decision (if (do_core_rd) … else if (rd_en …)) at the heart of the FWFT wrapper.
  • Case Statements — the multi-way control the read/write commit conditions compile into.
  • Physical Data Types — the vector widths (WIDTH, the AW+1 pointers) whose deliberate sizing the FIFO depends on.

11. Summary

  • A FIFO's read side is a contract about when dout is valid. Standard and FWFT drain the same buffer in the same order; they disagree only on the timing of dout relative to rd_en. Confuse them and the first word of every burst is wrong.
  • Standard (normal-latency): registered read, one cycle late. Assert rd_en in cycle N and the popped word appears on dout in cycle N+1. rd_en is a request; empty low means only a pop is permitted. Simplest internally — one output flop, no prefetch.
  • FWFT (show-ahead): zero latency, rd_en acknowledges. The head word is already on dout whenever the FIFO is non-empty; rd_en is an acknowledge that advances to the next word. empty low means a valid head word is on dout now — effectively a valid. Built by wrapping a standard core with a prefetch output stage that pulls the next word ahead of demand, at the cost of one extra entry and a cycle of pipeline.
  • FWFT suits handshakes and pipelines; standard suits latency-tolerant consumers. FWFT's !empty/rd_en map directly onto valid/ready, so it drops into a handshake or a straight-through pipeline with no adapter; standard is the leaner choice where the consumer already waits a cycle.
  • The signature bug is the interface mismatch. An FWFT-style consumer on a standard FIFO samples dout one cycle too early, latches the stale value as the first word, and shifts the whole stream by one; a standard-style consumer on an FWFT FIFO double-advances. The tell is a shift (all words present, each one late), not a loss — fixed by matching the contract (honor the one-cycle latency, or add an FWFT prefetch) and verified by asserting the first captured word equals the first word written. Built and self-check-verified here in SystemVerilog, Verilog, and VHDL.