Skip to content

RTL Design Patterns · Chapter 8 · Pipelining

The Valid-Bit Discipline

A pipeline register is full every cycle, but a value in a register is not the same as a real result. After reset the stages hold undefined state, a gap in the input flows down as a bubble, and an aborted operation leaves garbage behind. In all of these the data lines drive something the consumer must not use, and treating that something as an answer is the signature pipeline bug. The fix is the valid-bit discipline: carry one valid bit alongside the data through every stage, reset it to zero, advance it with the data, and use a result only when its valid is high. Data tells you what, valid tells you when it is real. This lesson builds the N-stage discipline with stall and flush, exposes the valid-data misalignment bug, and self-checks all of it in SystemVerilog, Verilog, and VHDL.

Intermediate16 min readRTL Design PatternsPipelineValid BitBubblesStall FlushData Alignment

Chapter 8 · Section 8.3 · Pipelining

1. The Engineering Problem

In 8.1 you split a slow combinational computation into stages, dropping a register between each so the clock could run fast, and you learned the alignment lesson the hard way: everything that must stay together with the data has to be delayed the same number of stages as the data. A control field that skips a register arrives a cycle early; an operand that takes an extra stage arrives a cycle late; either way the pieces no longer line up. That page ended on the instruction to delay everything that must stay aligned with the data. This page is the single most important thing that must stay aligned: whether the data is real at all.

Here is the concrete need. You have a 3-stage arithmetic pipeline — call it multiply, then round, then saturate — and downstream of it sits a consumer that writes each result into a memory. The pipeline is a chain of registers, so on every clock edge each stage latches whatever its predecessor was holding and drives it onward. That means the output port is driving a value on every single cycle, forever. But the producer feeding the pipeline does not supply a fresh operand every cycle. In the first two cycles after reset the pipeline is still filling — stage 3 is holding power-up X, not a computed result. Whenever the producer has a gap, a bubble flows down the pipe — a cycle with no real operand, whose stage contents are stale. And if an operation is aborted partway down, its in-flight contents are garbage that must never be written.

So the consumer faces a question the data lines cannot answer: is the value on the output bus this cycle a real result, or is it startup noise, a bubble, or an aborted operation? The bus looks identical in all four cases — a full-width word, driven cleanly. If the consumer writes on every cycle it will write undefined power-up state as the first two entries, write duplicate or stale words for every bubble, and write aborted operations it was told to discard. The data carries what; nothing in it carries when it is real. That missing bit of information — present exactly on the cycles that carry a genuine result and absent on all the others — is what this page adds and keeps aligned.

the-need.v — the pipe is always full; not every cycle is real
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // A 3-stage pipeline. Every clock edge, each stage latches its predecessor -> the
   // OUTPUT drives a value on EVERY cycle, whether or not a real operand went in.
   wire [31:0] out_data;   // driven every cycle, always -- a full word, cleanly
 
   // But the producer does NOT feed a real operand every cycle:
   //   - fill:   after reset, stages still hold power-up X   (not a result)
   //   - bubble: a gap cycle with no operand -> stale contents (not a result)
   //   - flush:  an aborted op in flight -> garbage           (must be discarded)
 
   // WRONG -- the consumer writes on every cycle, so it stores X, bubbles, aborts:
   //   always @(posedge clk) mem[waddr] <= out_data;   // no idea WHICH cycles are real
 
   // The need: a bit that is HIGH exactly on the cycles out_data is a real result,
   // aligned to the data to the exact cycle. That bit is the valid-bit discipline.

2. Mental Model

3. Pattern Anatomy

The structure is the 8.1 pipeline — a chain of data registers, one per stage — with a parallel chain of one-bit valid registers running alongside it, plus the enable and clear logic that handles stalls and flushes. Everything hard is in keeping the two chains in exact lockstep.

The paired per-stage registers. For a DEPTH-stage pipeline of WIDTH-bit data, there are DEPTH data registers data_pipe[0..DEPTH-1] and DEPTH valid registers valid_pipe[0..DEPTH-1]. Stage 0 latches the input pair (in_data, in_valid); every later stage latches its predecessor's pair, data_pipe[i] <= data_pipe[i-1] and valid_pipe[i] <= valid_pipe[i-1]. The two chains are the same length and clocked by the same edge, so a (data, valid) pair that enters at stage 0 stays paired all the way to data_pipe[DEPTH-1] / valid_pipe[DEPTH-1]. That structural pairing — not any comparison or computation — is what guarantees the valid bit arrives with its data.

