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)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
endProcess 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
endProcess 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
endSometimes, 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
endmoduleWhy 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.
- If the traffic light doesn’t change color, you look at Process 2.
- If the traffic light is red but the light doesn’t turn on, you look at Process 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.