文章来源:FPGA入门到精通
1、禁止状态机死锁
必须设置默认跳转逻辑,所有未定义的异常输入条件下,状态机自动跳回初始空闲状态,避免未知状态卡死不动;
2、异步输入信号同步化:外部按键、跨模块异步信号,必须先经过两级触发器同步打拍后,再接入状态机判断,避免亚稳态导致状态跳转错乱;
3、优先使用同步复位
FPGA状态机推荐同步复位,复位逻辑与时序时钟同步,时序更容易收敛,避免异步复位带来的时序毛刺和复位异常;
4、输出逻辑时序化处理
坚决避免组合逻辑直接输出,所有控制输出全部放在时序always块中赋值,彻底消除输出毛刺;
5、状态编码不手动硬写
Verilog代码中使用parameter宏定义状态编码,不直接写二进制数值,便于后期修改状态、维护代码。
代码示例
module fsm_best_practice (
input wire clk, // 时钟
input wire rst_n, // 同步复位,低有效(见要点3)
input wire async_btn, // 外部异步按键输入
output reg led_out // 控制LED输出(时序化,见要点4)
);
// =================================================
// 要点5:状态编码全部使用 parameter 宏定义,不手写二进制值
// =================================================
// 状态定义(独热码示例)
parameter IDLE = 4'b0001;
parameter PRESSED = 4'b0010;
parameter WAIT = 4'b0100;
parameter RELEASED = 4'b1000;
// 状态寄存器
reg [3:0] current_state, next_state;
// =================================================
// 要点2:异步输入信号同步化(两级触发器打拍)
// =================================================
reg sync_ff1, sync_ff2;
wire btn_synced;
always @(posedge clk) begin
sync_ff1 <= async_btn;
sync_ff2 <= sync_ff1; // 两级同步后,输出稳定的 btn_synced
end
assign btn_synced = sync_ff2; // 仅用于内部读取,实际连接为同步后信号
// =================================================
// 要点3:优先使用同步复位
// 复位逻辑在时序 always 块内随 clk 触发,避免异步复位的时序问题
// =================================================
always @(posedge clk) begin
if (!rst_n)
current_state <= IDLE; // 同步复位:状态回到 IDLE
else
current_state <= next_state;
end
// =================================================
// 三段式状态机:下一状态组合逻辑
// 要点1:必须设置 default 跳转逻辑,防止死锁
// =================================================
always @(*) begin
// 默认下一状态为 IDLE,覆盖所有未显式列出的情况
next_state = IDLE; // 安全默认值
case (current_state)
IDLE: begin
if (btn_synced) // 使用同步后的信号
next_state = PRESSED;
else
next_state = IDLE;
end
PRESSED: begin
// 按下后进入等待,避免重复触发
next_state = WAIT;
end
WAIT: begin
if (!btn_synced) // 等待按键释放
next_state = RELEASED;
else
next_state = WAIT;
end
RELEASED: begin
// 释放后返回 IDLE,完成一次按键响应
next_state = IDLE;
end
// default 已在 case 前通过赋值覆盖,这里可省略,但习惯保留
default: next_state = IDLE;
endcase
end
// =================================================
// 要点4:输出逻辑时序化处理 —— 全部放在带时钟的 always 块中
// 坚决避免组合逻辑直接输出,消除毛刺
// =================================================
always @(posedge clk) begin
if (!rst_n) begin
led_out <= 1'b0;
end else begin
// 时序输出:只在进入 PRESSED 状态的同一时钟沿点亮 LED
// 注意:这里使用 current_state 而非 next_state,保证已稳定跳转
if (current_state == IDLE && next_state == PRESSED)
led_out <= 1'b1;
else if (current_state == RELEASED && next_state == IDLE)
led_out <= 1'b0;
// 其余情况保持不变,避免毛刺
end
end
endmodule