Welcome back. In our last lesson, you mastered the SVA repetition operators, which allow you to define the shape of a temporal sequence. We learned to express complex patterns like "event B must happen exactly 4 times, non-consecutively, before C occurs."
Now, we shift our focus from the sequence itself to the statement that enforces it: the assert statement. SystemVerilog offers two fundamental types of assertions: concurrent and immediate. Your ability to strategically choose between them is not just a matter of syntax; it's a test of your understanding of the underlying SystemVerilog simulation scheduler—a topic that frequently surfaces in technical interviews.
This lesson will demystify the differences, arming you with a clear decision-making framework based on design intent and performance implications. By the end, you'll know precisely when to use a clock-driven concurrent assertion and when to leverage the power (and avoid the pitfalls) of an unclocked immediate assertion.
1. Concurrent Assertions: The Synchronous Workhorse
Concurrent assertions are the backbone of SVA. They are designed to verify temporal properties that span multiple clock cycles in synchronous designs. You've been using them implicitly in our previous lessons.
Key Characteristics:
- Clock-based: They are always associated with a clocking event (e.g.,
@(posedge clk)). - Declarative: They exist outside of procedural blocks like
alwaysorinitial. - Temporal: Their primary purpose is to check behavior over time using operators like
##,|->, and[*].
A typical concurrent assertion looks like this:
// Checks that if req is high, ack must follow within 1 to 3 cycles.
a_req_ack: assert property (@(posedge clk) req |=> ##[1:3] ack);
The most critical concept to grasp for concurrent assertions is when they sample their input values. They do so in the Preponed region of the SystemVerilog simulation time-slot. This means values are captured before any design logic (in always blocks) or non-blocking assignments have a chance to execute in that same time-slot. This design guarantees race-free, deterministic evaluation for synchronous logic—a major advantage.

2. Immediate Assertions: Procedural and Instantaneous
Immediate assertions are procedural statements. They behave like an if statement that triggers an error if the condition is false.
Key Characteristics:
- Procedural: They must be placed inside an
alwaysblock,initialblock,task, orfunction. - Unclocked (by nature): They are evaluated whenever the procedural block they are in is executed.
- Instantaneous: They check a condition at a single moment in time without inherent temporal semantics.
A simple immediate assertion looks like this:
always @(posedge clk) begin
// At every clock edge, check that read and write are mutually exclusive.
if (state == ACTIVE) begin
assert (read_en != write_en) else $error("Read and Write are both enabled!");
end
end
Immediate assertions execute in the Active region by default. This is where most procedural code and continuous assignments are evaluated. This timing has profound implications, leading to both powerful use cases and dangerous pitfalls.
3. The Strategic Choice: Intent and Simulation Semantics
Your choice between concurrent and immediate assertions depends on two factors: what you are trying to verify (design intent) and how the simulator will evaluate it (semantics).
The Pitfall: Glitchy Immediate Assertions
A common interview question involves combinational logic. An engineer might write:
// Anti-pattern: DO NOT DO THIS
always @*
a_combo_check: assert ( (a & b) == y ); // Is this a reliable check for an AND gate?
This is unreliable. Since multiple always @* blocks can execute in any order within the Active region, the assertion might evaluate before y has settled to its final value for that time step, causing a false failure. This non-determinism makes simple immediate assertions "glitchy" for checking combinational logic.

The Solution: Deferred Immediate Assertions
SystemVerilog (since the 2009 standard) provides an elegant solution: the deferred immediate assertion. By adding #0, you tell the simulator to evaluate the assertion in the Observed region, which occurs after the Active (where logic computes) and NBA (where flops update) regions.
// Correct way to check combinational logic
always_comb // Use always_comb for combinational logic
a_combo_check: assert #0 ( (a & b) == y );
This ensures the check happens only after all values have stabilized for that time step, eliminating the race condition.
The Killer App: Checking Asynchronous Resets
Now, let's tackle a classic problem: verifying an asynchronous reset. Consider this property:assert property (@(posedge async_reset) q == 0);
This will fail, even if the logic is correct. Why?
- The concurrent assertion samples its inputs (
q) in the Preponed region. - The
posedge async_resetoccurs. - The design logic reacts in the Active region and schedules
q <= 0;for the NBA region. - The assertion evaluates using the old value of
qsampled in step 1, sees it's not zero, and fails.
The correct tool here is a deferred immediate assertion, which waits until after the NBA update.
[PDF] Asynchronous Behaviors Meet Their Match with SystemVerilog ...
The following paper provides an excellent, in-depth explanation of why checking asynchronous behaviors is tricky and how to solve it. We'll read the sections that introduce the problem and present the modern solution.
First, read section 2.1.2 'Checking asynchronous events' (starts on page 3). This explains why concurrent assertions fail for async checks by introducing the SystemVerilog scheduling regions. Then, skip to section 3.5 'Deferred Immediate Assertions' (page 8). This presents the modern, elegant solution using assert #0. Focus on understanding the role of the Preponed, Active, NBA, and Observed regions.
As you read, the key insight is that for asynchronous events, you must delay the check until after the design has updated. The deferred immediate assertion (assert #0) is the cleanest way to achieve this.
The correct way to check our asynchronous reset is:
always @(posedge async_reset)
a_async_reset_check: assert #0 (q == 0) else $error("q is not 0 after async reset!");
A Decision Framework for Interviews
When an interviewer asks "Which assertion type would you use for X?", use this mental model:
-
Is the property checking a synchronous, temporal relationship over multiple clock cycles?
- Yes: This is the 95% case. Use a concurrent assertion. It's race-free for synchronous logic and highly expressive.
-
Is the property checking an immediate reaction to an asynchronous signal (like a reset)?
- Yes: Use a deferred immediate assertion (
assert #0) inside analwaysblock sensitive to that async signal. This correctly schedules the check after the design updates.
- Yes: Use a deferred immediate assertion (
-
Is the property checking combinational logic that must be true at all times?
- Yes: Use a deferred immediate assertion (
assert #0) inside analways_combblock. This avoids the glitchy behavior of simple immediate assertions.
- Yes: Use a deferred immediate assertion (
-
Is the property a sanity check at a specific point within a procedural testbench task (e.g., in a driver or monitor)?
- Yes: A simple immediate assertion (
assert) is fine here. You control the procedural flow, so there's no race condition to worry about.
- Yes: A simple immediate assertion (
Conclusion
You now have a robust framework for selecting between concurrent and immediate assertions. You've moved beyond syntax to understand the crucial role of the SystemVerilog simulation scheduler. This knowledge is key to writing correct, efficient, and defensible verification code.
Key Takeaways:
- Concurrent assertions are for synchronous, temporal properties. They sample in the Preponed region, making them ideal for clocked designs.
- Immediate assertions are for procedural, instantaneous checks. They evaluate in the Active region by default, which can be unreliable for RTL checks due to race conditions.
- Deferred immediate assertions (
assert #0) are the solution for robust asynchronous and combinational checks. They evaluate in the Observed region, after the design state has stabilized. - Your choice of assertion is a strategic decision based on design intent and an understanding of the SV scheduler (Preponed -> Active -> NBA -> Observed).
In the next lesson, we will apply these concepts to write specific assertions for data integrity checks, such as verifying data stability and preventing data corruption—common tasks in any verification plan.
🧠Interview & Practice Drill
Let's put this knowledge to the test.
🎤 Interview Drill Section
-
Rapid Fire Theory:
- An interviewer says, "I want to check that my asynchronous reset puts my FIFO's
emptyflag to 1. I wroteassert property (@(posedge rst_n) fifo_empty);. Why is my team lead telling me this is wrong?" - What is the difference in execution timing between a simple immediate assertion and a deferred immediate assertion? Name the relevant simulation regions.
- When would you ever use a simple
assertinstead ofassert #0or a concurrent assertion? Give a concrete example. - "Performance is critical. Is it better to write 100 individual assertions for a 100-bit bus comparison or one assertion on the whole bus?"
- An interviewer says, "I want to check that my asynchronous reset puts my FIFO's
-
Scenario-Based Problem:
- You are verifying a module with a configuration register
CFGthat is written once at time zero and should never change. You need to write a property to ensureCFGremains stable. - Question: What are two different ways you could verify this using SVA? Discuss the pros and cons of each approach.
- (Hint 1: A concurrent approach using
$stable()). - (Hint 2: An immediate approach using
$past()).
- (Hint 1: A concurrent approach using
- You are verifying a module with a configuration register
🧩 Coding Exercises
Write the SVA code for the following scenarios. Assume a default clock posedge clk.
-
Asynchronous Set: A register
status_reghas a synchronous clear onclkbut an asynchronoussetinput (async_set). Whenasync_setgoes high,status_regshould immediately become1. Write a robust assertion to check this behavior. -
Combinational Decoder: You have a 3-to-8 decoder with a 3-bit input
addrand an 8-bit one-hot outputsel. Write an assertion to verify that for any givenaddr, the correctselbit is high and all others are low. For example, ifaddris3'b101, thensel[5]must be1. The check should be robust against glitches. -
Procedural Check in a Task: You have a testbench task
write_bus(input [31:0] data, input [15:0] addr);. Inside this task, before driving the signals, you need to assert that the address is within a valid range (e.g.,16'h1000to16'hFFFF). Write the task with the appropriate assertion.