Scheduling Semantics Interview Questions

Master SystemVerilog simulation: blocking vs non-blocking assignments, delta cycles, event regions, and race conditions.

Beginner Level

Fundamentals
What is the fundamental difference between a blocking assignment (=) and a non-blocking assignment (<=)?

Blocking Assignment (=):

  • Executes immediately when encountered.
  • The next statement sees the updated value.
  • Blocks execution until complete—hence "blocking."

Non-Blocking Assignment (<=):

  • Schedules the update for the NBA (Non-Blocking Assignment) region.
  • All RHS values are evaluated first (using current values).
  • All LHS updates happen together at end of time step.
// Blocking: a becomes 1, then b becomes 1
a = 1; b = a;  // b = 1
// Non-blocking: both use OLD values
a <= 1; b <= a;  // If a was 0, b becomes 0!
In which type of always block should you use =? In which should you use <=? What is the general rule?

General Rules:

Block Type Assignment Logic Type
always_comb = (blocking) Combinational
always_ff @(posedge clk) <= (non-blocking) Sequential
always_latch = (blocking) Latch

Why? Non-blocking in sequential logic prevents race conditions by modeling true hardware parallelism—all flip-flops update simultaneously.

Intermediate Level

Industry Standard
Draw a simplified diagram of the SystemVerilog simulation time slot. Where do $display and $strobe execute?
+-----------------------------+
|         SIMULATION TIME SLOT            |
├-----------------------------┤
|  1. Active Region                       |
|     - Blocking assignments              |
|     - $display (prints CURRENT values)  |
|     - Continuous assignments            |
├-----------------------------┤
|  2. Inactive Region                     |
|     - #0 delay statements               |
├-----------------------------┤
|  3. NBA Region                          |
|     - Non-blocking LHS updates          |
├-----------------------------┤
|  4. Observed Region                     |
|     - Concurrent assertion evaluation   |
├-----------------------------┤
|  5. Reactive Region                     |
|     - Program block code                |
├-----------------------------┤
|  6. Postponed Region                    |
|     - $strobe (prints FINAL values)     |
|     - $monitor                          |
+-----------------------------+

Key Insight: $display shows values at execution time (may be pre-NBA). $strobe shows values after all updates are complete.

(Code Analysis) Predict the output of the following code:
module test;
  logic a = 0;
  initial begin
    a <= 1;        // Non-blocking - schedules for NBA
    $display("a in Active: %0d", a);
    #0;            // Move to Inactive, then back to Active
    $display("a after #0: %0d", a);
  end
  initial #0 $display("a in Inactive: %0d", a);
endmodule

Output:

a in Active: 0       // NBA not applied yet
a in Inactive: 0     // Still before NBA
a after #0: 1        // After #0, NBA applied, back to Active

Explanation: The NBA update happens AFTER Inactive region. The second initial block runs in Inactive (#0). The first block's #0 causes re-entry to Active AFTER NBA, so it sees the updated value.

What is a delta cycle? If two signals continuously trigger each other, what prevents an infinite loop?

Delta Cycle: A zero-time iteration through simulation regions. Used to order events that logically occur at the same simulation time.

// Example: a and b toggle each other
always_comb a = ~b;
always_comb b = ~a;  // Infinite delta cycles!

Prevention: Simulators have a maximum delta count (e.g., 200). If exceeded, simulation terminates with an error like "Iteration limit reached."

Real-World Fix: Add a delay or use proper sequential logic to break the combinational loop.

Advanced Level

Expert
(Race Condition Bug) This code sometimes gives y=0 and sometimes y=1. Explain the race and fix it.
module dut(input logic clk, output logic y);
  logic a, b;
  always @(posedge clk) a = 1;       // Blocking
  always @(posedge clk) b = a;       // Blocking
  always @(posedge clk) y = b;       // Blocking
endmodule

Problem: All three blocks trigger at the same time. With blocking assignments, the execution order is non-deterministic. Depending on which block runs first:

  • If order is 1→2→3: a=1, b=1, y=1
  • If order is 3→2→1: y=old_b, b=old_a, a=1 → y=0

Fix: Use non-blocking assignments:

always_ff @(posedge clk) a <= 1;
always_ff @(posedge clk) b <= a;
always_ff @(posedge clk) y <= b;

Now all RHS values are sampled BEFORE any LHS updates. Result is deterministic: takes 3 cycles for 1 to propagate to y.

At posedge clk, three always_ff blocks have NBA updates scheduled for signals x, y, and z. In what order are these updates applied? Is it deterministic?

Answer: The order of NBA updates to different signals is not specified by the LRM. However, this doesn't matter!

Why it's safe: All RHS expressions are evaluated using values from BEFORE any NBA update. So regardless of LHS update order, the result is the same.

// All three sample their RHS simultaneously (before any update)
always_ff @(posedge clk) x <= a;
always_ff @(posedge clk) y <= b;
always_ff @(posedge clk) z <= c;

The "snapshot" of a, b, c is taken first, then x, y, z are updated (order irrelevant).

What is the Reactive region? Which SystemVerilog construct schedules code in this region? Why is this separation important?

Reactive Region: Where program block code executes.

Why Separate?

  • Design code (modules) runs in Active/NBA regions.
  • Testbench code (programs) runs in Reactive region.
  • This ensures TB sees stable, post-NBA values from the DUT.
module dut;
  logic data;
  always_ff @(posedge clk) data <= compute();
endmodule
program tb;
  initial begin
    @(posedge clk);
    // Runs in Reactive - sees stable 'data' value
    $display("data = %0d", dut.data);
  end
endprogram

Note: In modern UVM, program blocks are less common; module-based TBs with clocking blocks are preferred.

A verification engineer uses always @(posedge clk) to drive DUT inputs. Simulation has intermittent failures. Switching to a clocking block fixes it. Explain.

Root Cause: The TB's always @(posedge clk) block and the DUT's sequential logic both execute at the same simulation time (both in Active/NBA regions). The order is non-deterministic.

  • Sometimes TB drives before DUT samples → works.
  • Sometimes DUT samples before TB drives → sees old value → failure.

Clocking Block Fix:

  • Input skew: Samples inputs BEFORE clock edge (stable DUT outputs).
  • Output skew: Drives outputs AFTER clock edge (DUT sees stable inputs).
clocking cb @(posedge clk);
    default input #1step output #0;  // Safe defaults
    input  dut_out;   // Sample before edge
    output dut_in;    // Drive after edge
endclocking

This explicit timing eliminates race conditions entirely.