VHDL Case Statements
Quick Syntax
case my_state is
when "00" =>
my_output <= 0;
when "01" =>
my_output <= 2;
when "10" =>
my_output <= 4;
when others =>
my_output <= 7;
end case;
Purpose
The case statement is one of your main bread and butter abilities in VHDL. You can do so many different things with it, it's important to master using it. For example, you can do things like advanced muxes, state machines, decoders, etc. It's typically found all over the place in protocol interfaces, memory interfaces, register reads and writes, and so on.
Keep in mind that the case statement has to be inside a process block. It's intended to allow you to specifically name the states or you can enumerate your own custom states which is recommended in state machines.
Example
In this example, we are using a case statement to create registers that we can write to on a bus. You would normally have an equivalent read process and case statement to handle the reads. Using constants at the top (or even in a package) makes it convenient for changing the bus addresses for re-use.
constant FPGA_VERSION : std_logic_vector(7 downto 0) := x"10";
constant OUTPUT_SETTINGS : std_logic_vector(7 downto 0) := x"11";
constant REPORT_SETTINGS : std_logic_vector(7 downto 0) := x"12";
constant SET_BUFFER : std_logic_vector(7 downto 0) := x"13";
-- Write Command
PROC_WRITE_CMD : process (clk)
begin
if rising_edge(clk) then
-- defaults
response_pending <= '0';
read_error <= '0';
-- write enable
if write_en = '1' then
case write_addr is
when FPGA_VERSION =>
response_pending <= '1';
when OUTPUT_SETTINGS =>
out_settings_int <= cmd_message_in_data;
when REPORT_SETTINGS =>
response_pending <= '1';
report_settings_int <= cmd_message_in_data(7 downto 0);
when SET_BUFFER =>
buffer_update_int <= cmd_message_in_data(15 downto 8);
---- many other address registers here as needed
when others =>
read_error <= '1';
end case;
end if;
end if;
end process;
If you want to enumerate the states, like in a state machine, here's how you can do it:
-- states
type my_states is (idle, start, stop);
signal sm_state : my_states;
PROC_SM : process (clk)
begin
if rising_edge(clk) then
case sm_state is
when idle =>
my_output <= 0;
when start =>
my_output <= 2;
when stop =>
my_output <= 4;
when others =>
my_output <= 7;
end case;
end if;
end process;
Best Practices
1. Use case statements instead of if statements when the if statement has more than 3-5 options. Why? Case statements are much easier to read and edit and make for much cleaner code. It's debatable on whether one or the other is more efficient in hardware given today's advanced synthesis tools. It just depends on the situation.
2. Cover all of the possible states with your when statements. Also, always use a when others at the end, even if you have covered all of the known states. Why? Because someone (or even you) might come in after you and remove a state above thinking it's not needed anymore for the design and now it opens the design up to a bug.
3. For bigger case statements with many output signals, I like to use "defaults" at the top before the case to cover signals that don't change when going from state to state, that way you don't have to keep listing the same signals and driving them at each state. It makes for much cleaner code and you only have to code the signals that do change at each state. Remember a process goes from top to bottom and a signal becomes the last valid line in the process on the next clock cycle. So using defaults at the top makes sure that all signals are driven unless updated below in the process.
4. For clocked case statements, if an input signal is used to determine the state, it must be on the same clock domain as the case block, otherwise you will run into metastability problems which can make the state unknown or wrong. All incoming signals should adhere to proper clock domain crossing best practices, like registering it twice (or 3 times) on the new clock domain before reading the signal to determine the state. If all inputs and outputs in regards to the case statement are on the same clock, you don't have to worry about this.