verilog-implementacion-fsm-plantilla-3-procesos

How to Implement an FSM in Verilog with Three Processes

  • 5 min

An FSM in Verilog is a state machine written as separate logic blocks, typically state memory, transition logic, and output logic.

In the previous article, we looked at the theory of Finite State Machines (FSMs). We saw circles, arrows, and debated Moore vs. Mealy. Today, it’s time to get down to the nitty-gritty: We are going to write the code.

If you search online, you’ll see there are a thousand ways to write an FSM in Verilog. Some people put everything in one giant block, others use two…

We are going to use the 3-Process Technique. It is the most organized, readable, and error-proof methodology (especially against inadvertently creating Latches). It consists of mentally and in code dividing the machine into three distinct hardware blocks.

State Definition

Before the processes, we need to define our states. To keep the code readable, never use hardcoded numbers (case(0)). Use symbolic constants.

In modern Verilog (localparam), we usually let the synthesizer choose the encoding (Binary or One-Hot), so we just provide names.

// State definition with readable names
localparam IDLE_STATE    = 2'b00;
localparam WORK_STATE    = 2'b01;
localparam DONE_STATE    = 2'b10;

// We need TWO state registers
reg [1:0] state;       // Current State (Memory)
reg [1:0] next_state;  // Next State (Calculated)
Copied!

Notice this distinction

  • state: This is what we ARE NOW
  • next_state: This is what we will be in the NEXT clock cycle.

Process 1: State Memory

This is the only block that has a clock (posedge clk) and uses non-blocking assignment (<=). Its only mission is to update the current state with the calculated value of the next state or reset the machine.

// --- PROCESS 1: State Update ---
always @(posedge clk) begin
    if (reset) begin
        state <= IDLE_STATE; // Synchronous reset
    end else begin
        state <= next_state;  // Advance to the future
    end
end
Copied!

Process 2: Next State Logic

This is where the intelligence of the transitions (the arrows in the diagram) resides. It is a purely combinational block (always @*). Its mission is to look at the current state and the inputs, and decide what next_state should be.

Latch Danger!

To avoid creating unwanted memory, we MUST ALWAYS assign a default value to next_state at the beginning of the block. Generally, the default value is “stay the same”.

// --- PROCESS 2: Next State Calculation ---
always @(*) begin
    // 1. Default value (Avoids Latches)
    next_state = state; 

    // 2. Transition logic
    case (state)
        IDLE_STATE: begin
            if (start_button == 1'b1)
                next_state = WORK_STATE;
        end

        WORK_STATE: begin
            if (task_done == 1'b1)
                next_state = DONE_STATE;
        end

        DONE_STATE: begin
            // Return to start unconditionally
            next_state = IDLE_STATE;
        end
        
        default: next_state = IDLE_STATE; // Safety
    endcase
end
Copied!

Process 3: Output Logic

Finally, we decide what the FPGA output pins do based on the state. If we are designing a Moore machine, the outputs only depend on state.

// --- PROCESS 3: Output Decoding ---
// Example: Control an LED and a Motor
always @(*) begin
    // 1. Default values (Off)
    led_status = 1'b0;
    motor_run  = 1'b0;

    // 2. Activation based on state
    case (state)
        IDLE_STATE: begin
            led_status = 1'b1; // LED on, waiting
        end

        WORK_STATE: begin
            motor_run = 1'b1;  // Motor running
        end
        
        // In DONE_STATE we use default values (all 0)
    endcase
end
Copied!

Sometimes, if the output logic is very simple, you can replace this always block with a simple assign.

assign motor_run = (state == WORK_STATE);

Complete Example: A Simple Traffic Light

Let’s put it all together in a real example. A traffic light that changes from Green to Yellow to Red when time passes. We’ll assume we have a timer_done input that tells us when to change.

module traffic_light (
    input wire clk,
    input wire rst,
    input wire timer_done,      // External time signal
    output reg [2:0] lights      // R, Y, G
);

    // 1. State Definition (simple binary encoding)
    localparam GREEN  = 2'b00;
    localparam YELLOW = 2'b01;
    localparam RED    = 2'b10;

    reg [1:0] state, next_state;

    // --- PROCESS 1: Memory ---
    always @(posedge clk) begin
        if (rst) state <= GREEN;
        else     state <= next_state;
    end

    // --- PROCESS 2: Transitions ---
    always @(*) begin
        next_state = state; // Default: Stay put

        case (state)
            GREEN: begin
                if (timer_done) next_state = YELLOW;
            end
            YELLOW: begin
                if (timer_done) next_state = RED;
            end
            RED: begin
                if (timer_done) next_state = GREEN;
            end
            default: next_state = GREEN;
        endcase
    end

    // --- PROCESS 3: Outputs (Moore) ---
    always @(*) begin
        lights = 3'b000; // Default all off

        case (state)
            GREEN:  lights = 3'b001; // Bit 0 ON
            YELLOW: lights = 3'b010; // Bit 1 ON
            RED:    lights = 3'b100; // Bit 2 ON
        endcase
    end

endmodule
Copied!

Why do it this way?

You might think: “That’s a lot of code for something so simple! I could have used nested ifs.”

The advantage of this template is Scalability and Debugging.

  1. If the traffic light doesn’t change color, you look at Process 2.
  2. If the traffic light is red but the light doesn’t turn on, you look at Process 3.
  3. If the machine resets by itself, you look at Process 1.

Separating concerns allows us to design state machines with dozens or hundreds of states without going crazy.