Finite State Machines are the backbone of digital controllers, interfaces, and protocol handlers. This comprehensive guide covers FSM architecture (Mealy vs Moore), state encoding strategies (one-hot, binary, gray code), RTL coding patterns, synthesis optimization, and production-grade design techniques used in real ASIC projects from design entry through timing closure.
A Finite State Machine is a mathematical model of computation consisting of a finite number of discrete states, transitions between those states triggered by inputs, and output logic that may depend on the current state and inputs. FSMs are universally used to model sequential behavior in hardware and software.
Every FSM is defined by:
Finite collection of discrete states (e.g., IDLE, BUSY, DONE). Typically encoded as binary or one-hot values.
External signals that trigger state transitions. May include control signals, data, and synchronous clock.
Signals produced by the FSM, either dependent on state alone (Moore) or state and inputs (Mealy).
The state entered upon reset. Usually IDLE or a safe known state. Determined by reset logic.
Logic that determines next state based on current state and inputs: next_state = f(current_state, inputs)
Logic that determines outputs. Moore: output = g(state). Mealy: output = h(state, inputs)
The two fundamental FSM architectures differ in when outputs are generated:
| Aspect | Moore Machine | Mealy Machine |
|---|---|---|
| Output Timing | Outputs depend ONLY on current state | Outputs depend on state AND current inputs |
| Output Update | Synchronous (latched on clock edge) | Partially asynchronous (combinational on inputs) |
| Output Delay | Always one clock cycle latency | Minimal latency (combinational path) |
| State Complexity | Often requires more states for same behavior | Typically fewer states |
| Area (Gates) | Larger (more states = more flip-flops) | Smaller (fewer states, combinational logic) |
| Timing | Easier (no combinational paths on output) | Harder (combinational logic must close timing) |
| Verification | Simpler (outputs are registered) | More complex (need to verify combinational behavior) |
| Use Case | Controllers, simple sequences, glitch-free outputs | High-throughput logic, protocol handling, optimized area |
Design Choice: Start with Moore FSM for simplicity and robustness. Optimize to Mealy only if timing or area constraints require it. Many production designs use hybrid approaches.
Moore Machine Architecture: Outputs depend only on current state
Mealy Machine Architecture: Outputs depend on state AND inputs
State encoding determines how states are represented in binary. Different encodings have dramatic impacts on area, timing, and power. Choosing the right encoding is a critical design decision.
States are represented as sequential binary values. For N states, requires ⌈log₂(N)⌉ bits.
// Three states, 2 bits required (log2(3) = 2)
parameter [1:0] IDLE = 2'b00;
parameter [1:0] BUSY = 2'b01;
parameter [1:0] DONE = 2'b10;
// NOT used: 2'b11 (unused state)
Advantages:
Disadvantages:
Each state is represented by a single bit set to 1. For N states, requires exactly N bits (one per state). All other bits are 0.
// Three states, 3 bits required (one bit per state)
parameter [2:0] IDLE = 3'b001; // Bit 0 = 1
parameter [2:0] BUSY = 3'b010; // Bit 1 = 1
parameter [2:0] DONE = 3'b100; // Bit 2 = 1
// Unused: 3'b000, 3'b011, 3'b101, 3'b110, 3'b111 (five invalid codes)
Advantages:
Disadvantages:
Industry Standard: One-hot encoding is preferred for FSMs with <32 states in ASIC and FPGA design. The timing and simplicity gains outweigh the area cost for most applications.
Gray code (reflected binary) is a binary encoding where adjacent values differ by only one bit. Useful for avoiding glitches in certain transitions.
// Binary vs Gray for 4 states
// Index Binary Gray
// 0 00 00
// 1 01 01
// 2 10 11
// 3 11 10
parameter [1:0] IDLE = 2'b00; // Gray 00
parameter [1:0] BUSY = 2'b01; // Gray 01
parameter [1:0] WAIT = 2'b11; // Gray 11
parameter [1:0] DONE = 2'b10; // Gray 10
When to use Gray code:
There are two main approaches to coding FSMs in Verilog: single always block and three always block pattern. Each has tradeoffs in readability, maintainability, and synthesis efficiency.
All logic (next state, outputs, state register) in one always block. Simpler but less readable for complex FSMs.
module simple_fsm (
input wire clk,
input wire reset_n,
input wire go,
output reg done
);
// State declarations (one-hot)
parameter [2:0] IDLE = 3'b001;
parameter [2:0] BUSY = 3'b010;
parameter [2:0] DONE_ST = 3'b100;
reg [2:0] state, next_state;
always @(*) begin
// Default outputs
done = 1'b0;
next_state = state; // Default: stay in current state
case (state)
IDLE : begin
if (go)
next_state = BUSY;
end
BUSY : begin
next_state = DONE_ST; // Unconditional transition
end
DONE_ST : begin
done = 1'b1; // Output in this state
next_state = IDLE;
end
default : next_state = IDLE;
endcase
end
// State register (sequential)
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
state <= IDLE;
else
state <= next_state;
end
endmodule
Drawback: Combinational and sequential logic mixed; harder to optimize.
Separates next-state logic, output logic, and state register into three always blocks. Clean, readable, easier for synthesis tools to optimize.
module smart_fsm (
input wire clk,
input wire reset_n,
input wire go,
output wire done
);
// State declarations (one-hot)
parameter [2:0] IDLE = 3'b001;
parameter [2:0] BUSY = 3'b010;
parameter [2:0] DONE_ST = 3'b100;
reg [2:0] state, next_state;
// ─────────────────────────────────────────
// Block 1: Next State Logic (Combinational)
// ─────────────────────────────────────────
always @(*) begin
next_state = state; // Default: hold state
case (state)
IDLE : begin
if (go)
next_state = BUSY;
end
BUSY : begin
next_state = DONE_ST;
end
DONE_ST : begin
next_state = IDLE;
end
default : next_state = IDLE;
endcase
end
// ─────────────────────────────────────────
// Block 2: State Register (Sequential)
// ─────────────────────────────────────────
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
state <= IDLE;
else
state <= next_state;
end
// ─────────────────────────────────────────
// Block 3: Output Logic (Combinational)
// ─────────────────────────────────────────
assign done = (state == DONE_ST);
endmodule
Advantages:
Moore Output Logic: Output depends on state only
// Block 3a: Moore Output (combinational, depends on state only)
always @(*) begin
case (state)
IDLE : led = 3'b001; // Red
BUSY : led = 3'b010; // Green
DONE_ST : led = 3'b100; // Blue
default : led = 3'b000;
endcase
end
Mealy Output Logic: Output depends on state AND inputs
// Block 3b: Mealy Output (combinational, depends on state & inputs)
always @(*) begin
case (state)
IDLE : begin
if (go)
led = 3'b010; // Green when transitioning to BUSY
else
led = 3'b001; // Red while IDLE
end
BUSY : begin
if (error)
led = 3'b000; // Black on error
else
led = 3'b010; // Green
end
default : led = 3'b000;
endcase
end
⚠️ Mealy Hazard: Mealy outputs can glitch if inputs change while in state. For glitch-free outputs, register Mealy outputs with an extra flip-flop or use Moore logic.
Proper reset strategy ensures FSMs enter a safe, known state during power-up or system reset. Poor reset design leads to metastability, invalid states, and unpredictable behavior.
| Aspect | Asynchronous Reset | Synchronous Reset |
|---|---|---|
| Trigger | Independent of clock (reset_n) | Synchronized with clock (reset @ posedge clk) |
| Timing | Takes effect immediately | Takes effect on next clock edge |
| Synchronous Design | Violates synchronous design principles | Strictly synchronous |
| Metastability Risk | High (reset can release near clock edge) | Low (synchronous) |
| Silicon Area | Smaller (one control signal per flip-flop) | Larger (reset must be synchronized) |
| Power-Up | Essential (system may not be clocking) | Cannot initialize (no clock at power-up) |
| Typical Use | Power-on reset (POR) for initial setup | Mid-operation reset or test mode |
Industry Practice: Use asynchronous reset for initialization (synchronized externally by design team) and optional synchronous reset for mid-operation scenarios. Never mix unsynchronized asynchronous reset directly on flip-flops during operation — use a synchronizer circuit.
module fsm_with_reset (
input wire clk,
input wire reset_n, // Active-low, asynchronous
input wire trigger,
output reg output_sig
);
parameter [1:0] IDLE = 2'b00;
parameter [1:0] BUSY = 2'b01;
parameter [1:0] DONE = 2'b10;
reg [1:0] state, next_state;
// Next state logic
always @(*) begin
next_state = state;
case (state)
IDLE : if (trigger) next_state = BUSY;
BUSY : next_state = DONE;
DONE : next_state = IDLE;
endcase
end
// State register with asynchronous reset
// ⚠️ IMPORTANT: reset_n in sensitivity list (asynchronous)
always @(posedge clk or negedge reset_n) begin
if (!reset_n) // Reset: return to known state
state <= IDLE;
else // Normal operation
state <= next_state;
end
// Output logic
always @(*) begin
case (state)
IDLE : output_sig = 1'b0;
BUSY : output_sig = 1'b1;
DONE : output_sig = 1'b1;
endcase
end
endmodule
Reset best practices:
or negedge reset_n in sensitivity listFor robustness, add logic to detect entry into undefined states and recover:
// At top level or in testbench
always @(posedge clk) begin
// For one-hot encoding, state should have exactly one bit set
// Count number of '1' bits in state
if (state != IDLE && state != BUSY && state != DONE) begin
$warning("FSM entered invalid state: %b", state);
// Force return to IDLE on next clock (or raise error)
end
end
In production, use formal verification tools (not simulation) to prove FSM cannot enter invalid states.
FSM synthesis is one of the most optimized areas in EDA tools, but understanding the process helps you write better code and achieve timing closure more easily.
The binary encoding affects the complexity of next-state logic, which directly impacts timing:
Binary Encoding: Tight packing of states often produces deep logic trees for some transitions
// Binary: 3 states in 2 bits
IDLE = 2'b00
BUSY = 2'b01
DONE = 2'b10
// Next state logic may require many gates:
// next_state[0] = (state == IDLE & go) | (state == DONE & ~hold)
// Requires multiple gates and muxes
One-Hot Encoding: More flip-flops, but simpler next-state logic
// One-hot: 3 states in 3 bits
IDLE = 3'b001
BUSY = 3'b010
DONE = 3'b100
// Next state logic is much simpler:
// next_state[BUSY] = current_state[IDLE] & go
// next_state[DONE] = current_state[BUSY]
// Mostly just muxes and simple gates
Synthesis Tool Guidance: Modern tools prefer one-hot for up to 16-32 states. If timing fails with binary, switch to one-hot before manual optimization.
1. Pipelining FSM Outputs — If output logic is on critical path:
// Instead of direct combinational output
// assign output = (state == DONE_ST);
// Register the output
always @(posedge clk) begin
output <= (next_state == DONE_ST);
end
2. Early Exit Signals — If you know transition before clock edge:
// Combinational signal for next cycle
wire will_be_idle_next = (state == BUSY) && condition;
// Use this in downstream logic that can tolerate one-cycle latency
3. Parallel State Encoders — For high-frequency designs:
// Maintain multiple state encodings in parallel
wire [7:0] state_binary, state_onehot;
// Binary for compact logic, one-hot for fast decoding
// Choose which to use based on timing
Incomplete Case Statements: Can infer latches or unexpected logic
always @(*) begin
case (state)
IDLE : next_state = go ? BUSY : IDLE;
BUSY : next_state = DONE;
// Missing: DONE case — synthesizer may infer latch!
endcase
end
✓ Good: Complete Case
always @(*) begin
next_state = state; // Default
case (state)
IDLE : next_state = go ? BUSY : IDLE;
BUSY : next_state = DONE;
DONE : next_state = IDLE;
default : next_state = IDLE;
endcase
end
A real-world example: a simple protocol controller (e.g., for SPI, I2C handshake) demonstrating all best practices.
module protocol_fsm (
input wire clk,
input wire reset_n,
input wire start,
input wire data_ready,
input wire ack,
output wire transmit,
output wire receive,
output wire done
);
// One-hot state encoding
parameter [3:0] IDLE = 4'b0001;
parameter [3:0] REQ_DATA = 4'b0010;
parameter [3:0] WAIT_ACK = 4'b0100;
parameter [3:0] COMPLETE = 4'b1000;
reg [3:0] state, next_state;
// ─────────────────────────────────────────────────────
// BLOCK 1: Next State Logic (Combinational)
// ─────────────────────────────────────────────────────
always @(*) begin
next_state = state; // Default: hold state
case (state)
IDLE : begin
if (start)
next_state = REQ_DATA;
end
REQ_DATA : begin
if (data_ready)
next_state = WAIT_ACK;
end
WAIT_ACK : begin
if (ack)
next_state = COMPLETE;
end
COMPLETE : begin
next_state = IDLE; // Always return to IDLE
end
default : next_state = IDLE;
endcase
end
// ─────────────────────────────────────────────────────
// BLOCK 2: State Register (Sequential)
// ─────────────────────────────────────────────────────
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
state <= IDLE;
else
state <= next_state;
end
// ─────────────────────────────────────────────────────
// BLOCK 3: Output Logic (Combinational / Moore)
// ─────────────────────────────────────────────────────
assign transmit = (state == REQ_DATA); // TX pulse
assign receive = (state == WAIT_ACK); // RX pulse
assign done = (state == COMPLETE); // Completion flag
endmodule
Design highlights:
Learn from the mistakes of others and design FSMs that synthesize efficiently, close timing, and are easy to verify.
Problem: Binary encoding leaves unused state codes. If entered due to bugs, FSM gets stuck or behaves unpredictably.
Solution: Use one-hot (only valid states representable) or add explicit guards to return to IDLE from invalid states.
Problem: Missing signals in @(*) causes simulation-synthesis mismatch.
Solution: Always use @(*) for combinational logic — it's equivalent to listing all inputs.
Problem: Mealy outputs glitch when inputs change asynchronously to state transitions.
Solution: Use Moore logic or register Mealy outputs. For glitch-free outputs, Moore is always preferred.
Problem: FSM stuck in state if input conditions never met. No escape path to IDLE.
Solution: Design FSMs with guaranteed escapes. Use watchdog timers or always include timeout paths.
Include explicit default cases that return to IDLE. Use asynchronous reset on all flip-flops. Test reset path thoroughly.
Include state diagrams in comments. State names must be descriptive. Document transition conditions. Version control the FSM design document alongside RTL.
Now that you understand FSM fundamentals, explore advanced topics that appear in production designs.
Most synthesis tools support pragmas to hint FSM optimization:
// Synopsys Synthesis Pragmas (examples)
/* synopsys enum_state current_state */ // Hint: current_state is an FSM
/* synopsys encoding_one_hot */ // Force one-hot encoding
/* synopsys safe_fsm */ // Enable FSM safety checks
Consult your synthesis tool documentation for exact syntax. Modern tools are smart enough to recognize FSMs automatically.