状态机写不对?先画转移图再写代码

FPGA小白
文章2026-09-09
25 0 0 0

写 Verilog 状态机时,仿真波形乱跳、输出毛刺、综合报错,根源往往只有一个:没理清状态转移关系就直接敲代码。本文给出一套从画转移图到写代码的完整流程,并在 Vivado 中仿真验证一个按键消抖状态机,最终看到正确的状态跳转波形。核心方法就一句话:先画图,再编码,最后用波形验收。

目标与验收:3 状态按键消抖 Moore 机

最小可运行范围(MVP):设计一个 3 状态按键消抖状态机,用 Moore 型写法,在仿真中验证状态跳转和输出。验收标准如下:

  • 仿真波形中状态变量按预期顺序跳转:IDLE → CHECK → CONFIRM → IDLE。
  • 输出信号 key_out 在 CONFIRM 状态时拉高一个时钟周期,无毛刺。
  • 使用 Vivado 自带仿真器(或 ModelSim)跑通 testbench,无编译错误。

模块划分与接口定义

模块名职责接口要点
debounce_fsm状态机核心:按键消抖逻辑输入:clk, rst_n, key_in;输出:key_out
testbench生成时钟和激励,监测状态实例化 debounce_fsm,初始化信号
顶层模块(可选)板级验证,连接 LED若上板,需约束引脚

核心模块 debounce_fsm 内部包含状态寄存器和组合逻辑。状态编码用独热码(3’b001/010/100),便于综合优化和波形观察。

实现步骤:从画图到跑通仿真

步骤 1:前置条件

安装 Vivado(或 Quartus),本文以 Vivado 2023.2 为例,菜单路径可能因版本而异,以你安装的版本为准。不需要板子,仿真即可完成全部验收。

步骤 2:画状态转移图

在纸上或 draw.io 等工具中画出状态转移图,明确每个状态的输入条件和输出。按键消抖示例:

  • IDLE:等待按键按下(key_in=0),按下则进入 CHECK。
  • CHECK:持续采样(用计数器延时),若按键稳定为低,则进入 CONFIRM;若中途变高(抖动),回到 IDLE。
  • CONFIRM:输出 key_out=1 一个周期,然后无条件回到 IDLE。

转移图是代码的蓝图。画完后建议用表格列出每个状态的转移条件,再写代码,能避免漏条件。

步骤 3:编写状态机代码(三段式)

根据转移图,用三段式写法(状态寄存器、次态组合逻辑、输出逻辑)编写 Verilog:

