Finite State Machines in Verilog

A Finite State Machine (FSM) is a digital circuit that moves between a finite number of predefined states in response to inputs. FSMs are the backbone of control logic in nearly every digital system.

What is an FSM?

An FSM consists of:

  • A finite set of states (e.g., IDLE, RUN, DONE).

  • Transitions between states, triggered by input conditions.

  • Outputs that depend on the current state (and optionally the inputs).

In hardware, an FSM is implemented using:

  1. A state register — holds the current state.

  2. Next-state logic — combinational logic that determines the next state.

  3. Output logic — combinational or registered logic that generates outputs.

Moore vs Mealy Machines

Property

Moore Machine

Mealy Machine

Outputs depend on

Current state only

Current state and current inputs

Output stability

Stable between clock edges

Can change as inputs change

Typical use

Simpler to design and debug

Fewer states needed for same function

State Encoding

State values must be encoded as binary numbers stored in the state register. Three common encoding styles:

Encoding

Description

Binary

Minimum bits. 4 states → 2-bit encoding. Dense but slower decode.

One-hot

One bit per state; only one bit is high at a time. Faster decode on FPGAs.

Gray code

Adjacent states differ by only one bit. Reduces glitches in output.

FPGAs typically benefit from one-hot encoding because they have abundant flip-flops and fast local logic.

3-Block FSM Template

The industry-standard Verilog FSM template separates concerns into three always blocks:

  1. State register (sequential) — clocked, stores current state.

  2. Next-state logic (combinational) — determines the next state.

  3. Output logic (combinational or registered) — generates outputs.

module simple_fsm (
    input  wire clk,
    input  wire reset,
    input  wire start,
    output reg  done
);

    localparam IDLE = 2'b00;
    localparam RUN  = 2'b01;
    localparam DONE = 2'b10;

    reg [1:0] state;
    reg [1:0] next_state;

    // ── Block 1: State Register ────────────────────────────────
    always @(posedge clk or posedge reset) begin
        if (reset)
            state <= IDLE;
        else
            state <= next_state;
    end

    // ── Block 2: Next-State Logic ──────────────────────────────
    always @(*) begin
        next_state = state;  // Default: stay in current state

        case (state)
            IDLE: begin
                if (start)
                    next_state = RUN;
            end

            RUN: begin
                next_state = DONE;
            end

            DONE: begin
                next_state = IDLE;
            end

            default: begin
                next_state = IDLE;
            end
        endcase
    end

    // ── Block 3: Output Logic ──────────────────────────────────
    always @(*) begin
        done = 1'b0;  // Default output

        case (state)
            DONE: done = 1'b1;
            default: done = 1'b0;
        endcase
    end

endmodule

Example: Traffic Light Controller

A classic FSM example: a simple two-direction traffic light with timed transitions.

module traffic_light (
    input  wire       clk,
    input  wire       reset,
    output reg  [1:0] ns_light,  // North-South: 00=RED, 01=YELLOW, 10=GREEN
    output reg  [1:0] ew_light   // East-West:   same encoding
);

    localparam NS_GREEN  = 2'd0;
    localparam NS_YELLOW = 2'd1;
    localparam EW_GREEN  = 2'd2;
    localparam EW_YELLOW = 2'd3;

    localparam GREEN  = 2'b10;
    localparam YELLOW = 2'b01;
    localparam RED    = 2'b00;

    reg [1:0] state;
    reg [1:0] next_state;
    reg [4:0] timer;

    // State Register
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            state <= NS_GREEN;
            timer <= 5'd0;
        end else begin
            if (timer == 5'd0)
                state <= next_state;
            timer <= (timer == 5'd0) ? 5'd30 : timer - 1'b1;
        end
    end

    // Next-State Logic
    always @(*) begin
        case (state)
            NS_GREEN:  next_state = NS_YELLOW;
            NS_YELLOW: next_state = EW_GREEN;
            EW_GREEN:  next_state = EW_YELLOW;
            EW_YELLOW: next_state = NS_GREEN;
            default:   next_state = NS_GREEN;
        endcase
    end

    // Output Logic (Moore)
    always @(*) begin
        case (state)
            NS_GREEN:  begin ns_light = GREEN;  ew_light = RED;    end
            NS_YELLOW: begin ns_light = YELLOW; ew_light = RED;    end
            EW_GREEN:  begin ns_light = RED;    ew_light = GREEN;  end
            EW_YELLOW: begin ns_light = RED;    ew_light = YELLOW; end
            default:   begin ns_light = RED;    ew_light = RED;    end
        endcase
    end

endmodule

Common Mistakes

  • Forgetting a default assignment — outputs left unassigned in some states will infer latches.

  • Using blocking assignments in the state register — use <= for all clocked assignments.

  • No default case — missing a default in case statements can create unreachable states with undefined behavior.

See Also