SystemVerilog
Assertions (SVA)
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:
- Immediate assertions — check a condition at a single point in procedural code (like a C-style assert)
- Concurrent assertions — check temporal properties that span multiple clock cycles, running continuously in the background
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.
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
$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:
// 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
| Syntax | Meaning | Example |
|---|---|---|
| ##1 | Exactly 1 clock cycle later | req ##1 ack |
| ##[1:4] | Between 1 and 4 cycles later | req ##[1:4] ack |
| ##[0:$] | 0 or more cycles (eventually) | valid ##[0:$] last |
| ##[*] | Same as ##[0:$] | busy[*] ##1 done |
| ##0 | Same clock edge (simultaneous) | valid ##0 !busy |
Properties — asserting a sequence
// 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."
| Operator | Name | Consequent starts at |
|---|---|---|
| |-> | Overlapping implication | Same clock edge as last antecedent cycle |
| |=> | Non-overlapping implication | One cycle AFTER last antecedent cycle |
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:
| Function | Returns 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 |
// 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:
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
| Keyword | Role | Fails if | Used For |
|---|---|---|---|
| assert property | Bug checker | Property violated | RTL verification, protocol checking |
| assume property | Input constraint | — | Formal verification — constrain stimulus |
| cover property | Coverage point | Sequence never seen | Ensure 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:
// 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:
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 / Function | Meaning |
|---|---|
| ##N | Exactly 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 |