The reset asymmetry. The valid registers are reset to 0; the data registers need no reset. This is deliberate and it is the whole reason startup is safe: after reset, valid_pipe is all zeros, so for the first DEPTH cycles — while the pipe fills with whatever X the data flops powered up holding — every output cycle is marked invalid. Only once a real in_valid has walked all DEPTH stages does valid_pipe[DEPTH-1] go high. Resetting valid but not data is a real area saving (you flop the datapath's reset away) and it is correct, because the datapath's undefined contents are never consumed while their valid is low.

The qualified output. The pipeline exposes two outputs that must be read together: out_data = data_pipe[DEPTH-1] and out_valid = valid_pipe[DEPTH-1]. out_valid is the qualifier — the consumer acts on out_data if and only if out_valid is high. This is the same shape you will meet again as the AXI-style valid line in 9.1: a data bus plus a one-bit qualifier, where the data is meaningful only in the cycles the qualifier asserts.

Bubbles. A bubble is simply a cycle presented at the input with in_valid = 0. It flows through the pipeline exactly like a real operand — it occupies a stage, advances every cycle — but its valid bit is 0, so it is an invalid slot. When it reaches the output, out_valid is low and the consumer ignores it. Bubbles are not a special case in the RTL; they fall out for free the moment valid is pipelined alongside data. A gap in the input stream becomes a gap in the valid stream, automatically aligned.

Stall via clock-enable. To freeze the pipeline (or a stage) — because the consumer is not ready, or an upstream resource is busy — you gate the registers with a clock-enable: when en is low, hold both the data and the valid registers. Holding data alone would let the valid bit advance while its data stood still, tearing the pair apart; holding valid alone would do the reverse. The rule is absolute: a stall holds the (data, valid) pair as a unit, or not at all. A held stage keeps its exact contents and its exact valid, so no result is dropped and none is duplicated.

Flush. To invalidate the whole pipeline at once — on a branch mispredict, an abort, a mode change — you clear all the valid registers to 0 in a single cycle, valid_pipe[*] <= 0. You do not need to touch the data registers: clearing the valids alone turns every in-flight word into an invalid slot, so nothing already in the pipe is ever consumed as a result. Flush is the exact dual of reset applied at runtime — reset clears valids so startup X is harmless; flush clears valids so aborted operations are harmless.

Data and valid march together — a real word, then a bubble, aligned stage by stage

data flow
Data and valid march together — a real word, then a bubble, aligned stage by stageclkclkclkqualifyinput (data,valid)stage 0 latches the pair each cyclestage 0 : data +validreal word: valid = 1stage 1 : data +validbubble behind it: valid = 0stage 2 : data +validpair advances every clock togetherout_data +out_validconsumer uses data only if valid = 1
Each stage carries a data register and a valid register, clocked by the same edge and delayed the same number of stages. A real word rides with valid = 1; a bubble right behind it rides with valid = 0; both advance one stage per clock, so the valid bit is always paired with the exact data it describes. At the output the consumer qualifies: it takes out_data only on the cycles out_valid is high, so bubbles and startup cycles pass through harmlessly. This alignment is the 8.1 lesson applied to validity, and it is identical in SystemVerilog, Verilog, and VHDL.

The reset and flush behaviour is worth stating precisely because it is where the pattern earns its safety. Take DEPTH = 3. After reset, valid_pipe = 000 and the data flops hold X. Push a real operand at cycle 1 (in_valid = 1): the valid bit walks valid_pipe[0] = 1 at cycle 1, valid_pipe[1] = 1 at cycle 2, valid_pipe[2] = 1 at cycle 3 — so out_valid first rises on cycle 3, exactly when the first operand's data reaches data_pipe[2], and stays low through the two fill cycles when the output was still driving X. If instead a flush fires at some later cycle, all three valid bits clear to 0 in that one cycle, so the up-to-three in-flight words are all marked invalid at the output over the following cycles and none is consumed — even though their data lines still carry their (now meaningless) values.

The second visual is the one that makes the decision tangible: the output data bus is the same wire in every case; the valid bit is what routes it to "use it" or "ignore it."

Qualify the output — same data bus, opposite verdict, decided by the valid bit

data flow
Qualify the output — same data bus, opposite verdict, decided by the valid bitvalidinvalidout_data (drivenevery cycle)a full word, always presentout_valid = 1a real result reached the endout_valid = 0fill / bubble / flushed slot=> USE the dataconsumer takes out_data=> IGNORE thedatastartup X, bubble, aborted op
The output data bus is driven on every single cycle — there is no cycle where it is genuinely empty. Whether the consumer should act on it is undecidable from the data alone; the valid bit decides. When out_valid is high a real result has propagated all the way to the last stage, so the consumer uses out_data. When out_valid is low the word on the bus is a fill-cycle X, a bubble, or a flushed operation, so the consumer ignores it. This single qualify decision is what the entire discipline exists to make possible, and it is the same data-plus-qualifier shape as the valid/ready handshake in 9.1.

4. Real RTL Implementation

This is the core of the page. We build three things — (a) an N-stage pipeline with the valid-bit discipline (per-stage data_pipe/valid_pipe registers, valid reset to 0, output qualified by the final valid, WIDTH and DEPTH generic, supports bubbles); (b) stall via clock-enable and flush (hold both data and valid on a stall; clear all valids on flush); and (c) the valid-misalignment bug beside its fix — and each is shown in SystemVerilog, Verilog, and VHDL with an RTL block and a self-checking clocked testbench. The reasoning is identical in all three; only the syntax differs.

One discipline runs through every RTL block, inherited from the register pattern and 8.1: the registers update on the clock with non-blocking assignments, reset is explicit (synchronous, active-high rst here) and clears the valid chain, and the valid registers are pipelined the same number of stages as the data registers so the pair never tears apart.

4a. The N-stage valid-bit pipeline — three ways

The pipeline carries WIDTH-bit data across DEPTH stages. Two parallel register arrays, data_pipe and valid_pipe, advance every clock. Reset clears valid_pipe only; the output is (data_pipe[DEPTH-1], valid_pipe[DEPTH-1]). A bubble is just an input cycle with in_valid = 0.

pipe_valid.sv — N-stage pipeline with the valid-bit discipline
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid #(
       parameter int WIDTH = 32,                      // datapath width
       parameter int DEPTH = 3                        // number of pipeline stages
   )(
       input  logic             clk,
       input  logic             rst,                  // synchronous, active-high
       input  logic             in_valid,            // 1 = real operand this cycle; 0 = bubble
       input  logic [WIDTH-1:0] in_data,
       output logic             out_valid,           // 1 = out_data is a real result
       output logic [WIDTH-1:0] out_data
   );
       // Two PARALLEL register chains, same length, same clock. The valid chain is
       // pipelined IDENTICALLY to the data chain, so a (data, valid) pair that enters
       // at stage 0 stays paired all the way to stage DEPTH-1.
       logic [WIDTH-1:0] data_pipe [DEPTH];
       logic             valid_pipe [DEPTH];
 
       always_ff @(posedge clk) begin
           if (rst) begin
               // Reset clears the VALID chain only. The data flops may hold X -- that
               // is harmless because every cycle is marked invalid until a real
               // operand has walked all DEPTH stages.
               for (int i = 0; i < DEPTH; i++)
                   valid_pipe[i] <= 1'b0;
           end else begin
               // Stage 0 latches the input pair; a bubble is simply in_valid = 0.
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;
               // Every later stage latches its predecessor's pair, in lockstep.
               for (int i = 1; i < DEPTH; i++) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1];   // valid moves exactly with data
               end
           end
       end
 
       // The output is QUALIFIED: out_data is meaningful only when out_valid is high.
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];
   endmodule

The clocked testbench does the thing the flagship stands or falls on: it drives a stream with bubbles interleaved, and checks that out_valid is high on exactly the cycles a real operand reaches the end — never during the DEPTH fill cycles, never on a bubble — and that out_data matches the operand whenever out_valid is high. It models the pipeline as a reference delay line of the same depth.

pipe_valid_tb.sv — inject bubbles; out_valid high exactly on real cycles, data checked
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_tb;
       localparam int WIDTH = 32;
       localparam int DEPTH = 3;
       localparam int N     = 12;                     // cycles of stimulus
       logic clk = 1'b0, rst, in_valid, out_valid;
       logic [WIDTH-1:0] in_data, out_data;
       int errors = 0;
 
       pipe_valid #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .in_valid(in_valid), .in_data(in_data),
           .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       // Reference: a DEPTH-deep delay line of the (valid, data) pair, reset-cleared.
       logic             ref_v [DEPTH];
       logic [WIDTH-1:0] ref_d [DEPTH];
 
       // Stimulus pattern: 1 = real operand, 0 = bubble. Deliberately gappy.
       localparam bit stim_v [N] = '{1,1,0,1,0,0,1,1,1,0,1,0};
 
       initial begin
           rst = 1'b1; in_valid = 1'b0; in_data = '0;
           for (int i = 0; i < DEPTH; i++) ref_v[i] = 1'b0;
           @(posedge clk); #1; rst = 1'b0;
 
           for (int t = 0; t < N + DEPTH; t++) begin
               // Drive input (bubbles after the pattern ends).
               @(negedge clk);
               in_valid = (t < N) ? stim_v[t] : 1'b0;
               in_data  = 32'hD000 + t[15:0];         // a unique tag per cycle
               // Advance the reference delay line by one stage, in lockstep with the DUT.
               for (int i = DEPTH-1; i > 0; i--) begin
                   ref_v[i] = ref_v[i-1]; ref_d[i] = ref_d[i-1];
               end
               ref_v[0] = in_valid; ref_d[0] = in_data;
 
               @(posedge clk); #1;
               // out_valid must match the reference EXACTLY -- high only on real cycles.
               assert (out_valid === ref_v[DEPTH-1])
                   else begin $error("t=%0d out_valid=%b exp=%b", t, out_valid, ref_v[DEPTH-1]); errors++; end
               // When valid, the data must be the operand that entered DEPTH cycles ago.
               if (out_valid)
                   assert (out_data === ref_d[DEPTH-1])
                       else begin $error("t=%0d out_data=%h exp=%h", t, out_data, ref_d[DEPTH-1]); errors++; end
           end
 
           if (errors == 0) $display("PASS: out_valid high exactly on real cycles; data aligned");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

The Verilog form is the same structure with reg/wire typing; because Verilog cannot pack a 2-D port array cleanly across all tools, the pipeline uses simple indexed register arrays and a genvar-free loop inside the clocked block.

pipe_valid.v — N-stage valid-bit pipeline in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid #(
       parameter WIDTH = 32,
       parameter DEPTH = 3
   )(
       input  wire             clk,
       input  wire             rst,                  // synchronous, active-high
       input  wire             in_valid,
       input  wire [WIDTH-1:0] in_data,
       output wire             out_valid,
       output wire [WIDTH-1:0] out_data
   );
       reg [WIDTH-1:0] data_pipe  [0:DEPTH-1];
       reg             valid_pipe [0:DEPTH-1];
       integer i;
 
       always @(posedge clk) begin
           if (rst) begin
               for (i = 0; i < DEPTH; i = i + 1)
                   valid_pipe[i] <= 1'b0;            // clear the VALID chain only
           end else begin
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;           // bubble = in_valid == 0
               for (i = 1; i < DEPTH; i = i + 1) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1]; // valid moves with data
               end
           end
       end
 
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];       // qualified output
   endmodule
