VHDL · Chapter 5.5 · Sequential Statements & Processes
The case Statement
When a decision is based on the value of one expression rather than a list of prioritized conditions, the case statement is the right tool. It selects a branch by the value of a single expression, the choices are mutually exclusive and have no priority, and they must cover every possible value, which is why VHDL requires a catch-all others branch. That makes case synthesize to a balanced multiplexer or decoder rather than a priority chain, and it is the cleanest way to write opcode decoders and, later, finite state machines. Built on the process execution model, this lesson contrasts case with the priority if, shows ranges and grouped choices, and explains why exhaustive coverage keeps your logic free of unwanted latches.
Foundation14 min readVHDLcase statementDecoderMultiplexerFSMSequential
1. Engineering intuition — decode by value, no priority
Some decisions are not a priority ladder but a clean lookup: "for this opcode do this; for that
opcode do that." Every value maps to exactly one action, and no action outranks another. The case
statement expresses that as a balanced decode — the selector value picks one branch directly,
with all branches equal rank. Where if builds a priority chain, case builds a flat,
single-layer selection, which is both clearer to read and cheaper in logic depth for value-based
choices.
2. Formal explanation — exhaustive, mutually exclusive choices
case expr is
when val1 => -- statements
when val2 | val3 => -- grouped choices (val2 OR val3)
when val4 to val7 => -- a range of choices
when others => -- covers EVERY remaining value (required)
end case;The selector expr is evaluated once and compared against each when choice; the matching branch
runs. Choices must be mutually exclusive and cover the entire value space of the selector —
VHDL enforces this, so a when others (or an explicit full enumeration) is mandatory. As with the
combinational if, every output must be assigned on every branch (or defaulted at the top) to stay
latch-free.
3. Production RTL — decoders, ALUs, and state
library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all;
-- (A) Opcode decode (balanced): each opcode selects one result.
alu : process (all)
begin
result <= (others => '0'); -- default → latch-free
case opcode is
when "00" => result <= std_logic_vector(unsigned(a) + unsigned(b));
when "01" => result <= std_logic_vector(unsigned(a) - unsigned(b));
when "10" => result <= a and b;
when others => result <= a or b; -- covers "11" and metavalues
end case;
end process;
-- (B) case on an enumerated type — the FSM idiom (Module 9 builds on this).
type state_t is (IDLE, RUN, DONE);
nxt : process (all)
begin
next_state <= state; -- default: hold
case state is
when IDLE => if go = '1' then next_state <= RUN; end if;
when RUN => if fin = '1' then next_state <= DONE; end if;
when DONE => next_state <= IDLE;
end case; -- enum fully covered → no 'others' needed
end process;What hardware does this become? (A) a 4:1 result multiplexer driven by opcode (with the
arithmetic via numeric_std); (B) the next-state combinational logic of a state machine. Note that
when the selector is an enumerated type and all literals are listed, the choices are already
exhaustive, so no when others is required.
4. Hardware interpretation — a balanced mux/decoder
5. Simulation interpretation — match once, run one branch
By the execution model, when the process wakes the selector is evaluated and matched against the
choices; the single matching branch runs (zero time, signals scheduled), and the others do not. There
is no ordered scan as in if — the match is a flat decode, which is why there is no priority.
An ALU case: opcode value routes the selected result
8 cycles6. Debugging example — the incomplete case
Expected: a clean value decode that compiles and synthesises to a mux. Observed: a compile
error that the choices do not cover all cases, or (when adapted to a process without a default) an
inferred latch. Root cause: the selector's full value space was not covered — a
std_logic_vector has more values than the "expected" patterns once metavalues are counted, so the
choices are incomplete. Fix: add when others (and a top-of-process default for any output not
set in every branch). Engineering takeaway: case is exhaustive by design; when others both
satisfies that and defines behaviour for unexpected/metavalue selectors — never omit it on a
std_logic-based selector.
-- BUG: only the four expected patterns → std_logic has more values → incomplete.
case sel is
when "00" => y <= a; when "01" => y <= b;
when "10" => y <= c; when "11" => y <= d; -- no others → error / latch
end case;
-- FIX: 'when others' makes the choices exhaustive and metavalue-safe.
case sel is
when "00" => y <= a; when "01" => y <= b;
when "10" => y <= c; when others => y <= d;
end case;7. Common mistakes & what to watch for
- Omitting
when others. Astd_logic_vectorselector is never fully covered by the expected patterns alone; withoutothersit will not compile (or infers a latch in a process). - Using
casefor prioritised conditions.caseis balanced; for precedence useif(lesson 5.4). - Forgetting to assign every output on every branch. Default at the top of the process to stay
latch-free, just as with
if. - Overlapping or duplicate choices. Choices must be mutually exclusive; duplicates are an error.
- Assuming choice order matters. It does not — only the selector value does.
8. Engineering insight
case is the construct for value-based decisions, and its exhaustiveness requirement is a feature:
it forces you to define behaviour for every selector value, eliminating the missing-case latch that an
incomplete if invites. It synthesises to a flat, balanced mux/decoder — efficient and readable for
opcodes, address decode, and especially the next-state and output logic of state machines, where a
case on an enumerated state type is the industry-standard idiom. Pair case (balanced decode) with
if (priority) and you have the complete decision toolkit for process-based logic.
9. Summary
The sequential case selects a branch by the value of one expression — a balanced, no-priority decode
synthesising to a multiplexer or decoder. Choices are mutually exclusive and must be exhaustive, so
when others is required for std_logic-based selectors (an enum with all literals listed is already
complete). Default-assign outputs to stay latch-free. Use case for value decode, if for priority.
10. Learning continuity
if and case give a process its decisions. The next lesson adds repetition: Loops — for, while,
and loop — where a for loop unrolls into replicated hardware (the process-side analog of
generate), letting you describe bit-sliced logic, reductions, and array operations from a single
loop body, all still governed by the execution model from the anchor.