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.
Truth table (highest-priority active input wins):
| req[3] | req[2] | req[1] | req[0] | enc[1:0] | valid |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 2'b00 | 0 |
| 0 | 0 | 0 | 1 | 2'b00 | 1 |
| 0 | 0 | 1 | X | 2'b01 | 1 |
| 0 | 1 | X | X | 2'b10 | 1 |
| 1 | X | X | X | 2'b11 | 1 |
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.
| Statement | z/? in item = don't-care | x in item = don't-care | x in expression = don't-care | Safe for RTL? |
|---|---|---|---|---|
case | No | No | No | Yes |
casez | Yes | No | No | Yes — preferred |
casex | Yes | Yes | Yes | No — dangerous |
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
// 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
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
? 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)
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
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.
Key synthesis implications:
- Critical path grows linearly with the number of inputs — a 32-input encoder has 32 MUX levels in the worst case. For large encoders (16+ inputs), consider a tree-based (hierarchical) priority encoder.
- The first matching branch in casez is selected — exactly like an if-else chain. This is correct by definition for priority encoding.
- Unlike parallel case (see below), casez does not assume branches are mutually exclusive — synthesis correctly generates priority logic.
- Adding the
// synthesis translate_off/onguardrail or/* synthesis full_case */pragma can reduce the default latch — but only if you understand the 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.
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 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
| Feature | priority case | unique case | plain case / casez |
|---|---|---|---|
| Branches evaluated | In order (priority) | In parallel (1-hot) | In order (priority) |
| Overlapping branches allowed? | Yes | No (simulation assertion) | Yes |
| Must cover all values? | Yes (simulation error if none match) | Yes (simulation error) | No (latch possible) |
| Synthesizes priority chain? | Yes | No — parallel MUX | Yes |
| Use for priority encoder? | Yes | No | Yes (add defaults) |
| Use for 1-hot decode? | Works but suboptimal | Yes — optimal | Works but suboptimal |
Anti-Patterns to Avoid
Anti-pattern 1 — using casex instead of casez
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
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
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
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
// 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).
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
Quick Reference
| Scenario | Best construct | Reason |
|---|---|---|
| Priority encoder, don't-cares in pattern | casez with ? | Safe don't-care, x propagates correctly |
| 1-hot / fully decoded select | unique case | Parallel MUX, no priority chain needed |
| Priority encoder in SystemVerilog | priority casez | Explicit priority intent, linter checks |
| Don't-care matching of any kind | casex | x in expression silently matches — never use |
| FSM state decode (one-hot) | unique case | Parallel MUX, assertion on invalid state |
| All outputs need safe defaults | Default assignment before casez | Prevents latch inference |
| Large (16+) priority encoder | Hierarchical tree | O(log N) delay vs O(N) flat chain |
| Interrupt controller grant logic | casez or priority casez | Multiple simultaneous interrupts must be handled |
Frequently Asked Questions
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.unique case tells the synthesizer that branches are mutually exclusive, generating a parallel MUX that does not implement priority.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.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).