Cache Coherence

Mastering AXI ACE (Dark Mode) - VLSI Wizard

Mastering AXI ACE: The Definitive Guide

Deep Technical Insights for VLSI Engineers (Dark Edition)

In high-performance multi-core SoCs, maintaining data consistency across private caches isn't optional. Today, we're doing a full technical teardown of the ARM AMBA 4 ACE protocol.

Why Coherence?
  • Prevents silent data corruption in parallel software.
  • Cache-to-cache transfers beat DRAM latency by 10-100x.
  • Reduces memory controller power consumption.

1. The MOESI Engine

StateFull NameVerification Key
MModifiedExclusive & Dirty. Must write-back.
OOwnedShared & Dirty. Supplies data to snoopers.
EExclusiveClean & Unique. Silent upgrade to M.
SSharedClean & Shared. Read-only copies.
IInvalidNot present. Triggers snoop/miss.

2. AxSNOOP Encoding: The Command Center

ARSNOOP (4 Bits) - Read Transactions

ValueTransactionSnoop Effect
0100ReadSharedOthers keep copies in S state.
0101ReadUniqueOthers forced to I state.
0001ReadOnceCoherent read (non-caching).

AWSNOOP (3 Bits) - Write Transactions

ValueTransactionSnoop Effect
001WriteUniqueInvalidates others, then writes.
011WriteBackEvict dirty line (M → I).

3. The Gating Signals

AxCACHE[3:0]

Bit[1] (Modifiable): The master switch. If 0, the interconnect ignores coherence.

AxDOMAIN[1:0]

  • 00: Non-shareable
  • 01: Inner Shareable (Cluster)
  • 10: Outer Shareable (System)
  • 11: ILLEGAL for ACE transactions.
WIZARD VERIFICATION TIP: Deadlocks often hide in the CD (Snoop Data) channel. Ensure your UVM scoreboards track the M → S transition carefully. Also, remember AWSNOOP is 3 bits—don't truncate a 4-bit signal into it!

© 2026 VLSI Wizard | Dark Mode Edition

SystemVerilog Assertions — A Comprehensive Guide

SystemVerilog Assertions: A Comprehensive Guide
SystemVerilog Formal Verification Design Verification

SystemVerilog
Assertions
A Comprehensive Guide

From Boolean building blocks to advanced temporal properties — everything you need to write robust formal verification properties for your hardware design, with real waveform intuition for every operator.

Jagadish  ·  Senior Verification Engineer Standard: IEEE 1800-2017 ~15 min read

01 Why SystemVerilog Assertions?

In hardware design, creating a functionally correct circuit is only half the battle. The other half — proving it works exactly as intended across all possible scenarios — demands a rigorous, mathematically grounded approach. This is the domain of formal verification.

SystemVerilog Assertions (SVAs) are the industry-standard mechanism for encoding design intent as mathematical properties. A formal verification tool can then exhaustively check these properties against your RTL — eliminating entire classes of bugs that simulation might miss even after months of random testing.

Formal vs. Simulation

Simulation checks specific scenarios you thought to write. Formal verification checks all reachable states — including corner cases you didn't think of. A single well-written assertion can catch bugs that 10 million simulation cycles would never expose.

SVA operates in two contexts: simulation (assertions fire at runtime, flagging failures immediately) and formal property verification (FPV) (a solver mathematically proves or disproves properties without running a testbench). Both use the same syntax — write once, verify everywhere.

02 The Three Statement Types

Every SVA statement is one of three kinds. Understanding the distinction is fundamental — they answer different questions about your design.

assert
Assertions

Behaviors that must always be true. A failing assertion is a definitive bug. Example: two mutually exclusive grant signals must never be high simultaneously.

assume
Assumptions

Constraints on the input environment. The formal tool restricts its exploration to inputs that satisfy all assumptions — they define the "legal" operating space of your DUT.

cover
Cover Properties

Scenarios that must be reachable. A cover failure means the behavior is impossible given your assumptions — often exposing over-constrained environments.

The Vacuity Trap

An assertion whose antecedent is never triggered always passes — but trivially. This is called vacuous passing and is one of the most dangerous failure modes in formal verification. Always pair every assert property with a corresponding cover property to confirm the trigger condition is actually reachable.

03 Immediate vs. Concurrent Assertions

Aspect Immediate Concurrent
Evaluation modelProcedural — like an if statementClock-driven — at every specified edge
Time spanSingle point in timeCan span multiple cycles
Clock awarenessNoneExplicit clock specification required
Used in FPV?No (simulation only)Yes — primary FPV tool
PlacementInside procedural blocksModule, interface, or program scope
Reset handlingManual (wrap in if)Built-in via disable iff

Immediate Assertion

