4、二段式状态机:二段式写法结构、与三段式的对比、适用场景分析
聊完了状态机的基础概念,咱们今天来啃一块硬骨头——二段式状态机。
说实话,我刚入行那会儿,对二段式和三段式的选择特别纠结。总觉得三段式更高级,二段式就是偷懒。后来踩了几个坑才明白,没有最好的写法,只有最合适的写法。今天我就把二段式的底裤扒干净,让你彻底搞懂它。
4.1 二段式状态机的写法结构
二段式,顾名思义,就是把状态机分成两个过程块(always块)来写。
第一段:时序逻辑,描述状态跳转
这一段负责把当前状态更新为下一个状态。说白了就是每个时钟沿来的时候,把 next_state 赋值给 current_state。
// 第一段:状态寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
第二段:组合逻辑,描述状态转移条件和输出
这一段就热闹了。它既要算下一个状态是啥,又要根据当前状态和输入信号,决定输出什么。两个活儿都压在这一段里。
// 第二段:状态转移 + 输出(组合逻辑)
always @(*) begin
// 默认赋值,防止锁存器
next_state = current_state;
out1 = 1'b0;
out2 = 1'b0;
case (current_state)
IDLE: begin
if (start)
next_state = WORK;
else
next_state = IDLE;
out1 = 1'b0;
end
WORK: begin
if (done)
next_state = IDLE;
else
next_state = WORK;
out1 = 1'b1;
out2 = 1'b1;
end
default: begin
next_state = IDLE;
end
endcase
end
关键点:第二段是组合逻辑,所以一定要给所有信号赋默认值。不然综合出来就是一堆锁存器,那可就热闹了。我曾经在项目里吃过这个亏,仿真怎么都对,上板就乱跳,查了两天才发现是组合逻辑里漏了默认赋值。
4.2 二段式 vs 三段式:到底差在哪?
咱们直接上对比表,一目了然。
| 对比项 | 二段式 | 三段式 |
|---|---|---|
| 代码量 | 少,两个always块 | 多,三个always块 |
| 输出类型 | 组合逻辑输出(可能有毛刺) | 寄存器输出(干净无毛刺) |
| 时序性能 | 组合路径长,频率受限 | 寄存器隔离,频率更高 |
| 可读性 | 紧凑,但逻辑混在一起 | 清晰,各司其职 |
| 调试难度 | 中等,输出毛刺难抓 | 低,波形干净好分析 |
| 资源占用 | 略少(少一级寄存器) | 略多(多一级寄存器) |
你看,二段式最大的问题就是输出是组合逻辑。这意味着什么?意味着你的输出信号可能会因为输入的变化而瞬间抖动。如果这个输出直接连到时钟使能或者复位信号上,那后果不堪设想。
我记得有一次做SPI从机,用二段式写的状态机。仿真跑得飞起,结果接上示波器一看,片选信号上全是毛刺。后来加了一级输出寄存器才搞定。嗯,从那以后,只要输出信号要出芯片,我必用三段式。
4.3 二段式的适用场景分析
那二段式是不是就一无是处了?当然不是。它有自己的用武之地。
场景一:内部状态指示,不对外输出
如果你的状态机只是内部用,比如控制一个计数器什么时候开始计数、什么时候清零,那二段式完全够用。因为内部信号即使有毛刺,只要在时钟沿采样的时候是稳定的就行。
场景二:资源极度受限
有些小芯片,比如CPLD或者低端FPGA,逻辑资源就那么几百个LE。这时候能省一个寄存器是一个。二段式少一级输出寄存器,在资源紧张的时候就是救命稻草。
场景三:高速流水线中的简单控制
在高速流水线里,状态机本身很简单,就两三个状态。这时候用二段式,代码简洁,综合出来的逻辑也小,时序反而好收敛。我做过一个DDR3的读数据通道,状态机就三个状态,用二段式跑的稳稳的。
我的个人建议:新手入门先用三段式,把状态机的结构搞熟了。等你有两三年经验了,再根据实际情况灵活选择二段式。别一上来就追求代码量少,先把功能做稳定再说。
4.4 避坑指南
如果你决定用二段式,这几个坑一定要避开:
- 组合逻辑里一定要给所有输出赋默认值。我习惯在case前面先写一遍默认赋值,这样哪怕漏了某个分支,也不会生成锁存器。
- 输出信号如果跨时钟域,必须加同步器。二段式的输出是组合逻辑,跨时钟域的时候毛刺会被采到,造成亚稳态。
- 仿真时多看看输出波形。二段式的输出在仿真里可能看起来没问题,但实际综合后因为门延迟,毛刺就出来了。最好用后仿验证一下。
警告:千万不要在二段式的组合逻辑里写 @(posedge clk) 或者 wait 语句。这是不可综合的!我曾经见过有人把二段式的第二段写成时序逻辑,结果综合出来两个状态寄存器打架,整个状态机直接废了。
4.5 小结
二段式状态机,说白了就是用代码的紧凑换资源的节省。它适合内部简单控制、资源紧张、或者对输出毛刺不敏感的场景。但如果你要驱动外部器件,或者输出信号要跨模块传递,老老实实用三段式吧。
你想想看,一个状态机写错了,轻则功能异常,重则芯片流片回来直接废掉。所以我的原则是:能用三段式就用三段式,除非有充分的理由用二段式。
下一节咱们聊聊状态机的编码方式——二进制、格雷码、独热码到底怎么选。这个选对了,能省不少事。