Module 5: Behavioral Verilog for Synthesis ยท 5.3 Conditional and Case Statements

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.

โ† full_case and parallel_case for Loops and Synthesis โ†’