The new statement kinds in SystemVerilog — time-unit delays, unique/priority qualifiers for if and case, and the powerful pattern-matching system for dispatching on tagged unions and structures in case and if statements.
Verilog-2001 had most C-like statement types, with a few gaps. SystemVerilog fills them and adds some new constructs specific to hardware description and verification.
break, continue, return in loopsdo-while loopforeach for arraysfinal block (only initial)#10ns)unique/priority for if/casebreak, continue, return — C-style jumpdo...while — test-at-end loopforeach — iterate any arrayfinal block — run at end of simulation#10ns r = a;unique / priority qualifierscase and ifThe core blocking (=) and non-blocking (<=) assignment semantics are unchanged from Verilog-2001. SystemVerilog adds three enhancements.
// All 12 compound operators are now valid in blocking assignments: // += -= *= /= %= &= |= ^= <<= >>= <<<= >>>= always_ff @(posedge clk) begin count += 1; // same as count = count + 1 flags |= 8'h01; // set LSB end
// Nonblocking assignment to an automatic variable is always illegal task automatic t(); int auto_var; // auto_var <= 5; // ERROR: NBA to automatic variable auto_var = 5; // OK: blocking to automatic endtask
// The left-hand side determines the evaluation width for the right-hand side. // If LHS is narrower than RHS: information may be lost (tool warning). bit [3:0] x; x = 8'hFF; // 8-bit assigned to 4-bit: x = 4'hF (truncated, warning) // Arrays of identical type can be assigned as a whole int a[4], b[4]; a = b; // whole-array copy — OK
SystemVerilog allows a time-unit suffix (ns, ps, us, etc.) directly in delay specifications inside assignment statements. The simulator scales the value to the current timescale precision automatically.
// Verilog-2001 — raw time units (depends on `timescale) #10 r = a; // "10 time units" — meaning depends on `timescale // SystemVerilog — explicit time unit (portable) #1ns r = a; // blocking: delay 1 ns then assign r = #1ns a; // intra-assignment: sample a now, assign after 1 ns r <= #1ns a; // nonblocking with time-unit delay // Valid for any of the 7 time units: fs ps ns us ms s step #500ps clk = ~clk; // half-period of a 1 GHz clock #0.5ns data <= din; // real value in ns
#10 ties the simulation behaviour to whatever `timescale is active. Different files or tools may compile with different timescales. Writing #10ns is portable — it always means 10 nanoseconds regardless of the active timescale, and the simulator will automatically convert to the current precision.SystemVerilog adds two keywords that can qualify both if chains and case statements. They communicate design intent to both the simulator (runtime checking) and the synthesis tool (structure of generated logic).
else/default.else/default.unique and priority are runtime-checked — the simulator issues warnings when violations occur. They also carry synthesis intent: unique signals “use parallel evaluation”, priority signals “use first-match evaluation”. This replaces the full_case/parallel_case attributes because those attributes only affect synthesis, while unique/priority affect simulation too — so there is no risk of simulation/synthesis mismatch.The qualifiers go before the if keyword and apply to the entire if-else-if chain that follows. They cannot appear before any intermediate else if.
// Values 3, 5, 6, 7 cause a runtime error (no match, no else) unique if ((a==0) || (a==1)) $display("0 or 1"); else if (a == 2) $display("2"); else if (a == 4) $display("4"); // No else: if a==3,5,6,7 → runtime error "no condition matched" // Tool also errors if two conditions can be true simultaneously
// If a == 0: satisfies both condition 1 and condition 2. // priority tells the tool: evaluate in order, first-match wins. priority if (a[2:1] == 0) $display("0 or 1"); // a=0 matches this else if (a[2] == 0) $display("2 or 3"); // a=0 also matches this else $display("4 to 7"); // all values covered → no error // Synthesis: generates priority encoder (first-match wins)
if (A) ... else priority if (B) ... is illegal — the priority keyword can only appear before the very first if. To nest a prioritised sub-chain inside another if, wrap the inner chain in begin...end:
if (outer) begin priority if (B) ... else if (C) ... end// Synthesis can use a one-hot mux — no priority chain unique if (sel == 2'b00) y = d0; else if (sel == 2'b01) y = d1; else if (sel == 2'b10) y = d2; else if (sel == 2'b11) y = d3; // All 4 conditions parallel → 4-to-1 mux
// Synthesis must use priority logic priority if (req[0]) grant = 4'b0001; else if (req[1]) grant = 4'b0010; else if (req[2]) grant = 4'b0100; else if (req[3]) grant = 4'b1000; // req[0] has highest priority → chain of comparators
The same two qualifiers apply to all three case statement forms: case, casez, and casex. They appear before the case keyword.
bit [2:0] a; // Values 3, 5, 6, 7 not listed → runtime warning "no case matched" unique case(a) 0, 1: $display("0 or 1"); // comma-list: single item matches either 2: $display("2"); 4: $display("4"); endcase // Tool: warning if two items could match (they don't here) // Tool: warning if no item matches and no default
// Values 4, 5, 6, 7 not covered → runtime warning "no case matched" // priority means: first matching item wins (top-to-bottom scan) priority casez(a) 3'b00?: $display("0 or 1"); // ? = don't care bit — matches 000, 001 3'b0??: $display("2 or 3"); // matches 010, 011 (first item already took 0,1) endcase // a=0: first item matches (3'b00?) — priority stops here, second skipped
unique case and priority case are simulated and synthesised with the same semantics — no gap between the two.typedef enum logic[1:0] {IDLE, FETCH, EXEC, DONE} state_t; state_t state, next_state; always_comb begin unique case(state) IDLE: next_state = FETCH; FETCH: next_state = EXEC; EXEC: next_state = DONE; DONE: next_state = IDLE; endcase // All 4 states of the 2-bit enum covered → no warnings expected // unique: synthesis knows these are mutually exclusive → parallel mux end
Pattern matching in case and if statements provides a clean, type-safe way to inspect tagged unions and structures. Instead of manual tag checks and member accesses, you describe what you expect to see and bind names to the pieces you care about in a single expression.
// Identifier pattern — catches anything, binds it case(v) matches n: $display("matched, n=%0d", n); // n bound to v's current value // Wildcard — discards the value case(v) matches tagged Valid .*: $display("valid but don't care about value"); // Constant — checks for a specific value case(v) matches tagged Valid .0: $display("valid with value exactly zero"); tagged Valid n: $display("valid with non-zero value %0d", n);
Add the keyword matches after the case expression. Each item’s LHS becomes a pattern, with an optional && filter_expr guard.
typedef union tagged { void Invalid; int Valid; } VInt; VInt v; case(v) matches tagged Invalid : $display("v is Invalid"); tagged Valid n : $display("v is Valid with value %0d", n); endcase // n is automatically bound to the Valid member value when that branch executes // n is only accessible inside the "tagged Valid" branch
typedef union tagged { struct { bit[4:0] reg1, reg2, regd; } Add; union tagged { bit[9:0] JmpU; struct { bit[1:0] cc; bit[9:0] addr; } JmpC; } Jmp; } Instr; Instr instr; case(instr) matches // Add: bind r1,r2,rd — filter: only execute if rd != 0 tagged Add {r1, r2, rd} && (rd != 0): rf[rd] = rf[r1] + rf[r2]; // Jmp: bind nested union to j, then dispatch again tagged Jmp j : case(j) matches tagged JmpU a : pc = pc + a; tagged JmpC {c,a}: if(rf[c]) pc = a; endcase endcase
// Eliminate the rd=0 "discard" case using wildcards and a constant case(instr) matches tagged Add {.*, .*, .0} :; // rd==0: no-op (discard) tagged Add {r1, r2, rd} : rf[rd] = rf[r1] + rf[r2]; // rd!=0: execute tagged Jmp j : case(j) matches tagged JmpU a : pc = pc + a; tagged JmpC {c,a}: if(rf[c]) pc = a; endcase endcase
// Named-member patterns: order of members in {} does not matter case(instr) matches tagged Add {reg2:r2, regd:rd, reg1:r1} && (rd != 0): rf[rd] = rf[r1] + rf[r2]; // same as positional but named tagged Jmp (tagged JmpU a) : pc = pc + a; tagged Jmp (tagged JmpC {addr:a, cc:c}): if(rf[c]) pc = a; endcase
unique case(v) matches ... or priority case(v) matches ... carries the same meaning as with regular case — unique means items are mutually exclusive and parallel evaluation is safe; priority means first-match wins.An if predicate can contain one or more expression matches pattern clauses joined by &&. The clauses form a sequential conjunction — if any clause fails, the rest are not evaluated. Pattern identifiers bound in earlier clauses are visible in later clauses and in the then branch.
VInt v = tagged Valid 42; // Test if v is tagged Valid, bind value to n if (v matches tagged Valid n) $display("v is Valid, n = %0d", n); // n only in scope here else $display("v is Invalid");
Instr e; // Match a conditional jump instruction in one if condition if (e matches (tagged Jmp (tagged JmpC {cc:c, addr:a}))) // c and a are in scope here $display("JmpC: addr=%0d cc=%0b", a, c); else $display("Not a conditional jump");
// Two matches clauses chained with && // Identifiers bound in the first are available in the second if (e matches (tagged Jmp j) // j bound to the Jmp member && j matches (tagged JmpC {cc:c, addr:a})) // j used here $display("Conditional jump: addr=%0d, cc=%0b", a, c); else $display("Not a conditional jump");
// Pattern match PLUS a boolean condition in the same predicate // "if e is a conditional jump AND the condition register is non-zero" if (e matches (tagged Jmp (tagged JmpC {cc:c, addr:a})) && (rf[c] != 0)) // boolean guard — c is in scope from the pattern pc = a; // jump taken else pc++; // fall through
&& clauses in a pattern-matching predicate evaluate left-to-right and stop as soon as one fails. If the first e matches tagged Jmp j fails (e is not a Jmp), the second clause is not evaluated at all — and j is never bound. This is exactly the safe evaluation order you want when chaining pattern matches.| Feature | Syntax | Key point |
|---|---|---|
| Time-unit in delay | #10ns r = a; | Portable — scales to current timescale automatically |
| NBA to automatic var | illegal | Nonblocking assignment to automatic variable always illegal |
| unique if / case | unique if / unique case | Conditions mutually exclusive; parallel eval; error if no match and no else |
| priority if / case | priority if / priority case | First-match wins; overlaps allowed; error if no match and no else |
| Pattern-matching case | case(v) matches | Dispatch on tagged union/struct tags; auto-bind fields to identifiers |
| Pattern-matching if | if (e matches pattern) | Test and bind in one expression; chain with &&; short-circuits |
unique when all branches are guaranteed mutually exclusive — synthesis gets a simple parallel mux.priority when branches can overlap but first-match is the intent — synthesis gets a priority encoder.default/else — but tools will warn at runtime if no branch matches..* always matches and discards; constant .N matches the exact value.&& in a case matches item is a filter guard — not the same as the && between clauses in an if predicate.do-while, enhanced for, foreach), jump statements (break, continue, return), final blocks, named blocks and statement labels, the disable statement, and the iff qualifier on event control (sections 8.5–8.10).