pipe_valid_tb.v — self-checking; bubbles injected, PASS/FAIL on alignment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_tb;
       parameter WIDTH = 32, DEPTH = 3, N = 12;
       reg  clk, rst, in_valid;
       reg  [WIDTH-1:0] in_data;
       wire out_valid;
       wire [WIDTH-1:0] out_data;
       integer t, i, errors;
 
       pipe_valid #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .in_valid(in_valid), .in_data(in_data),
           .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       // Reference DEPTH-deep delay line of the (valid, data) pair.
       reg              ref_v [0:DEPTH-1];
       reg  [WIDTH-1:0] ref_d [0:DEPTH-1];
       // Gappy stimulus: 1 = real, 0 = bubble.
       reg  stim_v [0:N-1];
 
       initial begin
           stim_v[0]=1; stim_v[1]=1; stim_v[2]=0; stim_v[3]=1;
           stim_v[4]=0; stim_v[5]=0; stim_v[6]=1; stim_v[7]=1;
           stim_v[8]=1; stim_v[9]=0; stim_v[10]=1; stim_v[11]=0;
 
           errors = 0; clk = 1'b0; rst = 1'b1; in_valid = 1'b0; in_data = 0;
           for (i = 0; i < DEPTH; i = i + 1) ref_v[i] = 1'b0;
           @(posedge clk); #1; rst = 1'b0;
 
           for (t = 0; t < N + DEPTH; t = t + 1) begin
               @(negedge clk);
               in_valid = (t < N) ? stim_v[t] : 1'b0;
               in_data  = 16'hD000 + t;
               for (i = DEPTH-1; i > 0; i = i - 1) begin
                   ref_v[i] = ref_v[i-1]; ref_d[i] = ref_d[i-1];
               end
               ref_v[0] = in_valid; ref_d[0] = in_data;
 
               @(posedge clk); #1;
               if (out_valid !== ref_v[DEPTH-1]) begin
                   $display("FAIL: t=%0d out_valid=%b exp=%b", t, out_valid, ref_v[DEPTH-1]);
                   errors = errors + 1;
               end
               if (out_valid && (out_data !== ref_d[DEPTH-1])) begin
                   $display("FAIL: t=%0d out_data=%h exp=%h", t, out_data, ref_d[DEPTH-1]);
                   errors = errors + 1;
               end
           end
 
           if (errors == 0) $display("PASS: out_valid high exactly on real cycles; data aligned");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule

In VHDL the two chains are arrays of vector / of std_logic, updated in a clocked process; reset clears the valid array with an aggregate, and the output pair is a concurrent assignment of the last stage.

pipe_valid.vhd — N-stage valid-bit pipeline in VHDL (numeric_std)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity pipe_valid is
       generic (
           WIDTH : positive := 32;
           DEPTH : positive := 3
       );
       port (
           clk       : in  std_logic;
           rst       : in  std_logic;                 -- synchronous, active-high
           in_valid  : in  std_logic;                 -- 1 = real operand; 0 = bubble
           in_data   : in  std_logic_vector(WIDTH-1 downto 0);
           out_valid : out std_logic;
           out_data  : out std_logic_vector(WIDTH-1 downto 0)
       );
   end entity;
 
   architecture rtl of pipe_valid is
       type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal data_pipe  : data_arr;
       signal valid_pipe : std_logic_vector(DEPTH-1 downto 0) := (others => '0');
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   valid_pipe <= (others => '0');     -- clear the VALID chain only
               else
                   data_pipe(0)  <= in_data;
                   valid_pipe(0) <= in_valid;         -- bubble = in_valid = '0'
                   for i in 1 to DEPTH-1 loop
                       data_pipe(i)  <= data_pipe(i-1);
                       valid_pipe(i) <= valid_pipe(i-1);   -- valid moves with data
                   end loop;
               end if;
           end if;
       end process;
 
       out_data  <= data_pipe(DEPTH-1);
       out_valid <= valid_pipe(DEPTH-1);              -- qualified output
   end architecture;
pipe_valid_tb.vhd — self-checking; bubbles injected, assert severity on misalignment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity pipe_valid_tb is
   end entity;
 
   architecture sim of pipe_valid_tb is
       constant WIDTH : positive := 32;
       constant DEPTH : positive := 3;
       constant N     : positive := 12;
       signal clk       : std_logic := '0';
       signal rst, in_valid, out_valid : std_logic := '0';
       signal in_data, out_data : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
 
       -- Gappy stimulus: '1' = real operand, '0' = bubble.
       type bit_arr is array (0 to N-1) of std_logic;
       constant stim_v : bit_arr :=
           ('1','1','0','1','0','0','1','1','1','0','1','0');
   begin
       dut : entity work.pipe_valid
           generic map (WIDTH => WIDTH, DEPTH => DEPTH)
           port map (clk => clk, rst => rst, in_valid => in_valid, in_data => in_data,
                     out_valid => out_valid, out_data => out_data);
 
       clkgen : process
       begin
           clk <= '0'; wait for 5 ns;
           clk <= '1'; wait for 5 ns;
       end process;
 
       stim : process
           -- Reference DEPTH-deep delay line of the (valid, data) pair.
           type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
           variable ref_v : std_logic_vector(DEPTH-1 downto 0) := (others => '0');
           variable ref_d : data_arr;
           variable iv    : std_logic;
           variable id    : std_logic_vector(WIDTH-1 downto 0);
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
 
           for t in 0 to N + DEPTH - 1 loop
               wait until falling_edge(clk);
               if t < N then iv := stim_v(t); else iv := '0'; end if;
               id := std_logic_vector(to_unsigned(16#D000# + t, WIDTH));
               in_valid <= iv;
               in_data  <= id;
               -- Advance the reference delay line by one stage.
               for i in DEPTH-1 downto 1 loop
                   ref_v(i) := ref_v(i-1); ref_d(i) := ref_d(i-1);
               end loop;
               ref_v(0) := iv; ref_d(0) := id;
 
               wait until rising_edge(clk); wait for 1 ns;
               assert out_valid = ref_v(DEPTH-1)
                   report "out_valid mismatch: marked wrong cycle valid" severity error;
               if out_valid = '1' then
                   assert out_data = ref_d(DEPTH-1)
                       report "out_data mismatch: valid word carries wrong data" severity error;
               end if;
           end loop;
 
           report "pipe_valid self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. Stall (clock-enable) and flush

Two runtime controls sit on top of the discipline. A stall (en = 0) freezes the pipeline by holding both chains — data and valid — so a paused stage keeps its exact contents and its exact valid, dropping and duplicating nothing. A flush clears all the valid bits in one cycle, turning every in-flight word into an invalid slot without touching the data. Both are shown here in all three HDLs.

pipe_valid_ctrl.sv — stall holds (data, valid); flush clears all valids
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_ctrl #(
       parameter int WIDTH = 32,
       parameter int DEPTH = 3
   )(
       input  logic             clk,
       input  logic             rst,
       input  logic             en,                  // clock-enable: 0 = STALL (hold everything)
       input  logic             flush,               // 1 = clear ALL valids this cycle
       input  logic             in_valid,
       input  logic [WIDTH-1:0] in_data,
       output logic             out_valid,
       output logic [WIDTH-1:0] out_data
   );
       logic [WIDTH-1:0] data_pipe  [DEPTH];
       logic             valid_pipe [DEPTH];
 
       always_ff @(posedge clk) begin
           if (rst) begin
               for (int i = 0; i < DEPTH; i++) valid_pipe[i] <= 1'b0;
           end else if (flush) begin
               // FLUSH: invalidate everything in flight. Data is left as-is -- clearing
               // the valids alone makes every in-flight word an invalid slot.
               for (int i = 0; i < DEPTH; i++) valid_pipe[i] <= 1'b0;
           end else if (en) begin
               // Normal advance -- only when enabled.
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;
               for (int i = 1; i < DEPTH; i++) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1];
               end
           end
           // else (en == 0, no flush): STALL -- hold BOTH chains as a unit by not
           // assigning them, so no (data, valid) pair is torn apart.
       end
 
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];
   endmodule
