有限状态机(FSM)设计:Moore 与 Mealy 型状态机、三段式写法、状态编码

状态机这东西,说白了就是数字电路里的“大脑”。你想想看,一个复杂的逻辑控制,如果没有状态机来管着,那代码写出来就是一锅粥。我做了十几年芯片设计,可以负责任地告诉你——状态机写得好不好,直接决定了你的模块稳不稳。

一、Moore 与 Mealy:两种基本流派

先聊聊这两种状态机的区别。其实很简单:

  • Moore 型:输出只取决于当前状态。换句话说,状态变了,输出才变。
  • Mealy 型:输出取决于当前状态 和 输入。输入一变,输出可能立刻跟着变。

我个人习惯,在控制逻辑比较复杂的场景下优先用 Moore。为什么?因为它稳定。输出不会因为输入毛刺而乱跳。但 Mealy 也有它的优势——同样的功能,Mealy 往往能用更少的状态实现。

举个例子:检测序列“101”。用 Moore 需要 4 个状态,用 Mealy 只需要 3 个。我在一个通信协议解析的项目里,就因为这个差异,省下了不少面积。

核心区别一句话总结:Moore 的输出是“慢一拍”的,Mealy 的输出是“实时”的。选哪个,看你更在意稳定性还是响应速度。

二、三段式写法:我强烈推荐

很多初学者喜欢把状态机写在一个 always 块里。嗯,这样也能跑,但调试起来真的很痛苦。我建议你从一开始就养成三段式的习惯。

三段式指的是:

  1. 第一段:时序逻辑,描述状态跳转(当前态 → 次态)
  2. 第二段:组合逻辑,描述次态如何产生(根据输入和当前态)
  3. 第三段:输出逻辑,可以是时序也可以是组合

为什么这么分?因为可读性强,综合工具也好优化。我曾经接手过一个同事的代码,他把所有逻辑塞在一个 always 块里,足足 300 行。我花了三天才理清楚。从那以后,我团队里谁写一段式我就让谁重写。

来看一个 Moore 型状态机的三段式模板:

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

// 第二段:次态组合逻辑
always @(*) begin
  case (state)
    IDLE:   next_state = (start) ? S1 : IDLE;
    S1:     next_state = (done)  ? S2 : S1;
    S2:     next_state = IDLE;
    default: next_state = IDLE;
  endcase
end

// 第三段:输出逻辑(Moore 型,时序输出)
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    out <= 1'b0;
  else begin
    case (next_state)  // 注意这里用 next_state
      S2: out <= 1'b1;
      default: out <= 1'b0;
    endcase
  end
end

一个小技巧:第三段输出逻辑里,我习惯用 next_state 而不是 state。这样输出会比状态变化早一个周期稳定,能有效避免毛刺。当然,具体用哪个要看你的时序要求。

三、状态编码:Binary、Gray、One-hot 怎么选?

这个问题,面试的时候经常被问到。其实没有的好坏,关键看你的场景。

编码方式 状态数 触发器数 组合逻辑 适用场景
Binary N log₂(N) 中等 状态数较多,面积敏感
Gray N log₂(N) 中等 跨时钟域、低功耗
One-hot N N 简单 速度要求高,状态数少

说说我的经验:

  • Binary 编码:最省触发器。如果状态数超过 16 个,我一般会用 Binary。但要注意,状态跳转时可能有多个 bit 同时变化,容易产生毛刺。
  • Gray 编码:每次只变一个 bit。我在做跨时钟域的状态传递时,一定会用 Gray。比如一个异步 FIFO 的指针,用 Gray 编码可以避免亚稳态传播。
  • One-hot 编码:每个状态一个触发器。组合逻辑极其简单,速度最快。但状态数一多,面积就爆炸。我记得有个项目用了 32 个状态的 One-hot,综合出来面积比 Binary 大了 3 倍。所以,状态数超过 8 个,我就不太推荐 One-hot 了。

避坑指南:我曾经在一个项目里用了 Binary 编码,但没做非法状态处理。结果芯片在极端条件下跳到了一个未定义状态,整个模块死锁了。从那以后,我每个状态机都会加 default 分支,把非法状态引导回 IDLE。这个习惯救了我好几次。

四、知识体系一览

下面这张图,是我自己总结的 FSM 设计核心逻辑。你看一眼,基本就明白整个章节在讲什么了。

FSM 设计核心知识体系 状态机类型 Moore vs Mealy 三段式写法 状态跳转 · 次态 · 输出 状态编码 Binary · Gray · One-hot Moore:输出仅依赖状态 Mealy:输出依赖状态+输入 选择依据:稳定性 vs 响应 实际项目:优先 Moore 段1:状态寄存器(时序) 段2:次态组合逻辑 段3:输出逻辑 推荐:可读性强,易调试 Binary:省触发器 Gray:跨时钟域首选 One-hot:速度快,面积大 经验:状态数≤8用One-hot 核心原则:结构清晰 + 编码合理 + 非法状态处理

这张图把 FSM 设计的三个核心维度串起来了。你写状态机的时候,脑子里要有这个框架——先定类型,再选写法,最后定编码。顺序别搞反了。

最后说一句:状态机设计没有银弹。我见过用 One-hot 写出 200 个状态的“怪物”,也见过用 Binary 编码但组合逻辑路径长到爆的例子。关键是你得理解每种方案的 trade-off,然后根据你的项目需求做选择。嗯,多流几次片,你自然就懂了。

可可精品资料库