4. 有限状态机基础:状态机概念、Moore型与Mealy型状态机、状态转移图、状态编码
各位同学,今天我们来聊聊有限状态机。说实话,这是时序控制器设计的灵魂。我做了十几年数字IC,见过太多因为状态机没写好导致芯片翻车的案例。你想想看,一个复杂的控制逻辑,如果没有状态机来管理,代码会乱成一锅粥。
4.1 什么是有限状态机?
有限状态机,简称FSM。说白了,就是一个系统在有限个状态之间跳来跳去。每个状态代表系统当前所处的某种情况,根据输入信号决定下一步去哪。
举个最简单的例子——电梯门。它有“开门中”、“已开门”、“关门中”、“已关门”四个状态。你按开门按钮,它就从“已关门”跑到“开门中”。这不就是状态机吗?
核心三要素:
- 状态集合:系统所有可能的状态,有限个
- 转移条件:什么情况下从一个状态跳到另一个
- 输出:每个状态下系统做什么
我在一个MCU项目中,状态机写了32个状态。刚开始觉得太多了,后来发现每个状态职责清晰,调试起来反而比那些“万能状态”好使。
4.2 Moore型 vs Mealy型状态机
这两种类型,是面试必考题。我当年也被问过,答得磕磕巴巴的。现在给你讲清楚。
4.2.1 Moore型状态机
Moore型的特点是:输出只取决于当前状态。跟输入没关系。
打个比方:你到了一个十字路口,红灯就停,绿灯就走。这个“停”或“走”只取决于当前是红灯还是绿灯状态,跟你之前怎么来的没关系。
// 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) ? RUN : IDLE;
RUN: next_state = (done) ? IDLE : RUN;
default: next_state = IDLE;
endcase
end
// 输出只跟当前状态有关
assign out = (state == RUN);
我的经验:Moore型状态机更安全。输出不会因为输入毛刺而抖动。我在做电源管理芯片时,所有状态机都用Moore型,因为输出直接控制功率管,不能乱跳。
4.2.2 Mealy型状态机
Mealy型就不一样了:输出同时取决于当前状态和输入。
还是电梯的例子。你按了关门按钮,电梯门开始关。但如果有人挡在中间(输入),门立刻又打开。这个“开门”动作,既取决于当前“关门中”的状态,也取决于“有人挡门”这个输入。
// Mealy型状态机示例
always @(*) begin
case (state)
IDLE: begin
next_state = (start) ? RUN : IDLE;
out = (start) ? 1'b1 : 1'b0; // 输出依赖输入
end
RUN: begin
next_state = (done) ? IDLE : RUN;
out = (done) ? 1'b0 : 1'b1;
end
endcase
end
注意:Mealy型状态机输出可能产生毛刺。因为输入变化会立刻影响输出,组合逻辑路径上的竞争可能导致短暂错误。我曾经在一个通信协议控制器里用了Mealy型,结果输出信号被采样到错误值,查了两天才发现是毛刺问题。
4.2.3 两者对比
| 特性 | Moore型 | Mealy型 |
|---|---|---|
| 输出依赖 | 仅当前状态 | 状态+输入 |
| 状态数量 | 通常较多 | 通常较少 |
| 输出延迟 | 慢一个时钟周期 | 组合逻辑直接输出 |
| 抗毛刺能力 | 强 | 弱 |
| 典型应用 | 控制类、安全关键 | 高速数据通路 |
4.3 状态转移图
写代码之前,先把状态转移图画出来。这是规矩。我见过有人直接上手写代码,结果状态跳转逻辑自己都理不清,最后改得面目全非。
状态转移图用圆圈表示状态,箭头表示转移方向,箭头旁边标注转移条件。Moore型的输出写在圆圈里,Mealy型的输出写在箭头上。
下面我用SVG画一个典型的状态转移图,展示一个简单的串行数据检测器——检测到“101”序列就输出高电平。
画图的时候,我习惯用不同颜色区分不同输入条件。这样一眼就能看出哪些转移是并行的,哪些是互斥的。嗯,这个习惯帮我避免过好几次状态冲突的bug。
4.4 状态编码
状态编码,就是把状态名字翻译成二进制数。这里面门道不少。
4.4.1 二进制编码
用最少的触发器。N个状态需要log2(N)个触发器。比如4个状态用2个触发器。省面积,但组合逻辑复杂,容易出毛刺。
4.4.2 独热码
每个状态用一个触发器。N个状态需要N个触发器。面积大,但组合逻辑简单,速度快。FPGA里特别流行,因为FPGA触发器多,查找表资源有限。
4.4.3 格雷码
相邻状态之间只有1位不同。适合状态转移路径固定的场景,比如计数器。能减少毛刺和功耗。
| 状态 | 二进制 | 独热码 | 格雷码 |
|---|---|---|---|
| IDLE | 00 | 0001 | 000 |
| S1 | 01 | 0010 | 001 |
| S2 | 10 | 0100 | 011 |
| S3 | 11 | 1000 | 010 |
我的建议:FPGA项目用独热码,ASIC项目用二进制码。如果状态少于8个,独热码更省事。多于16个状态,二进制码更划算。我曾经在一个128状态的控制器里用了独热码,结果触发器用掉一大半,被领导骂了一顿。
4.5 避坑指南
最后分享几个我踩过的坑:
- 漏掉default分支:综合工具会生成锁存器,仿真时状态可能跑到未知态。我曾经因为这个,芯片在高温下随机死机。
- 状态转移条件重叠:两个转移条件同时满足,状态机不知道该去哪。一定要保证互斥。
- 输出信号未寄存:Mealy型输出直接来自组合逻辑,容易被毛刺干扰。关键输出加一级寄存器。
- 状态编码未考虑安全:工业级设计要用“三模冗余”或“状态保护”,防止单粒子翻转导致状态跑飞。
好了,状态机的基础就讲到这里。记住一句话:状态机是时序控制器的骨架。骨架歪了,肉长得再好也没用。下次我们讲状态机的Verilog实现模板,到时候我会手把手教你写一个健壮的状态机。
公众号:蓝海资料掘金营,微信deep3321