SV-01

SystemVerilog
Assertions (SVA)

EcrioniX · SystemVerilog Series · ~20 min read · 8 Code Examples
Concurrent Assertion — AXI Handshake Property
clk valid ready antecedent check assert property (@posedge clk) valid |-> ##[1:3] ready;

What are SVA Assertions?

SystemVerilog Assertions (SVA) let you embed formal behavioral specifications directly inside RTL or testbenches. Instead of checking outputs manually in `$display` statements, assertions declare what must always be true — and the simulator reports a failure the instant it's violated.

There are two kinds:

1. Immediate Assertions

An immediate assertion evaluates a Boolean expression right now, at the simulation time it executes. It lives inside an always or initial block.

SystemVerilog
module tb;
  logic clk, valid, ready;

  always @(posedge clk) begin
    // Immediate assertion: fires at THIS clock edge
    assert (valid !== 1'bx)
      else $error("valid is X at time %0t", $time);

    // Named assertion — name appears in waveform tool
    chk_no_x: assert (ready !== 1'bx)
      else $fatal("ready driven X — fatal error");
  end
endmodule
💡
Severity levels: The else clause can use $warning, $error, $fatal, or $info. $fatal stops the simulation. $error reports and continues. $warning is non-stopping advisory.

2. Concurrent Assertions — Sequences & Properties

Concurrent assertions use sampled values (clocked snapshots) to check behavior across multiple clock cycles. They are built from sequences (describe what happens) and properties (assert the sequence must hold).

Sequences — the building blocks

A sequence describes a pattern of signal values across clock cycles:

SystemVerilog
// req must be high, then exactly 1 cycle later ack must be high
sequence req_then_ack;
  req ##1 ack;
endsequence

// req high for 4 consecutive cycles
sequence req_burst;
  req [*4];           // req ##0 req ##0 req ##0 req
endsequence

// valid holds while !last, then last goes high within 1..8 cycles
sequence axi_beat;
  (valid && !last)[*1:8] ##1 (valid && last);
endsequence

The ##N delay operator

SyntaxMeaningExample
##1Exactly 1 clock cycle laterreq ##1 ack
##[1:4]Between 1 and 4 cycles laterreq ##[1:4] ack
##[0:$]0 or more cycles (eventually)valid ##[0:$] last
##[*]Same as ##[0:$]busy[*] ##1 done
##0Same clock edge (simultaneous)valid ##0 !busy

Properties — asserting a sequence

SystemVerilog
// Every time req goes high, ack must follow within 1..4 cycles
property p_req_ack;
  @(posedge clk)
  req |-> ##[1:4] ack;
endproperty

// Instantiate as an assertion
A_req_ack: assert property (p_req_ack)
  else $error("req-ack timeout at %0t", $time);

// Cover: track how often the sequence fires
C_req_ack: cover property (p_req_ack);

3. Implication Operators: |-> and |=>

Implication makes an assertion conditional: "if the antecedent matches, then check the consequent."

OperatorNameConsequent starts at
|->Overlapping implicationSame clock edge as last antecedent cycle
|=>Non-overlapping implicationOne cycle AFTER last antecedent cycle
SystemVerilog — Implication examples
property p_overlap;
  @(posedge clk)
  req |-> ack;       // when req=1, ack must ALSO be 1 at same edge
endproperty

property p_nonoverlap;
  @(posedge clk)
  req |=> ack;       // when req=1, ack must be 1 the NEXT cycle
endproperty

// AXI: when valid=1 and ready=0, valid must not drop (data stability)
property p_axi_stable;
  @(posedge clk)
  (valid && !ready) |=> valid;
endproperty

A_axi_stable: assert property (p_axi_stable)
  else $error("AXI protocol violation: valid dropped before ready");

4. $rose, $fell, $stable, $past

These built-in sequence functions sample signal edges and historical values:

FunctionReturns true when
$rose(sig)sig was 0 at previous edge, is 1 now
$fell(sig)sig was 1 at previous edge, is 0 now
$stable(sig)sig has the same value as previous edge
$past(sig)value of sig at the previous clock edge
$past(sig,N)value of sig N cycles ago
SystemVerilog
// Data must be stable while valid is asserted
property p_data_stable;
  @(posedge clk)
  (valid && !$rose(valid)) |-> $stable(data);
endproperty

// Output must equal input delayed by 2 cycles (pipeline check)
property p_pipeline;
  @(posedge clk)
  disable iff (rst)
  dout === $past(din, 2);
endproperty

A_pipe: assert property (p_pipeline)
  else $error("Pipeline mismatch: dout=%0h, expected=%0h", dout, $past(din,2));

5. disable iff — Suppressing During Reset

During reset, most signals are undefined. The disable iff clause suppresses an assertion when a condition (like reset) is active, preventing false failures:

SystemVerilog
property p_no_double_grant;
  @(posedge clk) disable iff (!rst_n)
  $onehot0(grant);    // at most one grant bit high at a time
endproperty

// Without disable iff, this would fire during reset when grant is X
A_grant: assert property (p_no_double_grant)
  else $fatal("Multiple grants detected!");

6. assert vs assume vs cover

KeywordRoleFails ifUsed For
assert propertyBug checkerProperty violatedRTL verification, protocol checking
assume propertyInput constraintFormal verification — constrain stimulus
cover propertyCoverage pointSequence never seenEnsure corner cases are exercised

7. Binding Assertions to Modules

The bind statement attaches an assertion module to a DUT without touching the DUT source. This is the cleanest way to add assertions non-intrusively:

SystemVerilog — bind statement
// Separate assertion module
module fifo_assertions (
  input clk, rst_n, wr_en, rd_en, full, empty
);
  property p_no_wr_when_full;
    @(posedge clk) disable iff (!rst_n)
    full |-> !wr_en;
  endproperty

  property p_no_rd_when_empty;
    @(posedge clk) disable iff (!rst_n)
    empty |-> !rd_en;
  endproperty

  A_wr_full:  assert property (p_no_wr_when_full)  else $error("Write to full FIFO!");
  A_rd_empty: assert property (p_no_rd_when_empty) else $error("Read from empty FIFO!");
endmodule

// Bind it to the DUT instance — zero DUT code changes
bind sync_fifo fifo_assertions u_chk (
  .clk(clk), .rst_n(rst_n),
  .wr_en(wr_en), .rd_en(rd_en),
  .full(full), .empty(empty)
);

8. Complete Example — AXI-Stream Checker

A realistic SVA checker for AXI-Stream protocol rules — TVALID must not drop while TREADY is low, and TLAST must eventually arrive:

SystemVerilog — AXI-Stream SVA
module axis_checker (
  input clk, rst_n,
  input tvalid, tready, tlast,
  input [7:0] tdata
);

  // AXI-Stream Rule 1: once valid asserted, hold until handshake
  property p_valid_stable;
    @(posedge clk) disable iff (!rst_n)
    (tvalid && !tready) |=> tvalid;
  endproperty

  // AXI-Stream Rule 2: data must be stable while valid & !ready
  property p_data_stable;
    @(posedge clk) disable iff (!rst_n)
    (tvalid && !tready) |=> $stable(tdata);
  endproperty

  // AXI-Stream Rule 3: tlast must arrive within 256 beats of burst start
  property p_burst_end;
    @(posedge clk) disable iff (!rst_n)
    $rose(tvalid) |-> ##[1:256] (tvalid && tlast);
  endproperty

  A_valid:  assert property (p_valid_stable)  else $error("TVALID dropped before handshake");
  A_data:   assert property (p_data_stable)   else $error("TDATA changed before handshake");
  A_burst:  assert property (p_burst_end)     else $error("Burst has no TLAST within 256 beats");

  // Coverage: ensure both paths (last, no-last) are exercised
  C_tlast:  cover property (@(posedge clk) tvalid && tready && tlast);
  C_nodata: cover property (@(posedge clk) !tvalid);

endmodule

Common SVA Operators Quick Reference

Operator / FunctionMeaning
##NExactly N cycle delay
##[m:n]m to n cycles delay range
[*N]Repeat exactly N times
[*m:n]Repeat m to n times
|->Overlapping implication (same cycle)
|=>Non-overlapping implication (next cycle)
$rose(s)Signal had 0→1 transition
$fell(s)Signal had 1→0 transition
$stable(s)Signal unchanged from previous edge
$past(s,N)Value of s N cycles ago
$onehot(v)Exactly one bit of v is 1
$onehot0(v)At most one bit of v is 1
disable iff (c)Suppress assertion while c is true
🔗
Next: Now that you can write assertions, learn SystemVerilog Classes & OOP (SV-02) — the foundation of constrained-random testbenches.