module debounce_fsm (
    input clk,
    input rst_n,
    input key_in,
    output reg key_out
);
    // 独热码状态编码
    localparam IDLE = 3'b001;
    localparam CHECK = 3'b010;
    localparam CONFIRM = 3'b100;

    reg [2:0] state, next_state;
    reg [19:0] cnt; // 延时计数

    // 状态寄存器(时序逻辑)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) state <= IDLE;
        else state <= next_state;
    end

    // 次态组合逻辑
    always @(*) begin
        next_state = state;
        case (state)
            IDLE: if (key_in == 1'b0) next_state = CHECK;
            CHECK: begin
                if (cnt == 20'd999_999) next_state = CONFIRM; // 50MHz 下约 20ms
                else if (key_in == 1'b1) next_state = IDLE;   // 抖动,回到 IDLE
            end
            CONFIRM: next_state = IDLE;
            default: next_state = IDLE;
        endcase
    end

    // 计数器逻辑(时序逻辑)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) cnt <= 0;
        else if (state == CHECK) cnt <= cnt + 1'b1;
        else cnt <= 0;
    end

    // 输出逻辑(Moore 型)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) key_out <= 1'b0;
        else if (state == CONFIRM) key_out <= 1'b1;
        else key_out <= 1'b0;
    end
endmodule

注意:计数器阈值 999_999 假设时钟 50MHz,产生约 20ms 延时。实际值需根据你的时钟频率调整,否则仿真时间会非常长(20ms 需要 100 万时钟周期)。

步骤 4:编写 testbench

创建 testbench 文件,生成时钟和复位,模拟按键抖动:

module tb_debounce_fsm;
    reg clk = 0;
    reg rst_n = 0;
    reg key_in = 1;
    wire key_out;

    // 50MHz 时钟,周期 20ns
    always #10 clk = ~clk;

    debounce_fsm dut (
        .clk(clk), .rst_n(rst_n),
        .key_in(key_in), .key_out(key_out)
    );

    initial begin
        #20 rst_n = 1;
        #20 key_in = 0; // 按下
        #10 key_in = 1; // 抖动
        #10 key_in = 0; // 稳定按下
        #2000 key_in = 1; // 释放
        #100 $finish;
    end
endmodule

关键点:上面 testbench 故意加入抖动,但实际计数阈值太大(999999),仿真中不会进入 CONFIRM。为了快速验证状态机,请临时把阈值改小,比如改为 5,仿真就能在几十纳秒内看到完整跳转。

步骤 5:运行仿真与排错

在 Vivado 中:添加设计文件和 testbench → 点击 Run Simulation → Run Behavioral Simulation。观察波形窗口中的 state 和 key_out。

常见编译错误:Error: [VRFC 10-716] variable 'next_state' is not a constant。原因是组合逻辑中赋值不完整,比如 case 语句缺少 default 分支,或某个分支未给 next_state 赋值。确保 case 有 default,且所有路径都有赋值。

步骤 6:验证输出波形

在波形中检查:当 state 进入 CONFIRM 时,key_out 应拉高一个时钟周期,然后回到 IDLE 时自动清零。如果 key_out 长时间为高,说明输出逻辑写错——比如用了 if (state == CONFIRM) 但没有在下一个周期用 else 清零。

步骤 7:下一步最小扩展

仿真通过后,把计数器阈值改回真实值(999999),添加真正的 20ms 消抖;或增加一个状态检测按键释放,区分短按和长按。

仿真与调试:激励怎么给、波形看什么

激励给法:在 testbench 中通过改变 key_in 模拟按键抖动——先拉低再拉高再拉低,观察状态机是否只在稳定低电平后才跳转到 CONFIRM。波形观察重点:state 信号按 IDLE→CHECK→CONFIRM 顺序跳转,key_out 只在 CONFIRM 状态拉高。

失败排查清单

  • 失败 1:状态跳转不按预期——检查次态组合逻辑是否漏条件或赋值不完整,用 default 分支兜底。
  • 失败 2:key_out 一直为高——输出逻辑应在非 CONFIRM 状态清零,检查是否用了 else if 而不是 else。
  • 失败 3:仿真卡死或超时——计数器阈值太大,仿真时间不够。临时调小阈值,验证功能后再改回。

扩展方向与风险控制

下一步可加功能:增加按键释放检测,区分短按和长按;改用 Mealy 型状态机,在输入变化时立即输出,提高响应速度。

风险点:计数器阈值过大可能占用较多寄存器资源,但一般没问题;状态机时钟频率过高时,组合逻辑可能成为关键路径导致时序违例。建议用 create_clock -period 20 [get_ports clk] 创建时钟约束(以实际时钟为准),综合后查看时序报告。接口时序假设:所有信号在同一个时钟域(clk)下工作,复位为异步复位。若与外部异步信号交互,需先做同步处理,否则可能产生亚稳态。

FAQ

Q:卡在哪一步最常见?

A:次态组合逻辑写错——漏条件或赋值不完整。建议画完转移图后,用表格列出每个状态的转移条件(当前状态 → 输入条件 → 下一状态),再写代码。

Q:怎么证明自己做对了?

A:仿真波形中,状态变量按预期顺序跳转,且输出只在特定状态拉高一个周期。如果上板,观察 LED 是否按预期亮灭。

Q:没有板子能练什么?

A:完全可以只做仿真。FPGA 开发中仿真验证占很大比重。用纯 HDL 实现各种状态机(如序列检测、交通灯),用 testbench 验证,直到熟练。

想系统学习 FPGA 并就业,可了解成电国芯 FPGA 就业实战班,含零基础教学、企业项目、就业内推。地址:请在后台填写,电话:请在后台填写。

分类
FPGA大赛
标签
fpgaVerilog状态机
浏览 25收藏 0赞 0评论 0
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

FPGA小白查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站