A testbench that drives stimulus and checks data values is necessary but not sufficient. A DUT can produce correct output values while simultaneously violating the AXI protocol — for example, by dropping VALID between beats without a handshake, or by issuing a burst that crosses a 4KB page boundary. These violations will cause failures in a real system but may go undetected in a functional simulation unless protocol rules are explicitly checked. SystemVerilog Assertions (SVA) are the right tool for this. This post explains the four checker modules in verilaxi and how they work with Verilator.
SVA in one minute
Before diving into AXI-specific properties, it helps to decode the basic SVA syntax. A property is a statement about signal behaviour over time. In practice, most protocol checks answer questions such as: "if this happened on one clock edge, what must still be true on the next edge?" or "if this handshake occurred, what condition must hold immediately?".
property p_example;
@(posedge clk) disable iff (!rst_n)
antecedent |=> consequent;
endproperty
Skeleton of a clocked SVA property
In that form:
@(posedge clk)means the property is sampled on every rising clock edge.disable iff (!rst_n)turns the property off during reset.- the antecedent is the condition being watched.
|=>means "if the antecedent is true now, the consequent must be true on the next sampled cycle".- the consequent is the rule that must hold.
For AXI, that maps naturally onto handshake rules. A property such as
tvalid && !tready |=> tvalid;
simply says: if VALID is high and READY is low on this clock edge, then VALID must still be high on the next clock edge. In other words, once the master has offered a transfer, it must keep offering it until the handshake completes.
Another common operator is |->, which is used for same-cycle implications. In protocol checkers it is useful for statements like "if a beat is accepted with WLAST high, the internal beat counter must already be at the last beat." The key point is that SVA is not magic syntax to memorise in isolation; it is a compact way of writing temporal protocol rules that designers already describe informally.
One important caveat: Verilator post-NBA evaluation
Before showing the properties, there is one Verilator-specific behaviour worth understanding upfront, because it affects how every VALID stability property must be written.
Verilator evaluates assert property consequents in the post-NBA region — after always_ff blocks have settled — rather than in the preponed region mandated by the SystemVerilog specification. This means the checker sees signal values that have already been updated by the current clock edge. If a master correctly deasserts VALID one cycle after a handshake completes, the naive property sees valid=0 at that edge and fires a false positive — even though the behaviour is correct.
Two guards are applied consistently across all properties in verilaxi to handle this:
|| treadyin the consequent — allows the master to deassert VALID if READY is high at the next cycle, meaning the handshake completed!$fell(tready)in the antecedent — excludes the cycle where READY transitions from 1 to 0, which can happen when a FIFO fills on the same edge as a handshake
All properties shown below include both guards. A spurious deassert — VALID going low while READY is already low — is still caught.
The four checkers
Verilaxi includes four SVA checker modules, each focused on a specific aspect of the AXI protocol:
axis_checker.sv— AXI4-Stream: TVALID stability, payload stability, no-X checksaxil_checker.sv— AXI4-Lite: per-channel VALID stability, payload stability, error response detectionaxi_mm_checker.sv— AXI4-Full: write and read channel rules, WLAST and RLAST alignmentaxi_4k_checker.sv— 4KB boundary: no AW or AR burst may cross a 4KB page boundary
Each checker is a standalone module instantiated inside the test, alongside the DUT. It receives the same interface signals and runs continuously throughout the simulation. A failing assertion stops the simulation immediately and reports the time, hierarchy path, and property name.
VALID stability
The AXI specification requires that once a master asserts VALID it must not deassert it until the handshake completes (READY goes high). This is the most commonly violated rule when an FSM or driver has a bug. The assertion is straightforward:
Read the property directly in AXI terms: "if the channel is stalled this cycle (VALID=1, READY=0), then the offer must still be present next cycle." That is exactly the English-language rule from the AXI specification, expressed as a one-line temporal check.
// Once TVALID is asserted it must remain high until TREADY goes high
property p_tvalid_stable;
@(posedge clk) disable iff (!rst_n)
tvalid && !tready |=> tvalid;
endproperty
assert property (p_tvalid_stable)
else $error("TVALID deasserted without a handshake");
Basic TVALID stability property
In Verilator, however, this property fires a false positive at the handshake cycle — for the reason explained at the top of this post. The fix applies the two guards:
// Corrected for Verilator post-NBA evaluation
property p_tvalid_stable;
@(posedge clk) disable iff (!rst_n)
tvalid && !tready && !$fell(tready) |=> tvalid || tready;
endproperty
VALID stability property corrected for Verilator's post-NBA evaluation
Payload stability
While VALID is asserted and READY is low (no handshake yet), all payload signals — address, data, WLAST, burst parameters — must remain stable. A driver that updates these signals before the handshake completes is presenting incoherent data to the slave.
This is where the combination of AXI and SVA becomes especially natural. Once the handshake is stalled, the channel payload is logically frozen. The property is therefore the same shape as the VALID rule, but with $stable(...) in the consequent instead of the signal itself.
// Payload must not change while TVALID is high and TREADY is low
property p_tdata_stable;
@(posedge clk) disable iff (!rst_n)
tvalid && !tready && !$fell(tready) |=> $stable(tdata) || tready;
endproperty
assert property (p_tdata_stable)
else $error("TDATA changed while TVALID high and TREADY low");
Payload stability property for AXI-Stream TDATA
WLAST alignment
For AXI4-Full write bursts, WLAST must be asserted on exactly the last beat of the burst — the beat numbered AWLEN. A DUT that asserts WLAST early (truncating the burst) or late (extending it) will confuse the interconnect. The axi_mm_checker counts W-channel beats after each AW handshake and checks that WLAST arrives exactly at beat AWLEN+1.
Here the property is paired with a small amount of supporting state. SVA is excellent at expressing time relationships, but it often works best when simple helper logic captures context such as the expected burst length. In other words, the checker is not "only assertions"; it is a small monitor plus assertions over that monitor state.
// WLAST must be asserted on beat number awlen+1 and not before
int unsigned beat_count;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
beat_count <= 0;
else if (wvalid && wready) begin
if (wlast) beat_count <= 0;
else beat_count <= beat_count + 1;
end
end
property p_wlast_correct;
@(posedge clk) disable iff (!rst_n)
wvalid && wready && wlast |-> (beat_count == captured_awlen);
endproperty
assert property (p_wlast_correct)
else $error("WLAST asserted at beat %0d, expected beat %0d",
beat_count, captured_awlen);
WLAST alignment check (simplified from tb/assertions/axi_mm_checker.sv)
The 4KB boundary rule
The AXI4 specification forbids a burst from crossing a 4KB address boundary. This rule exists because many interconnect fabrics map 4KB pages to different slaves; a burst crossing the boundary would require the interconnect to split it, which most implementations do not support. The rule is subtle: it depends on the burst base address, beat size, and burst length together.
The check computes the address of the last byte in the burst and verifies it falls within the same 4KB page as the first byte:
This is a good example of why protocol assertions matter even when the RTL appears straightforward. A burst can have the correct AWLEN, correct AWSIZE, and correct VALID/READY behaviour, yet still be illegal if the resulting address range crosses a page boundary. SVA lets that derived rule be checked continuously instead of relying on a human to infer it from the waveform.
// No AW burst may cross a 4KB boundary
property p_aw_no_4k_cross;
logic [ADDR_WIDTH-1:0] last_byte_addr;
@(posedge clk) disable iff (!rst_n)
(awvalid && awready,
last_byte_addr = awaddr + ((awlen + 1) << awsize) - 1)
|->
(awaddr[11:0] + ((awlen + 1) << awsize)) <= 12'hFFF + 1;
endproperty
assert property (p_aw_no_4k_cross)
else $error("AW burst crosses 4KB boundary: addr=0x%0h len=%0d size=%0d",
awaddr, awlen, awsize);
4KB boundary checker for the AW channel (from tb/assertions/axi_4k_checker.sv)
This is the check that verilaxi's S2MM and MM2S engines are specifically designed to satisfy. Both engines include a two-stage PREP pipeline that clips burst length at 4KB boundaries before issuing the AW or AR address. The assertion fires immediately if that logic has a bug.
Enabling assertions in Verilator
Assertions are compiled but silently ignored unless the --assert flag is passed to Verilator. Without it, all assert property and cover property statements are no-ops. In verilaxi this flag is always enabled in mk/config.mk:
VERILATOR_FLAGS += --assert
When an assertion fires, Verilator prints the simulation time, the full hierarchical path of the failing property, and the message from the else $error(...) clause, then terminates the simulation. The FST waveform written up to that point can be opened in Surfer or GTKWave to inspect the signals at and before the failure time.
Summary
SVA protocol checkers are a lightweight and highly effective addition to any AXI testbench. VALID stability and payload stability catch the most common driver and FSM bugs. WLAST and RLAST alignment checkers catch burst-count errors. The 4KB boundary checker is essential for any engine that issues multi-beat bursts near page boundaries. All four checks in verilaxi are Verilator-compatible, with the post-NBA sampling caveat handled by the !$fell(tready) guard and the || tready consequent. The full checker source is available in verilaxi.
Read next:
Building SystemVerilog AXI VIP for Fast Bring-Up for the task-based environment that these assertions watch.
AXI DMA — Moving Data Without the CPU for a concrete example where WLAST and 4KB checks matter in practice.
Implementation pointers in verilaxi: tb/assertions/axis_checker.sv, tb/assertions/axil_checker.sv, tb/assertions/axi_mm_checker.sv, and tb/assertions/axi_4k_checker.sv.
References:
[1] ARM. AMBA AXI and ACE Protocol Specification. 2011
[2] IEEE Std 1800-2017. SystemVerilog Language Reference Manual.
[3] Building SystemVerilog AXI VIP for Fast Bring-Up — sistenix.com
Also available in GitHub.