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

AboutContactPrivacyTerms
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
    • SVA Interview Drills
      • Handshake Drills
      • Pulse & Stability Drills
      • Ordering & Counting Drills
      • Data & Reset Drills
      • FSM & Arbiter Drills
      • Q&A: Assertion Fundamentals
      • Q&A: Operators & Semantics
      • Q&A: Vacuity, Debug & Formal
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹SVA Interview Drills
  1. Topics
  2. SystemVerilog
  3. SystemVerilog Assertions — Interview-Critical Deep Dive
  4. SVA Interview Drills
  5. Q&A: Assertion Fundamentals

Part 4 · Assertions (SVA) · Intermediate

Q&A: Assertion Fundamentals

Immediate versus concurrent assertions, sampling, attempts, roles, and naming.

On this page

    Why this matters

    Asked when an assertion samples its signals, a candidate said 'at the clock edge' and lost the round. The answer is the preponed region, just before it.

    After this lesson you can: explain immediate versus concurrent assertions, sampling, attempts, and what each directive means in simulation and formal.

    Note

    Revision page. Answer each question before opening it; the full lessons are SVA Fundamentals.

    Predict first A concurrent assertion and the waveform disagree about a signal that changes at the failing edge. Who is right?

    Both. The assertion used the value from just before the edge; the viewer draws the value after it.

    Predict first $rose(req) |-> ##[1:4] ack sees requests at cycles 1 and 3 and one ack at 4. How many attempts pass?

    Both. Each attempt is independent, and the ack at 4 is inside both windows.

    Predict first In simulation, what does assume property do?

    It is checked exactly like an assert. Only formal tools treat it as a constraint.

    Immediate and concurrent

    An immediate assertion is a procedural statement, assert (expr) inside an always block, task, or function. It evaluates instantly with current, possibly mid-timestep values, like an if statement that errors.

    A concurrent assertion is declarative: assert property (@(posedge clk) ...). It evaluates on clock edges using sampled values and can describe multi-cycle temporal behavior.

    This fragment is an illustration, so the course checks skip it; the bench at the end of the page runs the same ideas.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet
    // Immediate: procedural, instant, current values
    // a_imm: assert (onehot_grant) else $error("grant not onehot");
    
    // Concurrent: declarative, clocked, sampled values, temporal
    a_con: assert property (
      @(posedge clk) disable iff (!rst_n)
      $rose(req) |-> ##[1:4] ack);

    Immediate assertions can sit anywhere procedural code runs, including classes and testbench components, which is why assert (obj.randomize()) is an immediate assertion. They are vulnerable to glitches and ordering races because they fire mid-timestep.

    Concurrent assertions live at module, interface, program or checker scope, or inside always and initial blocks (procedural concurrent assertions), but never in classes. They are race-free by construction, because they read sampled values. The sampling model, not the syntax, is the real difference.

    Check yourself A concurrent assertion and the waveform viewer disagree by one cycle on a signal that transitions at the failing edge. Who is right?

    Both. The assertion used the preponed value from just before the edge, while the viewer shows the post-edge value. Read the waveform one sample to the left.

    Sampling and attempts

    A concurrent assertion samples in the Preponed region of the timestep: the values just before the clock edge, ahead of any updates from that edge. Evaluation happens later in the same timestep, in the Observed region, and action blocks run after that.

    The region order is Preponed, then Active with its NBA subregion where flops update, then Observed, then Reactive for testbench code, then Postponed where settled values can be printed. An assertion never sees the new value a flop takes on the same edge: it sees the old one, exactly what a real flop would capture.

    The simulator starts a new evaluation attempt at every sampling edge. Each attempt checks the antecedent with that edge's sampled values and exits vacuously on a non-match. On a match it spawns a consequent obligation that may run for many cycles, so several attempts of one assertion can be alive at once.

    cycle

    0

    1

    2

    3

    4

    5

    req

    0

    1

    0

    1

    0

    0

    ack

    0

    0

    0

    0

    1

    0

    attempts

    starts

    waits

    second starts

    both pass

    Three practical consequences follow. Level antecedents on held signals multiply live attempts into a retrigger storm. Local variables are per attempt, which is why tag matching works and shared counting does not. Each attempt passes or fails independently.

    Roles and names

    Assert, assume, and cover share syntax and differ only in obligation. Assert says the design must satisfy the property. Assume says the environment is promised to satisfy it: in formal the tool only explores stimulus where it holds, and in simulation most tools check it like an assert.

    Cover records whether the scenario ever occurred. Green needs both sides: an assert with an unexercised antecedent is vacuously green, and a hit cover with no assert is observation without checking.

    This fragment is an illustration, so the course checks skip it; the bench at the end of the page runs the same ideas.

    design.sv
    1
    2
    3
    4
    // snippet
    a_ack_pulse: assert property (@(posedge clk) $rose(ack) |=> !ack);
    m_req_legal: assume property (@(posedge clk) $rose(req) |=> !req);
    c_req_gap1:  cover property (@(posedge clk) req ##2 req);   // two pulses, one idle cycle apart

    A wrong assume in formal yields false proofs: the tool prunes legal stimulus, the assert proves over the remainder, and contradictory assumes prove everything vacuously. The senior habit is cover-checking the assumes, proving interesting stimulus still exists under them.

    Label every assertion. The label becomes the hierarchical name used by logs, triage scripts, waveform tracks, $assertoff control, formal tables, and plan mapping. Tool-generated names drift across edits and break waivers silently. Designers write white-box invariants at the cause, DV writes black-box protocol checks at the contract, and bind files carry the protocol checks without touching RTL.

    Check yourself The regression is green. Convince the reviewer the assertions earned it.

    Show per-assertion non-vacuous pass counts above zero, paired covers hit including boundary variants, and at least one historical or injected failure per family proving each checker can fire.

    Common mistakes

    Common pitfall

    Saying concurrent assertions see the waveform values: they see preponed values, one sample earlier than the viewer cursor. Debugging from the cursor instead of the sampled-value track chases a one-cycle ghost.

    Interview

    Likely question: “assert, assume, cover — same property, what changes?”

    • Answer obligation only: must hold, promised to hold, measured whether it happened.

    • Add the sim-versus-formal split for assume and the false-proof danger.

    • Close with cover-your-assumes discipline.

    Check yourself A new assertion fails on mature RTL. A mature assertion fails on fresh RTL. Where do you look first in each case?

    The new assertion is usually the bug: off-by-one, level antecedent, or missing disable. The mature failure on fresh RTL is usually the RTL. That prior orders the triage fork.

    Directive

    Obligation

    Green means

    assert

    Design must satisfy

    No violation on exercised traces

    assume

    Environment promised

    Proof restricted to legal stimulus

    cover

    None, measures only

    Scenario was reached

    Testbench

    The bench checks each answer on Verilator: the overlapping attempts from the table, then an unanswered request, an assume broken by a two-cycle request, a double grant caught by an immediate check, and a cover that counts the two-pulse pattern.

    design.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
    module tb_qa_fundamentals;
      logic clk = 0, rst_n = 0, req = 0, ack = 0;
      logic [3:0] grant = 0;
      always #5 clk = ~clk;
      int win_fails = 0, imm_fails = 0, assume_fails = 0, gaps = 0;
    
      // Concurrent: an attempt at every edge; overlapping requests overlap attempts.
      a_con: assert property (@(posedge clk) disable iff (!rst_n)
        $rose(req) |-> ##[1:4] ack) else win_fails++;
      // assume is checked in simulation exactly like assert.
      m_req_legal: assume property (@(posedge clk) disable iff (!rst_n)
        $rose(req) |=> !req) else assume_fails++;
      c_req_gap1: cover property (@(posedge clk) req ##2 req) gaps++;
      // Immediate: procedural, instant, current values.
      always @(posedge clk) if (rst_n) a_imm: assert ($onehot0(grant)) else imm_fails++;
    
      initial begin
        repeat (2) @(negedge clk);
        rst_n = 1;
        // The trace from the diagram: req at cycles 1 and 3, one ack at 4.
        @(negedge clk) req = 1; @(negedge clk) req = 0;
        @(negedge clk) req = 1; @(negedge clk) req = 0; ack = 1;
        @(negedge clk) ack = 0;
        repeat (6) @(negedge clk);
        $display("two overlapping requests, one ack inside both windows: %0d failures", win_fails);
        // The second request never answered.
        @(negedge clk) req = 1; @(negedge clk) req = 0; ack = 1; @(negedge clk) ack = 0;
        @(negedge clk) req = 1; @(negedge clk) req = 0;
        repeat (6) @(negedge clk);
        $display("second request unanswered: %0d failure", win_fails);
        @(negedge clk) req = 1; repeat (2) @(negedge clk); req = 0; ack = 1;   // a 2-cycle req breaks the assume
        @(negedge clk) ack = 0;
        repeat (2) @(negedge clk);
        @(negedge clk) grant = 4'b0110; @(negedge clk) grant = 0;      // two grants: the immediate check fires
        repeat (2) @(negedge clk);
        $display("assume broken by a 2-cycle req: %0d; immediate one-hot check: %0d; cover req ##2 req hit %0d times",
                 assume_fails, imm_fails, gaps);
        if (win_fails != 1 || assume_fails != 1 || imm_fails != 1 || gaps == 0) $fatal(1, "FAIL: %0d %0d %0d %0d", win_fails, assume_fails, imm_fails, gaps);
        $display("PASS: overlapping attempts passed on their own, and assert, assume, cover and the immediate check each did their job");
        $finish;
      end
    endmodule
    Terminal
    two overlapping requests, one ack inside both windows: 0 failures
    second request unanswered: 1 failure
    assume broken by a 2-cycle req: 1; immediate one-hot check: 1; cover req ##2 req hit 1 times
    PASS: overlapping attempts passed on their own, and assert, assume, cover and the immediate check each did their job

    Remember

    • Concurrent assertions sample just before the edge.

    • Every edge starts an independent attempt.

    • assume is checked like assert in simulation.

    Rule of thumb: answer the direct question first, then volunteer the follow-up.

    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

    • Handshake Drills

    • Q&A: Operators & Semantics

    Previous

    138 / 259

    Next: Q&A: Operators & Semantics

    Up next

    Up nextQ&A: Operators & Semantics

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Handshake Drills
    • Pulse & Stability Drills
    • Ordering & Counting Drills