← All guides

State machines: Moore, Mealy, and encodings

Every state machine, from a traffic light to a bus protocol engine, is the same circuit: a register holding the current state, a combinational cloud computing the next state, and a combinational cloud computing the outputs. Once you see an FSM as that picture, most of the design questions (Moore or Mealy? which encoding? why did I get a latch?) become questions about one of the three parts. Schematics make the picture literal, so this guide leans on them.

The idiom

Keep the state register and the next-state logic in separate blocks. The register is the only clocked thing; the next-state logic is a case with a default assignment first, so no path can infer a latch:

localparam IDLE = 2'd0, RUN = 2'd1, DONE = 2'd2;
reg [1:0] state, next;

// State register: the only clocked part of the machine
always @(posedge clk) begin
  if (reset) state <= IDLE;
  else       state <= next;
end

// Next-state logic: combinational, default first (no latches)
always @(*) begin
  next = state;
  case (state)
    IDLE:    if (start) next = RUN;
    RUN:     if (stop)  next = DONE;
    DONE:    next = IDLE;
    default: next = IDLE;
  endcase
end

assign busy = (state == RUN);  // Moore output: state only

Here it is live. Find the three parts in the schematic: the state flops, the case logic feeding them, and the lone comparator making busy. Then try adding a state, or an input:

Interactive schematic by RapidRTL

The next = state default line is doing two jobs: it makes “stay put” the implicit behaviour of every state, and it guarantees every path assigns next, which is the latch rule applied to state machines. The case itself follows the ordinary case-statement rules; in SystemVerilog you’d write the pair as always_ff and always_comb, and states as a typedef enum, which adds names in waveforms and stops you assigning a nonexistent state.

Moore vs Mealy

The question is only about the output cloud’s inputs:

The default that serves most designs: Moore, with registered outputs. Reach for Mealy when a cycle of latency genuinely breaks the protocol you’re implementing, and register its outputs too where timing allows.

A real one: sequence detector with overlap

The classic interview FSM: assert found when the input stream has just delivered 101, including overlapping matches (10101 contains two). The whole trick is in the S3 row: after a match, the machine doesn’t reset, it falls back to the state matching the 01 it has already seen.

Interactive schematic by RapidRTL

Being able to derive those fallback transitions (“what’s the longest suffix of what I’ve seen that’s a prefix of what I want?”) is worth practising; it’s the part interviewers actually probe.

Encodings: binary, one-hot, and honesty

With four states you need two flops (binary) or four (one-hot: one flop per state, exactly one high). Why would anyone spend double the registers?

Two honest footnotes. First, synthesis tools re-encode FSMs: Yosys and the vendor tools detect the state register and may pick their own encoding regardless of your localparam values, unless you pin it with attributes. Your encoding choice is a starting point, not a contract. Second, a case on a binary state vector has unreachable values (2'b11 above); the default arm decides where the machine goes if it ever wakes up there (radiation upset, bad reset), and “back to IDLE” is a deliberate recovery policy, not boilerplate.

The pitfalls checklist