FPGA 三段式状态机:写法与注意事项

一、三段式状态机概述

三段式状态机是 FPGA 设计中描述有限状态机(FSM)的经典方法,它将状态机的状态转移次态逻辑输出逻辑分别写在三个独立的 always 块中。相比于一段式、二段式,三段式结构具有代码清晰、时序明确、易于维护、避免竞争冒险等优点。

重点:三个 always 块各自分工,实现时序逻辑与组合逻辑分离


二、三段式状态机的标准写法

2.1 模块结构

module fsm_example (
    input       clk,
    input       rst_n,
    input       condition,      // 输入条件,决定状态转移
    output reg  out_signal      // 输出信号
);

2.2 第一段:状态寄存器(时序逻辑)

作用:当前时钟沿将 next_state 赋给 current_state,实现状态寄存。

// 第一段:状态转移(时序)
reg [1:0] current_state, next_state;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

注意

  • 使用非阻塞赋值 <=

  • 复位应使状态回到初始状态(如 IDLE

2.3 第二段:次态逻辑(组合逻辑)

作用:根据 current_state 和输入条件,计算 next_state

// 第二段:次态计算(组合)
always @(*) begin
    case (current_state)
        IDLE : if (condition) next_state = WORK;
              else            next_state = IDLE;
        WORK: if (done)       next_state = STOP;
              else            next_state = WORK;
        STOP:                 next_state = IDLE;
        default:              next_state = IDLE;
    endcase
end

注意

  • 使用阻塞赋值 =(组合逻辑)

  • 敏感列表写 * 代表所有输入信号(避免综合出锁存器)

  • 必须写 default 防止综合出锁存器

  • 所有可能状态分支都要明确赋值

2.4 第三段:输出逻辑(时序/组合均可)

输出逻辑可以有两种风格:时序输出(推荐,可滤除毛刺)或组合输出(速度快但有毛刺风险)。

✅ 推荐:时序输出(与状态寄存器同步)
// 第三段:输出逻辑(时序)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        out_signal <= 1'b0;
    else begin
        case (current_state)
            WORK : out_signal <= 1'b1;
            default: out_signal <= 1'b0;
        endcase
    end
end
组合输出写法(不推荐,除非对延迟要求极高)
always @(*) begin
    case (current_state)
        WORK : out_signal = 1'b1;
        default: out_signal = 1'b0;
    endcase
end

重点:时序输出能避免组合逻辑产生的毛刺,且方便时序约束,是 FPGA 工程实践的首选。

三、完整代码示例(以 Moore 型状态机为例)

module moore_fsm (
    input clk,
    input rst_n,
    input start,
    input done,
    output reg led
);

// 状态编码(建议使用 localparam)
localparam IDLE = 2'b00;
localparam WORK = 2'b01;
localparam STOP = 2'b10;

reg [1:0] current_state, next_state;

// ----- 第一段:状态寄存器 -----
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        current_state <= IDLE;
    else
        current_state <= next_state;
end

// ----- 第二段:次态逻辑 -----
always @(*) begin
    case (current_state)
        IDLE : next_state = start ? WORK : IDLE;
        WORK : next_state = done ? STOP : WORK;
        STOP : next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

// ----- 第三段:输出逻辑(时序)-----
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        led <= 1'b0;
    else begin
        case (current_state)
            WORK : led <= 1'b1;
            default: led <= 1'b0;
        endcase
    end
end

endmodule

四、重要注意事项(重点)

注意点详细说明
编码方式状态使用 localparam 定义,推荐独热码(one-hot)(占用触发器多但译码快、无毛刺)或格雷码(适用于高速异步时钟)。避免在代码中写硬编码的数字。
复位必须有异步或同步复位,使状态机进入确定初始状态。推荐异步复位negedge rst_n),注意复位释放需满足恢复时间。
第二段组合逻辑必须使用 always @(*),条件分支必须完整(case 中加 defaultif-else 中覆盖所有路径),否则综合出锁存器,导致功能错误。
第三段输出方式强烈推荐时序输出(放在时钟 always 中),避免组合输出的毛刺;如果输出需要立即响应(无延迟),可以组合输出,但需要手动处理毛刺(如加滤波)。
阻塞 vs 非阻塞时序逻辑(状态寄存、时序输出)用 <=;组合逻辑(次态计算、组合输出)用 =。混用会仿真出错。
状态转移条件避免在一个状态内同时判断多个复杂条件,可分解成多个小状态;条件表达式应消除竞争(如输入已同步到当前时钟域)。
综合属性可添加 syn_encoding 或 (* fsm_encoding = "one_hot" *) 指导综合器优化。
仿真调试建议在 testbench 中打印状态名(用 $display),避免直接看二进制难以调试。

五、常见错误及避免方法

  1. 缺少 default

    • 现象:综合出锁存器,状态机可能卡死。

    • 解决:case 语句最后加 default: next_state = IDLE;

  2. 次态逻辑中使用非阻塞赋值

    • 现象:仿真时 next_state 更新慢一拍,状态转移出错。

    • 解决:组合逻辑一律用 =

  3. 输出逻辑使用 current_state 却放在组合块中

    • 现象:输出有毛刺,导致后级误触发。

    • 解决:改用时序输出,或对组合输出加寄存器。

  4. 状态编码冲突

    • 现象:状态机进入无效状态。

    • 解决:在 default 分支中让 next_state 回到安全状态(如 IDLE),或添加状态保护逻辑

  5. 跨时钟域处理

    • 输入信号(如 startdone)若来自不同时钟域,必须先用两级寄存器同步,否则亚稳态会导致状态错误。


六、进阶技巧(提升可靠性)

  • 状态机“一 hot”写法:可以使用 (* fsm_encoding = "one_hot" *) 让综合器自动生成独热码,提高速度和抗干扰能力。

  • 三段式 + 输出寄存器:即使需要组合输出,也建议在输出后加一级寄存器(即第四段),彻底消除毛刺。

  • 仿真保护:在 testbench 中模拟进入非法状态,检查状态机是否能自动恢复到有效状态。

  • 使用 SystemVerilog:可以用 enum 定义状态,更清晰且支持自动编码。


七、总结

段落功能敏感信号赋值方式关键要点
第一段状态寄存时钟/复位非阻塞 <=同步或异步复位
第二段次态计算所有输入(*阻塞 =完整分支 + default
第三段输出逻辑时钟/复位(时序输出)或 *(组合输出)非阻塞/阻塞优先选时序输出避免毛刺

核心原则
时序逻辑与组合逻辑严格分离,每个 always 块扮演唯一角色。这样写出的状态机可读性高、易调试、综合结果可靠,是工业级 FPGA 设计的最佳实践。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值