pipe_valid_ctrl_tb.sv — stall holds the pair; flush kills in-flight valids
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_ctrl_tb;
       localparam int WIDTH = 32, DEPTH = 3;
       logic clk = 1'b0, rst, en, flush, in_valid, out_valid;
       logic [WIDTH-1:0] in_data, out_data;
       int errors = 0;
 
       pipe_valid_ctrl #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .en(en), .flush(flush),
           .in_valid(in_valid), .in_data(in_data), .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       task drive(input logic v, input logic [WIDTH-1:0] d, input logic e, input logic f);
           @(negedge clk); in_valid = v; in_data = d; en = e; flush = f;
           @(posedge clk); #1;
       endtask
 
       initial begin
           rst = 1'b1; en = 1'b1; flush = 1'b0; in_valid = 1'b0; in_data = '0;
           @(posedge clk); #1; rst = 1'b0;
 
           // Push one real word, then STALL for two cycles: it must NOT reach the
           // output early and must NOT be duplicated -- the pair is held.
           drive(1'b1, 32'hCAFE0001, 1'b1, 1'b0);     // enters stage 0
           drive(1'b0, 32'h0,        1'b0, 1'b0);      // STALL: hold everything
           drive(1'b0, 32'h0,        1'b0, 1'b0);      // STALL again
           // Two cycles frozen: nothing advanced, so still no valid at the output.
           assert (!out_valid) else begin $error("stall let a word advance"); errors++; end
           // Release: the word now walks the remaining stages and appears once.
           drive(1'b0, 32'h0, 1'b1, 1'b0);
           drive(1'b0, 32'h0, 1'b1, 1'b0);
           assert (out_valid && out_data === 32'hCAFE0001)
               else begin $error("word lost/duplicated across stall"); errors++; end
           drive(1'b0, 32'h0, 1'b1, 1'b0);
           assert (!out_valid) else begin $error("word duplicated after stall"); errors++; end
 
           // Now fill the pipe and FLUSH: every in-flight word must vanish (invalid).
           drive(1'b1, 32'hBBBB0001, 1'b1, 1'b0);
           drive(1'b1, 32'hBBBB0002, 1'b1, 1'b0);
           drive(1'b0, 32'h0,        1'b1, 1'b1);      // FLUSH -- clears all valids
           drive(1'b0, 32'h0, 1'b1, 1'b0);
           assert (!out_valid) else begin $error("flushed word leaked out as valid"); errors++; end
           drive(1'b0, 32'h0, 1'b1, 1'b0);
           assert (!out_valid) else begin $error("second flushed word leaked out"); errors++; end
 
           if (errors == 0) $display("PASS: stall holds the pair; flush invalidates in-flight words");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule
pipe_valid_ctrl.v — stall + flush in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_ctrl #(
       parameter WIDTH = 32,
       parameter DEPTH = 3
   )(
       input  wire             clk,
       input  wire             rst,
       input  wire             en,                   // 0 = STALL (hold both chains)
       input  wire             flush,                // 1 = clear ALL valids
       input  wire             in_valid,
       input  wire [WIDTH-1:0] in_data,
       output wire             out_valid,
       output wire [WIDTH-1:0] out_data
   );
       reg [WIDTH-1:0] data_pipe  [0:DEPTH-1];
       reg             valid_pipe [0:DEPTH-1];
       integer i;
 
       always @(posedge clk) begin
           if (rst) begin
               for (i = 0; i < DEPTH; i = i + 1) valid_pipe[i] <= 1'b0;
           end else if (flush) begin
               for (i = 0; i < DEPTH; i = i + 1) valid_pipe[i] <= 1'b0;   // data untouched
           end else if (en) begin
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;
               for (i = 1; i < DEPTH; i = i + 1) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1];
               end
           end
           // else: STALL -- no assignment holds BOTH chains as a unit.
       end
 
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];
   endmodule
