RTL Deep-Dive

Priority Encoder
casez, casex & Synthesis

Master Verilog's case statement variants for don't-care matching, build 4-to-2 and 8-to-3 priority encoders that synthesize correctly, understand why casex masks simulation bugs, and use SystemVerilog's priority case to express intent explicitly.

~18 min read
Verilog + SystemVerilog
High Interview Frequency

What is a Priority Encoder?

A priority encoder takes N active-high request inputs and produces a binary-encoded output indicating which input has the highest priority — when multiple inputs are active simultaneously, only the highest-priority one is acknowledged.

A 4-to-2 priority encoder has 4 request inputs (req[3:0]) and 2-bit encoded output (enc[1:0]), plus a valid output indicating at least one request is active.

PRIORITY ENCODER 4-to-2 req[3] ─ highest priority req[2] req[1] req[0] ─ lowest priority enc[1:0] valid

Truth table (highest-priority active input wins):

req[3]req[2]req[1]req[0]enc[1:0]valid
00002'b000
00012'b001
001X2'b011
01XX2'b101
1XXX2'b111

X = don't-care (lower-priority inputs are ignored when a higher-priority request is active)

casez vs casex — Don't-Care Semantics

Verilog has three case statement variants: case, casez, and casex. They differ in how they handle the 4-state values 0, 1, x, and z in pattern matching.

Statementz/? in item = don't-carex in item = don't-carex in expression = don't-careSafe for RTL?
caseNoNoNoYes
casezYesNoNoYes — preferred
casexYesYesYesNo — dangerous
Why casex is dangerous: when req contains x (from an uninitialized register, a reset state, or a CDC signal), casex silently matches the first case item whose pattern has x or z. Your simulation appears to work correctly but you are testing the wrong branch. casex hides x-propagation bugs that should surface as simulation warnings.
Rule: always use casez (not casex) for don't-care pattern matching. Use ? as the don't-care character — it is identical to z in a case item but clearer in intent.

Matching rules illustrated

casez vs casex — how x propagates
// Signal with unknown value after power-up:
// req = 4'bx100  (bit 3 is x — uninitialized)

casez (req)
  4'b1???: // does NOT match — x ≠ 1 in expression bit[3]
  4'b01??: // does NOT match
  4'b001?: // does NOT match
  4'b0001: // does NOT match
  default: // ← lands here → x propagates to output (good!)
endcase

// Same signal with casex:
casex (req)
  4'b1???: // MATCHES — x in expression treated as don't-care!
             // Bug silently masked. req[3]=x accepted as req[3]=1.
endcase

4-to-2 Priority Encoder with casez

Verilog — 4-to-2 Priority Encoder
module priority_enc4 (
  input  wire [3:0] req,
  output reg  [1:0] enc,
  output reg        valid
);

  always @(*) begin
    // Default assignments first — prevents latch inference
    enc   = 2'b00;
    valid = 1'b0;

    casez (req)
      4'b1???: begin enc = 2'b11; valid = 1'b1; end  // req[3] highest
      4'b01??: begin enc = 2'b10; valid = 1'b1; end
      4'b001?: begin enc = 2'b01; valid = 1'b1; end
      4'b0001: begin enc = 2'b00; valid = 1'b1; end
      // default: enc=0, valid=0 already set above
    endcase
  end

endmodule
The ? character in casez patterns is identical to z — both mean don't-care. Using ? is more readable. The default assignments at the top of the always block prevent latch inference when no case branch matches.

Equivalent if-else chain (same synthesis result)

Verilog — if-else style (identical RTL)
always @(*) begin
  enc   = 2'b00;
  valid = 1'b0;

  if      (req[3]) begin enc = 2'b11; valid = 1'b1; end
  else if (req[2]) begin enc = 2'b10; valid = 1'b1; end
  else if (req[1]) begin enc = 2'b01; valid = 1'b1; end
  else if (req[0]) begin enc = 2'b00; valid = 1'b1; end
end

8-to-3 Priority Encoder

Verilog — 8-to-3 Priority Encoder (casez)
module priority_enc8 (
  input  wire [7:0] req,
  output reg  [2:0] enc,
  output reg        valid
);

  always @(*) begin
    enc   = 3'd0;
    valid = 1'b0;

    casez (req)
      8'b1???????: begin enc = 3'd7; valid = 1'b1; end
      8'b01??????: begin enc = 3'd6; valid = 1'b1; end
      8'b001?????: begin enc = 3'd5; valid = 1'b1; end
      8'b0001????: begin enc = 3'd4; valid = 1'b1; end
      8'b00001???: begin enc = 3'd3; valid = 1'b1; end
      8'b000001??: begin enc = 3'd2; valid = 1'b1; end
      8'b0000001?: begin enc = 3'd1; valid = 1'b1; end
      8'b00000001: begin enc = 3'd0; valid = 1'b1; end
    endcase
  end

