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 Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
    • SVA Fundamentals
    • Sequences: Temporal Building Blocks
    • Implication and Property Operators
    • Sampled Value Functions
    • Protocol Assertion Cookbook
    • Advanced SVA: Local Variables, Multiclock & More
    • Assertion Debug & Methodology
      • Debugging Assertion Failures
      • When Passing Is the Bug: Vacuity & Coverage
      • Assertion Performance & Noise Control
      • Where Assertions Live
      • The Assertion Review Checklist
    • SVA Interview Drills
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Assertion Debug & Methodology
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. Assertion Debug & Methodology
  5. Where Assertions Live
On this page

    Part 4 of 10, lesson 31 of 40Part 4 of 10/Assertions (SVA)/Lesson 31 of 40Intermediate

    Where Assertions Live

    Place assertions inline, in interfaces, or in bound checkers, and reuse them.

    A synthesis run failed on a design that had passed every review. An assertion in an interface had no synthesis guard and went to the synthesis tool with the interface.

    After this lesson you can: place assertions inline, in interfaces or in bound checkers, and keep them out of synthesis.

    Three homes, three tradeoffs

    Every assertion lives inline in the RTL next to the logic, in an interface beside the signals, or in a separate checker attached with bind. The choice decides ownership, visibility, reuse, and flow handling.

    Placement is an architecture decision wearing a style-question costume. It also routes triage: inline failures page designers, bound-checker failures page verification first.

    Criterion

    Inline RTL

    Interface

    Bind and checker

    Sees internals

    Yes, full scope

    Ports only

    Yes, bind scope

    Owner

    Designer

    Shared, protocol owner

    Verification

    RTL edits

    Yes

    No

    None at all

    Reuse

    Travels with the module

    Travels with the protocol

    Travels with the check library

    Synthesis exposure

    Needs guards

    Needs guards if the interface is synthesized

    Never compiled

    Best for

    Intent, state machine and counter invariants

    Handshake rules at every instance

    Legacy code and rule sets

    Writing protocol rules inline in one manager's RTL is the reuse trap. The next project uses a different manager, the rules do not travel, and the two rewritten copies diverge.

    Check yourself A third-party FIFO cannot be edited and needs overflow checks. Which home, and why?

    A bound checker. It needs no RTL edits, still sees internals through bind scope, and stays out of synthesis file lists entirely.

    Each option in code

    The interface and bind forms below are fragments, so they are not run in this course's checks; Verilator 5.048 compiles both inside a complete design. The testbench proves the inline form on a small FIFO.

    axi_lite_if.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // not-run: fragment: an interface with no module that instantiates it
    // INTERFACE: the protocol rule rides with the bus definition;
    // every instance of the interface checks itself.
    interface axi_lite_if (input logic clk, rst_n);
      logic awvalid, awready; logic [31:0] awaddr;
      ap_aw_stable: assert property (@(posedge clk) disable iff (!rst_n)
        awvalid && !awready |=> awvalid && $stable(awaddr))
        else $error("AW channel unstable while stalled");
    endinterface
    design.sv
    1
    2
    3
    4
    5
    6
    7
    // not-run: fragment: fifo_core and fifo_chk are not defined here
    // BIND: zero RTL edits; a verification-owned file attaches a checker
    // to every instance of a module type, seeing its internals.
    bind fifo_core fifo_chk #(.DEPTH(16)) u_chk (
      .clk(clk), .rst_n(rst_n),
      .push(wr_en && !full), .pop(rd_en && !empty),
      .count(occupancy_q));            // internal signal: bind can see it

    Designers own inline assertions: micro-architectural invariants only they know, written while the intent is fresh. Protocol owners own interface assertions. Verification owns bind files and checker libraries.

    Bind by module type, not by instance path. Newly added instances of the type are then checked automatically, instead of silently escaping.

    Surviving synthesis and tool flows

    Concurrent assertions are not synthesizable. Most synthesis tools ignore them, but relying on every tool in the flow to ignore them is how builds break at night.

    design.sv
    1
    2
    3
    4
    5
    // snippet: standard guard pattern for inline assertions
    `ifndef SYNTHESIS
      ap_no_ov: assert property (@(posedge clk) disable iff (!rst_n)
        !(push && full));
    `endif

    Bound checkers are excluded from synthesis file lists structurally. Inline and interface assertions get guarded, since a synthesized interface carries its assertions into synthesis: one macro convention per project, linted for unguarded stragglers.

    The same property text serves simulation and formal tools. Bind plus checker is the most formal-friendly home: the formal tool compiles RTL plus bind files with no testbench.

    Keep properties pure and side effects in action blocks only. At block boundaries, input-protocol assertions flip from assert to assume, and simulation-tuned bounds relax back to liveness behind a parameter.

    Likely question: “where would you put assertions for a FIFO”. Answer with a framework: structural invariants inline or bound since they need internals, valid and ready rules in the interface so every instance inherits them, end-to-end integrity in a verification-owned bound checker. Mention ownership and the synthesis guard; forgetting the guard is the classic follow-up trap.

    Check yourself The same bound of 200 cycles suits simulation but not formal. How do you share the property?

    Parameterize the bound. Simulation instantiates it at 200, formal relaxes it or restores liveness, and one property text serves both engines.

    Testbench

    A four-deep FIFO with designer-owned inline invariants. The stimulus fills it, drops one extra push, drains in order, and pops once while empty.

    tiny_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
    module tiny_fifo #(
      parameter int DEPTH = 4,
      parameter int W = 8
    ) (
      input logic clk, rst_n,
      input logic push, pop,
      input logic [W-1:0] wdata,
      output logic [W-1:0] rdata,
      output logic full, empty
    );
      localparam int AW = $clog2(DEPTH);
      logic [W-1:0] mem [DEPTH];
      logic [AW-1:0] wptr, rptr;
      logic [AW:0] count;
    
      assign full = (count == DEPTH);
      assign empty = (count == '0);
      assign rdata = mem[rptr];
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          wptr <= '0; rptr <= '0; count <= '0;
        end else begin
          if (push && !full) begin
            mem[wptr] <= wdata;
            wptr <= wptr + 1'b1;
          end
          if (pop && !empty) begin
            rptr <= rptr + 1'b1;
          end
          case ({push && !full, pop && !empty})
            2'b10:   count <= count + 1'b1;
            2'b01:   count <= count - 1'b1;
            default: count <= count;
          endcase
        end
      end
    
      `ifndef SYNTHESIS
      ap_count_bound: assert property (@(posedge clk) disable iff (!rst_n)
        count <= DEPTH);
      ap_flags_agree: assert property (@(posedge clk) disable iff (!rst_n)
        !(full && empty));
      `endif
    endmodule
    
    Terminal
    PASS: fifo held four words, dropped the extra push, and drained in order
    • Internal invariants: inline or bound. Protocol rules: the interface.

    • End-to-end checks: a verification-owned bound checker.

    • Guard inline and interface assertions from synthesis.

    Rule of thumb: every assertion has an owner and a home, decided before it is written.

    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.

    Related topics

    • The Assertion Review Checklist

    Previous

    131 / 259

    Next: The Assertion Review Checklist

    Up next

    Up nextThe Assertion Review Checklist

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Debugging Assertion Failures
    • When Passing Is the Bug: Vacuity & Coverage