三段式状态机写法:从理论到实战

状态机的写法,说白了就三种:一段式、两段式、三段式。前面我们聊过一段式,今天重点说说三段式。我个人习惯用三段式,为什么?因为它结构清晰,代码可读性强,而且不容易出错。

先看一个简单的例子:一个四状态的状态机,状态分别是 IDLE、S1、S2、S3。我们用三段式来实现它。

第一段:时序逻辑描述状态转移

这一段负责把当前状态更新为次态。说白了,就是每个时钟沿来的时候,把 next_state 赋值给 current_state。

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

嗯,这里要注意:复位信号我用的是异步复位,低电平有效。这是我在项目中常用的写法,因为很多 FPGA 芯片的复位引脚默认就是低电平复位。

第二段:组合逻辑描述次态

这一段根据当前状态和输入信号,计算出下一个状态是什么。它是组合逻辑,没有时钟沿的概念。

// 第二段:次态逻辑
always @(*) begin
    next_state = current_state;
    case (current_state)
        IDLE: if (start) next_state = S1;
        S1:   if (done)  next_state = S2;
        S2:   if (ready) next_state = S3;
        S3:   next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

这里有个小技巧:我习惯在 case 前面先给 next_state 赋默认值,这样能避免产生锁存器。你想想看,如果某个分支没覆盖到,next_state 会保持原来的值,组合逻辑里保持值就意味着锁存器,这是个大坑。

避坑指南:我曾经在项目里漏写了一个状态分支,结果综合出来一堆锁存器,时序怎么都跑不过。后来查了两天才发现是这里的问题。所以,组合逻辑里一定要给所有输出赋默认值。

第三段:组合逻辑描述输出

这一段根据当前状态(或者次态)产生输出信号。输出可以是 Moore 型(只与当前状态有关),也可以是 Mealy 型(与当前状态和输入都有关)。

// 第三段:输出逻辑(Moore 型)
always @(*) begin
    case (current_state)
        IDLE: out = 3'b001;
        S1:   out = 3'b010;
        S2:   out = 3'b100;
        S3:   out = 3'b111;
        default: out = 3'b000;
    endcase
end

如果是 Mealy 型输出,那就把输入信号也加进来判断。比如:

// 第三段:输出逻辑(Mealy 型)
always @(*) begin
    case (current_state)
        IDLE: if (start) out = 3'b001; else out = 3'b000;
        S1:   if (done)  out = 3'b010; else out = 3'b000;
        S2:   if (ready) out = 3'b100; else out = 3'b000;
        S3:   out = 3'b111;
        default: out = 3'b000;
    endcase
end

三段式与两段式的对比

两段式就是把第二段和第三段合并在一起,用组合逻辑同时计算次态和输出。看起来代码少了,但实际用起来问题不少。

对比项 两段式 三段式
代码结构 两个 always 块 三个 always 块
可读性 一般,次态和输出混在一起 好,各司其职
输出时序 组合逻辑输出,容易有毛刺 可以加寄存器输出,干净
修改难度 改输出可能影响次态逻辑 改输出不影响状态转移
综合结果 可能产生不必要的锁存器 结构清晰,综合结果好

我个人强烈推荐用三段式。为什么?因为两段式里,输出和次态逻辑混在一起,你改输出的时候一不小心就把次态逻辑改坏了。我在项目中遇到过这种情况:改了一个输出信号,结果状态机跑飞了,查了半天才发现是次态逻辑被连带改了。

核心要点:三段式的精髓在于「分离」。状态转移、次态计算、输出产生,三者各管各的。这样代码好读、好改、好维护。

知识体系结构图

下面这张图展示了我对三段式状态机的理解框架:

三段式状态机知识体系 第一段 时序逻辑 状态转移 current_state <= next_state 第二段 组合逻辑 次态计算 case + 默认赋值 第三段 组合逻辑 输出产生 Moore / Mealy 三段式 vs 两段式 ✅ 三段式:结构清晰,各司其职 ✅ 三段式:输出可加寄存器,无毛刺 ✅ 三段式:修改输出不影响状态转移 ❌ 两段式:次态和输出混在一起 ❌ 两段式:组合输出容易有毛刺 ❌ 两段式:改输出可能改坏次态

小提示:如果你刚开始学状态机,建议先用三段式把逻辑理清楚。等熟练了,再根据实际需求选择其他写法。但说实话,我做了这么多年项目,大部分场景还是用三段式,因为它最稳。

最后说一句:状态机的写法没有的对错,但三段式在可读性、可维护性、综合质量上都有明显优势。尤其是团队协作的项目里,三段式能让别人一眼看懂你的设计意图。嗯,这就是我推荐它的原因。


可可精品资料库