endmodule

How Synthesis Maps casez

A casez priority encoder synthesizes to a priority MUX chain — a series of 2-to-1 multiplexers where each level is gated by the higher-priority condition. The select of each MUX is req[N] ORed with all higher-priority requests.

SYNTHESIZED PRIORITY CHAIN (4-to-2 example) MUX 2'b11 ─ (next) 2'b11 req[3] MUX req[2] & ~req[3] 2'b10 MUX req[1] & ~req[3:2] 2'b01 enc[1:0] Critical path: through all priority gates req[3] → req[2] → req[1] → req[0]

Key synthesis implications:

SystemVerilog: priority case & unique case

SystemVerilog adds priority and unique modifiers to if/case that communicate synthesis intent to both the tool and the linter.

priority case — use for priority encoders
SystemVerilog
module priority_enc8_sv (
  input  logic [7:0] req,
  output logic [2:0] enc,
  output logic       valid
);
  always_comb begin
    enc   = 3'd0;
    valid = 1'b0;
    priority casez (req)
      8'b1???????: begin
        enc = 3'd7; valid = 1'b1;
      end
      8'b01??????: begin
        enc = 3'd6; valid = 1'b1;
      end
      8'b001?????: begin
        enc = 3'd5; valid = 1'b1;
      end
      // ... remaining cases
    endcase
  end
endmodule
unique case — NOT for priority encoders
SystemVerilog — unique removes priority
// unique case tells synthesis:
// "branches are mutually exclusive"
// → tool generates PARALLEL logic (no priority)
// → WRONG for priority encoders!

always_comb begin
  unique case (sel)   // OK for non-overlapping decode
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    2'b11: y = d;
  endcase
end

// Simulation: fires assertion if two
// branches match simultaneously.
// Good for 1-hot or decoded selects.

priority case vs unique case — key differences

Featurepriority caseunique caseplain case / casez
Branches evaluatedIn order (priority)In parallel (1-hot)In order (priority)
Overlapping branches allowed?YesNo (simulation assertion)Yes
Must cover all values?Yes (simulation error if none match)Yes (simulation error)No (latch possible)
Synthesizes priority chain?YesNo — parallel MUXYes
Use for priority encoder?YesNoYes (add defaults)
Use for 1-hot decode?Works but suboptimalYes — optimalWorks but suboptimal

Anti-Patterns to Avoid

Anti-pattern 1 — using casex instead of casez

Dangerous — casex
Anti-pattern
always @(*) begin
  enc   = 2'b00;
  valid = 1'b0;
  casex (req)  // ← BUG: x in req
    4'b1xxx: begin   // matches req=4'bx100!
      enc = 2'b11;
      valid = 1'b1;
    end
    // ...
  endcase
end
Safe — casez with ?
Correct
always @(*) begin
  enc   = 2'b00;
  valid = 1'b0;
  casez (req)  // ← safe: x in req
    4'b1???: begin   // does NOT match x100
      enc = 2'b11;
      valid = 1'b1;
    end
    // ...
  endcase
end

Anti-pattern 2 — missing default → latch inference

Latch inferred — no default assignment
Bug
always @(*) begin
  // No default! If req=4'b0000,
  // enc and valid keep old value
  // → synthesis infers latch
  casez (req)
    4'b1???: begin
      enc = 2'b11; valid = 1'b1;
    end
    // ... no default case
  endcase
end
Correct — default assignment at top
Fixed
always @(*) begin
  enc   = 2'b00;  // default first
  valid = 1'b0;

  casez (req)
    4'b1???: begin
      enc = 2'b11; valid = 1'b1;
    end
    // all outputs already set above
    // → no latch, no need for default:
  endcase
end

Anti-pattern 3 — using unique case with overlapping inputs

Bug — unique asserts mutual exclusion, wrong for multi-hot req
// If req = 8'b1100_0000 (req[7] and req[6] both active):
unique casez (req)   // ← fires a simulation assertion error!
  8'b1???????: enc = 3'd7;
  8'b01??????: enc = 3'd6;  // both match → unique violation
  // ...
endcase

