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:
- Moore: outputs depend on the state alone. Output changes happen one place, on clock edges, so outputs are glitch-free by construction and easy to reason about. Cost: reacting to an input takes a cycle, because the input must first change the state.
- Mealy: outputs depend on state and current inputs. Reaction is same-cycle, and machines often need fewer states. Cost: outputs can glitch when inputs do, and combinational paths now run straight through the machine into whatever consumes the output.
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?
- Binary minimises flops, but every next-state equation decodes the full state vector: more logic per flop, deeper paths.
- One-hot spends flops to make the logic shallow: “am I in RUN?” is reading one wire, and next-state equations become short OR-chains of transitions. On FPGAs, where flip-flops are abundant and routing/logic depth is what hurts, one-hot is the usual winner. On ASICs, binary or gray encodings save area and power.
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
- Missing default assignment in the next-state block builds a latch on
next. Default first, always. - Outputs decoded from state with long combinational tails: register them if the consumer is timing-critical.
- Unreachable states with no way home: make
default(and unused encodings) resolve to a safe state. - Mealy outputs feeding another clock domain: they glitch; a synchronizer captures glitches as events. Register first.