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

case

If if-else can express any selection, why have case at all? Because it says something different. An if chain says “test these in order and stop at the first that holds”. A case says “compare this expression with these values”, and when the values are distinct constants, which they nearly always are, exactly one can match and there is no order to respect. The synthesiser can build a decoder and a flat mux instead of a chain.

What it builds

always @(*) begin
    case (alu_op)
        2'b00:   result = a + b;
        2'b01:   result = a - b;
        2'b10:   result = a & b;
        2'b11:   result = a | b;
        default: result = 'x;
    endcase
end

Written as an if chain, an eight-way decode is up to seven comparisons in series on the worst path. As a case on distinct constants it is a decoder, whose outputs are all at the same depth, feeding a mux tree. For a 2-bit select it hardly matters. For the 32-entry opcode table of a real decoder it is the difference between a clean decode stage and the critical path.

To be exact about the language: Verilog case picks the first matching arm, so if two arms could both match, the earlier one wins and there is priority after all. With distinct constants that cannot happen, and the tool knows it, which is what lets it flatten the logic. Modern tools also recognise an if chain of comparisons against distinct constants and flatten that too; the point of case is that the intent is on the page and the tool does not have to prove it.

Several values, one arm

case (state)
    IDLE, RESET: next = INIT;
    ...
endcase

A comma-separated list makes several values share an arm, which is an OR of their decodes.

default

An unlisted value on which nothing is assigned is a latch, exactly as with a missing else. The default closes it, and what you put there says something:

default: result = 'x;     // these values cannot occur; optimise freely
default: result = 1'b0;   // these values can occur; this is the safe answer

The 'x is a don’t-care and the synthesiser uses it to shrink the logic. That is right when the values really are unreachable, and it is a bug moved three modules downstream when they are not. Choose the one that is true.

Decoding a state

case is most at home decoding a state register, where the encodings are distinct by construction:

always @(*) begin
    active_led = 1'b0;
    done_led   = 1'b0;
    case (state)
        ACTIVE:  active_led = 1'b1;
        DONE:    done_led   = 1'b1;
        default: ;             // IDLE and anything illegal: both off
    endcase
end

An ALU decoder

`timescale 1ns / 1ps
`default_nettype none

module alu_decoder (
    input  wire [2:0] op,
    input  wire [7:0] a, b,
    output reg  [7:0] result
);

    always @(*) begin
        case (op)
            3'b000:  result = a + b;
            3'b001:  result = a - b;
            3'b010:  result = a & b;
            3'b011:  result = a | b;
            3'b100:  result = a ^ b;
            3'b101:  result = ~a;
            3'b110:  result = a << 1;
            3'b111:  result = a >> 1;
            default: result = 8'hxx;   // only x or z on op reaches this
        endcase
    end

endmodule

module tb_alu_decoder;
    reg  [2:0] op;
    reg  [7:0] a, b;
    wire [7:0] result;

    alu_decoder dut (.op(op), .a(a), .b(b), .result(result));

    task check;
        input [2:0] o;
        input [7:0] exp;
        begin
            op = o; #5;
            if (result !== exp)
                $display("FAIL: op=%03b result=%h (exp %h)", o, result, exp);
            else
                $display("PASS: op=%03b result=%h", o, result);
        end
    endtask

    initial begin
        $dumpfile("tb.vcd");
        $dumpvars(0, tb_alu_decoder);

        a = 8'h0F; b = 8'h03;
        check(3'b000, a + b);
        check(3'b001, a - b);
        check(3'b010, a & b);
        check(3'b011, a | b);
        check(3'b100, a ^ b);
        check(3'b101, ~a);
        check(3'b110, a << 1);
        check(3'b111, a >> 1);

        $finish;
    end
endmodule

Work out op = 100 by hand before running it: 0x0F ^ 0x03 is 0x0C. When the simulation agrees, you have seen one arm per value and no fall-through.

Use case when the alternatives exclude each other, opcodes and states, and if when one must beat another. The next article adds wildcards to case, and explains why one of the two wildcard forms should never be written again.

โ† if and else casez, and Why Not casex โ†’