3、FSM设计方法:状态转移图绘制、状态编码、状态表设计

状态机设计,说白了就是给硬件画一张「行为地图」。你告诉它:遇到什么情况,该往哪儿走,输出什么信号。我做了这么多年硬件,发现很多新手一上来就写代码,结果状态跑飞了都不知道。其实,画好状态转移图、选对编码方式、填好状态表,这三步走稳了,后面基本不会出大问题。

3.1 状态转移图绘制

状态转移图是FSM的「灵魂草图」。我个人习惯,先用纸笔画一遍,再转到工具里。为什么?因为手绘时思路最自由,不会被工具的操作打断。

画图时记住三个要素:状态圆圈、转移箭头、转移条件。状态用圆圈表示,箭头指向下一个状态,条件写在箭头旁边。嗯,这里要注意:条件必须互斥且完备。什么叫互斥?就是同一时刻只能有一个条件为真。什么叫完备?就是所有可能的情况都覆盖到了。

举个例子,一个简单的交通灯控制器:

状态:IDLE -> GREEN -> YELLOW -> RED -> GREEN
转移条件:
  IDLE → GREEN: 启动信号有效
  GREEN → YELLOW: 计时到30秒
  YELLOW → RED: 计时到5秒
  RED → GREEN: 计时到25秒

我在项目中遇到过,有人画图时漏了一个转移条件,结果仿真时状态机卡在某个状态出不来。排查了整整两天,最后发现是条件没写全。所以,画完图后一定要对着需求一条条核对。

核心原则:每个状态都必须有明确的出口条件,不能有「悬空」的状态。

下面我用SVG画一张典型的状态转移图,展示一个三段式FSM的完整结构:

典型FSM状态转移图示例 S0 IDLE S1 WORK S2 DONE S3 ERROR start=1 done=1 ack=1 error=1 reset=1 图例: 正常转移 异常转移 返回转移 状态节点

3.2 状态编码:三种主流方式

状态编码就是把状态名字翻译成二进制数。选哪种编码,直接影响电路的面积和速度。我见过有人不管三七二十一全用独热码,结果小设计浪费了大量触发器。你想想看,每种编码都有自己的脾气。

3.2.1 二进制编码

最省触发器的方案。N个状态只需要log2(N)个触发器。比如4个状态,用2个触发器就够了。

状态二进制编码
S000
S101
S210
S311

优点:触发器用得少,适合状态数多的设计。
缺点:组合逻辑复杂,译码路径长,速度可能上不去。

我的经验:状态数超过16个时,我优先考虑二进制编码。但要注意,相邻状态之间的跳转如果涉及多位变化,容易产生毛刺。

3.2.2 格雷码编码

相邻状态之间只有1位不同。这玩意儿在跨时钟域传输时特别好用。

状态格雷码
S000
S101
S211
S310

优点:减少毛刺,适合顺序跳转的场景。
缺点:非顺序跳转时优势不明显。

我曾经在一个异步FIFO的设计里用了格雷码,读写指针跨时钟域传递时,再也没有出现亚稳态问题。说白了,格雷码就是为这种场景量身定做的。

3.2.3 独热码编码

每个状态对应一个触发器,只有1位为1。N个状态需要N个触发器。

状态独热码
S00001
S10010
S20100
S31000

优点:译码逻辑最简单,速度最快。适合高速设计。
缺点:浪费触发器,状态数多了吃不消。

警告:独热码状态机必须保证任何时候只有1位为1。如果因为干扰出现多位为1,状态机就乱了。所以,一定要加「非法状态检测」逻辑。

3.3 状态表设计

状态表是状态转移图的「数字化表达」。它把图形信息变成一行行的表格数据,方便写代码和做验证。

一个完整的状态表包含三部分:当前状态、输入条件、下一状态。如果是Mealy型状态机,还要加上输出。

举个例子,还是那个交通灯控制器:

当前状态输入条件下一状态输出
IDLEstart=1GREENlight=GREEN
IDLEstart=0IDLElight=RED
GREENtimer_exp=1YELLOWlight=YELLOW
GREENtimer_exp=0GREENlight=GREEN
YELLOWtimer_exp=1REDlight=RED
YELLOWtimer_exp=0YELLOWlight=YELLOW
REDtimer_exp=1GREENlight=GREEN
REDtimer_exp=0REDlight=RED

填表时有个小技巧:先把所有状态列出来,然后对每个状态,列出所有可能的输入组合。这样就不会漏掉任何转移条件。

避坑指南:我曾经因为状态表里少写了一行「保持当前状态」的转移,导致综合出来的电路在某个输入组合下状态不确定。后来我养成了一个习惯:每个状态至少有一个「else」分支,要么跳转到其他状态,要么保持自身。

最后说一句,状态表不仅是写代码的依据,也是验证的「金标准」。仿真时拿着状态表一条条对,比看波形图快多了。嗯,这就是我这么多年总结出来的经验。

实用建议:设计FSM时,先画图、再编码、最后填表。这三步走完,代码写起来就是「填空」了。别急着写代码,磨刀不误砍柴工。

可可精品资料库