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.
Related articles
✅[Verilog] Asynchronous Signal Processing: CDC and Metastability
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-Always: Putting state transitions and outputs all in one block. (Worst readability, not recommended)
- 2-Always: Status register (Seq) + next status logic (Comb). (Standard method)
- 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).
// 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
endTip: 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).
| Method | explain (4 states example) | Advantage | Disadvantage | 추천 환경 |
| Binary | 00, 01, 10, 11 | Minimize register usage (N states -> log2(N) bits) | Decoding logic becomes more complex (slows down) | ASIC (area-focused) |
| One-hot | 0001, 0010, 0100 | Decoding is very fast (just look at one bit) | Uses a lot of registers (N states -> N bits) | FPGA (register rich, speed focused) |
| Gray | 00, 01, 11, 10 | Only one bit changes at a time | Minimized glitches, low power | High-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
- 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.
- 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).
Related articles
✅[Verilog] Asynchronous Signal Processing: CDC and Metastability
References: DigiKey