一、状态机基础:从概念到实战

大家好,我是你们的FPGA讲师。今天咱们聊聊状态机——这个在数字设计里绕不开的核心概念。

说实话,我刚入行那会儿,觉得状态机就是个“高级开关”。后来做项目多了才发现,它其实是整个数字系统的“大脑”。你想想看,一个复杂的通信协议、一个流水线控制器、甚至一个简单的按键消抖,背后都离不开状态机。

1.1 什么是状态机?

状态机,全称有限状态机(FSM)。说白了,就是一个有记忆的逻辑系统

它有三个核心要素:

  • 状态:系统当前所处的“模式”
  • 转移条件:什么情况下会切换状态
  • 输出:在当前状态下,系统对外表现的行为

我习惯用一个比喻来解释:状态机就像一台自动售货机。你投币(输入),它根据当前状态(待机/选货/出货)决定下一步动作(输出)。

核心公式:

下一个状态 = f(当前状态, 输入)

输出 = g(当前状态) 或 g(当前状态, 输入)

1.2 状态机的分类:Moore vs Mealy

这里有个经典问题:Moore和Mealy到底怎么选?

我当年面试时就被问过这个问题。当时我答得磕磕巴巴,后来做项目多了才真正理解。

特性 Moore型 Mealy型
输出依赖 仅依赖当前状态 依赖当前状态 + 输入
输出时序 稳定,与时钟同步 可能随输入变化,组合逻辑输出
状态数量 通常较多 通常较少
响应速度 慢一拍(需等时钟沿) 快(输入变化立即响应)
抗干扰能力 强(输出稳定) 弱(易受毛刺影响)

Moore型:输出只跟当前状态有关。比如一个交通灯控制器,红灯亮30秒,绿灯亮20秒——输出完全由状态决定。

Mealy型:输出还跟输入有关。比如一个序列检测器,检测到“101”时输出高电平——这个输出不仅取决于当前状态,还取决于刚收到的那个bit。

我的经验:

我个人习惯:能用Moore就用Moore。为什么?因为Moore的输出是寄存器输出,干净、稳定、时序好分析。Mealy虽然状态少、响应快,但组合逻辑输出容易出毛刺,调试起来很头疼。

当然,如果对延迟要求极高(比如高速串行协议),Mealy是更好的选择。

1.3 状态机在FPGA中的应用场景

状态机在FPGA里无处不在。我随便列几个:

  • 通信协议控制器:I2C、SPI、UART的收发逻辑
  • 数据流控制:FIFO读写控制、DMA传输管理
  • 数字信号处理:FFT的蝶形运算调度
  • 接口时序控制:DDR内存的初始化序列
  • 按键消抖:经典的3状态消抖机

我记得有一次做视频接口项目,需要实现一个HDMI的接收控制器。那个状态机有20多个状态,涉及时钟恢复、数据对齐、解扰码等多个阶段。当时我画状态转移图就画了整整一天。

避坑指南:

我曾经犯过一个低级错误:状态机里漏了一个状态转移条件。结果芯片上电后,在某些特定输入组合下,状态机直接“飞”了——跑到一个未定义状态,再也回不来。

所以我现在写状态机,一定会加一个default分支,把所有未定义状态都跳转到复位状态。这叫“安全状态机”。

1.4 知识体系框架图

下面这张图,是我自己总结的状态机知识体系。你把它存下来,学完整个章节再回来看,会有更深的理解。

状态机知识体系 有限状态机 (FSM) 状态 (State) 转移条件 (Transition) 输出 (Output) Moore型 Mealy型 通信协议 数据流控制 DSP调度 接口时序 设计要点:安全状态机 | 状态编码 | 时序约束

1.5 一个简单的例子:按键消抖状态机

光说不练假把式。我给大家看一个最简单的状态机——按键消抖。

按键按下时,机械触点会抖动几毫秒。如果不做处理,FPGA会检测到多次跳变。状态机可以完美解决这个问题。

// 按键消抖状态机 - Moore型
module debounce_fsm (
    input  wire clk,
    input  wire rst_n,
    input  wire key_in,
    output reg  key_out
);

    // 状态定义
    localparam IDLE     = 2'b00;  // 空闲态
    localparam PRESS    = 2'b01;  // 按下态
    localparam RELEASE  = 2'b10;  // 释放态

    reg [1:0] state, next_state;
    reg [19:0] cnt;  // 20ms计数器

    // 状态寄存器
    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: begin
                if (!key_in)  // 检测到按下
                    next_state = PRESS;
            end
            PRESS: begin
                if (cnt == 20'd999_999)  // 20ms稳定
                    next_state = RELEASE;
                else if (key_in)  // 抖动反弹
                    next_state = IDLE;
            end
            RELEASE: begin
                if (key_in)  // 按键释放
                    next_state = IDLE;
            end
            default: next_state = IDLE;  // 安全状态
        endcase
    end

    // 输出逻辑
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            key_out <= 1'b1;
        else if (state == RELEASE)
            key_out <= 1'b0;  // 输出有效按下信号
        else
            key_out <= 1'b1;
    end

endmodule

代码解读:

这个状态机只有3个状态。IDLE等待按键按下,PRESS等待20ms消抖,RELEASE输出有效信号。

注意看default分支——我把所有未定义状态都跳回IDLE。这就是我前面说的“安全状态机”。

嗯,状态机的基础就讲到这里。你可能会问:状态图怎么画?状态编码怎么选?三段式怎么写?别急,这些内容会在后续章节逐一展开。

记住一句话:状态机是FPGA设计的骨架。骨架搭好了,血肉(逻辑)自然就丰满起来。


专注资料整理