第四节 状态表设计:从逻辑到表格的转化
状态表,说白了就是状态机的「身份证」。它把状态机里那些抽象的状态跳转、输出逻辑,全部整理成一张清清楚楚的表格。我刚开始学状态机时,总觉得画状态图就够了,直到有一次项目里状态多了起来,才发现没有状态表,调试起来简直像在迷宫里找路。
今天我们就来聊聊状态表设计的三个核心问题:表的结构长什么样?状态怎么编码?如何从状态图推导出状态表?
一、状态表的结构:三列定乾坤
一张标准的状态表,其实就三列:当前状态、下一状态、输出。嗯,就这么简单。但你别小看这三列,它们把状态机的全部行为都装进去了。
我习惯把状态表写成这样:
| 当前状态 | 输入条件 | 下一状态 | 输出 |
|---|---|---|---|
| S0 | in=0 | S0 | out=0 |
| S0 | in=1 | S1 | out=0 |
| S1 | in=0 | S0 | out=1 |
| S1 | in=1 | S1 | out=1 |
你看,每一行就是一个「如果当前在某个状态,遇到某个输入,那么下一步去哪、输出什么」。这就是状态机的全部逻辑。
核心要点:状态表是状态图的「表格化翻译」。状态图里每个箭头,对应状态表里的一行。箭头越多,行数越多。
二、状态编码:三种常见套路
状态编码,就是把状态名字(比如S0、S1)换成二进制数。为什么?因为硬件只认识0和1。这里我给大家介绍三种最常见的编码方式,每种都有它的脾气。
1. 二进制编码
最直观的方式。比如4个状态,就用2位二进制数:00、01、10、11。说白了就是按顺序编号。
优点:用的触发器最少。4个状态只需要2个触发器。
缺点:状态跳转时可能有多位同时变化,容易产生毛刺。我在一个高速计数器项目里吃过这个亏,当时状态跳转时出现了短暂的错误状态,排查了好久才发现是二进制编码的锅。
2. 格雷码编码
格雷码的特点是:相邻状态之间只有1位不同。比如:00→01→11→10。你想想看,这样跳转时就不会有多位同时变化的问题了。
优点:抗干扰能力强,适合高速电路。
缺点:编码逻辑比二进制复杂一点。
我的建议:如果你的状态机是顺序跳转的(比如计数器、流水线控制),格雷码是首选。我曾经在一个DDR控制器里用了格雷码,时序收敛轻松了很多。
3. 独热码编码
独热码,就是每个状态用一个独立的触发器。比如4个状态,就用4位:0001、0010、0100、1000。任何时候只有1位是1,其他都是0。
优点:译码逻辑超级简单,速度快。而且状态跳转时只有2位变化(旧状态变0,新状态变1)。
缺点:用的触发器多。N个状态需要N个触发器。
注意:独热码虽然浪费触发器,但在FPGA里反而很受欢迎。因为FPGA的触发器资源丰富,而查找表资源有限。独热码的译码逻辑简单,能省查找表。我做过一个项目,状态机有20多个状态,用二进制编码时序跑不过,换成独热码后一次通过。
三、状态表推导:从图到表的实战
好了,理论说完了,我们来动手。假设有一个简单的状态机:检测输入序列「101」,检测到后输出1。
状态图是这样的:
从这张图推导状态表,其实就三步:
- 列出所有状态:S0、S1、S2
- 对每个状态,列出所有可能的输入:in=0和in=1
- 根据箭头,填写下一状态和输出
推导出来的状态表是这样的:
| 当前状态 | 输入 | 下一状态 | 输出 |
|---|---|---|---|
| S0 | 0 | S0 | 0 |
| S0 | 1 | S1 | 0 |
| S1 | 0 | S2 | 0 |
| S1 | 1 | S1 | 0 |
| S2 | 0 | S0 | 1 |
| S2 | 1 | S2 | 1 |
推导口诀:「每个状态每个输入,看箭头找下一站,看圆圈找输出。」
嗯,这里要注意一点:如果某个状态在某个输入下没有箭头,那说明这个组合不会出现。但在实际设计中,我建议你还是补上这一行,让下一状态指向一个「非法状态」或者回到初始状态。为什么?因为硬件不会「不会出现」,万一干扰信号来了,状态机跑飞了怎么办?
四、编码后的状态表
假设我们用二进制编码:S0=00, S1=01, S2=10。那么状态表就变成了:
| 当前状态(Q1Q0) | 输入(in) | 下一状态(D1D0) | 输出(out) |
|---|---|---|---|
| 00 | 0 | 00 | 0 |
| 00 | 1 | 01 | 0 |
| 01 | 0 | 10 | 0 |
| 01 | 1 | 01 | 0 |
| 10 | 0 | 00 | 1 |
| 10 | 1 | 10 | 1 |
你看,到了这一步,状态表已经可以直接用来写Verilog代码了。D1和D0就是触发器的输入,out就是组合逻辑输出。
避坑指南:我曾经在编码时漏掉了一个状态(比如11没用到),结果仿真没问题,上板子就出bug。后来养成了习惯:编码时把未用状态也写上,指向一个安全状态。这叫「容错设计」,说白了就是给状态机买个保险。
五、三种编码的对比总结
| 编码方式 | 触发器数量 | 速度 | 抗干扰 | 适用场景 |
|---|---|---|---|---|
| 二进制 | 最少(log2N) | 中等 | 一般 | 状态少、资源紧张 |
| 格雷码 | 最少(log2N) | 中等 | 好 | 顺序跳转、高速电路 |
| 独热码 | 最多(N个) | 快 | 好 | FPGA、状态多、要求速度 |
我个人习惯是:FPGA项目用独热码,ASIC项目用二进制或格雷码。当然,这不是铁律,具体用哪种,还得看你的资源情况和时序要求。
好了,状态表设计就聊到这里。记住:状态表是状态机的「施工图纸」,画好了它,后面的代码实现就是照图施工的事。
可可精品资料库