SystemVerilog Evaluated when procedural code reaches this line
always_comb begin
    // Fires immediately — no clock, no history
    assert (!(grant_a && grant_b))
        else $error("[ASSERT] Mutual exclusion violated at time %0t", $time);
end

Concurrent Assertion

SystemVerilog Checked at every posedge clk — can span cycles
// Named assertion — label is critical for debug reports
P_req_ack: assert property (
    @(posedge clk) disable iff (rst)
    req |-> ##[1:4] ack
) else $error("[P_req_ack] ACK not received within 4 cycles of REQ!");

04 SVA Syntax: A Layered Architecture

SVA syntax is a four-layer stack. Each layer builds on the one below — more expressive, higher-level constructs are composed from simpler primitives. Understanding this hierarchy makes complex properties much easier to read and write.

L4
Assertion Directives
assert / assume / cover — instantiate a property and define pass/fail actions
L3
Properties
Combine sequences with implication (|->, |=>), not, disable iff, s_eventually
L2
Sequences
Ordered Boolean expressions across clock cycles using ##, [*], [->], and, or
L1
Boolean Expressions
Combinatorial conditions that evaluate true/false at a single clock tick — the atomic unit of SVA
SystemVerilog All four layers in one property
// L1: Boolean expressions — req, ack (simple signal references)
// L2: Sequence — req ##[1:4] ack (spans multiple cycles)
// L3: Property — with clock, reset, implication
property p_handshake;
    @(posedge clk) disable iff (rst)
    req |-> ##[1:4] ack;
endproperty

// L4: Directive — instantiate as assertion
P_handshake: assert property (p_handshake);
C_handshake: cover  property (p_handshake); // Vacuity check

05 Key Sequence Operators

These operators form the grammar of SVA sequences. Each one has a precise timing meaning — the waveforms below make the behavior concrete.

