8-to-3 Priority Encoder Verilog Code with Valid Flag
A priority encoder collapses a set of request lines into a compact binary index, reporting which of several simultaneous requests is the most important. The 8-to-3 version accepts eight inputs and produces a 3-bit code plus a valid flag that tells downstream logic whether any request was active at all. This tutorial builds a clean, synthesizable 8-to-3 priority encoder in Verilog and then verifies the one behavior every encoder must get right: priority tie-breaking when more than one input is high at the same time.
📘 New to the concept? Read the theory first: Encoders & Priority Encoders: theory.
module priority_encoder_8to3 (
input wire [7:0] req, // request lines, req[7] = highest priority
output reg [2:0] code, // index of highest active request
output reg valid // 1 when at least one request is active
);
always @(*) begin
valid = 1'b1;
if (req[7]) code = 3'd7;
else if (req[6]) code = 3'd6;
else if (req[5]) code = 3'd5;
else if (req[4]) code = 3'd4;
else if (req[3]) code = 3'd3;
else if (req[2]) code = 3'd2;
else if (req[1]) code = 3'd1;
else if (req[0]) code = 3'd0;
else begin
code = 3'd0; // defined output when idle
valid = 1'b0;
end
end
endmodule
module tb_priority_encoder_8to3;
reg [7:0] req;
wire [2:0] code;
wire valid;
priority_encoder_8to3 dut (.req(req), .code(code), .valid(valid));
initial begin
$display(" time | req | code | valid | note");
$display("-------+------------+------+-------+--------------------");
req = 8'b0000_0000; #5
$display(" %4t | %b | %0d | %b | no request", $time, req, code, valid);
req = 8'b0000_0001; #5
$display(" %4t | %b | %0d | %b | only bit0", $time, req, code, valid);
req = 8'b1000_0000; #5
$display(" %4t | %b | %0d | %b | only bit7", $time, req, code, valid);
req = 8'b1010_0100; #5
$display(" %4t | %b | %0d | %b | tie 7,5,2 -> 7 wins", $time, req, code, valid);
req = 8'b0011_0010; #5
$display(" %4t | %b | %0d | %b | tie 5,4,1 -> 5 wins", $time, req, code, valid);
req = 8'b0000_0110; #5
$display(" %4t | %b | %0d | %b | tie 2,1 -> 2 wins", $time, req, code, valid);
$display("-------+------------+------+-------+--------------------");
$display("Priority tie-breaking verified: highest active index always wins.");
$finish;
end
endmoduletime | req | code | valid | note
-------+------------+------+-------+--------------------
5 | 00000000 | 0 | 0 | no request
10 | 00000001 | 0 | 1 | only bit0
15 | 10000000 | 7 | 1 | only bit7
20 | 10100100 | 7 | 1 | tie 7,5,2 -> 7 wins
25 | 00110010 | 5 | 1 | tie 5,4,1 -> 5 wins
30 | 00000110 | 2 | 1 | tie 2,1 -> 2 wins
-------+------------+------+-------+--------------------
Priority tie-breaking verified: highest active index always wins.
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpooof6dna/priority_encoder_8to3.v:47: $finish called at 30 (1s)How the Verilog code works
The design is written behaviorally inside a single always @(*) block, so the synthesis tool infers pure combinational logic rather than a clocked register. Because there is no clock or reset, the output follows the inputs the instant they change, which is exactly what an encoder should do.
The heart of the module is the if / else if ladder. In Verilog an if chain has an implicit top-to-bottom priority: the first condition that is true wins, and every later branch is skipped. By testing req[7] first and req[0] last, the ladder encodes a fixed rule — the highest-numbered active request always determines the output. That is precisely how tie-breaking is resolved when several request bits are high together.
Two details keep the logic safe for synthesis. First, valid is assigned 1'b1 at the top of the block and only pulled low in the final else, so it always has a known value. Second, the trailing else assigns both code and valid, meaning every signal is driven on every path. Fully assigning the outputs on all branches is what stops the tool from inferring an unwanted latch.
Why the valid flag matters
Without a valid flag the output is ambiguous. A code of 3'b000 could mean “only req[0] is active” or “nothing is active at all,” and downstream logic cannot tell the two apart. The valid bit removes that ambiguity: it is 1 whenever at least one request is asserted and 0 only when the whole input bus is idle. The simulation log above shows the idle vector producing code = 0, valid = 0, while the single-bit-0 vector produces code = 0, valid = 1.
| req[7:0] | code | valid | reason |
|---|---|---|---|
| 00000000 | 0 | 0 | no request active |
| 00000001 | 0 | 1 | only bit 0 |
| 10100100 | 7 | 1 | bits 7,5,2 tie, bit 7 wins |
| 00110010 | 5 | 1 | bits 5,4,1 tie, bit 5 wins |
A self-checking casez alternative
The if / else if style is readable, but many engineers prefer a casez statement because the wildcard pattern makes the priority order visually obvious. In casez, the ? symbol means “don’t care,” so the first pattern 8'b1???_???? matches any vector whose top bit is set, regardless of the lower bits. The patterns are checked in order, giving the same top-down priority as the ladder. The testbench below is self-checking: it compares the encoder against an independent reference function that scans for the most-significant set bit, and prints PASS or FAIL for each vector.
module priority_encoder_casez (
input wire [7:0] req,
output reg [2:0] code,
output reg valid
);
always @(*) begin
valid = 1'b1;
casez (req) // ? means "don't care" in casez
8'b1???_????: code = 3'd7;
8'b01??_????: code = 3'd6;
8'b001?_????: code = 3'd5;
8'b0001_????: code = 3'd4;
8'b0000_1???: code = 3'd3;
8'b0000_01??: code = 3'd2;
8'b0000_001?: code = 3'd1;
8'b0000_0001: code = 3'd0;
default: begin code = 3'd0; valid = 1'b0; end
endcase
end
endmodule
module tb_priority_encoder_casez;
reg [7:0] req;
wire [2:0] code;
wire valid;
integer i;
integer errors;
reg [3:0] exp;
reg ok;
priority_encoder_casez dut (.req(req), .code(code), .valid(valid));
// reference: index of the most significant set bit, 8 means "none"
function [3:0] msb_index;
input [7:0] v;
integer k;
begin
msb_index = 4'd8;
for (k = 0; k < 8; k = k + 1)
if (v[k]) msb_index = k[3:0];
end
endfunction
initial begin
errors = 0;
$display(" req | code valid | expected | result");
$display("----------+------------+----------+-------");
for (i = 0; i < 8; i = i + 1) begin
// several bits set at once to stress priority tie-breaking
req = (8'b1 << i) | (8'b1 << (i / 2));
#5;
exp = msb_index(req);
ok = (valid && (code === exp[2:0]));
if (!ok) errors = errors + 1;
$display(" %b | %0d %b | %0d | %s",
req, code, valid, exp, ok ? "PASS" : "FAIL");
end
req = 8'b0000_0000;
#5;
ok = (valid == 1'b0);
if (!ok) errors = errors + 1;
$display(" %b | %0d %b | none | %s",
req, code, valid, ok ? "PASS" : "FAIL");
$display("----------+------------+----------+-------");
if (errors == 0)
$display("ALL CHECKS PASSED: casez encoder matches MSB-priority reference.");
else
$display("FAILURES: %0d", errors);
$finish;
end
endmodulereq | code valid | expected | result ----------+------------+----------+------- 00000001 | 0 1 | 0 | PASS 00000011 | 1 1 | 1 | PASS 00000110 | 2 1 | 2 | PASS 00001010 | 3 1 | 3 | PASS 00010100 | 4 1 | 4 | PASS 00100100 | 5 1 | 5 | PASS 01001000 | 6 1 | 6 | PASS 10001000 | 7 1 | 7 | PASS 00000000 | 0 0 | none | PASS ----------+------------+----------+------- ALL CHECKS PASSED: casez encoder matches MSB-priority reference. /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmp729ilj4s/priority_encoder_casez.v:69: $finish called at 45 (1s)
Common mistakes to avoid
- Dropping the final else/default. If a branch leaves
codeorvalidunassigned, the combinational block infers a latch and simulation shows red X values. Always drive every output on every path. - Using nonblocking assignments. Inside a combinational
always @(*)block, use blocking=. Nonblocking<=is for clocked (sequential) logic and can produce misleading simulation here. - Reaching for casex.
casextreats unknownxvalues in the input as don’t-cares too, which can silently mask a real unknown.casezonly wildcards high-impedance and?, so it is the safer choice. - Encoding the wrong priority direction. Decide early whether the most-significant or least-significant active bit should win, and order the branches to match. Reversing them quietly flips the tie-breaking rule.
- Leaving code undefined when valid is 0. Even when nothing is requested, assign a known value such as
3'd0so downstream logic never samples an X.
Quick check
If req[7], req[3] and req[0] are all high at once, what is the 3-bit code?
7 (binary 111). The if ladder tests req[7] first, so the highest-numbered active request wins the tie and lower requests are ignored.
Why does the encoder need a separate valid output?
Because code = 000 is ambiguous on its own: it could mean “only request 0 is active” or “no request is active.” The valid flag is 1 whenever any input is asserted and 0 only when the bus is idle, so downstream logic can tell the two cases apart.
What unwanted hardware is inferred if you omit the else/default branch?
A latch. In a combinational block, any path that leaves an output unassigned forces the tool to hold the previous value, which requires a latch and usually signals a design bug.
What is the difference between casez and casex for the wildcard?
casez treats z (high impedance) and ? as don’t-cares. casex additionally treats x (unknown) as a don’t-care, which can hide genuine unknown values during simulation, so casez is generally preferred for priority encoders.
Share your question in comments or talk to our mentor team for batch guidance.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses