3、FSM设计方法:状态转移图绘制、状态编码、状态表设计
状态机设计,说白了就是给硬件画一张「行为地图」。你告诉它:遇到什么情况,该往哪儿走,输出什么信号。我做了这么多年硬件,发现很多新手一上来就写代码,结果状态跑飞了都不知道。其实,画好状态转移图、选对编码方式、填好状态表,这三步走稳了,后面基本不会出大问题。
3.1 状态转移图绘制
状态转移图是FSM的「灵魂草图」。我个人习惯,先用纸笔画一遍,再转到工具里。为什么?因为手绘时思路最自由,不会被工具的操作打断。
画图时记住三个要素:状态圆圈、转移箭头、转移条件。状态用圆圈表示,箭头指向下一个状态,条件写在箭头旁边。嗯,这里要注意:条件必须互斥且完备。什么叫互斥?就是同一时刻只能有一个条件为真。什么叫完备?就是所有可能的情况都覆盖到了。
举个例子,一个简单的交通灯控制器:
状态:IDLE -> GREEN -> YELLOW -> RED -> GREEN
转移条件:
IDLE → GREEN: 启动信号有效
GREEN → YELLOW: 计时到30秒
YELLOW → RED: 计时到5秒
RED → GREEN: 计时到25秒
我在项目中遇到过,有人画图时漏了一个转移条件,结果仿真时状态机卡在某个状态出不来。排查了整整两天,最后发现是条件没写全。所以,画完图后一定要对着需求一条条核对。
下面我用SVG画一张典型的状态转移图,展示一个三段式FSM的完整结构:
3.2 状态编码:三种主流方式
状态编码就是把状态名字翻译成二进制数。选哪种编码,直接影响电路的面积和速度。我见过有人不管三七二十一全用独热码,结果小设计浪费了大量触发器。你想想看,每种编码都有自己的脾气。
3.2.1 二进制编码
最省触发器的方案。N个状态只需要log2(N)个触发器。比如4个状态,用2个触发器就够了。
| 状态 | 二进制编码 |
|---|---|
| S0 | 00 |
| S1 | 01 |
| S2 | 10 |
| S3 | 11 |
优点:触发器用得少,适合状态数多的设计。
缺点:组合逻辑复杂,译码路径长,速度可能上不去。
3.2.2 格雷码编码
相邻状态之间只有1位不同。这玩意儿在跨时钟域传输时特别好用。
| 状态 | 格雷码 |
|---|---|
| S0 | 00 |
| S1 | 01 |
| S2 | 11 |
| S3 | 10 |
优点:减少毛刺,适合顺序跳转的场景。
缺点:非顺序跳转时优势不明显。
我曾经在一个异步FIFO的设计里用了格雷码,读写指针跨时钟域传递时,再也没有出现亚稳态问题。说白了,格雷码就是为这种场景量身定做的。
3.2.3 独热码编码
每个状态对应一个触发器,只有1位为1。N个状态需要N个触发器。
| 状态 | 独热码 |
|---|---|
| S0 | 0001 |
| S1 | 0010 |
| S2 | 0100 |
| S3 | 1000 |
优点:译码逻辑最简单,速度最快。适合高速设计。
缺点:浪费触发器,状态数多了吃不消。
3.3 状态表设计
状态表是状态转移图的「数字化表达」。它把图形信息变成一行行的表格数据,方便写代码和做验证。
一个完整的状态表包含三部分:当前状态、输入条件、下一状态。如果是Mealy型状态机,还要加上输出。
举个例子,还是那个交通灯控制器:
| 当前状态 | 输入条件 | 下一状态 | 输出 |
|---|---|---|---|
| IDLE | start=1 | GREEN | light=GREEN |
| IDLE | start=0 | IDLE | light=RED |
| GREEN | timer_exp=1 | YELLOW | light=YELLOW |
| GREEN | timer_exp=0 | GREEN | light=GREEN |
| YELLOW | timer_exp=1 | RED | light=RED |
| YELLOW | timer_exp=0 | YELLOW | light=YELLOW |
| RED | timer_exp=1 | GREEN | light=GREEN |
| RED | timer_exp=0 | RED | light=RED |
填表时有个小技巧:先把所有状态列出来,然后对每个状态,列出所有可能的输入组合。这样就不会漏掉任何转移条件。
最后说一句,状态表不仅是写代码的依据,也是验证的「金标准」。仿真时拿着状态表一条条对,比看波形图快多了。嗯,这就是我这么多年总结出来的经验。
可可精品资料库