VHDL · Chapter 9.6 · Finite State Machines
The Three-Process FSM
The three-process style takes the two-process split one step further and gives the output logic its own process, separate from next-state. The machine becomes three clearly labelled blocks: a clocked state register, a combinational next-state process, and a dedicated output process that can be combinational or registered. The payoff is clarity and flexibility, because each process does exactly one job, and you can change the output discipline, whether Moore, Mealy, or registered, by editing only the output process without touching the transition logic. For large or output-heavy FSMs this separation pays for its extra verbosity, while for tiny machines the two-process style is usually enough. This lesson presents the three-process template and explains when its clarity is worth it.
Foundation13 min readVHDLFSMThree-ProcessOutput LogicCoding StyleRTL
1. Engineering intuition — one job per process
An FSM really has three distinct jobs: hold the state (sequential), decide the next state (combinational), and drive the outputs (combinational or registered). The three-process style gives each its own process, so the code reads like the textbook block diagram. The big practical benefit is isolation of the output logic: when outputs are complex, or when you want to switch an output from combinational (Mealy) to registered for timing, you edit only the output process and leave the transition logic untouched. The cost is more text — three processes for what a smaller machine could say in two — so it shines on larger, output-rich controllers.
2. Formal explanation — the three-process template
library ieee; use ieee.std_logic_1164.all;
type state_t is (IDLE, RUN, DONE);
signal state, next_state : state_t;
-- (1) STATE REGISTER (clocked).
sreg : process (clk, rst_n)
begin
if rst_n = '0' then state <= IDLE;
elsif rising_edge(clk) then state <= next_state;
end if;
end process;
-- (2) NEXT-STATE logic (combinational) — ONLY transitions, no outputs.
nlogic : process (all)
begin
next_state <= state; -- default: stay
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;
end process;
-- (3) OUTPUT logic (combinational here) — ONLY outputs, isolated and easy to restyle.
ologic : process (all)
begin
busy <= '0'; done <= '0'; -- default every output (latch-free)
case state is
when RUN => busy <= '1'; -- Moore outputs from state
when DONE => done <= '1';
when others => null;
end case;
end process;Three single-purpose processes: the clocked sreg holds the state; the combinational nlogic computes only
next_state (defaulted to stay); the combinational ologic computes only the outputs (every output defaulted
to avoid latches). The output process is self-contained — to make busy registered, or Mealy, you change only
ologic.
3. Production RTL — swapping the output style in isolation
library ieee; use ieee.std_logic_1164.all;
-- Same sreg and nlogic as above; only the OUTPUT process changes to register the outputs:
oreg : process (clk, rst_n)
begin
if rst_n = '0' then busy <= '0'; done <= '0';
elsif rising_edge(clk) then
busy <= '0'; done <= '0'; -- registered outputs (glitch-free, +1 cycle)
case next_state is -- decode the state being ENTERED → output aligns
when RUN => busy <= '1';
when DONE => done <= '1';
when others => null;
end case;
end if;
end process;What hardware does this become? The same state register and next-state logic, but now the outputs come from
flip-flops decoded from next_state (so a registered output aligns with the state it represents). The point is
that this change touched only the output process — the transition logic is untouched. That isolation is the
three-process style's signature advantage.
4. Hardware interpretation — three blocks, three processes
5. Simulation interpretation — same behaviour, clearer code
Three-process FSM: state register, next-state, and outputs each from their own process
8 cycles6. Debugging example — the duplicated default, or output/next-state confusion
Expected: clean, independent processes. Observed: a latch on an output or next_state, or outputs that
do not match the state. Root cause: one of the combinational processes (next-state or output) missed its
defaults — next_state <= state in nlogic, or every output in ologic — inferring a latch; or the output
process decoded the wrong state signal (current vs next). Fix: default next_state in the next-state
process and every output in the output process; decide deliberately whether outputs decode the current state
(aligned with combinational) or the next state (for registered alignment). Engineering takeaway: each
combinational FSM process needs its own complete defaults, and the output process must decode the intended state
signal — the separation that gives clarity also means each process must independently be latch-free.
-- next-state process: default next_state. output process: default every output.
nlogic : process(all) begin next_state <= state; case state is ... end case; end process;
ologic : process(all) begin busy <= '0'; done <= '0'; case state is ... end case; end process;7. Common mistakes & what to watch for
- Missing defaults in either combinational process. Default
next_stateand every output; each process must be independently latch-free. - Decoding the wrong state in the output process. Current state for combinational outputs; next state for registered outputs that should align with the entered state.
- Using three processes for a trivial FSM. Overkill; the two-process style is usually enough for small machines.
- Mixing next-state and output logic across the processes. Keep transitions in the next-state process and outputs in the output process — that separation is the whole point.
- Inconsistent style across FSMs in a project. Pick a default style and apply it consistently; reserve three-process for the output-heavy cases.
8. Engineering insight & continuity
The three-process FSM is the maximum-clarity style: one process each for holding state, deciding transitions, and driving outputs, so the code mirrors the textbook diagram and the output logic can be restyled in isolation. It pays for its verbosity on large, output-rich controllers; for small machines the two-process style is leaner. Across all three styles (one-, two-, three-process), the choice is really about where the outputs live and how much separation you want — which makes output logic a first-class design decision. The next lesson, FSM Output Logic and Registered Outputs, treats exactly that: how to design Moore, Mealy, and registered outputs cleanly, whichever coding style you use.