Skip to content

Finite-state machines

An FSM stores a discrete state and chooses the next state from current state plus inputs.

Design on paper first

Write:

  1. Every state and its meaning.
  2. Reset state.
  3. Conditions on each outgoing transition.
  4. Outputs active in each state or transition.
  5. Behavior for invalid or simultaneous inputs.
  6. Timing: whether outputs change on an edge or combinationally.

Moore and Mealy outputs

Style Output depends on Effect
Moore Current state Usually stable for a whole cycle
Mealy Current state and current inputs Can respond within the cycle and can glitch

Registered outputs often simplify interface timing even when derived from Mealy conditions.

Two-process template

type state_t is (IDLE, ACTIVE, DONE);
signal state_q : state_t := IDLE;
signal state_d : state_t;

state_register : process(clk_i)
begin
  if rising_edge(clk_i) then
    if reset_i = '1' then
      state_q <= IDLE;
    else
      state_q <= state_d;
    end if;
  end if;
end process;

next_state_logic : process(all)
begin
  state_d <= state_q; -- hold by default

  case state_q is
    when IDLE =>
      if start_i = '1' then
        state_d <= ACTIVE;
      end if;

    when ACTIVE =>
      if complete_i = '1' then
        state_d <= DONE;
      end if;

    when DONE =>
      if acknowledge_i = '1' then
        state_d <= IDLE;
      end if;
  end case;
end process;

busy_o <= '1' when state_q = ACTIVE else '0';
done_o <= '1' when state_q = DONE else '0';

Advantages:

  • State register and transition logic are visibly separate.
  • Moore outputs are easy to read.
  • Waveforms show current and next state.

Risk: forgetting defaults in the combinational process infers latches.

One-process template

process(clk_i)
begin
  if rising_edge(clk_i) then
    if reset_i = '1' then
      state_q <= IDLE;
      busy_o  <= '0';
      done_o  <= '0';
    else
      done_o <= '0';

      case state_q is
        when IDLE =>
          busy_o <= '0';
          if start_i = '1' then
            state_q <= ACTIVE;
            busy_o  <= '1';
          end if;

        when ACTIVE =>
          if complete_i = '1' then
            state_q <= DONE;
            busy_o  <= '0';
            done_o  <= '1';
          end if;

        when DONE =>
          if acknowledge_i = '1' then
            state_q <= IDLE;
          end if;
      end case;
    end if;
  end if;
end process;

Advantages:

  • All state and registered outputs update at the edge.
  • No accidental combinational latches.

Risk: long machines can become difficult to read, and output latency must be understood carefully.

Both styles are valid. Adopt a team convention and test the timing contract.

Transition priority

If two conditions can be true simultaneously, source order in an if/elsif chain creates priority. Document it:

if emergency_i = '1' then
  state_d <= SAFE;
elsif complete_i = '1' then
  state_d <= DONE;
end if;

State encoding

Vivado may choose:

  • Binary: few flip-flops, more decode logic.
  • One-hot: one flip-flop per state, often simple/faster decode.
  • Gray: one-bit changes between adjacent encoded states when the transition graph allows it.

Do not optimize encoding prematurely. Meet functionality and timing first, then use synthesis reports and constraints.

Verification plan

An FSM testbench should cover:

  • Reset from each meaningful point.
  • Every legal transition.
  • Conditions that must remain in the current state.
  • Simultaneous/priority conditions.
  • Minimum and maximum dwell times.
  • Output timing around edges.
  • Recovery behavior if illegal states are considered.

Example state assertion:

assert not (busy_o = '1' and done_o = '1')
  report "busy and done must never be active together"
  severity failure;

Keep a coverage table in the test plan:

From Condition To Tested?
IDLE start=1 ACTIVE yes
ACTIVE complete=1 DONE yes
DONE acknowledge=1 IDLE yes

Common FSM mistakes

  • Coding before drawing the transition table.
  • Leaving a transition condition ambiguous.
  • Generating glitches on external control outputs.
  • Forgetting default assignments in two-process machines.
  • Assuming initial values replace a reset strategy.
  • Counting cycles inconsistently: define whether entry counts as cycle zero or one.