Part 1 · Language Foundations · Intermediate

unique, unique0 & priority

Case and if qualifiers, runtime violation warnings, and the full_case/parallel_case pragmas they safely replace.

What the qualifiers assert

Prefixing case or if with unique, unique0, or priority attaches a checked claim about the branch structure that both the simulator and the synthesis tool act on. The simulator verifies the claim at runtime every time the statement executes and emits a violation warning when it is false; the synthesis tool uses the same claim to pick cheaper hardware (a parallel mux instead of a priority chain). Because both tools read the same keyword, the claim cannot drift out of sync the way comment pragmas did.

diagram
QUALIFIER SEMANTICS

  qualifier   claim                              runtime violation when
  ─────────   ────────────────────────────────   ───────────────────────────
  unique      exactly ONE branch matches         zero match  OR  >1 match
  unique0     at most one branch matches         >1 match (no-match is OK)
  priority    at least one branch matches;       zero match
              overlaps OK, first wins

  synthesis interpretation:
  unique / unique0    branches are mutually exclusive  parallel mux
  priority            full coverage  no latch, keep priority chain

unique claims the selector hits exactly one branch: items are mutually exclusive and collectively cover every value that occurs. unique0 relaxes the coverage half — no match is acceptable (useful when a default assignment before the case handles the no-match path). priority claims at least one branch always matches and that overlapping items are intentional, with textual order deciding the winner.


Replacing full_case / parallel_case pragmas

Before SystemVerilog, designers used the synthesis comment pragmas // synopsys full_case and // synopsys parallel_case to make the same claims. The fatal flaw: pragmas are invisible to the simulator. If the claim was wrong — two requests asserted simultaneously under parallel_case — synthesis built hardware assuming exclusivity while simulation modeled priority, and the gate-level netlist behaved differently from RTL sim. These pragma-induced mismatches caused real silicon escapes. unique and priority fix this by making the simulator police the claim continuously: every violated assumption prints a warning during regressions instead of surfacing in the lab.

systemverilog
// OLD (dangerous): claim visible only to synthesis
always @(*) begin
  case (sel)  // synopsys parallel_case full_case
    2'b01: y = a;
    2'b10: y = b;
  endcase
end

// NEW: simulator checks the claim on every evaluation
always_comb begin
  unique case (sel)
    2'b01: y = a;
    2'b10: y = b;
    2'b00, 2'b11: y = '0;
  endcase
end
// If sel ever makes two items true, or none:
//   ** Warning: unique case violation at time ...

always_comb begin
  priority if (irq_nmi)      vec = VEC_NMI;   // overlap intended,
  else if   (irq_timer)      vec = VEC_TMR;   // order = priority
  else if   (irq_soft)       vec = VEC_SW;
  else                       vec = VEC_NONE;  // covers no-match
end

Violation reporting and the glitch caveat

Violation checks run whenever the statement executes — including on intermediate combinational glitches partway through a delta cycle. A momentary two-hot selector during settling triggers a unique warning even though the settled value is legal. Simulators mitigate this by deferring the check to the end of the time step in newer LRM revisions, but tool behavior varies, so teams often filter these warnings to clocked contexts or accept some noise. The warnings are warnings, not errors by default — promote them to errors in regression flows or they scroll past unread, defeating the purpose.

Interview angle: unique vs priority

  1. unique asserts mutual exclusivity AND full coverage — simulator flags zero or multiple matches; synthesis builds a parallel mux.

  2. priority asserts only coverage — overlaps are intentional, first match wins, hardware keeps the priority chain.

  3. Both replace full_case/parallel_case pragmas, with the critical improvement that simulation verifies the claim.

  4. unique0 is unique minus the coverage claim — useful with a preceding default assignment.

diagram
CHOOSING A QUALIFIER

  Are branch conditions mutually exclusive by construction?
        │
   ┌────┴────┐
   YES       NO (overlap is intended, order matters)
   │              │
   │              └──► priority case / priority if
   │
   Is every selector value covered by an item?
        │
   ┌────┴────┐
   YES       NO (default assignment before the case)
   │              │
   ▼              ▼
  unique        unique0

Key takeaways

  • unique = exactly one match, unique0 = at most one, priority = at least one with ordered overlap.

  • Qualifiers make the simulator verify what synthesis assumes — eliminating pragma-induced sim/gate mismatches.

  • Violations are runtime warnings; promote them to errors in regression or they go unnoticed.

  • Glitch-time violations on combinational paths are a known noise source — know your tool's deferral behavior.

Common pitfalls

  • Treating unique case as just a lint hint — it changes synthesized hardware to a parallel mux.

  • Using unique when two branches can legitimately overlap — every overlap fires a runtime violation.

  • Keeping full_case/parallel_case pragmas alongside qualifiers — conflicting claims confuse tools.

  • Ignoring unique-violation warnings in regression logs — they are exactly the sim/synth mismatch you wanted to catch.