VHDL · Chapter 9.4 · Finite State Machines
The Two-Process FSM
With the FSM's parts and choices understood, the question is how to organise them in code, and the most widely recommended answer is the two-process style. One clocked process holds just the state register and its reset, while one combinational process computes the next state and the outputs from the current state and inputs. This split maps directly onto the hardware, with sequential state in one place and combinational logic in the other, which makes the FSM easy to read, review, and get right: the clocked process is trivially correct, and the combinational process is a normal default-then-override block. Both Moore and Mealy outputs fit naturally. This lesson presents the template, explains why it is the standard, and shows the latch-free discipline it relies on.
Foundation14 min readVHDLFSMTwo-ProcessCoding StyleLatch-freeRTL
1. Engineering intuition — separate the state from the logic
An FSM has exactly two kinds of thing in it: the state, which is sequential (it lives in flip-flops, updates on the clock, resets to a known value), and the logic that decides the next state and the outputs, which is combinational. The two-process style puts each in its own process — one clocked, one combinational — so the code mirrors the hardware one-to-one. That separation is why it is the recommended style: each process is simple and checkable on its own, and the boundary between "what is registered" and "what is logic" is explicit rather than tangled together.
2. Formal explanation — the two-process template
library ieee; use ieee.std_logic_1164.all;
type state_t is (IDLE, RUN, DONE);
signal state, next_state : state_t;
-- PROCESS 1 — clocked: the STATE REGISTER only (with reset). Trivially correct.
state_reg : process (clk, rst_n)
begin
if rst_n = '0' then state <= IDLE;
elsif rising_edge(clk) then state <= next_state;
end if;
end process;
-- PROCESS 2 — combinational: NEXT-STATE + OUTPUTS. Default everything → latch-free.
comb : process (all)
begin
next_state <= state; -- default: stay (no latch on next_state)
busy <= '0'; -- default outputs (no latch)
done <= '0';
case state is
when IDLE => if go = '1' then next_state <= RUN; end if;
when RUN => busy <= '1';
if fin = '1' then next_state <= DONE; end if;
when DONE => done <= '1';
next_state <= IDLE;
end case;
end process;The clocked state_reg process does one thing: register next_state into state on the edge, reset to a
known start (Module 8). The combinational comb process is process(all) with defaults at the top —
next_state <= state plus every output — then a case that overrides per state (Module 6's default-then-
override). This guarantees the combinational logic is latch-free, and it holds whether the outputs are Moore
(from state) or Mealy (from state and inputs).
3. Production RTL — a two-process handshake controller
library ieee; use ieee.std_logic_1164.all;
type st_t is (IDLE, REQ, WAITA, DONE);
signal st, st_n : st_t;
st_reg : process (clk, rst_n)
begin
if rst_n = '0' then st <= IDLE;
elsif rising_edge(clk) then st <= st_n;
end if;
end process;
st_comb : process (all)
begin
st_n <= st; -- defaults
req <= '0'; busy <= '0';
case st is
when IDLE => if start = '1' then st_n <= REQ; end if;
when REQ => req <= '1'; busy <= '1'; st_n <= WAITA;
when WAITA => req <= '1'; busy <= '1'; if ack = '1' then st_n <= DONE; end if;
when DONE => busy <= '1'; st_n <= IDLE;
end case;
end process;What hardware does this become? A state register (the clocked process) plus combinational next-state and
output logic (the combinational process): req/busy are Moore outputs decoded from the state, and the
transitions decode from state and inputs. The two-process split makes it obvious which flip-flops exist (only
st) and which logic is combinational — exactly the structure a reviewer wants to see.
4. Hardware interpretation — code mirrors the hardware
5. Simulation interpretation — register advances, logic settles
Two-process FSM: combinational logic computes next_state; the register captures it on the edge
8 cycles6. Debugging example — the latch in the combinational process
Expected: clean combinational next-state/output logic. Observed: synthesis infers a latch on
next_state or an output, or the FSM behaves oddly in a state. Root cause: the combinational process did
not default next_state (and every output) at the top, so a state/branch that did not assign one left it
unassigned → a latch (Module 6.3). Fix: at the top of the combinational process, default next_state <= state and assign every output its inactive value, then override per state. Engineering takeaway: the
two-process style is only as safe as its combinational defaults — always default the next state and all outputs
first; the clocked process needs no such care (a register holds correctly).
comb : process (all) begin
next_state <= state; -- default next state (no latch)
y <= '0'; -- default every output (no latch)
case state is
when S0 => if c = '1' then next_state <= S1; end if;
when S1 => y <= '1'; next_state <= S0;
end case;
end process;7. Common mistakes & what to watch for
- No default for
next_state/outputs. Infers latches; default everything at the top of the combinational process. - Putting outputs in the clocked process by accident. That registers them (changing timing); keep combinational outputs in the combinational process (or register deliberately).
- Incomplete sensitivity on the combinational process. Use
process(all). - Resetting in the combinational process. Reset belongs in the clocked state-register process.
- Comparing state to bit patterns. Use enum names (lesson 9.3); keep the FSM encoding-independent.
8. Engineering insight & continuity
The two-process FSM is the standard because it makes the FSM's structure explicit: a trivially-correct clocked state register and a normal latch-free combinational block for next-state and outputs. That clarity is its own correctness — each process is simple, the register/logic boundary is visible, and both Moore and Mealy outputs slot in naturally. It is the style to reach for by default. The next lessons present the alternatives and their trade-offs — the One-Process FSM (everything in one clocked process; more compact but registers the outputs) and the Three-Process FSM (state, next-state, and output split further) — followed by output logic, safe states, and the common FSM bugs.