Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Glossary
  • Metrics Reference
  • Formal Cheat Sheet
  • Formal Foundations19
  • Properties and SVA16
  • Property Development and Constraints19
  • Proof Engines and Convergence23
  • Formal Applications20
    • Connectivity Checking
    • Register (CSR) Verification
    • Deadlock, Livelock and Starvation Checks
    • Reset Determinism and X
    • Data, Handshake and Pipeline Property Sets
      • FIFO Ordering and Data Integrity
      • Handshake Bridge Properties
      • Pipeline Latency and Stall Properties
    • Other Applications, Honestly Scoped
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Data, Handshake and Pipeline Property Sets
  1. Topics
  2. Formal Verification
  3. Formal Applications
  4. Data, Handshake and Pipeline Property Sets
  5. Pipeline Latency and Stall Properties
On this page

    Part 5 of 10, lesson 17 of 20Part 5 of 10/Formal Applications/Lesson 17 of 20All levels

    Pipeline Latency and Stall Properties

    Three-cycle latency proved with input history, a reset property, and the set the equivalence proof reuses.

    Why it matters

    A pipeline's contract is its latency: the output three cycles after an input is the specified function of that input, every time. The symptom of getting it wrong is a result attributed to the wrong input, found as occasional wrong answers under exactly the traffic the unit test skipped. History registers make the contract checkable: keep the last three inputs alongside the pipe and compare.

    The core idea

    Count clean cycles since reset, because the output is only specified once the pipe has filled: before that the registers hold reset values, not results. Shift the low nibbles of every input through three history stages in plain registers, compute the expected result from the three-old values, and compare with the output. The reset property pins the output register at zero on the cycle after reset. The same history shape reappears in the equivalence section, where aligning two pipelines needs the same latency argument. This pipeline has no stall or flush input: a design that adds one must freeze its history with it, as the last question shows.

    Worked example: latency proved

    The pipeline property set proves with an induction depth of three: the base case covers the fill, and the induction step needs the full window because the output depends on three-old inputs. The history width is four bits per operand, matching the cone the function actually reads; the upper nibbles are optimisation-removed and excluded by construction:

    pipe3.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    // formal: prove 4 expect pass slang top pipe3
    `define FORMAL
    module pipe3 #(parameter CHECK = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
    `ifdef FORMAL
      generate if (CHECK) begin : g_chk
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
    
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'd7);
      end endgenerate
    `endif
    endmodule

    Worked example: the swapped operand and the latency-two claim

    Two mistakes, each failing. First, a mutant that squares the stage sum instead of scaling it by the coefficient, the shape of a swapped stage operand: the timing is right and the value is wrong, and the latency property fails at step 4, the first step at which the output reflects an input. Second, the correct pipeline checked against a two-cycle claim: the history is one stage short, the window opens one step early, and the property fails at step 3 where the pipe has not yet delivered:

    pipe3_swap.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // formal: bmc 5 expect fail slang top pipe3_swap
    `define FORMAL
    module pipe3_swap (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * s[3:0];
          y  <= p + 8'd1;
        end
      end
    
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2, sa3, sb3, sc3;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
        sa3 <= sa2;    sb3 <= sb2;    sc3 <= sc2;
      end
      wire [3:0] sum3 = sa3 + sb3;
    
      always @(*) if (!init && hist == 2'd3) a_latency3: assert (y == (sum3 * sc3) + 8'd1);
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd3 && y == 8'h7);
    endmodule
    pipe3_lat2.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    // formal: bmc 4 expect fail slang top pipe3_lat2
    `define FORMAL
    module pipe3_lat2 (
      input  wire       clk,
      input  wire       rst,
      input  wire [7:0] a,
      input  wire [7:0] b,
      input  wire [7:0] c,
      output reg  [7:0] y,
      output wire [7:0] dbg_p
    );
      reg [7:0] s;
      reg [3:0] c1;
      reg [7:0] p;
      assign dbg_p = p;
      always @(posedge clk) begin
        if (rst) begin s <= 8'd0; c1 <= 4'd0; p <= 8'd0; y <= 8'd0; end
        else begin
          s  <= a + b;
          c1 <= c[3:0];
          p  <= s[3:0] * c1;
          y  <= p + 8'd1;
        end
      end
    
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      reg [1:0] hist;
      always @(posedge clk) begin
        if (rst) hist <= 2'd0;
        else if (hist != 2'd3) hist <= hist + 2'd1;
      end
    
      reg [3:0] sa1, sb1, sc1, sa2, sb2, sc2;
      always @(posedge clk) begin
        sa1 <= a[3:0]; sb1 <= b[3:0]; sc1 <= c[3:0];
        sa2 <= sa1;    sb2 <= sb1;    sc2 <= sc1;
      end
      wire [3:0] sum2 = sa2 + sb2;
    
      always @(*) if (!init && hist == 2'd2) a_latency2: assert (y == (sum2 * sc2) + 8'd1);
      always @(posedge clk) if (!init && $past(rst)) a_reset_zero: assert (y == 8'd0);
      always @(*) if (!init) c_y_nonzero: cover (hist == 2'd2 && y == 8'h7);
    endmodule

    What the review looks for

    • The latency number is derived from the stage count, and the history depth matches it exactly.

    • The specified window excludes the fill: no assertion judges the output before the pipe is full.

    • The history covers exactly the bits the function reads: extra bits slow the proof for nothing.

    Verify and debug

    A latency failure first asks which cycle the output reflects: count the registers between input and output in the trace, then check the history depth matches. A swapped-operand failure shows correct timing with wrong values: compare the expected expression term by term against the stage registers. A fill-window failure is the guard, not the logic: check the clean-cycle counter.

    History one stage short: the symptom is a latency property that fails on a correct pipeline at the fill boundary, found when the expected value uses younger inputs than the output reflects. The cause is counting the output register but not the input sampling. The fix is matching history depth to stage count and covering the fill explicitly.

    Expect: prove a three-cycle latency. Answer with the history registers and the clean-cycle guard, state the induction depth, and say why the proof needs the full window. Then point forward: the same alignment argument decides whether two pipelines are equivalent.

    Check yourself The pipeline gains a stall input that freezes all stages. What changes in the property set?

    The history must freeze with the pipe: gate the shift registers and the clean-cycle counter with the stall, add a hold property for stalled outputs, and cover a stalled-then-drained sequence so the freeze is exercised, not assumed.

    Related topics

    • Handshake Bridge Properties

    • Formal: Sequential Equivalence

    Previous

    99 / 183

    Next: Other Applications, Honestly Scoped

    Up next

    Up nextOther Applications, Honestly Scoped

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • FIFO Ordering and Data Integrity