跳转到主要内容

FPGA状态机设计一些避坑要点

judy 提交于

文章来源:<span id="profileBt"><a href="https://mp.weixin.qq.com/s/b9h5YoffGoRMwgbFpVlfjQ">FPGA入门到精通</a></span&…;

1、禁止状态机死锁

必须设置默认跳转逻辑,所有未定义的异常输入条件下,状态机自动跳回初始空闲状态,避免未知状态卡死不动;

2、异步输入信号同步化:外部按键、跨模块异步信号,必须先经过两级触发器同步打拍后,再接入状态机判断,避免亚稳态导致状态跳转错乱;

3、优先使用同步复位

FPGA状态机推荐同步复位,复位逻辑与时序时钟同步,时序更容易收敛,避免异步复位带来的时序毛刺和复位异常;

4、输出逻辑时序化处理

坚决避免组合逻辑直接输出,所有控制输出全部放在时序always块中赋值,彻底消除输出毛刺;

5、状态编码不手动硬写

Verilog代码中使用parameter宏定义状态编码,不直接写二进制数值,便于后期修改状态、维护代码。

<strong>代码示例</strong>
<pre>
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
</pre>