Immediate Vs Concurrent Assertions
In short: An immediate assertion checks a condition the moment the simulator reaches it in procedural code, like a plain if-check. A concurrent assertion checks behaviour over time, sampling signals on a clock edge and spanning one or more cycles. Use immediate for a quick single-point check, and concurrent for anything that involves “on the next cycle” or a sequence.
Both kinds of SystemVerilog assertions catch bugs early, but they answer different questions. One asks “is this true right now?” The other asks “does this pattern hold across clock cycles?” This guide to immediate vs concurrent assertions shows how to tell them apart, so you reach for the right one and keep your checks meaningful.
A simple way to picture it
Think of a security guard at a door. An immediate assertion is the guard glancing at your badge the instant you walk up: pass or fail, decided on the spot. A concurrent assertion is the guard watching a rule that plays out over time, such as “after you swipe in, you must swipe out within three checkpoints.” The second rule cannot be judged in a single glance; the guard has to watch several moments in order. That is the heart of the difference: one point in time versus a pattern across clocked time.
Immediate assertions
An immediate assertion lives inside a procedural block (like always_comb, always_ff, initial, or a task). It evaluates the expression right when the flow of execution reaches it, using the current values of the variables. There is no clock and no sampling; it behaves like an if statement that reports a failure. Because of that, it is only meaningful in simulation, not in formal tools.
module imm_example;
logic [3:0] x, y;
always_comb begin
// Checks the condition the instant this line runs
a_xy_equal: assert (x == y)
else $error("x (%0d) not equal to y (%0d)", x, y);
end
endmoduleThe label a_xy_equal is optional but strongly recommended: it shows up in the simulator log and makes failures easy to trace. The else branch is the action block that runs on failure. You can also add a pass action before the else if you want a message on success.
Immediate vs deferred immediate
Plain immediate assertions can fire on transient glitches in combinational logic, because they run every time the block re-evaluates. The IEEE 1800 standard adds deferred immediate assertions (assert final and assert #0) that wait until values settle at the end of the time step, which avoids false failures from momentary glitches.
always_comb begin
// Deferred: judged after signals settle in this time step
a_settled: assert #0 (x == y)
else $error("mismatch after settle");
endConcurrent assertions
A concurrent assertion checks behaviour over clocked time. It samples the values of its signals on a clock edge (in the preponed region, just before the edge) and then evaluates a property, which may span many cycles. Because it is clock-based and uses stable sampled values, it works in both simulation and static formal verification. You place it in a module, an interface, or a program block, outside procedural flow.
module conc_example (input logic clk, z, x, y);
// If z is high on a cycle, then on the NEXT cycle x must equal y
a_next_eq: assert property (@(posedge clk) z |=> (x == y))
else $error("x != y on the cycle after z");
endmoduleRead the property left to right: on each rising edge of clk, if z is true (the antecedent), then one cycle later x == y must hold (the consequent). The |=> is the non-overlapping implication operator. For a same-cycle check use |->. To go deeper on these operators, see our basic assertions examples post.
The clocking and sampling idea
The most common surprise for newcomers is that concurrent assertions do not use the value at the exact edge; they use the value sampled just before the edge. This is why an assertion and your RTL can look like they disagree in a waveform: the assertion judged the older, stable value. This sampling rule is exactly what lets the same assertion run in a formal tool, where there is no notion of “current simulation moment.”
Side-by-side comparison
| Aspect | Immediate assertion | Concurrent assertion |
|---|---|---|
| Time model | No clock; runs when reached | Clocked; samples on an edge |
| Where it lives | Procedural block (always, initial, task) | Module, interface, or program |
| Spans cycles? | No, single point in time | Yes, can span many cycles |
| Values used | Current values | Sampled (preponed) values |
| Operators | Plain expressions | Sequences, |->, |=>, ##, $rose, and more |
| Formal support | Simulation only | Simulation and formal |
| Typical use | Quick sanity check on a value | Protocol and timing rules over time |
When to use which
- Reach for immediate when the check is a single yes/no on current values, such as a one-hot state or a legal enum value inside a function.
- Reach for concurrent when the rule mentions “next cycle”, “within N cycles”, a handshake, or any pattern across time.
- Use deferred immediate inside combinational blocks so glitches do not cause false failures.
- Prefer concurrent if you ever plan to run the same checks in a formal engine.
Common mistakes to avoid
- Putting a concurrent
assert propertyinside analwaysblock. Concurrent assertions belong outside procedural code. - Expecting a concurrent assertion to react to the exact-edge value; it uses the sampled value from just before the edge.
- Forgetting the clocking event, so the tool cannot decide when to sample.
- Using a plain immediate assertion on glitchy combinational logic and getting false failures; use the deferred form instead.
- Leaving assertions unlabeled, which makes failure logs hard to read.
Expected output, in plain words
In the immediate example, if x and y differ when the block runs, the simulator prints the $error line with the two values and marks that assertion as failed for that moment. In the concurrent example, on any cycle where z was sampled high but x != y on the following cycle, you get a failure tied to the start cycle of the attempt. Note: these descriptions come from reading the IEEE 1800 rules, not from a captured simulator run, and exact log wording differs between tools. Always confirm on your own simulator.
Assertions pair naturally with functional coverage and constrained-random tests. If you are preparing for interviews, our interview questions section revisits these operators, and the wider SystemVerilog category covers the language features they rely on.
Frequently asked questions
What is the main difference between immediate and concurrent assertions?
An immediate assertion checks a condition at a single point in time when procedural code reaches it, with no clock. A concurrent assertion samples signals on a clock edge and can check a pattern that spans one or more cycles.
Can immediate assertions be used in formal verification?
No. Immediate assertions are meaningful only in runtime simulation because they depend on the order of procedural execution. Concurrent assertions, being clock-based and using sampled values, work in both simulation and formal tools.
Where do I place a concurrent assertion?
Place it in a module, an interface, or a program block, outside any procedural block. Putting a concurrent assert property inside an always block is a common error.
What are deferred immediate assertions?
They are immediate assertions written as assert #0 or assert final that wait until values settle at the end of the time step. This avoids false failures caused by momentary glitches in combinational logic.
Why does my concurrent assertion seem to use an old signal value?
Concurrent assertions use values sampled just before the clock edge, in the preponed region. That sampled value can look one step behind the waveform, which is expected and is what allows the same assertion to run in formal tools.
What is the difference between |-> and |=> ?
The |-> overlapping implication checks the consequent in the same cycle the antecedent is true. The |=> non-overlapping implication checks the consequent one cycle later, and is equivalent to |-> ##1.



