Procedural Statements & Control Flow
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.
💡 What SV Adds to 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.
Verilog-2001 — what was missing
- No
break,continue,returnin loops - No
do-whileloop - No
foreachfor arrays - No
finalblock (onlyinitial) - No time-unit suffix in delays (
#10ns) - No
unique/priorityfor if/case - No pattern matching for tagged unions/structs
SystemVerilog additions (§8)
break,continue,return— C-style jumpdo...while— test-at-end loopforeach— iterate any arrayfinalblock — run at end of simulation- Time-unit suffix:
#10ns r = a; unique/priorityqualifiers- Pattern matching on
caseandif
← Blocking & Nonblocking — SV Additions
The core blocking (=) and non-blocking (<=) assignment semantics are unchanged from Verilog-2001. SystemVerilog adds three enhancements.
1 — Compound assignment operators
// 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
2 — NBA to automatic variables is illegal
// 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
3 — LHS size provides context for RHS
// 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
🕐 Time-Unit Suffix in Assignment Delays
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.
⚡ unique and priority — The Two New Qualifiers
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).
unique
- Conditions/items are mutually exclusive — at most one can match.
- Tool error if two conditions can simultaneously be true.
- Tool error if no condition matches and there is no
else/default. - Synthesis can evaluate all conditions in parallel (simple mux, no priority encoder).
- No default needed — tool knows all cases are covered if none match = error.
priority
- Conditions/items are evaluated in declared order; first match wins.
- Conditions can overlap — that is expected and allowed.
- Tool error if no condition matches and there is no
else/default. - Synthesis generates a priority encoder (first-match logic).
- No default needed — tool knows all cases are covered by design intent.
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.
❓ unique if / priority if
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.
unique if — mutually exclusive conditions
// 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
priority if — ordered evaluation, overlaps allowed
// 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
Side-by-side: synthesis implications
unique if → parallel mux
// 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
priority if → priority encoder
// 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
📌 unique case / priority case
The same two qualifiers apply to all three case statement forms: case, casez, and casex. They appear before the case keyword.
unique case — parallel matching
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
priority casez — first-match with don’t-cares
// 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.
unique case — FSM state machine example
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 — Concepts
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.
Key properties of pattern matching
- Pattern matching is statically type-checked — the compiler knows the type of every pattern and every bound variable at compile time. No runtime surprises from type errors.
- A match result is always 0 or 1 — never X or Z, even if the matched value contains them.
- When a pattern succeeds, pattern identifiers are automatically bound (via ordinary procedural assignment) to the corresponding parts of the matched value.
- Each pattern creates its own local scope. Identifiers bound in one pattern branch can be reused in another branch.
- Identifiers must be unique within a single pattern — the same name cannot appear twice in one pattern.
📌 Six Pattern Forms
// 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);
📋 Pattern Matching in case Statements
Add the keyword matches after the case expression. Each item’s LHS becomes a pattern, with an optional && filter_expr guard.
Basic VInt tagged union dispatch
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
Instruction-set dispatch with && filter
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
Wildcard and constant patterns to filter special cases
// 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 independent)
// 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.
✌ Pattern Matching in if Statements
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.
Single pattern in if
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");
Nested pattern in if
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");
Sequential pattern clauses — bound in one, used in another
// 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 + boolean guard in if
// 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.
📋 Quick Reference
New statement additions at a glance
| 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 vs priority decision guide
- Use
uniquewhen all branches are guaranteed mutually exclusive — synthesis gets a simple parallel mux. - Use
prioritywhen branches can overlap but first-match is the intent — synthesis gets a priority encoder. - Both issue runtime simulation warnings when violations occur, preventing simulation/synthesis mismatch.
- Neither requires a
default/else— but tools will warn at runtime if no branch matches.
Pattern matching rules summary
- Always statically type-checked — no runtime type errors.
- Result is always 0 or 1 — never X, even on X/Z data.
- Bound identifiers are local to each pattern branch scope.
- Identifiers must be unique within a single pattern.
- In named structure patterns, unspecified members are ignored.
- Wildcard
.*always matches and discards; constant.Nmatches the exact value. &&in acase matchesitem is a filter guard — not the same as the&&between clauses in anifpredicate.
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).