OperatorNameMeaning & Example
##n Fixed Delay Exactly n clock cycles elapse.
req ##2 ack  →  ack must be high exactly 2 cycles after req
##[a:b] Range Delay Between a and b cycles inclusive. Use $ for unbounded.
req ##[1:4] ack  →  ack arrives in 1 to 4 cycles
[*n] Consecutive Repetition Boolean true for exactly n back-to-back cycles.
valid[*3]  →  valid must be high for 3 consecutive cycles
[*a:b] Variable Repetition True for a to b consecutive cycles.
busy[*1:8]  →  busy stays high for 1 to 8 cycles
[->n] Goto Repetition True exactly n times, not necessarily consecutive; last occurrence ends the sequence.
ack[->2]  →  ack must be seen 2 times total
[=n] Non-Consecutive Repetition Like [->n] but the sequence can continue after the last match.
ack[=2]  →  ack seen 2 times (sequence may continue after)
and Sequence AND Both sequences must match; they start together but can end at different times.
(a ##1 b) and (c ##2 d)
intersect Sequence Intersect Like and but both sequences must end at the same cycle.
(a[*3]) intersect (b ##1 c ##1 d)
within Subsequence First sequence occurs within the window of the second.
a within (b ##[0:$] c)

06 Implication Operators: Overlapped vs. Non-Overlapped

Implication is the most-used — and most-confused — concept in SVA. Both operators say "if the antecedent matches, then the consequent must also match," but they differ in when the consequent starts.

Overlapped Implication |->

The consequent starts in the same cycle as the last cycle of the antecedent. Think of it as "immediately check."

// Waveform: req |-> ack (overlapped)
T0
T1
T2
T3
T4
T5
clk
req
0
1
0
0
1
0
ack
0
1
0
0
0
0
result
PASS
FAIL
T1: req=1, ack=1 in SAME cycle → PASS  |  T4: req=1, ack=0 in same cycle → FAIL

Non-Overlapped Implication |=>

The consequent starts in the next cycle after the antecedent ends. Equivalent to |-> ##1. Use this for registered outputs — the DUT needs a clock edge to produce a response.

// Waveform: req |=> ack (non-overlapped)
T0
T1
T2
T3
T4
T5
clk
req
0
1
0
0
1
0
ack
0
0
1
0
0
0
result
PASS
FAIL
T1: req triggers → checks ack at T2 (next cycle) → PASS  |  T4: req triggers → checks T5, ack=0 → FAIL
🎯
Which to use?

Use |-> for combinatorial outputs (mux selects, arbitration grants that respond in the same cycle). Use |=> for registered outputs (pipeline stages, FIFO flags, FSM state transitions that take a clock cycle to respond).

07 Auxiliary Code & Sampled Value Functions

Complex behaviors are often impossible to capture in a single self-contained property. Auxiliary code — regular SystemVerilog logic that tracks state — bridges this gap. The principle: compute the complex state in an always block, then assert a simple property on the result.

SystemVerilog Auxiliary code tracks consecutive valid cycles
// Auxiliary: count consecutive valid beats
int valid_run;
always @(posedge clk)
    valid_run <= valid ? valid_run + 1 : 0;

// Simple property — never hold valid for more than 8 consecutive cycles
P_max_burst: assert property (
    @(posedge clk) disable iff (rst)
    valid_run <= 8
);

Sampled Value Functions

These built-in functions give your properties visibility into signal history across clock cycles:

$past(sig, n)

Value of sig as it was n clock cycles ago. Default n=1.

$rose(sig)

True if sig transitioned 0→1 on the current clock edge.

$fell(sig)

True if sig transitioned 1→0 on the current clock edge.

$changed(sig)

True if sig has a different value than the previous cycle.

$stable(sig)

True if sig has the same value as the previous cycle.

$countones(sig)

Returns number of bits set to 1. Essential for one-hot arbitration checks.

SystemVerilog Sampled functions in practice
// $past: output must equal input from 2 cycles ago (pipeline latency check)
P_pipe_latency: assert property (
    @(posedge clk) disable iff (rst)
    out_valid |-> (dout === $past(din, 2))
);

// $rose: on rising edge of valid, data must be stable for 1 more cycle
P_data_stable: assert property (
    @(posedge clk) disable iff (rst)
    $rose(valid) |=> $stable(data)
);

// $countones: one-hot grant check — exactly one grant at a time
P_one_hot_grant: assert property (
    @(posedge clk) disable iff (rst)
    |grant |-> ($countones(grant) === 1)
);

08 FPV Tool Flow

Writing good SVA properties is only half the job. Understanding how they fit into a real formal verification flow is equally important. Here's the typical FPV flow used in industry:

01
RTL Freeze
Stable DUT — at least block-level complete
02
Write Properties
assert / assume / cover in bind files
03
Constrain Inputs
assume blocks to limit legal input space
04
Run Formal Tool
JasperGold, Questa Formal, or VC Formal
05
Analyze CEX
Debug counterexamples in waveform viewer
06
Close Coverage
Ensure all covers pass, no vacuous asserts
💡
Bind Files Keep RTL Clean

Always place your property module in a separate file and use a bind statement. This gives the property module full port visibility into the DUT without touching the RTL — a clean design/verification boundary that's essential for tapeout.

09 Common Mistakes & How to Fix Them

Mistake 1: Forgetting disable iff

Without reset handling, assertions fire during reset — filling your log with false failures and masking real ones.

Bad Fires during reset
assert property (
  @(posedge clk)
  req |=> ack
);
Good Reset-safe
assert property (
  @(posedge clk)
  disable iff (rst)
  req |=> ack
);

Mistake 2: Vacuous Assertions Without Cover

Bad Could pass vacuously
P_arb: assert property (
  @(posedge clk)
  req |=> grant
);
// If req is never high, always passes!
Good Paired with cover
P_arb: assert property (
  @(posedge clk)
  req |=> grant
);
C_arb: cover property (
  @(posedge clk) req
); // Confirms req is reachable

Mistake 3: Using == Instead of === for X/Z Detection

Bad X propagates silently
// X == 1 evaluates to X (not true)
// Assertion doesn't fire on X values
assert property (
  @(posedge clk)
  state == 2'b00
);
Good Catches X/Z explicitly
// === includes X and Z in comparison
// Fires if state is X or unexpected
assert property (
  @(posedge clk)
  state !== 2'bxx
);

Mistake 4: Over-constraining with Assumptions

The Over-Constraint Trap

If your assume blocks are too tight, the formal tool will "prove" properties that only hold under impossible input conditions — and real silicon will fail. Always check: does every cover property pass? If a cover fails, your assumptions may be excluding the scenario you were trying to cover.

10 Advanced Features & Best Practices

Reset Handling with disable iff

SystemVerilog Async vs sync reset handling
// Asynchronous reset: disable iff checks continuously (not just at clock edges)
P_fifo_no_overflow: assert property (
    @(posedge clk) disable iff (rst_n === 1'b0)
    fifo_full |-> !wr_en
);

// Synchronous reset: use $rose to catch the exact reset release edge
P_sync_reset_check: assert property (
    @(posedge clk)
    $rose(rst_n) |=> (state === IDLE)
);

Liveness Properties with s_eventually

Safety properties say "bad things never happen." Liveness properties say "good things will always eventually happen." Without liveness, your design could be "safe" by doing nothing.

SystemVerilog Safety + Liveness pair
// Safety: grant is mutually exclusive
P_mutex: assert property (
    @(posedge clk) disable iff (rst)
    !(grant_a && grant_b)
);

// Liveness: every request must eventually be granted
P_liveness: assert property (
    @(posedge clk) disable iff (rst)
    req |-> s_eventually grant
);

// Together: safe arbitration that never starves

Best Practice Checklist

  • One behavior per assertion. When a property fails, you know exactly what broke. Compound properties make debug exponentially harder.
  • Always use auxiliary code for complex state. Keep the property expression itself simple — complexity lives in the helper logic.
  • Pair every assert with a cover. No exceptions. Vacuous passes are silent bugs.
  • Name every property. P_req_ack_4cyc is infinitely more useful than line 247 in a waveform failure report.
  • Use === / !== for safety-critical checks. These include X and Z in the comparison — == does not.
  • Write both safety and liveness properties. A correct arbiter must grant without deadlock — safety alone doesn't prove that.
  • Version your SVA alongside your RTL. Properties that don't track RTL changes become incorrect and misleading.
  • Check cover results, not just assert results. All covers must pass before you declare the FPV run complete.

11 Full Working Example

A complete, production-style FPV environment for a 4-bit counter with enable, synchronous reset, and a max_reached output flag. Demonstrates auxiliary code, all sampled functions, both implication operators, liveness, and the bind flow.

SystemVerilog counter.sv — DUT (do not modify for FPV)
module counter #(
    parameter WIDTH = 4
) (
    input  logic              clk, rst, en,
    output logic [WIDTH-1:0] count,
    output logic              max_reached
);
    always @(posedge clk) begin
        if (rst)           count <= '0;
        else if (en)      count <= count + 1;
    end
    assign max_reached = (count === {WIDTH{1'b1}});
endmodule
SystemVerilog counter_props.sv — Property module (bound to DUT)
module counter_props #(
    parameter WIDTH = 4
) (
    input logic              clk, rst, en,
    input logic [WIDTH-1:0] count,
    input logic              max_reached
);

// ── Auxiliary Code ──────────────────────────────────────────────────
// Track whether count has been monotonically increasing
logic count_was_incrementing;
always @(posedge clk) begin
    if (rst)
        count_was_incrementing <= 1'b0;
    else
        count_was_incrementing <= en && (count < {WIDTH{1'b1}});
end

// ── Input Constraints (Assumptions) ────────────────────────────────
// Assume rst deasserts cleanly (no glitches)
A_rst_stable: assume property (
    @(posedge clk)
    $fell(rst) |=> $stable(rst)[*4]
);

// ── Safety Properties ───────────────────────────────────────────────

// Reset clears counter in the next cycle (|=> non-overlapped: registered output)
P_reset_clears: assert property (
    @(posedge clk)
    $rose(rst) |=> (count === '0)
);

// Counter increments by exactly 1 (uses $past for previous value)
P_increment: assert property (
    @(posedge clk) disable iff (rst)
    en && (count < {WIDTH{1'b1}})
    |=> (count === ($past(count) + 1))
);

// Counter holds when not enabled (uses $stable)
P_hold_when_disabled: assert property (
    @(posedge clk) disable iff (rst)
    !en |=> $stable(count)
);

// max_reached flag is correct (combinatorial: |-> overlapped)
P_max_flag: assert property (
    @(posedge clk) disable iff (rst)
    max_reached |-> (count === {WIDTH{1'b1}})
);

// No X/Z propagation on outputs
P_no_x_count: assert property (
    @(posedge clk) disable iff (rst)
    !$isunknown(count)
);

// ── Liveness Properties ─────────────────────────────────────────────

// If enabled, count must eventually reach max
P_reaches_max: assert property (
    @(posedge clk) disable iff (rst)
    en |-> s_eventually max_reached
);

// ── Cover Properties (vacuity & reachability checks) ───────────────

// Confirm counter reaches mid-range
C_reaches_mid: cover property (
    @(posedge clk) disable iff (rst)
    (count === {1'b0, {(WIDTH-1){1'b1}}})
);

// Confirm counter wraps (max then zero next cycle)
C_wrap: cover property (
    @(posedge clk) disable iff (rst)
    (count === {WIDTH{1'b1}}) ##1 (count === '0)
);

// Confirm reset can occur mid-count
C_mid_reset: cover property (
    @(posedge clk)
    (count > '0) ##1 $rose(rst)
);

endmodule

// ── Bind Statement ──────────────────────────────────────────────────
// Connects property module to DUT without modifying RTL
bind counter counter_props #(.WIDTH(WIDTH)) u_props (
    .clk         (clk),
    .rst         (rst),
    .en          (en),
    .count       (count),
    .max_reached (max_reached)
);
🔍
Reading the Property Set

Notice how |=> is used for registered outputs (reset effect, increment) and |-> for the combinatorial max_reached flag. This distinction prevents false failures. The cover properties confirm that reset mid-count, the wrap case, and mid-range values are all reachable — ruling out vacuous assertion passes.

J
Jagadish
VLSI Design Verification Blog  ·  Senior Verification Engineer
IEEE 1800-2017 Compliant