SYSTEMVERILOG SERIES · SV-08A

SystemVerilog Series — SV-08a: Procedural Statements & Control Flow — VLSI Trainers
SystemVerilog Series · SV-08a

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, return in loops
  • No do-while loop
  • No foreach for arrays
  • No final block (only initial)
  • No time-unit suffix in delays (#10ns)
  • No unique/priority for if/case
  • No pattern matching for tagged unions/structs

SystemVerilog additions (§8)

  • break, continue, return — C-style jump
  • do...while — test-at-end loop
  • foreach — iterate any array
  • final block — run at end of simulation
  • Time-unit suffix: #10ns r = a;
  • unique / priority qualifiers
  • Pattern matching on case and if

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
Why time-unit suffixes matter: Writing #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.
Simulation vs synthesis behaviour: Both 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)
The qualifier applies to the WHOLE chain. Writing 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/priority replaces full_case/parallel_case attributes. The old synthesis-only attributes had a dangerous property: they changed synthesis behaviour without changing simulation behaviour, so a design might pass simulation but fail in silicon. 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
variable_name
Always matches. Binds the whole value (or member) to the identifier.
Wildcard
.*
Always matches. Discards the value — no binding. Use when you don’t need the value.
Constant
.constant_expr
Matches if the value equals the constant. The dot prefix distinguishes it from an identifier pattern.
Tagged union
tagged MemberName [pattern]
Matches if the union’s current tag is MemberName, and the nested pattern (if any) also matches.
Structure (positional)
{ p1, p2, p3 }
Matches if each member’s nested pattern matches. Members in declaration order.
Structure (named)
{ name:p1, name:p2 }
Named member patterns. Order irrelevant; unspecified members are ignored.
// 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 and priority apply to pattern-matching case too. Writing 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
Short-circuit evaluation applies. The && 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

FeatureSyntaxKey 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 unique when all branches are guaranteed mutually exclusive — synthesis gets a simple parallel mux.
  • Use priority when 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 .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.
Coming next: SV-08b covers the remaining Section 8 topics — loops (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).

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top