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

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
      • Valid/Ready Handshake
      • Req/Ack + Pulse Discipline
      • FIFO Rulesets
      • FSM Rulesets
      • Pipelines, Stall, Credits
      • Checking PacketFIFO
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Protocol Assertion Cookbook
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Protocol Assertion Cookbook
  5. Checking PacketFIFO
On this page

    Part 4 of 9, lesson 24 of 41Part 4 of 9/Assertions (SVA)/Lesson 24 of 41Intermediate

    Checking PacketFIFO

    The course project's assertions in one runnable checker: hold rules, overflow and underflow, packet rules and covers, with planted bugs.

    A FIFO checker passed its legal run, so the team trusted it. Nobody had planted an overflow, and a cover for the full FIFO would have shown that case was never reached.

    After this lesson you can: check a valid/ready FIFO from its ports alone: hold rules, occupancy limits, packet format and covers.

    Path: Optional project. It pulls together the five PacketFIFO steps from this part, so read it after Valid/Ready Handshake and FIFO Rulesets at least.

    One checker, four jobs

    PacketFIFO is the FIFO from The PacketFIFO Project: a valid/ready port on each side that carries packets as bytes. This checker watches only its ports and adds a little modeling code, so it never reads the design's internal state.

    Job

    Rules

    Taught in

    Hold the offer

    in_valid, in_data, out_valid, out_data held while the port is stalled

    Valid/Ready Handshake

    Respect capacity

    No byte accepted when full, none taken when empty

    FIFO Rulesets

    Follow the packet format

    len 1 to 8, last on the check byte, XOR of the packet is 0

    Building Data-Integrity Checks

    Prove it ran

    Covers for stalls, a full FIFO and a packet end

    Vacuous Success

    The listing

    The file holds the FIFO with a BUG parameter, the checker, and a bench. The bench runs two legal packets with stalls on both ports first, then plants one bug at a time. Every rule reports through fail() so the run continues, because Verilator 5.048 stops at the first $error. Instance 0 is the legal design; instance 1 has in_ready stuck high, and instance 2 has out_valid stuck high.

    packet_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
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    // PacketFIFO checker: handshake holds, occupancy limits and packet rules on the ports of packet_fifo.
    // BUG selects a planted design bug: 0 none, 1 in_ready stuck high (overflow), 2 out_valid stuck high (underflow).
    module packet_fifo #(
      parameter WIDTH = 9,
      parameter DEPTH = 4,
      parameter AW    = 2,
      parameter BUG   = 0
    ) (
      input  wire             clk,
      input  wire             rst_n,
      input  wire             in_valid,
      output wire             in_ready,
      input  wire [WIDTH-1:0] in_data,
      output wire             out_valid,
      input  wire             out_ready,
      output wire [WIDTH-1:0] out_data
    );
      reg [WIDTH-1:0] mem [0:DEPTH-1];
      reg [AW-1:0]    wptr, rptr;
      reg [AW:0]      count;
      wire push = in_valid && in_ready;
      wire pop  = out_valid && out_ready;
    
      assign in_ready  = (BUG == 1) ? 1'b1 : (count != DEPTH);
      assign out_valid = (BUG == 2) ? 1'b1 : (count != 0);
      assign out_data  = mem[rptr];
    
      always @(posedge clk or negedge rst_n)
        if (!rst_n) begin
          wptr  <= 0;
          rptr  <= 0;
          count <= 0;
        end else begin
          if (push) begin
            mem[wptr] <= in_data;
            wptr <= wptr + 1'b1;
          end
          if (pop) rptr <= rptr + 1'b1;
          case ({push, pop})
            2'b10:   count <= count + 1'b1;
            2'b01:   count <= count - 1'b1;
            default: count <= count;
          endcase
        end
    endmodule
    
    module pf_checker #(
      parameter int DEPTH = 4
    ) (
      input logic       clk, rst_n,
      input logic       in_valid, in_ready,
      input logic [8:0] in_data,
      input logic       out_valid, out_ready,
      input logic [8:0] out_data
    );
      int n_fail = 0, n_in_stall = 0, n_out_stall = 0, n_full = 0, n_pkt = 0;
      function automatic void fail(string msg);
        n_fail++;
        $display("[%0t] pf_checker: %s", $time, msg);
      endfunction
    
      // Modeling code: occupancy, and where we are inside the current packet.
      wire push = in_valid && in_ready;
      wire pop  = out_valid && out_ready;
      int         occ = 0;
      logic       hdr = 1'b1;       // next accepted byte is a header
      logic [4:0] left = 5'd0;      // bytes still to come in this packet, check byte included
      logic [7:0] xr = 8'h00;       // XOR of the bytes accepted so far in this packet
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin
          occ <= 0; hdr <= 1'b1; left <= 5'd0; xr <= 8'h00;
        end else begin
          occ <= occ + (push ? 1 : 0) - ((pop && occ > 0) ? 1 : 0);
          if (push) begin
            if (hdr) begin
              hdr <= 1'b0; left <= in_data[3:0] + 5'd1; xr <= in_data[7:0];
            end else begin
              xr <= xr ^ in_data[7:0]; left <= left - 5'd1;
              if (left == 5'd1) hdr <= 1'b1;
            end
          end
        end
    
      // Step 1: an offer is held, with the same byte, until it is accepted (both ports).
      a_in_valid_hold: assert property (@(posedge clk) disable iff (!rst_n)
        (in_valid && !in_ready) |-> ##1 in_valid)
        else fail("in_valid dropped before in_ready");
      a_in_data_hold: assert property (@(posedge clk) disable iff (!rst_n)
        (in_valid && !in_ready) |-> ##1 $stable(in_data))
        else fail("in_data changed while stalled");
      a_out_valid_hold: assert property (@(posedge clk) disable iff (!rst_n)
        (out_valid && !out_ready) |-> ##1 out_valid)
        else fail("out_valid dropped before out_ready");
      a_out_data_hold: assert property (@(posedge clk) disable iff (!rst_n)
        (out_valid && !out_ready) |-> ##1 $stable(out_data))
        else fail("out_data changed while stalled");
    
      // Step 2: never accept into a full FIFO, never hand out from an empty one.
      a_no_overflow: assert property (@(posedge clk) disable iff (!rst_n)
        (occ == DEPTH) |-> !push)
        else fail("overflow: byte accepted while full");
      a_no_underflow: assert property (@(posedge clk) disable iff (!rst_n)
        (occ == 0) |-> !pop)
        else fail("underflow: byte taken while empty");
    
      // Step 3: packet rules on the accepted bytes.
      a_len_legal: assert property (@(posedge clk) disable iff (!rst_n)
        (push && hdr) |-> (in_data[3:0] inside {[1:8]} && !in_data[8]))
        else fail("header: len outside 1 to 8, or last set");
      a_last_on_check: assert property (@(posedge clk) disable iff (!rst_n)
        (push && !hdr) |-> (in_data[8] == (left == 5'd1)))
        else fail("last is not on the check byte");
      a_check_xor: assert property (@(posedge clk) disable iff (!rst_n)
        (push && !hdr && left == 5'd1) |-> ((xr ^ in_data[7:0]) == 8'h00))
        else fail("check byte is not the XOR of the packet");
    
      // Step 4: covers prove the antecedents were reached.
      c_in_stall: cover property (@(posedge clk) disable iff (!rst_n)
        (in_valid && in_ready == 1'b0) ##1 (in_valid && in_ready)) n_in_stall++;
      c_out_stall: cover property (@(posedge clk) disable iff (!rst_n)
        (out_valid && out_ready == 1'b0) ##1 (out_valid && out_ready)) n_out_stall++;
      c_full: cover property (@(posedge clk) disable iff (!rst_n)
        occ == DEPTH) n_full++;
      c_packet: cover property (@(posedge clk) disable iff (!rst_n)
        push && in_data[8]) n_pkt++;
    endmodule
    
    Terminal
    two legal packets with stalls on both ports: 0 new report(s)
    covers: in-stall 2, out-stall 3, full 9, packet end 2
    [365] pf_checker: in_data changed while stalled
    payload changed during a stall: 1 new report(s)
    [565] pf_checker: header: len outside 1 to 8, or last set
    len 0 header: 1 new report(s)
    [695] pf_checker: check byte is not the XOR of the packet
    wrong check byte: 1 new report(s)
    [795] pf_checker: last is not on the check byte
    last on a payload byte: 1 new report(s)
    [945] pf_checker: overflow: byte accepted while full
    [955] pf_checker: out_data changed while stalled
    in_ready stuck high, fifth byte into a depth-4 FIFO: 2 report(s)
    [975] pf_checker: underflow: byte taken while empty
    out_valid stuck high, one read of an empty FIFO: 1 report(s)
    PASS: legal packets passed with every cover hit, and each planted bug was reported

    What the run shows

    Terminal
    two legal packets with stalls on both ports: 0 new report(s)
    covers: in-stall 2, out-stall 3, full 9, packet end 2
    [365] pf_checker: in_data changed while stalled
    payload changed during a stall: 1 new report(s)
    [565] pf_checker: header: len outside 1 to 8, or last set
    len 0 header: 1 new report(s)
    [695] pf_checker: check byte is not the XOR of the packet
    wrong check byte: 1 new report(s)
    [795] pf_checker: last is not on the check byte
    last on a payload byte: 1 new report(s)
    [945] pf_checker: overflow: byte accepted while full
    [955] pf_checker: out_data changed while stalled
    in_ready stuck high, fifth byte into a depth-4 FIFO: 2 report(s)
    [975] pf_checker: underflow: byte taken while empty
    out_valid stuck high, one read of an empty FIFO: 1 report(s)
    PASS: legal packets passed with every cover hit, and each planted bug was reported
    • Legal traffic: no reports, and all four covers hit.

    • One bug, one report: each planted packet or stall bug is reported by the rule that owns it.

    • Overflow: two reports, root cause first. The second is the oldest byte changing under a waiting out_valid.

    • Underflow: one report, from the rule that owns it.

    Common mistakes

    Modeling occ with the design's own count register makes the checker agree with any counting bug in the design. Keep the count in the checker, from the ports alone.

    Check yourself The legal run passes, but c_full never hits. What can you not conclude about the overflow rule?

    That it works. With the FIFO never full, the overflow antecedent never matched, so the rule passed vacuously. A cover on occ == DEPTH is the proof that the case was reached.

    Check yourself Why does the bench plant in_ready stuck high in a separate instance instead of editing the legal one?

    The legal instance has to stay silent to show that the rules are not too strict. A separate buggy instance shows that they are not too loose, on the same clock and the same checker code.

    • Keep the occupancy count in the checker, from the ports.

    • A legal run must stay silent and every cover must hit.

    • Plant each bug once and expect one report from the rule that owns it.

    Rule of thumb: a checker earns trust by catching planted bugs, not by passing.

    How this was checked

    Run with Verilator 5.048 (--binary --timing --assert, plus --coverage when a listing has a cover property) on 2026-10-18; every bench printed the PASS line shown. Verilator stops at the first $error or failing assertion without an action block, so the benches count failures in the action block. Icarus 13 has no concurrent assertions.

    Instance 0 is legal; instance 1 has in_ready stuck high and instance 2 has out_valid stuck high. The overflow gave two reports (the overflow, then the overwritten oldest byte), every other planted bug gave one. Run on 2026-10-25.

    Related topics

    • The PacketFIFO Project

    • FIFO Rulesets

    Previous

    126 / 275

    Next: Advanced SVA: Local Variables, Multiclock & More

    Up next

    Up nextAdvanced SVA: Local Variables, Multiclock & More

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Valid/Ready Handshake
    • Req/Ack + Pulse Discipline
    • FIFO Rulesets