有限状态机(FSM)设计:Moore 与 Mealy 型状态机、三段式写法、状态编码
状态机这东西,说白了就是数字电路里的“大脑”。你想想看,一个复杂的逻辑控制,如果没有状态机来管着,那代码写出来就是一锅粥。我做了十几年芯片设计,可以负责任地告诉你——状态机写得好不好,直接决定了你的模块稳不稳。
一、Moore 与 Mealy:两种基本流派
先聊聊这两种状态机的区别。其实很简单:
- Moore 型:输出只取决于当前状态。换句话说,状态变了,输出才变。
- Mealy 型:输出取决于当前状态 和 输入。输入一变,输出可能立刻跟着变。
我个人习惯,在控制逻辑比较复杂的场景下优先用 Moore。为什么?因为它稳定。输出不会因为输入毛刺而乱跳。但 Mealy 也有它的优势——同样的功能,Mealy 往往能用更少的状态实现。
举个例子:检测序列“101”。用 Moore 需要 4 个状态,用 Mealy 只需要 3 个。我在一个通信协议解析的项目里,就因为这个差异,省下了不少面积。
核心区别一句话总结:Moore 的输出是“慢一拍”的,Mealy 的输出是“实时”的。选哪个,看你更在意稳定性还是响应速度。
二、三段式写法:我强烈推荐
很多初学者喜欢把状态机写在一个 always 块里。嗯,这样也能跑,但调试起来真的很痛苦。我建议你从一开始就养成三段式的习惯。
三段式指的是:
- 第一段:时序逻辑,描述状态跳转(当前态 → 次态)
- 第二段:组合逻辑,描述次态如何产生(根据输入和当前态)
- 第三段:输出逻辑,可以是时序也可以是组合
为什么这么分?因为可读性强,综合工具也好优化。我曾经接手过一个同事的代码,他把所有逻辑塞在一个 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 设计的三个核心维度串起来了。你写状态机的时候,脑子里要有这个框架——先定类型,再选写法,最后定编码。顺序别搞反了。
最后说一句:状态机设计没有银弹。我见过用 One-hot 写出 200 个状态的“怪物”,也见过用 Binary 编码但组合逻辑路径长到爆的例子。关键是你得理解每种方案的 trade-off,然后根据你的项目需求做选择。嗯,多流几次片,你自然就懂了。
可可精品资料库