4、状态机设计模式:单次执行、循环执行、层次化、并行与事件驱动

状态机这东西,说白了就是数字逻辑里的“大脑”。我做了十几年FPGA,发现很多同学写状态机就是一套模板打天下。其实不同场景,得用不同的“脑回路”。

今天咱们就聊聊五种常用的状态机设计模式。每种模式都有它最擅长的舞台,也有它容易踩的坑。

4.1 单次执行状态机

这种模式最简单,也最基础。状态机从IDLE开始,一路执行到某个结束状态,然后停在那里,或者回到IDLE等下一次触发。

典型应用场景:配置寄存器、初始化外设、执行一次数据包处理。

核心特征:状态机执行完就“躺平”,不会自己循环。

// 单次执行状态机示例
typedef enum {
    IDLE,
    CONFIG,
    WAIT_DONE,
    FINISH
} state_t;

state_t current_state, next_state;

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

always_comb begin
    next_state = current_state;
    case (current_state)
        IDLE:    if (start) next_state = CONFIG;
        CONFIG:  next_state = WAIT_DONE;
        WAIT_DONE: if (done) next_state = FINISH;
        FINISH:  next_state = FINISH;  // 停在这里
    endcase
end

嗯,这里要注意。单次执行状态机最容易犯的错,就是忘记在FINISH状态加一个超时保护。我曾经遇到过配置芯片时,芯片没响应,状态机卡在WAIT_DONE,整个系统都挂了。从那以后,我每个单次执行状态机都会加一个看门狗计数器。

4.2 循环执行状态机

这种模式就是状态机跑完一圈,自动回到起点继续跑。说白了,就是个永动机。

典型应用场景:ADC采样循环、LED扫描、轮询检测。

// 循环执行状态机示例
always_comb begin
    next_state = current_state;
    case (current_state)
        SAMPLE:  next_state = PROCESS;
        PROCESS: next_state = OUTPUT;
        OUTPUT:  next_state = SAMPLE;  // 自动循环
    endcase
end

我个人习惯在循环状态机里加一个“心跳”信号。这样调试的时候,用示波器一看就知道状态机是不是在正常跑。你想想看,如果状态机卡死了,心跳信号就没了,定位问题快得很。

小技巧:循环状态机最好设计成“可暂停”的。加一个pause信号,让状态机停在当前状态。这在调试时特别有用。

4.3 层次化状态机

这个模式是我最喜欢的。当状态机变得复杂时,层次化设计能把混乱变成有序。

说白了,就是大状态机里套小状态机。父状态机决定“做什么”,子状态机决定“怎么做”。

举个例子:一个通信协议处理模块。

  • 父状态机:IDLE → RX → TX → IDLE
  • 子状态机(RX内部):WAIT_SOF → RECV_DATA → CHECK_CRC → DONE
  • 子状态机(TX内部):SEND_SOF → SEND_DATA → SEND_CRC → DONE
// 层次化状态机结构示意
always_comb begin
    case (main_state)
        IDLE: begin
            if (rx_req) main_next = RX;
            if (tx_req) main_next = TX;
        end
        RX: begin
            // 子状态机在这里运行
            case (rx_sub_state)
                WAIT_SOF: ...
                RECV_DATA: ...
                CHECK_CRC: ...
                DONE: main_next = IDLE;
            endcase
        end
        TX: begin
            // 另一个子状态机
            case (tx_sub_state)
                ...
            endcase
        end
    endcase
end

避坑指南:我曾经在层次化状态机里犯过一个低级错误——子状态机还没跑完,父状态机就跳走了。解决办法很简单:子状态机结束时,拉高一个done信号,父状态机看到done才切换。

4.4 并行状态机

这个模式解决的是“同时干多件事”的问题。多个状态机各自独立运行,互不干扰。

典型应用场景:同时控制多个外设、多通道数据处理。

状态机 功能 优先级
FSM_A SPI通信 高
FSM_B UART收发 中
FSM_C LED闪烁 低

并行状态机最怕什么?资源竞争。两个状态机同时要写同一个寄存器,那就乱套了。我的做法是:每个状态机只操作自己的寄存器,如果需要共享资源,就用一个仲裁器来调度。

4.5 事件驱动状态机

这种模式跟前面几种不太一样。它不是靠时钟节拍一步步走,而是靠“事件”来触发状态跳转。

典型应用场景:按键检测、中断处理、异步信号检测。

// 事件驱动状态机示例
always_comb begin
    next_state = current_state;
    case (current_state)
        IDLE: begin
            if (key_press_event) next_state = DEBOUNCE;
        end
        DEBOUNCE: begin
            if (debounce_done_event) next_state = CHECK;
        end
        CHECK: begin
            if (key_release_event) next_state = IDLE;
        end
    endcase
end

事件驱动状态机有个好处:省功耗。没有事件发生时,状态机可以进入低功耗模式。我在做电池供电的设备时特别喜欢用这种模式。

关键点:事件信号必须是“脉冲”形式的,不能是电平。否则状态机会被同一个事件反复触发。

4.6 五种模式对比

咱们用一张图来总结一下这五种模式的核心逻辑。

五种状态机设计模式对比 单次执行 IDLE→...→FINISH 执行一次就停 循环执行 S1→S2→S3→S1 自动循环运行 层次化 父状态机 嵌套子状态机 并行 FSM_A || FSM_B 同时独立运行 事件驱动 事件→跳转 无事件则休眠 适用场景 配置寄存器 初始化外设 适用场景 ADC采样 LED扫描 适用场景 协议处理 复杂控制 适用场景 多外设控制 多通道数据 适用场景 按键检测 中断处理 选择建议 简单任务→单次执行 | 周期性任务→循环执行 | 复杂逻辑→层次化 多任务→并行 | 低功耗/异步→事件驱动

这五种模式不是互斥的。我在实际项目中经常把它们组合起来用。比如一个层次化状态机,它的子状态机可能是循环执行的,而父状态机可能是事件驱动的。灵活组合,才是高手之道。

我的建议:刚开始学的时候,先把单次执行和循环执行练熟。这两个是基础。层次化和并行是进阶。事件驱动嘛,等你对状态机有了感觉再玩,不然容易把自己绕进去。

好了,这五种模式就聊到这儿。每种模式都有它的脾气,用对了事半功倍,用错了...嗯,我当年踩过的坑就是最好的教材。记住一点:没有最好的模式,只有最合适的模式。

可可精品资料库