Tutorial 13 · Verilog Series

Verilog Finite State Machines

Finite State Machines are the backbone of digital control logic — protocol controllers, parsers, sequencers, and arbiters all reduce to FSMs. This tutorial covers Moore vs Mealy, encoding styles, the canonical 3-always coding pattern, and four complete examples ready to synthesize.

Moore / Mealyone-hotbinary encoding 3-always stylesequence detectortraffic light UART RXdefault case
MOORE vs MEALY FSM ARCHITECTURE MOORE FSM Input State Reg Output next-state logic Output = f(state) only changes on clock edge · glitch-free MEALY FSM Input State Reg Output Output = f(state, input) 1 cycle faster · may glitch

1. Moore vs Mealy FSM

PropertyMooreMealy
Output depends onState onlyState AND current input
Output changesOnly on clock edgeCombinationally (can glitch)
Response latencyOne extra cycleSame cycle as input
Typical state countMore states neededFewer states
Glitch riskNoneYes — register output to fix
Synthesis preferenceSimpler and saferFaster but needs care
Practical rule: Start with Moore FSM. Convert to Mealy only if you need the one-cycle latency reduction and can guarantee glitch-free inputs.

2. State Encoding: Binary, One-Hot, Gray

EncodingFlip-flops neededNext-state logicBest for
Binarylog₂(N)More complexASIC, many states
One-hotNSimpler (single-bit checks)FPGA, fewer states
Gray codelog₂(N)ModerateLow power, CDC crossing
// Binary encoding (synthesis tool assigns by default)
parameter IDLE  = 2'b00,
          FETCH = 2'b01,
          EXEC  = 2'b10,
          DONE  = 2'b11;
reg [1:0] state, next_state;

// One-hot encoding
parameter IDLE  = 4'b0001,
          FETCH = 4'b0010,
          EXEC  = 4'b0100,
          DONE  = 4'b1000;
reg [3:0] state, next_state;

3. The 3-Always Coding Style

The 3-always pattern separates concerns cleanly and is the most widely accepted FSM template in RTL design:

// Block 1 — State Register (sequential)
always @(posedge clk or negedge rst_n)
  if (!rst_n) state <= IDLE;
  else        state <= next_state;

// Block 2 — Next-State Logic (combinational)
always @(*) begin
  next_state = state;    // default: stay in current state
  case (state)
    IDLE:  if (start) next_state = FETCH;
    FETCH: next_state = EXEC;
    EXEC:  if (done)  next_state = IDLE;
    default: next_state = IDLE;  // self-correcting
  endcase
end

// Block 3 — Output Logic (combinational — Moore)
always @(*) begin
  out = 0;              // default outputs
  case (state)
    IDLE:  out = 1;
    EXEC:  out = 0;
    default: out = 0;
  endcase
end
Always set defaults first in every combinational block. Writing next_state = state and out = 0 before the case statement prevents synthesis from inferring latches when a case branch omits an assignment.

4. Traffic Light Controller (Moore FSM)

A classic Moore FSM — outputs (RED, YELLOW, GREEN) depend only on the current state, not on external inputs during a state.

