Priority or Parallel
This section has given you if for priority, case for parallel lookup, casez for wildcards, and the pragmas that try to force one or the other. The remaining question is how to choose for a given specification, rather than by whichever compiled first.
The trade-off
| Structure | Delay | When |
|---|---|---|
if chain |
grows with the number of conditions | one condition must beat another |
case on distinct values |
about $\log$ of the number of arms | the alternatives exclude each other |
| one-hot decode | a flat AND-OR, constant | inputs guaranteed one-hot |
Priority gives a defined answer when several conditions are true together, and pays for it in depth, because each condition waits on the ones before it. Parallel decode is shallower and smaller, and correct only if the assumption holds that one alternative is true at a time.
One-hot decode
When the input is known to be one-hot, the case (1'b1) idiom writes the decode as a table:
always @(*) begin
grant = 4'b0000;
case (1'b1)
req[0]: grant = 4'b0001;
req[1]: grant = 4'b0010;
req[2]: grant = 4'b0100;
req[3]: grant = 4'b1000;
default: grant = 4'b0000;
endcase
end
Each item is a one-bit expression compared with 1, and exactly one matches. Strictly, case semantics still give the first match priority, so the tool builds a chain unless it is told otherwise; with a genuinely one-hot input the chain is harmless, and a unique case in SystemVerilog lets the tool flatten it and the simulator check the claim.
The same decoder twice
// as a chain
always @(*) begin
if (opcode == 3'b000) result = op0_result;
else if (opcode == 3'b001) result = op1_result;
else if (opcode == 3'b010) result = op2_result;
else if (opcode == 3'b011) result = op3_result;
else if (opcode == 3'b100) result = op4_result;
else if (opcode == 3'b101) result = op5_result;
else if (opcode == 3'b110) result = op6_result;
else result = op7_result;
end
// as a table
always @(*) begin
case (opcode)
3'b000: result = op0_result;
3'b001: result = op1_result;
3'b010: result = op2_result;
3'b011: result = op3_result;
3'b100: result = op4_result;
3'b101: result = op5_result;
3'b110: result = op6_result;
3'b111: result = op7_result;
endcase
end
Same function. Read literally, the chain makes 3'b111 wait behind seven comparisons and 3'b000 behind none, an accident of the order the arms were typed in; the table gives every opcode the same depth. In practice a synthesiser will usually spot that the comparisons in the chain are against distinct constants and build the table anyway. The case says so directly and does not depend on the tool proving it, and for a 32-entry decode that is the version to write.
When priority is the specification
None of this makes chains wrong. A non-maskable interrupt must beat a maskable one; a processor must beat a DMA engine on a shared bus. Those are priority by specification, and if is the honest way to write them.
The bug to fear is the reverse: an arbiter that has priority by accident. Written as an if chain, it grants the lowest-listed request last whenever two arrive together, not because the specification said so but because of the order someone typed. Every test passes, because none of them asserted two requests in the same cycle, and the field finds the case the tests did not.
Say which you mean where you write it:
// priority encoder: nmi must override irq by specification
// parallel decode: req is one-hot by construction, from the arbiter upstream
and back the comment with unique or priority when the tools allow, so that the simulator checks the claim.
A parameterised priority encoder
`timescale 1ns / 1ps
`default_nettype none
module priority_encoder #(
parameter N = 4
) (
input wire [N-1:0] req,
output reg [$clog2(N)-1:0] grant,
output reg valid
);
integer i;
always @(*) begin
grant = {$clog2(N){1'b0}};
valid = 1'b0;
for (i = N - 1; i >= 0; i = i - 1) begin
if (req[i] && !valid) begin // the first hit from the top wins
grant = i[$clog2(N)-1:0];
valid = 1'b1;
end
end
end
endmodule
module tb_priority_encoder;
parameter N = 4;
reg [N-1:0] req;
wire [$clog2(N)-1:0] grant;
wire valid;
priority_encoder #(.N(N)) dut (.req(req), .grant(grant), .valid(valid));
integer k;
initial begin
$dumpfile("tb.vcd");
$dumpvars(0, tb_priority_encoder);
for (k = 0; k < (1 << N); k = k + 1) begin // every input combination
req = k[N-1:0];
#5;
$display("req=%04b grant=%0d valid=%b", req, grant, valid);
end
$finish;
end
endmodule
The loop runs from the top down and the !valid guard stops it overwriting the first hit. Exhaustive testing is the only way to be sure of priority behaviour at every corner, and for four inputs it is sixteen vectors. The for loop that makes this scale is the subject of the next section, used here a little early because an encoder is its most natural application.
Practice Exercise
Three requests, high_pri_req, med_pri_req and low_pri_req, can be asserted in the same cycle, and the resource goes to the highest. Priority or parallel, and write the grant logic.
Solution
Priority, by definition: the specification says one condition overrides the others when several hold.
// priority encoder: high_pri wins ties by specification
always @(*) begin
grant = 2'b00;
if (high_pri_req) grant = 2'b01;
else if (med_pri_req) grant = 2'b10;
else if (low_pri_req) grant = 2'b11;
end
A case has no notion of overlapping conditions with a winner, so writing this as a case means building the priority into the items by hand, which is the if chain again with worse readability.
That closes section 5.3. Section 5.4 is loops: how a for in RTL is a shorthand for repeated hardware rather than a thing that runs, and what that allows and forbids.