In programming languages, case
(or switch
) statements are used as
a conditional statement in which a selection is made based on
different values of a particular variable or expression. A general
discussion of these statements can be found
here .
In hardware description languages (HDL) such as VHDL and
(System)Verilog, case
statements are also available.
case sel is -- VHDL
when "00" => y <= a;
when "01" => y <= b;
when "10" => y <= c;
when "11" => y <= d;
end case;
case (sel) // (System)Verilog
"00" : y = a;
"01" : y = b;
"10" : y = c;
"11" : y = d;
endcase
The above code fragments demonstrate the use of a case
statement to
describe a 4-to-1 multiplexer, a common case where a case statement is
used. Using case
in VHDL has the advantage that the language
guarantees that all cases are covered. Any choice not covered in
a VHDL case
statement will lead to a compilation error. As a
consequence, a case
statement is preferred over an if
-else if
(or elsif
) tree.
But what if a number of cases trigger the same action? Code duplication should be avoided, because that makes the code harder to maintain. Fortunately, VHDL case statements can handle ranges and lists of values.
variable bmi: natural range 0 to 100;
-- code omitted
case bmi is
when 18 downto 0 => verdict <= "too low "; -- range
when 20 => verdict <= "perfect "; -- single value
when 19 | 21 | 22 | 23 | 24 => verdict <= "good "; -- list of values
when 25 to 29 => verdict <= "a bit high"; -- range
when 30 to 100 => verdict <= "too high "; -- range
end case;
SystemVerilog (but not Verilog) has a case inside
statement with similar functionality:
logic [3:0] sel;
// code omitted
case (sel) inside
[4'h0:4'h7] : y = a; // range
[4'h9:4'hD] : y = b; // range
4'h8, 4'hE : y = c; // list of values
4'hF : y = d; // single value
endcase
Verilog has casex
and casez
statements, in which some bits of the
selection pattern can be marked as don’t care. These statements should
be avoided for range matching. The wildcard match could hide an
undefined or illegal signal value of x
, i.e. it could hide a design
flaw. Priority encoding is one example where casez
is a good fit:
casez (sel) // priority encoder
4'b???1 : prio = 0;
4'b??10 : prio = 1;
4'b?100 : prio = 2;
4'b1000 : prio = 3;
default : prio = 0; //no match
endcase
Finally, a default action may be added to the case statement using
an others
(VHDL) or default
(Verilog/SystemVerilog)
clause. This removes the requirement to enumerate every option in the
case statement. For clarity, the default should be the last clause
of the case statement.
logic [3:0] sel;
// code omitted
case (sel) inside
[4'h0:4'h7] : y = a; // particular values
[4'h9:4'hD] : y = b;
4'hF : y = d;
default : y = c; // default value, in this case `8` and `E`
endcase
The example below shows one of the most common uses of case statements, namely a finite state machine (FSM).
type t_state is (SIT, STAND, WALK, RUN, FLY);
signal state: t_state;
-- code omitted
p_state : process(clk) is
begin
if rising_edge(clk) then
if rst = '1' then
state <= SIT;
else
case state is
when SIT =>
if start_moving then
state <= STAND;
end if;
when STAND =>
state <= WALK;
when WALK =>
state <= RUN;
when RUN =>
if cleared_for_takeoff then
state <= FLY;
end if;
when others => -- default, includes state FLY
state <= SIT;
end case;
end if;
end if;
end process p_state;
A discussion exists
whether an others
or default
clause is recommended if all possible
cases have been enumerated in the case statement. We believe that it
should be up to the designer to make that decision, taking the
following considerations into account:
If the code is meant for simulation only, the presence or absence of a default clause will not affect the design. For clarity, it may be better to leave it out.
If the selection variable may be extended (e.g. more bits, more enumeration literals), a default clause will ensure that the design will still compile with the extension. This may be convenient, but there is a risk that the designer may forget to add specific selections.
A synthesis tool may add hardware to protect the circuit against illegal patterns. This may happen in particular if the selection variable is an enumerated type. If the enumerated type has a number of values that is not a power of 2, or if one-hot encoding is used, some bit patterns of the enumeration value don’t correspond to an enumerated value. In that case, a default value may be used by the synthesis tool to determine what should happen if an illegal selection value is seen, e.g. raise an alarm, or bring a finite state machine (FSM) to a safe state. Depending on the synthesis tool and nature of the circuit, it may be desirable or not to add a protective circuit.
In (System)Verilog, it is not required to cover all possible options of a
case
statement. Combinations of'x'
and'z'
would typically be omitted, potentially hiding the propagation of uninitialized data and leading to synthesis-simulation mismatch. Adding adefault
which produces'x'
-es on its outputs will reveal potential design flaws in simulation and will help debug the design.
In conclusion, case
in HDL is a powerful conditional control
statement. Case statements are easy to understand and maintain. Use
the most suitable variant in your design case for optimal design
quality and maintainability.
Sigasi Visual HDL offers case statement templates and configurable validations to improve designers' productivity.
See also
- Actual? Formal? What do they mean? (blog post)
- Wildcards in sensitivity lists in VHDL and Verilog (blog post)
- Sigasi's Software Development Kit Part 2 (legacy)
- Sigasi's Software Development Kit Part 1 (legacy)
- Prefix all signals in an instantiation (legacy)