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
    • Writing Properties That Can Fail and Say Something
      • Spec Sentence to Property
      • End to End Versus Local Properties
      • Symbolic Constants and Tracked Items
      • Widths, Signedness and Initial State
    • Assumptions as a Contract
    • Abstraction and Complexity
    • Assume-Guarantee and Black Boxes
    • Reusable Property Sets
  • Proof Engines and Convergence23
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Writing Properties That Can Fail and Say Something
  1. Topics
  2. Formal Verification
  3. Property Development and Constraints
  4. Writing Properties That Can Fail and Say Something
  5. Spec Sentence to Property
On this page

    Part 3 of 10, lesson 1 of 19Part 3 of 10/Property Development and Constraints/Lesson 1 of 19All levels

    Spec Sentence to Property

    Turn a requirement sentence into a property with a trigger, a response, a window and a scope, check bounded response and one-to-one pairing with runnable pairs, and prove the trigger can fire.

    Why it matters

    A four-entry FIFO shipped accepting a push while full; simulation never held it full under push and pop together. One property from the sentence "a full FIFO ignores a push" finds the fault. Job of this lesson: turn a sentence into a property with five questions, show each property failing on a planted fault, and teach the two questions that need more than $past: the window and one-to-one pairing.

    The core idea

    Five questions precede any property. Trigger: which event starts the obligation. Response: what must hold. Window: the same cycle, the next, or within N cycles. Scope: which states the rule covers. One-to-one: what ties this response to that request. A same-cycle fact is a combinational check, reported at the step where the bad state exists; a registered update is a clocked check that reads history and is reported one step later. A property that reads $past skips the cycle after reset, whose "previous" cycle is the reset cycle.

    Sentence

    Trigger and window

    Property

    Fails on

    A full FIFO ignores a push

    push, no pop, count 4: count unchanged next cycle

    a_ovf_ign

    BUG 1, step 7

    Occupancy stays within 0 to 4

    always, same cycle

    a_range

    BUG 1, step 6

    The flags match the count

    always, same cycle

    a_flags

    BUG 3, step 4

    Pointers and count agree (internal)

    always, same cycle

    a_ptr

    BUG 4, step 3 (no output property fails)

    Reset empties the FIFO

    the cycle after reset

    a_reset (added below)

    phantom word, step 2

    Worked example: one sentence, two runs

    The complete FIFO file proves at depth 4 (bounded check and induction step). With the fault of the first row switched on, a fifth push is accepted into a full FIFO:

    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // formal: prove 4 expect pass slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule
    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // formal: bmc 7 expect fail slang top sync_fifo define BUG=1
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    Step

    push

    count

    What the checks see

    0

    -

    any

    Reset assumed; assertions off

    1 to 4

    1

    0, 1, 2, 3

    Four pushes (the counts are the states before each push)

    5

    1

    4

    Full; the fifth push is accepted by the fault

    6

    -

    5

    a_range fails: occupancy 5 exists

    The other two rows of the table are planted faults in the same file: the flags wrong near full (BUG=3) and a count that forgets the pop when a push and a pop arrive together (BUG=4). Each fails only the property written for it: the flag fault a_flags at depth 5, which is step 4, and the count fault the internal a_ptr at depth 4, which is step 3, with no output property failing first.

    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // formal: bmc 5 expect fail slang top sync_fifo define BUG=3
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule
    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    // formal: bmc 4 expect fail slang top sync_fifo define BUG=4
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    `endif
    endmodule

    Depth 6 passes and depth 7 fails for the first fault: reset step, four fills, one attempt, one cycle to see it. The reset sentence has no property in the file: add one, and plant a fault only it sees (reset leaves one phantom word, pointers and count in agreement).

    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    // formal: prove 4 expect pass slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    
      // reset empties the FIFO: in the cycle after a reset cycle every control register holds its reset value
      always @(posedge clk) if (!init && $past(rst)) a_reset: assert (count == 3'd0 && wptr == 2'd0 && rptr == 2'd0);
    `endif
    endmodule
    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    // formal: bmc 3 expect fail slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd1;   // planted: reset leaves one phantom word
          rptr  <= 2'd0;
          count <= 3'd1;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    
      // reset empties the FIFO: in the cycle after a reset cycle every control register holds its reset value
      always @(posedge clk) if (!init && $past(rst)) a_reset: assert (count == 3'd0 && wptr == 2'd0 && rptr == 2'd0);
    `endif
    endmodule

    a_reset fails at step 2 (depth 3; depth 2 passes): the bad state is visible at step 1 and a clocked check reports it one step later. Is each antecedent reached? Two covers answer it.

    sync_fifo.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    // formal: cover 6 expect reached slang top sync_fifo
    `define FORMAL
    // sync_fifo: 4-entry, 8-bit synchronous FIFO. Running example of the Formal course.
    // Synchronous active-high reset. count is an output so properties can see it.
    // BUG selects a planted bug: 0 none, 1 push accepted when full, 2 pop accepted when
    // empty, 3 full flag one entry early, 4 simultaneous push+pop miscounts, 5 write
    // data bit 7 stuck at 0.
    module sync_fifo #(parameter BUG = 0, parameter HELPER = 1) (
      input  wire       clk,
      input  wire       rst,
      input  wire       push,
      input  wire [7:0] wdata,
      input  wire       pop,
      output wire [7:0] rdata,
      output wire       full,
      output wire       empty,
      output reg  [2:0] count
    );
      reg [7:0] mem [0:3];
      reg [1:0] wptr, rptr;
    
      assign full  = (BUG == 3) ? (count >= 3'd3) : (count == 3'd4);
      assign empty = (count == 3'd0);
      assign rdata = mem[rptr];
    
      wire do_push = push && (!full  || BUG == 1);
      wire do_pop  = pop  && (!empty || BUG == 2);
    
      always @(posedge clk) begin
        if (rst) begin
          wptr  <= 2'd0;
          rptr  <= 2'd0;
          count <= 3'd0;
        end else begin
          if (do_push) begin
            mem[wptr] <= (BUG == 5) ? {1'b0, wdata[6:0]} : wdata;
            wptr      <= wptr + 2'd1;
          end
          if (do_pop) rptr <= rptr + 2'd1;
          if (BUG == 4 && do_push && do_pop) count <= count + 3'd1;   // forgets the pop
          else                               count <= count + {2'b0, do_push} - {2'b0, do_pop};
        end
      end
    
    `ifdef FORMAL
      // ---- harness: first cycle is reset, state before it is arbitrary ----
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
    
      // ---- safety properties (after the reset cycle) ----
      always @(*) if (!init) begin
        a_range:   assert (count <= 3'd4);                       // no overflow, no underflow wrap
        a_flags:   assert (full == (count == 3'd4) && empty == (count == 3'd0));
        a_not_both: assert (!(full && empty));
        // helper invariant: occupancy equals pointer distance (mod 4) unless full
        a_ptr:     assert (count[1:0] == (wptr - rptr));
      end
    
      // ---- transition properties (use history: skip the cycle after reset) ----
      always @(posedge clk) if (!init && !$past(rst)) begin
        // push alone when not full adds one; pop alone when not empty removes one
        a_push_inc: assert (!($past(push) && !$past(pop) && !$past(full)) || count == $past(count) + 3'd1);
        a_pop_dec:  assert (!($past(pop) && !$past(push) && !$past(empty)) || count == $past(count) - 3'd1);
        // push into a full FIFO (and no pop) is ignored: occupancy unchanged
        a_ovf_ign:  assert (!($past(push) && !$past(pop) && $past(count) == 3'd4) || count == $past(count));
        // simultaneous push and pop (neither empty nor full) leaves occupancy unchanged
        a_both:     assert (!($past(push) && $past(pop) && !$past(full) && !$past(empty)) || count == $past(count));
      end
    
      // ---- data integrity by tracked-slot abstraction (one free slot, one shadow value) ----
      // free constant: (* anyconst *) alone is not honoured by the front end named in the header, so the
      // value is also assumed equal to its one-cycle-old copy (works in both front ends;
      // a clocked assume ($stable) lags one step and leaks at the last step: do not use it)
      (* anyconst *) wire [1:0] tslot;
      reg [1:0] tslot_q;
      always @(posedge clk) tslot_q <= tslot;
      always @(*) if (!init) assume (tslot == tslot_q);
      reg       tv;      // the tracked slot holds a value we are watching
      reg [7:0] td;      // the value that should be there
      always @(posedge clk) begin
        if (rst) tv <= 1'b0;
        else begin
          if (do_push && wptr == tslot) begin tv <= 1'b1; td <= wdata; end
          else if (do_pop && rptr == tslot) tv <= 1'b0;
        end
      end
      always @(*) if (!init && tv && !empty && rptr == tslot) a_data: assert (rdata == td);
      // helper invariant needed for the induction step of a_data
      generate if (HELPER) begin : g_helper
        always @(*) if (!init && tv) a_tracked: assert (mem[tslot] == td);
      end endgenerate
    
      // ---- covers: the properties are not vacuous ----
      always @(*) if (!init) begin
        c_full:  cover (count == 3'd4);
        c_empty_after_use: cover (empty && tv == 1'b0 && wptr == 2'd2);
      end
      always @(posedge clk) if (!init) c_both: cover (push && pop && !full && !empty);
    
      // antecedent covers for the transition properties
      always @(*) if (!init) begin
        c_ovf: cover (push && !pop && full);
        c_pop: cover (pop && !push && !empty);
      end
    `endif
    endmodule

    All covers are reached within depth 6 (c_ovf, a push into a full FIFO, at step 5). A cover not reached within N is inconclusive, not proof of unreachability.

    Window and one-to-one: bounded response

    A response unit answers a request d + 2 cycles later, where d (0 to 3) comes with the request. "Within 5 cycles" is bounded response, a safety property; "eventually" is liveness and needs another engine. Three forms look alike:

    Form

    Needs

    Catches

    Exactly N ago, $past(x, 3)

    Nothing

    Nothing: it fails on the good unit (latency 2 to 5)

    Within N cycles

    A counter (age)

    A slow answer

    One to one

    A counter of requests minus answers (pend)

    A second answer, which a window never sees

    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 14 expect pass slang top rsp_unit define LAT=1
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: cover 7 expect reached slang top rsp_unit
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 8 expect fail slang top rsp_unit define BUG=1
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 5 expect fail slang top rsp_unit define BUG=1 define LAT=1
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 14 expect pass slang top rsp_unit define BUG=2 define ONE=0
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 5 expect fail slang top rsp_unit define BUG=2
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule
    rsp_unit.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    // formal: bmc 5 expect fail slang top rsp_unit define EXACT=1
    `define FORMAL
    // rsp_unit: accepts one request at a time and answers it d + 2 cycles later (d is sampled with the request, so the
    // latency is 2 to 5 cycles). BUG: 0 none, 1 slow (one extra cycle, 3 to 6), 2 duplicate answer, 3 answer dropped.
    // ONE=1 checks one answer per request, EXACT=1 adds a wrong window, LAT=1 adds the exact-latency check.
    module rsp_unit #(parameter BUG = 0, parameter ONE = 1, parameter EXACT = 0, parameter LAT = 0) (
      input wire clk, input wire rst, input wire req, input wire [1:0] d, output reg rsp);
      reg busy; reg [2:0] cnt; reg dup;
      wire acc = req && !busy;
      always @(posedge clk) begin
        rsp <= 1'b0;
        if (rst) begin busy <= 1'b0; cnt <= 3'd0; dup <= 1'b0; end
        else begin
          dup <= 1'b0;
          if (acc) begin busy <= 1'b1; cnt <= {1'b0, d} + ((BUG == 1) ? 3'd1 : 3'd0); end
          else if (busy && cnt != 3'd0) cnt <= cnt - 3'd1;
          else if (busy) begin
            busy <= 1'b0;
            rsp <= (BUG != 3);
            dup <= (BUG == 2);
          end
          if (dup) rsp <= 1'b1;
        end
      end
    `ifdef FORMAL
      reg init = 1'b1;
      always @(posedge clk) init <= 1'b0;
      always @(*) if (init) assume (rst);
      // harness: waiting = a request is unanswered (cleared by the first answer), age = cycles it has waited,
      // pend = requests accepted minus answers seen
      reg waiting; reg [2:0] age; reg [1:0] pend;
      always @(posedge clk) begin
        if (rst) begin waiting <= 1'b0; age <= 3'd0; pend <= 2'd0; end
        else begin
          pend <= pend + {1'b0, acc} - {1'b0, rsp};
          if (rsp && waiting) begin waiting <= acc; age <= acc ? 3'd1 : 3'd0; end
          else if (acc) begin waiting <= 1'b1; age <= 3'd1; end
          else if (waiting) age <= (age == 3'd7) ? age : age + 3'd1;
        end
      end
      // bounded response (a safety property, not liveness): no request waits longer than 5 cycles
      always @(*) if (!init) a_within5: assert (age <= 3'd5);
      // exact latency: the answer comes d + 2 cycles after the request, where d is the value sampled WITH that request.
      // dacc holds d from the last accepted request, which is what a gated past value would give.
      reg [1:0] dacc;
      always @(posedge clk) if (acc) dacc <= d;
      generate if (LAT) begin : g_lat
        always @(*) if (!init && rsp && waiting) a_lat: assert (age == {1'b0, dacc} + 3'd2);
      end endgenerate
      // one answer per request: an answer needs an outstanding request, and requests are not stacked
      generate if (ONE) begin : g_one
        always @(*) if (!init) begin
          a_pend_max:   assert (pend <= 2'd1);
          a_no_phantom: assert (!rsp || pend != 2'd0);
        end
      end endgenerate
      // the wrong window: "answered exactly 3 cycles after the request"
      generate if (EXACT) begin : g_exact
        always @(posedge clk) if (!init && !$past(rst)) a_exact3: assert (!rsp || $past(acc, 3));
      end endgenerate
      always @(*) if (!init) begin c_acc: cover (acc); c_rsp: cover (rsp); c_slow: cover (rsp && age == 3'd5); end
    `endif
    endmodule

    The first run is bounded to 14, with no counterexample; the cover run reaches the slowest answer (c_slow) within depth 7. The slow fault is caught by the window at step 7 (depth 7 passes). The duplicate answer passes the window checks to depth 14 and fails the one-to-one check at step 4: the second pulse has no outstanding request. Exactly-3 fails on the good unit at step 4.

    The third argument of $past(x, n, gate) is a gating expression, not a window: $past(x, 1, 8) is $past(x, 1), since 8 is true. A gated past is the value at the last tick where the gate was true. The front end used here rejects it, so the runnable form is a hold register dacc, loaded on each accepted request. LAT=1 checks the answer comes d + 2 cycles after it; the slow fault fails that at step 4 (depth 5), before the window.

    design.sv
    1
    2
    3
    4
    // not-run: the front end used here rejects the gating argument of $past
    // a_lat: assert property (@(posedge clk) disable iff (rst) rsp |-> age == $past(d, 1, acc) + 2);
    // $past(d, 1, acc): d at the previous tick at which acc was true. It is not "d within the last acc" cycles.
    // Runnable equivalent in the unit above: reg [1:0] dacc; always @(posedge clk) if (acc) dacc <= d;

    What a reviewer looks for

    • Each property names its trigger and window, and says whether it is bounded response or liveness.

    • Every implication has a cover for its antecedent.

    • A pairing rule uses a counter or tracked item, never an implication alone.

    Verify and debug

    Run each property on the good design first. A failure in the first two cycles is the missing reset guard, not a bug. Then run the planted fault the property was written for and note the step: a_range fails at depth 7 for the first fault, a_flags at depth 5, a_ptr at depth 4 and a_reset at depth 3. A property that does not fail on its own fault is not yet a check. Finally read the cover list: an antecedent cover that is not reached within its depth means the rule never ran, and that is inconclusive until the cover is explained.

    The trigger that never fires. Symptom: every proof passes and the cover for the traffic is not reached. Cause: the antecedent names a state the design never reaches, such as count 5 on a four-entry FIFO. Fix: one antecedent cover per implication, treated as part of the property. Found by: reading the cover list, not the proof list. A failure on the good design in the first two cycles is the missing reset guard.

    "Write: a response comes within 4 cycles." A counter of cycles since the request, asserted never to pass 4, a cover that it reaches 4, and a pending counter so a duplicate answer cannot hide behind the window.

    Check yourself Why does exactly-3 fail on the good unit while within-5 passes?

    The unit answers 2, 3, 4 or 5 cycles after the request: exactly 3 holds for one latency, within 5 for all four.

    Related topics

    • SVA forms: Implication and Clocking

    • Cover Properties

    • SystemVerilog: FIFO Assertions

    • RTL: Common RTL Property Templates

    • Next: End to End Versus Local Properties

    Previous

    41 / 183

    Next: End to End Versus Local Properties

    Up next

    Up nextEnd to End Versus Local Properties

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Symbolic Constants and Tracked Items
    • Widths, Signedness and Initial State