module traffic_light (
  input        clk, rst_n,
  output reg [1:0] light   // 2'b00=RED, 2'b01=YELLOW, 2'b10=GREEN
);
  parameter RED    = 2'd0,
            GREEN  = 2'd1,
            YELLOW = 2'd2;
  reg [1:0] state, next_state;
  reg [4:0] cnt;              // timer (counts clock cycles)

  // State register
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin state <= RED; cnt <= 0; end
    else        begin state <= next_state; cnt <= cnt + 1; end
  end

  // Next-state logic
  always @(*) begin
    next_state = state;
    case (state)
      RED:    if (cnt == 5'd29) next_state = GREEN;   // 30 cycles red
      GREEN:  if (cnt == 5'd19) next_state = YELLOW;  // 20 cycles green
      YELLOW: if (cnt == 5'd4)  next_state = RED;     // 5 cycles yellow
      default: next_state = RED;
    endcase
  end

  // Output logic (Moore — state only)
  always @(*) begin
    case (state)
      RED:    light = 2'b00;
      GREEN:  light = 2'b10;
      YELLOW: light = 2'b01;
      default: light = 2'b00;
    endcase
  end
endmodule

5. "101" Sequence Detector (Mealy FSM)

Detects the pattern 101 in a serial bit stream. Output det goes high the same cycle the third bit is received.

// States: track how many consecutive bits of "101" matched
// S0=start, S1=got_1, S2=got_10, DONE on "101"
module seq_det_101 (
  input  clk, rst_n, din,
  output det
);
  localparam S0 = 2'd0, S1 = 2'd1, S2 = 2'd2;
  reg [1:0] state, next_state;
  reg        det_reg;

  // State register
  always @(posedge clk or negedge rst_n)
    if (!rst_n) state <= S0;
    else        state <= next_state;

  // Next-state + Mealy output (combinational)
  always @(*) begin
    next_state = S0;
    det_reg    = 0;
    case (state)
      S0: if (din) next_state = S1;  // got first 1
          else     next_state = S0;

      S1: if (!din) next_state = S2; // got 10
          else      next_state = S1; // got 11 → still at S1

      S2: if (din) begin             // got 101 !
            det_reg    = 1;           // Mealy output
            next_state = S1;         // overlapping: "1" could be next
          end else
            next_state = S0;
      default: next_state = S0;
    endcase
  end
  assign det = det_reg;
endmodule
In state S2, when din=1 completes the pattern, we go back to S1 (not S0) because the detected '1' could be the start of the next '101'. This is overlapping sequence detection.

6. UART Receiver FSM

A simplified UART receiver that detects the start bit, samples 8 data bits, and validates the stop bit. Clock runs at 8× the baud rate (oversampling by 8).

module uart_rx #(parameter CLK_PER_BIT=868) (
  input        clk, rst_n, rx,
  output reg [7:0] data,
  output reg       valid
);
  localparam IDLE  = 2'd0, START = 2'd1,
             DATA  = 2'd2, STOP  = 2'd3;

  reg [1:0] state;
  reg [9:0] clk_cnt;
  reg [2:0] bit_cnt;
  reg [7:0] shift_reg;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      state<=IDLE; clk_cnt<=0; bit_cnt<=0; valid<=0;
    end else begin
      valid <= 0;
      case (state)
        IDLE: if (!rx) begin           // start bit detected (low)
                state   <= START;
                clk_cnt <= 0;
              end

        START: if (clk_cnt == CLK_PER_BIT/2-1) begin
                 clk_cnt <= 0;
                 bit_cnt <= 0;
                 state   <= DATA;
               end else clk_cnt <= clk_cnt + 1;

        DATA: if (clk_cnt == CLK_PER_BIT-1) begin
                shift_reg <= {rx, shift_reg[7:1]};
                clk_cnt   <= 0;
                if (bit_cnt == 7) state <= STOP;
                else              bit_cnt <= bit_cnt + 1;
              end else clk_cnt <= clk_cnt + 1;

        STOP: if (clk_cnt == CLK_PER_BIT-1) begin
                data  <= shift_reg;
                valid <= rx;      // valid only if stop bit is high
                state <= IDLE;
                clk_cnt <= 0;
              end else clk_cnt <= clk_cnt + 1;
      endcase
    end
  end
endmodule

7. One-Hot FSM

One-hot is ideal for FPGAs because the next-state logic for each state only needs to check a single bit. Here is the same 3-state FSM rewritten with one-hot encoding:

module fsm_onehot (input clk, rst_n, go, done, output busy);
  localparam [2:0]
    IDLE  = 3'b001,
    WORK  = 3'b010,
    FINISH= 3'b100;

  reg [2:0] state;

  always @(posedge clk or negedge rst_n)
    if (!rst_n) state <= IDLE;
    else case (1'b1)                  // match against whichever bit is set
      state[0]: state <= go   ? WORK   : IDLE;
      state[1]: state <= done ? FINISH : WORK;
      state[2]: state <= IDLE;
      default:  state <= IDLE;
    endcase

  assign busy = state[1];  // WORK state — direct bit check
endmodule

The case (1'b1) idiom checks which bit in state is 1 and dispatches accordingly — every branch has a single-bit condition, which maps directly to fast look-up table (LUT) logic on FPGAs.

8. Best Practices and Common Bugs

PracticeWhy it matters
Always include a default caseSelf-correcting FSM; prevents latch inference; synthesis can use don't-cares only if you explicitly cover them
Set default outputs before case statementPrevents latch inference in the output block
Use localparam for state namesReadable code; prevents accidental override by defparam
Register Mealy outputsEliminates glitches at the cost of one extra cycle latency
Reset to a known stateAvoids undefined startup behavior and X propagation
Use $display in testbench to trace statesFar easier to debug transitions in simulation than waveforms alone
Assert each state is mutually exclusive in one-hotDetects bit-flip errors early in simulation
Common bug — missing default in next_state: If you write next_state = IDLE only inside case branches and omit the default assignment before the case, any uncovered state causes synthesis to infer a latch for next_state. Always assign before the case: next_state = state;