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

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
  • Advanced RTL28
    • Parameterization and Generate Hub
    • Top-Level Integration Hub
    • Synthesis Intent Hub
    • Style and Static Checks Hub
    • Assertions in RTL Hub
      • Immediate vs concurrent assertions
      • assert, cover, and assume roles
      • Common RTL property templates
      • SVA Cheat Sheet and Bug Hunt
    • Formal Verification Basics Hub
    • Reusable SystemVerilog for RTL
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Assertions in RTL Hub
  1. Topics
  2. RTL Design
  3. Advanced RTL
  4. Assertions in RTL Hub
  5. Immediate vs concurrent assertions

Part 8 · Advanced · Advanced

Immediate vs concurrent assertions

Immediate assertions check code as it runs; concurrent assertions check behavior across clock cycles.

On this page

    Why this matters

    An immediate assertion in always_comb can fire on a glitch that no clock edge ever sees. A concurrent assertion samples at the clock and cannot, so choosing the wrong one gives false failures or missed ones.

    After this lesson you can: choose immediate or concurrent form, write a request window with ##[1:3], and read the failing trace from a waveform.

    One moment or many cycles

    Use immediate assertions for single-time procedural checks. Use concurrent assertions for clocked temporal behavior across cycles.

    The two forms differ in when they evaluate and where they live.

    Immediate

    Concurrent

    Evaluated

    When execution reaches it

    Every clock edge, by sampling

    Written

    Inside procedural code

    Alongside the design, bound to a clock

    Checks

    One invariant at one moment

    Behavior across cycles

    Example

    assert (!(req && grant)) in always_comb

    req |-> ##[1:3] grant on posedge clk

    Operator cheat sheet

    Concurrent properties are built from a handful of operators. Each one is evaluated at a clock edge on the values sampled there.

    Operator

    Meaning

    Example

    Reads as

    a |-> b

    Overlapping implication: when a holds, b must hold at the same edge

    gnt |-> req

    A grant needs a request this cycle

    a |=> b

    Non-overlapping: b must hold at the next edge (same as a |-> ##1 b)

    !cfg_we |=> $stable(cfg_q)

    No write now, no change next cycle

    ##n, ##[m:n]

    Wait exactly n edges, or any count from m to n

    req |-> ##[1:3] grant

    Grant one to three cycles after the request

    $past(x, n)

    Value of x sampled n edges earlier (n defaults to 1)

    gnt == $past(req, 2)

    Grant equals the request from two cycles ago

    $rose(x), $fell(x)

    x changed 0 to 1 (or 1 to 0) since the previous edge

    $rose(rst_n) |-> state_q == IDLE

    Out of reset, the state is idle

    $stable(x)

    x has the same value as at the previous edge

    busy |-> $stable(addr)

    Address holds while busy

    disable iff (c)

    Stop checking, and drop attempts in flight, while c is true

    disable iff (!rst_n)

    Ignore cycles in reset

    The request window

    The property below says every request is answered within one to three cycles. The |-> opens the window when req holds, and ##[1:3] accepts a grant on any of the next three edges.

    design.sv
    // snippet
    assert property (@(posedge clk) disable iff (!rst_n)
      req |-> ##[1:3] grant);

    The request at slot 2 is answered at slot 6, outside the three-cycle window. The property fails at slot 5.

    • Slot 1: idle, req 0 and grant 0.

    • Slot 2: req rises, and the window starts.

    • Slot 3: one cycle on, grant still 0.

    • Slot 4: two cycles on, grant still 0.

    • Slot 5: three cycles on, grant still 0, so the window expires here.

    • Slot 6: grant finally rises, four cycles after the request, one cycle too late.

    • Slot 7: both low again.

    The procedural monitor below models this window for known 0/1 inputs, reset asserted across sampling edges, and reset release/input changes on falling edges. Concurrent SVA samples pre-edge values (Preponed); the monitor reads the same stable inputs before NBA updates. Its violation register is observed after NBA with #1. This is a scoped digital equivalence, not a claim about X semantics or every simulator's SVA support.

    D=2 edge

    Sampled req / cnt / grant

    After NBA cnt / grant

    k

    1 / 0 / 0: open window

    2 / 0

    k+1

    0 / 2 / 0: age 1

    1 / 1

    k+2

    0 / 1 / 1: age 2, satisfy

    0 / 0

    The combinational grant becomes high after k+1 but is sampled high at k+2. Counting it after NBA at k+1 would label the response one cycle too early. Sample grants before updates; observe registered violation flags after updates.

    Common mistakes

    Common pitfall

    An immediate assertion in always_comb evaluates on every combinational event, including brief glitches. A short overlap of req and grant during settling can fire it even when no clock edge sees both high. Use a deferred immediate assertion (assert final (...)) for glitch-free procedural checks, or a concurrent assertion for clocked behavior.

    design.sv
    1
    2
    3
    4
    5
    // snippet
    always_comb begin
      assert (!(req && grant))
        else $error("req and grant both high");
    end

    Interview

    Likely question: “When do immediate and concurrent assertions differ?”

    • Immediate checks current values when execution reaches it.

    • Concurrent samples values on the clock and reasons across cycles.

    • Answer with the glitch case: simple immediate assertions can fire on delta glitches; deferred (assert final) and concurrent assertions do not.

    Check yourself A request holds at cycle k. Which cycles may carry its grant under req |-> ##[1:3] grant?

    Cycles k+1, k+2, or k+3. A grant only at k+4 is a violation, as the waveform above shows.

    Check yourself An immediate check of req and grant fires, yet no clock edge ever sees both high. What happened?

    The always_comb block evaluated during a settling glitch. A deferred assert final checks only the settled values at the end of the time step, and the concurrent property, sampled on the clock, never sees the glitch.

    Testbench

    The window monitor, a two-cycle responder inside the window, a four-cycle responder outside it, and the testbench. It runs 200 request pulses against each responder.

    grant_resp.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
    // A responder with latency D: grant is high exactly D cycles after a req
    // pulse, as sampled on the clock.
    module grant_resp #(parameter [2:0] D = 2) (
      input  logic clk, rst_n, req,
      output logic grant
    );
      logic [2:0] cnt;
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) cnt <= 3'd0;
        else if (req) cnt <= D;
        else if (cnt != 3'd0) cnt <= cnt - 3'd1;
      end
      assign grant = (cnt == 3'd1);
    endmodule
    
    // Plain-RTL form of: req |-> ##[1:3] grant.
    // Each pending bit records one request age; overlapping windows are preserved.
    module grant_window_mon (
      input  logic clk, rst_n, req, grant,
      output logic violation
    );
      logic [2:0] pending;
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin pending <= 3'd0; violation <= 1'b0; end
        else begin
          violation <= pending[2] && (grant !== 1'b1);
          if (grant === 1'b1) pending <= {2'b00, req};
          else               pending <= {pending[1:0], req};
        end
      end
    endmodule
    
    • Expected for 200 pulses against the two-cycle responder: 200 sampled grants and no violations.

    • Expected for the same 200 pulses against the four-cycle responder: 200 violations, one per pulse.

    • Directed overlap checks: held requests still expire, and a same-edge grant does not satisfy a newly opened window.

    Remember

    • Immediate checks the values when execution reaches it; concurrent samples on the clock and reasons across cycles.

    • req |-> ##[1:3] gnt opens a window at the request and accepts a grant on any of the next three edges.

    • Use a deferred assertion in always_comb so only settled values are checked.

    Rule of thumb: sample grants before updates, and observe registered violation flags after updates.

    How this was checked

    Testbench run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It runs 200 request pulses against a two-cycle responder (no violations) and a four-cycle responder (200 violations). The procedural monitor is a scoped digital equivalence for known 0 and 1 inputs; it makes no claim about X semantics or every simulator's SVA support.

    Keep going

    • Code lab: Immediate assert

    • Interview: run a mock interview on this topic

    Related topics

    • assert, cover, and assume roles

    • Common RTL property templates

    • SVA cheat sheet and bug hunt

    Previous

    259 / 350

    Next: assert, cover, and assume roles

    Up next

    Up nextassert, cover, and assume roles

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Common RTL property templates
    • SVA Cheat Sheet and Bug Hunt
    clkreqgrant
    A request answered four cycles later, outside the ##[1:3] window