[Verilog] FSM (Finite State Machine) RTL Design Principles

All digital systems are broadly divided into two parts: the data path, which processes data, and the control logic that directs it. At the heart of this control logic is the Finite State Machine (FSM).

You might think, "Isn't it just a matter of using case statements to move states with an FSM?" However, when designing complex IP RTL with clock frequencies exceeding 500MHz and with over 50 states, a poorly designed FSM can become a major cause of timing violations and deadlocks.

In this article, we'll address common mistakes junior engineers make and cover safe and efficient FSM design patterns commonly used in practice.

1. Structure Choice: Moore vs. Mealy, and Registered Output

Textbooks distinguish between Moore and Mealy machines as follows:

  • Moore Machine: Output depends only on the current state (stable)
  • Mealy Machine: Output depends on current state + input. (Fast response)

Practical Dilemmas and Solutions

The Mealy machine responds immediately to input changes, but suffers from problems such as input glitches being directly transmitted to the output or the combinational path being too long. In contrast, the Moore machine responds one clock cycle late.

Senior's Choice: Registered Output Mealy (Class 3 FSM) In practice, to combine the advantages of both, the state decision logic is designed like Mealy, but a register (flip-flop) is always placed in the final output stage.

  • Merit: Since the output signal is cleanly synchronized to the clock (Glitch Free), the timing calculation (Setup Time) of the next block becomes very advantageous.

2. RTL Coding Style: 2-Always vs. 3-Always

There are three main ways to implement an FSM in Verilog.

  1. 1-Always: Putting state transitions and outputs all in one block. (Worst readability, not recommended)
  2. 2-Always: Status register (Seq) + next status logic (Comb). (Standard method)
  3. 3-Always: Status register (Seq) + next status logic (Comb) + output logic (Seq/Comb).

Recommendation: 3-Always (or separate Output Logic)

The most recommended way is to clearly separate state transitions (Next State Logic) and output decisions (Output Logic).

FSM 예시
FSM example
// 1. State Register (Sequential)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) current_state <= IDLE;
    else        current_state <= next_state;
end

// 2. Next State Logic (Combinational) - The Key to Timing
always @(*) begin
    case (current_state)
        IDLE:    if (start) next_state = READ;
                 else       next_state = IDLE;
        READ:    if (done)  next_state = WRITE;
                 else       next_state = READ;
        // ...
        default: next_state = IDLE; // essential!
    endcase
end

// 3. Output Logic (Sequential recommended for timing)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        cmd_out <= 0;
        en_out  <= 0;
    end else begin
        // Look ahead to determine the output by looking at the next state (Look-ahead)
        case (next_state) 
            IDLE:    {cmd_out, en_out} <= 2'b00;
            READ:    {cmd_out, en_out} <= 2'b01;
            WRITE:   {cmd_out, en_out} <= 2'b10;
            default: {cmd_out, en_out} <= 2'b00;
        endcase
    end
end

Tip: Writing output logic as always @(*) shortens the code, but can cause glitches. Looking ahead to the next state and outputting it to a register, as in the code above, will result in much cleaner timing.

3. State Encoding: Binary vs. One-Hot

The performance of the chip varies depending on how the state is expressed in bits (Encoding).

Methodexplain
(4 states example)
AdvantageDisadvantage추천 환경
Binary00, 01, 10, 11Minimize register usage (N states -> log2(N) bits)Decoding logic becomes more complex (slows down)ASIC (area-focused)
One-hot0001, 0010, 0100Decoding is very fast (just look at one bit)Uses a lot of registers (N states -> N bits)FPGA (register rich, speed focused)
Gray00, 01, 11, 10Only one bit changes at a timeMinimized glitches, low powerHigh-speed/low-power design
  • If you are an FPGA designer: The synthesis tool will automatically optimize it for you, but you can also explicitly specify the (* fsm_encoding = "one_hot" *) property to ensure timing.
  • If you are an ASIC designer: Usually, binary or gray codes are used to save area.

4. Safety: Deadlock and Recovery

Radiation or power noise can cause a single event upset (SEU), where a bit in the FSM's state register flips from 0 to 1. What if you jump to an invalid state and there's no way out? The chip will deadlock.

Safe FSM Design Rules

  1. Default Case Required: At the end of the case statement, you must put default: next_state = IDLE; so that it is initialized even if it is not defined.
  2. Explicit State Assignment: State values ​​must be explicitly assigned, such as parameter IDLE = 3'b000;, and the designer must be aware of where the remaining states (Unused States) will go.

5. Code Quality

  • Use parameters: Use meaningful names like state <= READ_Header instead of state <= 3'b001 .
  • Draw an FSM diagram: Before writing any code, draw a bubble diagram first. This will help you find any holes in your logic before you even start coding.
  • Separate output logic: Do not mix output assignments (cmd_out = 1) into state transition logic. Separate them.

6. Conclusion: FSM is all about ‘timing’

A well-crafted FSM goes beyond simply performing sequential operations; it delivers clean signals to the next block at the right time.

  • The habit of placing a register at the output terminal to break the timing path.
  • The meticulousness of taking care of the default case.
  • Select the encoding that suits your target (FPGA/ASIC).

References: DigiKey

Similar Posts