The SV additions to disable — what it reaches vs disable fork; new event control forms including iff guards, sequence events, and object-member sensitivity; level-sensitive sequence waits with triggered; and the status of procedural assign/deassign.
The Verilog-2001 disable statement terminates all processes currently executing a named block or task. SystemVerilog keeps disable but adds break, continue, and return for most common use cases, leaving disable for the specific scenarios where named-block termination is genuinely needed.
// disable: terminate all processes executing a named block // — works on any named block, even one not in the same scope module top; always always1: begin t1: task1(); do_other_work(); end always begin // Exit task1 (which was called from always1) disable u1.always1.t1; // hierarchical: stops all execs of t1 in u1.always1 end endmodule
disable blockName, it terminates all processes executing that block, regardless of which process forked them. This is a syntactic, not a dynamic, relationship. Two completely unrelated threads that both happen to be inside blockName will both be killed. Contrast with disable fork, which only terminates the children of the calling process.| Situation | Best tool |
|---|---|
| Exit the innermost loop | break |
| Skip to next loop iteration | continue |
| Exit the current task/function early | return |
| Kill all children of the current process | disable fork |
| Kill a specific named block from another scope | disable blockName |
| Kill all executions of a named task (from any caller) | disable taskName |
disable fork terminates all active descendants of the calling process — its children, their children, and so on down the dynamic process tree. It uses the runtime parent-child relationship, not the static block structure.
// Pattern: race to first device — kill the losers task get_first(output int adr); fork wait_device(1, adr); // three processes compete wait_device(7, adr); wait_device(13, adr); join_any // unblock when first one finishes disable fork; // kill the other two still waiting endtask
// Kills ALL processes inside that named block // regardless of who forked them // Can kill unrelated processes! disable blk_A; // kills every thread in blk_A
// Kills only THIS process's descendants // Other processes' children are NOT affected // Safe for use in concurrent testbenches disable fork; // kills MY children only
join_any, then disable fork to clean up the remaining N-1 workers.SystemVerilog adds an iff qualifier to the @ event control. The event only triggers if the iff expression is true at the moment the edge occurs. This is cleaner than a separate if inside the block for simple edge-with-condition patterns.
// Classic latch sensitivity — only when enable is high module latch(output logic[31:0] y, input[31:0] a, input enable); always @(a iff enable == 1) y <= a; // latch transparent mode — only update when enabled endmodule // Clock edge with reset guard always_ff @(posedge clk iff reset==0 or posedge reset) begin r1 <= reset ? 0 : r2 + 1; end // Sensitive to posedge clk ONLY when reset==0, OR posedge reset (unconditional)
a changes), not when enable changes. If a is stable and only enable changes from 0 to 1, the event does NOT trigger.iff has higher precedence than or in event expressions. Use parentheses if the intent might be ambiguous: @((a iff cond) or b).iff just filters whether those changes actually trigger the process.SystemVerilog significantly expands what is legal inside @(...) and wait(...) expressions compared to Verilog-2001.
guard is true at the moment of the change. New in SV.// Integral types — any change triggers int i; string s; @i; // triggers on any change to i @s; // triggers on any change to string s // ref argument (passed by reference) in event sensitivity task automatic watch(ref int r); @r; // triggers when r (the caller's variable) changes endtask // Array element — member access is legal int arr[8]; @(arr[3]); // triggers when element 3 changes // Dynamic array and queue — size/element changes real AOR[]; // dynamic array of reals byte stream[$]; // queue of bytes wait(AOR.size() > 0); // wait for array to be allocated wait($bits(stream) > 60); // wait for total bit count to exceed 60
Event expressions can reference members of class objects. The event fires when the object handle is updated to point to a different object, or when a referenced member’s value changes.
class Packet; int status; byte payload[]; endclass Packet p = new; // Packet 1 Packet q = new; // Packet 2 initial fork @(p.status); // wait for status field of Packet 1 to change @q; // wait for a write to the HANDLE q (not its members) begin #10 q = p; // writes the handle q → triggers @q // @(p.status) now watches status in Packet 2 // (because p and q both now point to Packet 2) end join
@q fires when the variable q (the handle) is written with a new value — i.e., when it is redirected to point at a different object. It does not fire when members of the object that q currently points to change. @(q.status) fires when the status member of whichever object q currently points to changes. After q = p, @(p.status) now monitors the same object that q points to — because p still points to Packet 1 and now q also points to Packet 1. Wait — re-read the example above: after q = p, both p and q point to Packet 2? Actually in the example q = p makes both point to what p points to (Packet 1). The @(p.status) event now monitors Packet 1’s status through the updated reference.// Non-virtual function methods can appear in event expressions // — they are re-evaluated when any referenced member changes class Monitor; int value; function int doubled(); return value * 2; endfunction endclass Monitor m = new(); @(m.doubled()); // re-evaluates when m.value changes // triggers if m.value*2 transitions // Restriction: must be a function (not a task), must return singular value // Task calls are ILLEGAL in event expressions
A named sequence can be used directly as an event control. The process blocks until the sequence reaches its endpoint — that is, until a complete match of the sequence is detected. This connects procedural code to assertion sequences without needing explicit handshaking signals.
sequence abc; @(posedge clk) a ##1 b ##1 c; endsequence program test; initial begin @abc; // block until abc completes (a then b then c) $display("Saw a-b-c"); L1: continue_test(); // continues after the sequence fires end endprogram
assert property statement when first reached at runtime.The @sequence event control is edge-sensitive — it unblocks when the sequence endpoint is newly reached. If the sequence has already completed, it will miss it. The triggered method on a sequence provides a level-sensitive alternative: it returns true if the sequence has reached its endpoint at any point during the current time step.
sequence abc; @(posedge clk) a ##1 b ##1 c; endsequence sequence de; @(negedge clk) d ##[2:5] e; endsequence program check; initial begin // Level-sensitive wait: unblocks if EITHER sequence has already triggered // or as soon as one of them triggers in any future time step wait(abc.triggered || de.triggered); // Now check which one(s) fired if(abc.triggered) $display("abc succeeded"); if(de.triggered) $display("de succeeded"); L2: continue_check(); end endprogram
@seq is reached, that completion is missed.triggered persists through the rest of the current time step.|| to wait for the first of N sequences.triggered property is set in the Observe region and remains true until simulation time advances. In the next time step it resets. This mirrors the behaviour of the SV event type’s .triggered property — both avoid the “missed trigger” race that plagued Verilog-2001’s plain event.Verilog-2001 provided assign and deassign as procedural statements that could override a variable’s normal procedural drivers. SystemVerilog keeps them for backward compatibility but marks them as candidates for removal in a future version of the language.
// procedural assign: overrides all procedural drivers of a variable // until a procedural deassign (or another assign) releases it reg q; initial begin assign q = preset; // q now follows preset continuously #10; deassign q; // q returns to normal — value holds at current preset end always @(posedge clk) // this driver is suppressed while assign is active q <= d;
force/release instead. For asynchronous preset/clear on flip-flops, model them explicitly in the always_ff sensitivity list (e.g. @(posedge clk or posedge preset)).// force: overrides both variables AND nets (more powerful than assign) // release: removes the force, variable/net returns to its normal drivers initial begin force dut.q = 1; // override DUT internal signal for testing #10; release dut.q; // restore normal operation end // force can also target nets (wire, tri) — procedural assign cannot force net_name = 0; // overrides net driver — for test stimulus injection release net_name; // driver resumes
| Feature | disable blockName | disable fork |
|---|---|---|
| What it terminates | All processes executing the named block, regardless of caller | Only the calling process’s descendants |
| Basis | Static syntactic scope | Dynamic parent-child relationship |
| Risk | Can accidentally kill unrelated threads | Safe in concurrent testbenches |
| Typical use case | Kill a specific named task across all callers | Kill worker threads after first responder wins |
iff has higher precedence than or — parenthesise if combining: @((sig iff cond) or other).string variables — new in SV.ref arguments (by-reference parameters) — new in SV.@seqName) — new in SV.@seq — edge-sensitive: unblocks only when sequence newly reaches endpoint. Misses if already completed.wait(seq.triggered) — level-sensitive: immediately unblocks if completed in the current time step.triggered persists through the rest of the current time step then resets.force/release in new testbench code — it also works on nets.force on a variable that has an active procedural assign overrides the assign; when the force is released, the assign effect becomes visible again.always_comb, always_ff, always_latch, the three fork-join variants (join, join_any, join_none), wait fork, disable fork, and the process built-in class for fine-grained process control.