pipe_valid_ctrl_tb.v — self-checking stall + flush test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_valid_ctrl_tb;
       parameter WIDTH = 32, DEPTH = 3;
       reg  clk, rst, en, flush, in_valid;
       reg  [WIDTH-1:0] in_data;
       wire out_valid;
       wire [WIDTH-1:0] out_data;
       integer errors;
 
       pipe_valid_ctrl #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .en(en), .flush(flush),
           .in_valid(in_valid), .in_data(in_data), .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       task drive(input v, input [WIDTH-1:0] d, input e, input f);
           begin
               @(negedge clk); in_valid = v; in_data = d; en = e; flush = f;
               @(posedge clk); #1;
           end
       endtask
 
       initial begin
           errors = 0; clk = 1'b0; rst = 1'b1; en = 1'b1; flush = 1'b0;
           in_valid = 1'b0; in_data = 0;
           @(posedge clk); #1; rst = 1'b0;
 
           drive(1'b1, 32'hCAFE0001, 1'b1, 1'b0);     // real word enters
           drive(1'b0, 0, 1'b0, 1'b0);                 // STALL
           drive(1'b0, 0, 1'b0, 1'b0);                 // STALL
           if (out_valid) begin $display("FAIL: stall let a word advance"); errors = errors + 1; end
           drive(1'b0, 0, 1'b1, 1'b0);
           drive(1'b0, 0, 1'b1, 1'b0);
           if (!(out_valid && out_data === 32'hCAFE0001)) begin
               $display("FAIL: word lost/duplicated across stall"); errors = errors + 1;
           end
           drive(1'b0, 0, 1'b1, 1'b0);
           if (out_valid) begin $display("FAIL: word duplicated after stall"); errors = errors + 1; end
 
           drive(1'b1, 32'hBBBB0001, 1'b1, 1'b0);
           drive(1'b1, 32'hBBBB0002, 1'b1, 1'b0);
           drive(1'b0, 0, 1'b1, 1'b1);                 // FLUSH
           drive(1'b0, 0, 1'b1, 1'b0);
           if (out_valid) begin $display("FAIL: flushed word leaked out"); errors = errors + 1; end
           drive(1'b0, 0, 1'b1, 1'b0);
           if (out_valid) begin $display("FAIL: second flushed word leaked out"); errors = errors + 1; end
 
           if (errors == 0) $display("PASS: stall holds the pair; flush invalidates in-flight words");
           else             $display("FAIL: %0d errors", errors);
           $finish;
       end
   endmodule
pipe_valid_ctrl.vhd — stall + flush in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity pipe_valid_ctrl is
       generic ( WIDTH : positive := 32; DEPTH : positive := 3 );
       port (
           clk, rst, en, flush : in  std_logic;       -- en=0 STALL; flush=1 clear valids
           in_valid            : in  std_logic;
           in_data             : in  std_logic_vector(WIDTH-1 downto 0);
           out_valid           : out std_logic;
           out_data            : out std_logic_vector(WIDTH-1 downto 0)
       );
   end entity;
 
   architecture rtl of pipe_valid_ctrl is
       type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal data_pipe  : data_arr;
       signal valid_pipe : std_logic_vector(DEPTH-1 downto 0) := (others => '0');
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   valid_pipe <= (others => '0');
               elsif flush = '1' then
                   valid_pipe <= (others => '0');      -- data untouched
               elsif en = '1' then
                   data_pipe(0)  <= in_data;
                   valid_pipe(0) <= in_valid;
                   for i in 1 to DEPTH-1 loop
                       data_pipe(i)  <= data_pipe(i-1);
                       valid_pipe(i) <= valid_pipe(i-1);
                   end loop;
               end if;
               -- else STALL: no assignment holds BOTH chains together.
           end if;
       end process;
 
       out_data  <= data_pipe(DEPTH-1);
       out_valid <= valid_pipe(DEPTH-1);
   end architecture;
pipe_valid_ctrl_tb.vhd — self-checking stall + flush test
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity pipe_valid_ctrl_tb is
   end entity;
 
   architecture sim of pipe_valid_ctrl_tb is
       constant WIDTH : positive := 32; constant DEPTH : positive := 3;
       signal clk : std_logic := '0';
       signal rst, en, flush, in_valid, out_valid : std_logic := '0';
       signal in_data, out_data : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
       signal errors : integer := 0;
 
       procedure drive(signal iv : out std_logic; signal idt : out std_logic_vector(WIDTH-1 downto 0);
                       signal ie : out std_logic; signal ifl : out std_logic;
                       constant v : std_logic; constant d : std_logic_vector(WIDTH-1 downto 0);
                       constant e : std_logic; constant f : std_logic;
                       signal clkin : in std_logic) is
       begin
           wait until falling_edge(clkin);
           iv <= v; idt <= d; ie <= e; ifl <= f;
           wait until rising_edge(clkin); wait for 1 ns;
       end procedure;
   begin
       dut : entity work.pipe_valid_ctrl
           generic map (WIDTH => WIDTH, DEPTH => DEPTH)
           port map (clk => clk, rst => rst, en => en, flush => flush,
                     in_valid => in_valid, in_data => in_data,
                     out_valid => out_valid, out_data => out_data);
 
       clkgen : process begin clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns; end process;
 
       stim : process
       begin
           rst <= '1'; en <= '1'; flush <= '0'; in_valid <= '0'; in_data <= (others => '0');
           wait until rising_edge(clk); wait for 1 ns; rst <= '0';
 
           drive(in_valid, in_data, en, flush, '1', x"CAFE0001", '1', '0', clk);  -- real word
           drive(in_valid, in_data, en, flush, '0', x"00000000", '0', '0', clk);  -- STALL
           drive(in_valid, in_data, en, flush, '0', x"00000000", '0', '0', clk);  -- STALL
           assert out_valid = '0' report "stall let a word advance" severity error;
           drive(in_valid, in_data, en, flush, '0', x"00000000", '1', '0', clk);
           drive(in_valid, in_data, en, flush, '0', x"00000000", '1', '0', clk);
           assert out_valid = '1' and out_data = x"CAFE0001"
               report "word lost/duplicated across stall" severity error;
 
           drive(in_valid, in_data, en, flush, '1', x"BBBB0001", '1', '0', clk);
           drive(in_valid, in_data, en, flush, '1', x"BBBB0002", '1', '0', clk);
           drive(in_valid, in_data, en, flush, '0', x"00000000", '1', '1', clk);  -- FLUSH
           drive(in_valid, in_data, en, flush, '0', x"00000000", '1', '0', clk);
           assert out_valid = '0' report "flushed word leaked out as valid" severity error;
 
           report "pipe_valid_ctrl self-check complete" severity note;
           wait;
       end process;
   end architecture;

4c. The valid-misalignment bug — buggy vs fixed, in all three HDLs

Pattern (c) is the signature failure, shown as buggy vs fixed RTL in each language, then dramatized narratively in §7. The bug is the same everywhere: the designer pipelines the data through DEPTH stages but drives out_valid from something that is not delayed the same DEPTH stages — here, out_valid is combinational from in_valid, so it leads the data by DEPTH cycles and marks the fill/X cycles valid. The fix is a valid register in every stage, reset to 0, advancing with the data.

pipe_bug.sv — BUGGY (valid not pipelined) vs FIXED (valid aligned + reset-cleared)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: data is delayed DEPTH stages, but out_valid is driven COMBINATIONALLY from
   //        in_valid -> valid leads the data by DEPTH cycles. During the fill cycles it
   //        asserts valid while data_pipe still holds reset-X -> garbage marked valid.
   module pipe_bad #(parameter int WIDTH = 32, parameter int DEPTH = 3) (
       input  logic clk, rst, in_valid,
       input  logic [WIDTH-1:0] in_data,
       output logic out_valid,
       output logic [WIDTH-1:0] out_data
   );
       logic [WIDTH-1:0] data_pipe [DEPTH];
       always_ff @(posedge clk) begin
           data_pipe[0] <= in_data;
           for (int i = 1; i < DEPTH; i++) data_pipe[i] <= data_pipe[i-1];
       end
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = in_valid;                   // WRONG: not delayed, not reset-cleared
   endmodule
 
   // FIXED: a valid register in EVERY stage, reset to 0, advancing WITH the data.
   module pipe_good #(parameter int WIDTH = 32, parameter int DEPTH = 3) (
       input  logic clk, rst, in_valid,
       input  logic [WIDTH-1:0] in_data,
       output logic out_valid,
       output logic [WIDTH-1:0] out_data
   );
       logic [WIDTH-1:0] data_pipe  [DEPTH];
       logic             valid_pipe [DEPTH];
       always_ff @(posedge clk) begin
           if (rst) begin
               for (int i = 0; i < DEPTH; i++) valid_pipe[i] <= 1'b0;
           end else begin
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;
               for (int i = 1; i < DEPTH; i++) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1];  // valid delayed EXACTLY like data
               end
           end
       end
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];         // qualified by the final-stage valid
   endmodule
pipe_bug_tb.sv — the fill cycles expose the misalignment; a bubble sharpens it
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_bug_tb;
       localparam int WIDTH = 32, DEPTH = 3, N = 8;
       logic clk = 1'b0, rst, in_valid, out_valid;
       logic [WIDTH-1:0] in_data, out_data;
       int errors = 0;
 
       // Bind to pipe_good to PASS; pipe_bad marks the fill-X cycles valid.
       pipe_good #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .in_valid(in_valid), .in_data(in_data),
           .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       logic             ref_v [DEPTH];
       logic [WIDTH-1:0] ref_d [DEPTH];
       localparam bit stim_v [N] = '{1,0,1,1,0,1,0,1};   // includes a bubble at t=1
 
       initial begin
           rst = 1'b1; in_valid = 1'b0; in_data = '0;
           for (int i = 0; i < DEPTH; i++) ref_v[i] = 1'b0;
           @(posedge clk); #1; rst = 1'b0;
           for (int t = 0; t < N + DEPTH; t++) begin
               @(negedge clk);
               in_valid = (t < N) ? stim_v[t] : 1'b0;
               in_data  = 32'hE000 + t[15:0];
               for (int i = DEPTH-1; i > 0; i--) begin ref_v[i]=ref_v[i-1]; ref_d[i]=ref_d[i-1]; end
               ref_v[0] = in_valid; ref_d[0] = in_data;
               @(posedge clk); #1;
               // pipe_bad fails HERE on the first DEPTH cycles: out_valid high while ref is 0.
               assert (out_valid === ref_v[DEPTH-1])
                   else begin $error("t=%0d valid=%b exp=%b (misaligned)", t, out_valid, ref_v[DEPTH-1]); errors++; end
               if (out_valid)
                   assert (out_data === ref_d[DEPTH-1])
                       else begin $error("t=%0d data=%h exp=%h", t, out_data, ref_d[DEPTH-1]); errors++; end
           end
           if (errors == 0) $display("PASS: valid aligned to data through fill and bubble");
           else             $display("FAIL: %0d errors (valid marks the wrong cycle)", errors);
           $finish;
       end
   endmodule

The Verilog buggy/fixed pair is the same story with reg/wire typing. Sweeping the fill cycles and a bubble in the testbench is what exposes the lead: pipe_bad asserts out_valid on the first DEPTH cycles, when out_data is still reset-X.

pipe_bug.v — BUGGY vs FIXED in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: out_valid combinational from in_valid -> leads the data by DEPTH cycles.
   module pipe_bad #(parameter WIDTH = 32, DEPTH = 3) (
       input  wire clk, rst, in_valid,
       input  wire [WIDTH-1:0] in_data,
       output wire out_valid,
       output wire [WIDTH-1:0] out_data
   );
       reg [WIDTH-1:0] data_pipe [0:DEPTH-1];
       integer i;
       always @(posedge clk) begin
           data_pipe[0] <= in_data;
           for (i = 1; i < DEPTH; i = i + 1) data_pipe[i] <= data_pipe[i-1];
       end
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = in_valid;                   // WRONG: not delayed, not reset-cleared
   endmodule
 
   // FIXED: valid register in every stage, reset to 0, advancing with the data.
   module pipe_good #(parameter WIDTH = 32, DEPTH = 3) (
       input  wire clk, rst, in_valid,
       input  wire [WIDTH-1:0] in_data,
       output wire out_valid,
       output wire [WIDTH-1:0] out_data
   );
       reg [WIDTH-1:0] data_pipe  [0:DEPTH-1];
       reg             valid_pipe [0:DEPTH-1];
       integer i;
       always @(posedge clk) begin
           if (rst) begin
               for (i = 0; i < DEPTH; i = i + 1) valid_pipe[i] <= 1'b0;
           end else begin
               data_pipe[0]  <= in_data;
               valid_pipe[0] <= in_valid;
               for (i = 1; i < DEPTH; i = i + 1) begin
                   data_pipe[i]  <= data_pipe[i-1];
                   valid_pipe[i] <= valid_pipe[i-1];  // valid delayed exactly like data
               end
           end
       end
       assign out_data  = data_pipe[DEPTH-1];
       assign out_valid = valid_pipe[DEPTH-1];
   endmodule
pipe_bug_tb.v — self-checking; fill + bubble expose the misalignment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module pipe_bug_tb;
       parameter WIDTH = 32, DEPTH = 3, N = 8;
       reg  clk, rst, in_valid;
       reg  [WIDTH-1:0] in_data;
       wire out_valid;
       wire [WIDTH-1:0] out_data;
       integer t, i, errors;
 
       // Bind to pipe_good to PASS; pipe_bad marks the fill-X cycles valid.
       pipe_good #(.WIDTH(WIDTH), .DEPTH(DEPTH)) dut (
           .clk(clk), .rst(rst), .in_valid(in_valid), .in_data(in_data),
           .out_valid(out_valid), .out_data(out_data));
 
       always #5 clk = ~clk;
 
       reg              ref_v [0:DEPTH-1];
       reg  [WIDTH-1:0] ref_d [0:DEPTH-1];
       reg  stim_v [0:N-1];
 
       initial begin
           stim_v[0]=1; stim_v[1]=0; stim_v[2]=1; stim_v[3]=1;
           stim_v[4]=0; stim_v[5]=1; stim_v[6]=0; stim_v[7]=1;
 
           errors = 0; clk = 1'b0; rst = 1'b1; in_valid = 1'b0; in_data = 0;
           for (i = 0; i < DEPTH; i = i + 1) ref_v[i] = 1'b0;
           @(posedge clk); #1; rst = 1'b0;
           for (t = 0; t < N + DEPTH; t = t + 1) begin
               @(negedge clk);
               in_valid = (t < N) ? stim_v[t] : 1'b0;
               in_data  = 16'hE000 + t;
               for (i = DEPTH-1; i > 0; i = i - 1) begin ref_v[i]=ref_v[i-1]; ref_d[i]=ref_d[i-1]; end
               ref_v[0] = in_valid; ref_d[0] = in_data;
               @(posedge clk); #1;
               if (out_valid !== ref_v[DEPTH-1]) begin
                   $display("FAIL: t=%0d valid=%b exp=%b (misaligned)", t, out_valid, ref_v[DEPTH-1]);
                   errors = errors + 1;
               end
               if (out_valid && (out_data !== ref_d[DEPTH-1])) begin
                   $display("FAIL: t=%0d data=%h exp=%h", t, out_data, ref_d[DEPTH-1]);
                   errors = errors + 1;
               end
           end
           if (errors == 0) $display("PASS: valid aligned to data through fill and bubble");
           else             $display("FAIL: %0d errors (valid marks the wrong cycle)", errors);
           $finish;
       end
   endmodule

In VHDL the same bug appears when out_valid is a concurrent copy of in_valid while the data is pipelined through the array; the fix is the valid_pipe vector reset-cleared and advanced alongside data_pipe.

pipe_bug.vhd — BUGGY (valid not pipelined) vs FIXED (valid aligned + reset-cleared)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   -- BUGGY: out_valid is a concurrent copy of in_valid -> leads the data by DEPTH cycles.
   entity pipe_bad is
       generic ( WIDTH : positive := 32; DEPTH : positive := 3 );
       port ( clk, rst, in_valid : in std_logic;
              in_data  : in  std_logic_vector(WIDTH-1 downto 0);
              out_valid : out std_logic;
              out_data  : out std_logic_vector(WIDTH-1 downto 0) );
   end entity;
   architecture rtl of pipe_bad is
       type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal data_pipe : data_arr;
   begin
       process (clk) begin
           if rising_edge(clk) then
               data_pipe(0) <= in_data;
               for i in 1 to DEPTH-1 loop data_pipe(i) <= data_pipe(i-1); end loop;
           end if;
       end process;
       out_data  <= data_pipe(DEPTH-1);
       out_valid <= in_valid;                          -- WRONG: not delayed, not reset-cleared
   end architecture;
 
   -- FIXED: a valid register in every stage, reset to 0, advancing with the data.
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
   entity pipe_good is
       generic ( WIDTH : positive := 32; DEPTH : positive := 3 );
       port ( clk, rst, in_valid : in std_logic;
              in_data  : in  std_logic_vector(WIDTH-1 downto 0);
              out_valid : out std_logic;
              out_data  : out std_logic_vector(WIDTH-1 downto 0) );
   end entity;
   architecture rtl of pipe_good is
       type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
       signal data_pipe  : data_arr;
       signal valid_pipe : std_logic_vector(DEPTH-1 downto 0) := (others => '0');
   begin
       process (clk) begin
           if rising_edge(clk) then
               if rst = '1' then
                   valid_pipe <= (others => '0');
               else
                   data_pipe(0)  <= in_data;
                   valid_pipe(0) <= in_valid;
                   for i in 1 to DEPTH-1 loop
                       data_pipe(i)  <= data_pipe(i-1);
                       valid_pipe(i) <= valid_pipe(i-1);   -- valid delayed exactly like data
                   end loop;
               end if;
           end if;
       end process;
       out_data  <= data_pipe(DEPTH-1);
       out_valid <= valid_pipe(DEPTH-1);
   end architecture;
pipe_bug_tb.vhd — self-checking; fill + bubble expose the misalignment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;
 
   entity pipe_bug_tb is
   end entity;
 
   architecture sim of pipe_bug_tb is
       constant WIDTH : positive := 32; constant DEPTH : positive := 3; constant N : positive := 8;
       signal clk : std_logic := '0';
       signal rst, in_valid, out_valid : std_logic := '0';
       signal in_data, out_data : std_logic_vector(WIDTH-1 downto 0) := (others => '0');
       type bit_arr is array (0 to N-1) of std_logic;
       constant stim_v : bit_arr := ('1','0','1','1','0','1','0','1');   -- bubble at t=1
   begin
       -- Bind to pipe_good to PASS; pipe_bad marks the fill-X cycles valid.
       dut : entity work.pipe_good
           generic map (WIDTH => WIDTH, DEPTH => DEPTH)
           port map (clk => clk, rst => rst, in_valid => in_valid, in_data => in_data,
                     out_valid => out_valid, out_data => out_data);
 
       clkgen : process begin clk <= '0'; wait for 5 ns; clk <= '1'; wait for 5 ns; end process;
 
       stim : process
           type data_arr is array (0 to DEPTH-1) of std_logic_vector(WIDTH-1 downto 0);
           variable ref_v : std_logic_vector(DEPTH-1 downto 0) := (others => '0');
           variable ref_d : data_arr;
           variable iv : std_logic;
           variable id : std_logic_vector(WIDTH-1 downto 0);
       begin
           rst <= '1'; wait until rising_edge(clk); wait for 1 ns; rst <= '0';
           for t in 0 to N + DEPTH - 1 loop
               wait until falling_edge(clk);
               if t < N then iv := stim_v(t); else iv := '0'; end if;
               id := std_logic_vector(to_unsigned(16#E000# + t, WIDTH));
               in_valid <= iv; in_data <= id;
               for i in DEPTH-1 downto 1 loop ref_v(i) := ref_v(i-1); ref_d(i) := ref_d(i-1); end loop;
               ref_v(0) := iv; ref_d(0) := id;
               wait until rising_edge(clk); wait for 1 ns;
               assert out_valid = ref_v(DEPTH-1)
                   report "valid marks the wrong cycle (misaligned)" severity error;
               if out_valid = '1' then
                   assert out_data = ref_d(DEPTH-1)
                       report "valid word carries wrong data" severity error;
               end if;
           end loop;
           report "pipe_bug self-check complete" severity note;
           wait;
       end process;
   end architecture;

Across all three languages the lesson is one sentence: pipeline the valid bit through the same number of stages as the data, reset it to zero, and qualify the output with the final-stage valid — then fill cycles, bubbles, and flushes can never leak out as a real result.

5. Verification Strategy

The valid-bit discipline is an alignment property, so verification lives at the alignment: drive a stream with bubbles and a flush, and check that out_valid is high on exactly the cycles carrying a real result — not one cycle early (the fill/misalignment bug) and not on a bubble — and that out_data is correct whenever out_valid is high.

out_valid is high on exactly the cycles a real operand reaches the last stage — low during the DEPTH fill cycles, low on every bubble, low after a flush — and whenever out_valid is high, out_data equals the operand that entered DEPTH cycles earlier.

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

  • Reference-delay-line self-check. Model the DUT as a DEPTH-deep delay line of the (valid, data) pair, reset-cleared, and advance it in lockstep with the DUT each cycle. Every cycle, assert out_valid === ref_valid[DEPTH-1] and, when valid, out_data === ref_data[DEPTH-1]. This is exactly what the §4 testbenches do (SV assert (out_valid === ref_v[DEPTH-1]), Verilog if (out_valid !== ref_v[DEPTH-1]) $display("FAIL…"), VHDL assert out_valid = ref_v(DEPTH-1) … severity error). The reference is trivially correct because the discipline is a delay line, so any deviation is a real bug.
  • Fill / startup check. Reset, then drive a real operand and assert out_valid is low for the first DEPTH cycles — while the pipe still holds power-up X — and rises only on the cycle the first operand reaches the last stage. This is the direct detector for the misalignment bug: a combinational or under-delayed valid asserts during the fill and fails here.
  • Bubble check. Interleave in_valid = 0 cycles into the stream and confirm each bubble stays invalid end-to-end: it must appear at the output as an out_valid = 0 cycle exactly DEPTH cycles after it was injected, and never mark a stale word valid. A gap in the input stream must become an aligned gap in the output valid stream.
  • Flush check. Fill the pipe with up to DEPTH valid words, assert flush for one cycle, and confirm every in-flight word is invalidated — out_valid stays low for the following cycles even though out_data still drives the (now meaningless) flushed values. A flush that clears only some stages, or that clears data but not valid, leaks a stale word.
  • Stall check. Push a word, then hold en = 0 for several cycles, and confirm the word neither advances early nor is duplicated: out_valid does not rise while stalled, and when en returns the word appears exactly once at the output. This catches a stall that holds data but not valid (duplicates a valid) or valid but not data (tears the pair apart).
  • Parameter-generic verification. Re-run the fill / bubble / stall / flush suite for DEPTH = 1, 2, 3, 5 and WIDTH = 1, 8, 32. A discipline that passes at DEPTH = 3 can still be wrong at DEPTH = 5 if the valid chain was hardcoded to a different length than the data chain — the two must always be equal, and a generic sweep is what proves it.
  • Expected waveform. On a correct run, after reset out_valid stays low for exactly DEPTH cycles, then tracks the input valid stream delayed by DEPTH — high on real cycles, low on bubbles — with out_data carrying the matching operand on every high cycle. The visual signature of the misalignment bug is out_valid rising DEPTH cycles too early (during fill, over reset-X data) or leading each bubble by a cycle; the signature of a flush bug is out_valid staying high for a word that should have been killed.

6. Common Mistakes

Valid not aligned with the data. The signature failure. If out_valid is delayed a different number of stages than the data — combinational from the input, delayed DEPTH-1 instead of DEPTH, or not pipelined at all — then the valid bit marks the wrong cycle, and the consumer latches the wrong word: a fill-cycle X, a bubble, or the neighbouring operand. The rule is exact: the valid bit must traverse the same number of registers, on the same clock and enable, as the data it qualifies. Pipeline them together, in the same block, and they cannot drift apart. (Post-mortem in §7.)

Not clearing valid on reset. At power-up the data flops hold X and the valid flops hold X too if you did not reset them — so on the first cycles the output can be marked valid over undefined data, and that X is consumed as a real result. Reset must clear the valid chain to 0 (the data chain need not be reset). Valid-reset-to-zero is precisely what makes the fill cycles harmless; skip it and startup noise leaks out as the first few results.

Using data without checking valid. The output data bus is driven on every cycle, so a consumer that acts on out_data unconditionally — writes it, accumulates it, forwards it — consumes bubbles and fill cycles as if they were results. out_valid is not decoration; it is the gate. Every use of the result must be predicated on its valid being high.

Not clearing valids on a flush. When an operation is aborted (a branch mispredict, a cancelled request), the in-flight words must be discarded — but if the flush clears only the first stage's valid, or clears the data but not the valid, the words already deeper in the pipe still carry valid = 1 and leak out as if the aborted operation had completed. A flush must clear all the valid registers in one cycle; clearing the data is neither necessary nor sufficient.

Losing valid during a stall. When a stage is frozen with a clock-enable, holding the data but letting the valid advance drops the held word's validity (it arrives at the output as an invalid slot — a lost result) or, symmetrically, holding valid while data advances duplicates a valid over the wrong word. A stall must hold the (data, valid) pair as a unit — gate both registers with the same enable, or neither. Any asymmetry tears the pair apart.

Resetting the data path instead of the valid path. A tempting inversion: reset the data flops to zero and leave valid un-reset, reasoning that zero data is safe. It is not — the consumer still sees valid (whatever it powers up to) high over that zero data and treats the zero as a genuine result. The safety comes from the valid being low, not from the data being any particular value. Reset the valid chain; the data chain's contents are irrelevant while their valid is low.

7. DebugLab

The pipeline that fired early — a valid/data misalignment that leaks garbage at every fill and after every bubble

The engineering lesson: a pipeline register is always full, so the only thing that makes an output real is the valid bit — and the valid bit is trustworthy only if it is delayed by exactly the same number of stages as the data, reset to zero, and cleared on every flush. The tell is a bug that appears at the edges of a stream — the first few cycles of a burst, the cycles right after a gap, the cycles right after an abort — and vanishes the moment the pipe is driven permanently full. A pipeline that is correct under saturated back-to-back traffic and corrupts at fill, after a bubble, or after a flush is a valid-alignment bug, not a datapath bug. Three habits make it impossible, and they are identical across SystemVerilog, Verilog, and VHDL: pipeline the valid alongside the data through the same stages, reset the valid chain to zero (never mind the data), and clear every stage's valid on a flush — then qualify every use of the output with the final-stage valid. This is the exact substrate the valid/ready handshake (9.1) and the skid buffer (9.3) are built on: both are just a valid bit paired with data, plus a back-pressure signal that says when the pair may advance.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the valid-alignment / reset-clear / stall-flush reasoning behind the discipline.

Exercise 1 — Trace the fill and a bubble

For a DEPTH = 4 pipeline, hand-trace valid_pipe[0..3] from reset through this input in_valid stream: 1, 1, 0, 1, 1 (the 0 is a bubble), then all zeros. State the exact cycle out_valid first rises, confirm it stays low through the four fill cycles, and mark the one output cycle that is low because of the bubble. In one line each, say what out_data is driving on the fill cycles and why writing it would be a bug.

Exercise 2 — Place the stall and flush controls

Take the same DEPTH = 4 pipeline and add (a) a clock-enable stall and (b) a flush. For each, state precisely which registers it must affect and which it must leave alone, and write the one-line rule that keeps the (data, valid) pair intact under a stall. Then answer: if a stall and a flush are asserted in the same cycle, which should win, and why?

Exercise 3 — Find the misalignment on paper

An engineer pipelines the data through DEPTH = 3 stages but writes valid_pipe[2] <= valid_pipe[0] for the last stage (skipping stage 1). Describe exactly what goes wrong: is out_valid early, late, or the wrong shape? Walk the fill cycles to show it, and state whether a saturated back-to-back test would catch it. Then give the correct assignment and explain why the valid chain must have exactly as many flops as the data chain.

Exercise 4 — Verify without shipping the bug

Write the test plan (not the RTL) that would have caught the §7 misalignment before tape-out. Specify: the reference model you compare against and why it is trivially correct; the fill-cycle assertion that is the direct detector for an early valid; the bubble injection and what you assert DEPTH cycles later; the flush step and the post-flush check; and the one every-cycle invariant that ties out_data to out_valid. State which single check in your plan directly catches a valid that leads the data.

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

  • Why Pipeline? — Chapter 8.1; the prerequisite that split a combinational path into registered stages and taught the alignment lesson — delay everything that must stay together with the data — which this page applies to validity.
  • Latency vs Throughput — Chapter 8.2; the sibling that quantifies what pipelining buys and costs — the fill latency the valid bit gates and the throughput the bubbles interrupt.
  • Pipeline Stalls & Flush — Chapter 8.4; the deep treatment of the clock-enable stall and the flush this page introduces — holding the pair and killing in-flight valids under real hazard control.
  • Pipeline Hazards — Chapter 8.5; the data and control hazards that cause stalls and flushes — why you freeze a stage or abort the pipe in the first place.
  • Pipelined Datapath — Chapter 8.6; a full multi-stage datapath where the valid-bit discipline qualifies every result across the whole compute path.
  • valid/ready Handshake — Chapter 9.1; the flow-control protocol built directly on this page's valid bit — adding a backward ready so the consumer can stall the producer, where valid must hold until ready is seen.
  • Skid Buffer — Chapter 9.3; the register that catches the in-flight word when back-pressure deasserts — a valid-bit pipeline stage sized to absorb one cycle of latency in the handshake.

Backward / in-track dependencies:

  • The Register Pattern (D-FF) — Chapter 2.1; the flop the data and valid registers are built from, and the reset-first clocked-update discipline every stage here follows.
  • Register with Enable / Load — Chapter 2.2; the clock-enable that becomes the pipeline stall — holding a register's contents when not advancing is exactly the hold-the-pair mechanism.
  • Shift Registers — Chapter 3.1; the data pipeline is a wide shift register, and the valid pipeline is a one-bit shift register running alongside it — the alignment is shift-register lockstep.
  • Synchronous FIFO Architecture — Chapter 7.1; the FIFO that decouples a producer and consumer where this page's aligned valid feeds the write side, and the FIFO's occupancy is the elastic version of a bubble.
  • Pulse & Strobe Generators — Chapter 5.4; the single-cycle valid strobe that must stay aligned to its data as it propagates — the same one-cycle-qualifier discipline in a narrower setting.
  • Reset Strategy — Chapter 2.5; the reset discipline that clears the valid chain to zero — why resetting valid (and not the data) is what makes startup safe.
  • The RTL Design Mindset — Chapter 0.2; the State and Verification lenses this page applies — the valid bits are the pipeline's control state, and qualifying the output is how you prove no garbage leaks out.
  • What Is an RTL Design Pattern? — Chapter 0.1; why the valid-bit discipline earns the name — a recurring problem, a reusable form, a synthesis reality, and a signature failure.

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

  • Blocking and Non-Blocking Assignments — why every stage's data and valid registers update with non-blocking (<=) so the whole pipeline shifts atomically each edge.
  • Initial & Always Blocks — the clocked always block that advances both chains, and the initial/reset that clears the valid chain.
  • If-Else Statements — the reset / flush / enable priority (if (rst) … else if (flush) … else if (en) …) that selects clear, invalidate, advance, or hold.
  • Physical Data Types — the vector widths (WIDTH data, one-bit valid) and register-array declarations whose deliberate sizing keeps the two chains the same length.

11. Summary

  • A pipeline register is always full, but not every cycle is real. After reset the stages hold power-up X; a gap in the producer sends a bubble down the pipe; an abort leaves garbage in flight. The output data bus is driven on every cycle, so from the data alone the consumer cannot tell a real result from startup noise, a bubble, or a flushed operation — and treating any of those as a result is the signature pipeline bug.
  • Carry a valid bit alongside the data through every stage. Add a one-bit valid register per data register, pipelined the same number of stages, on the same clock and enable, so a (data, valid) pair entering at stage 0 stays paired to data_pipe[DEPTH-1] / valid_pipe[DEPTH-1]. out_data = data_pipe[DEPTH-1], out_valid = valid_pipe[DEPTH-1], and the consumer uses out_data only when out_valid is high. Data tells you what; valid tells you when it is real.
  • Reset clears the valid chain — the data chain needs no reset. Because the valid flops reset to 0, every output cycle is invalid until a real operand has walked all DEPTH stages, so the undefined datapath X is never consumed. A bubble is simply an input cycle with in_valid = 0; it flows through as an aligned invalid slot for free.
  • Stall holds the pair; flush clears all the valids. A clock-enable stall freezes a stage by holding both its data and valid registers as a unit, dropping and duplicating nothing. A flush invalidates everything in flight by clearing all the valid registers in one cycle, leaving the data untouched — the exact dual of reset applied at runtime, for aborts and mispredicts.
  • The signature bug is valid/data misalignment. If the valid is delayed a different number of stages than the data — combinational from the input, under-delayed, or not reset-cleared — it marks the wrong cycle valid, and the consumer accepts a fill-X, a bubble, or a neighbouring word. The tell is corruption at the edges of a stream (fill, after a bubble, after a flush) that vanishes when the pipe is driven permanently full. Built and self-check-verified here in SystemVerilog, Verilog, and VHDL — and the substrate the valid/ready handshake (9.1) and skid buffer (9.3) are built on.