// Fix: use "priority casez" — priority correctly handles multi-hot

Scalable: Hierarchical (Tree) Priority Encoder

For 16 or more inputs, a flat casez chain becomes timing-critical. A hierarchical encoder breaks the problem into groups, encodes each group, then merges — achieving O(log N) delay instead of O(N).

Verilog — 8-to-3 built from two 4-to-2 encoders
module priority_enc8_tree (
  input  wire [7:0] req,
  output reg  [2:0] enc,
  output wire       valid
);
  wire [1:0] enc_hi, enc_lo;
  wire       vld_hi, vld_lo;

  // High group: req[7:4]
  priority_enc4 u_hi (.req(req[7:4]), .enc(enc_hi), .valid(vld_hi));
  // Low group: req[3:0]
  priority_enc4 u_lo (.req(req[3:0]), .enc(enc_lo), .valid(vld_lo));

  assign valid = vld_hi | vld_lo;

  always @(*) begin
    if (vld_hi)
      enc = {1'b1, enc_hi};  // high group wins (req[7:4] = higher priority)
    else
      enc = {1'b0, enc_lo};  // low group
  end

endmodule
The tree encoder has 2-level delay: one level for the 4-to-2 sub-encoders in parallel, one level for the merge MUX. For a 16-input encoder, cascade three levels: four 4-to-2 → two 4-to-2 → one 4-to-2 merge. Delay is proportional to log₂(N).

Quick Reference

ScenarioBest constructReason
Priority encoder, don't-cares in patterncasez with ?Safe don't-care, x propagates correctly
1-hot / fully decoded selectunique caseParallel MUX, no priority chain needed
Priority encoder in SystemVerilogpriority casezExplicit priority intent, linter checks
Don't-care matching of any kindcasexx in expression silently matches — never use
FSM state decode (one-hot)unique caseParallel MUX, assertion on invalid state
All outputs need safe defaultsDefault assignment before casezPrevents latch inference
Large (16+) priority encoderHierarchical treeO(log N) delay vs O(N) flat chain
Interrupt controller grant logiccasez or priority casezMultiple simultaneous interrupts must be handled

Frequently Asked Questions

In casez, the characters z and ? in a case item pattern are treated as don't-cares — they match 0, 1, x, and z in the expression being tested. x values in the expression still propagate as x. In casex, both x and z in the case item and x in the expression are treated as don't-cares — so a signal that is x (unknown, from uninitialized state or a reset condition) silently matches the first case item with an x in its pattern. This masks simulation bugs. Always prefer casez.
Because casez evaluates branches in order, exactly like an if-else chain. The synthesizer generates a priority MUX chain: first check req[N-1], then req[N-2], and so on. When multiple inputs are active, the first matching (highest-priority) branch is selected and lower-priority branches are masked. This is exactly the behavior a priority encoder needs. In contrast, unique case tells the synthesizer that branches are mutually exclusive, generating a parallel MUX that does not implement priority.
A plain (binary) encoder assumes exactly one input is asserted at a time. If two inputs are active simultaneously, the output is undefined. A priority encoder handles simultaneous active inputs by always selecting the highest-priority one. Priority encoders add: (1) defined behavior for multi-hot inputs, (2) a valid output indicating any request is active, and (3) a clear priority ordering. Real systems — interrupt controllers, bus arbiters, resource schedulers — almost always require priority encoders, not plain encoders.
Yes — but only if you assign all outputs to their safe defaults before the casez block. The pattern is: assign defaults unconditionally at the top of the always block, then override in the matching casez branch. This guarantees every output is assigned in every code path, so synthesis infers no latch. If you skip the default assignment and have no default casez branch, synthesis will infer latches for every output that is not assigned in the no-match case.
priority if is the if-else equivalent of priority case. It tells the simulator to fire a runtime error if no branch is taken (all conditions false), ensuring completeness. For synthesis, it communicates that the if-else chain is intentionally a priority structure — the synthesizer generates a priority chain and may suppress warnings about overlapping conditions. It is best paired with always_comb.
A one-hot to binary converter is essentially a priority encoder where inputs are guaranteed to be one-hot. The casez implementation is identical — the only difference is the design assumption. In an interview, use casez for the implementation but mention that: (1) if one-hot is guaranteed, unique casez in SystemVerilog allows the synthesizer to generate parallel (non-priority) logic, which is more area-efficient; (2) casez correctly produces the binary index from the one-hot position; (3) the valid output can be used to detect the zero case (no active input).