4、状态表设计:状态转移表、输出表、状态编码

状态表设计,说白了就是把状态机那张抽象的状态图,翻译成硬件能看懂的真值表。我刚开始做FPGA那会儿,总觉得画个状态图就完事了,结果综合出来一堆莫名其妙的latch。后来才明白——状态表才是状态机落地的关键一步。

这一节我们重点聊三张表:状态转移表、输出表,以及状态编码的三种常用方式。这三张表搞定,你的状态机基本就成型了。

4.1 状态转移表

状态转移表,就是把状态图中的每个箭头,一行一行列出来。每一行代表:当前在哪个状态,来了什么输入,下一步跳到哪个状态。

举个例子,一个简单的交通灯控制器,三个状态:RED、GREEN、YELLOW。输入是一个定时器超时信号 timer_done。

当前状态 输入条件 下一状态
RED timer_done == 0 RED
RED timer_done == 1 GREEN
GREEN timer_done == 0 GREEN
GREEN timer_done == 1 YELLOW
YELLOW timer_done == 0 YELLOW
YELLOW timer_done == 1 RED

嗯,这里要注意:每个状态都必须覆盖所有输入组合。如果某个输入组合没写,综合工具就会推断出latch。我曾经在一个项目里漏掉了一个else分支,结果仿真怎么都对,上板就乱跳。查了两天才发现是状态转移表不完整。

我的习惯:写状态转移表时,先列所有状态,再对每个状态列出所有输入组合。宁可多写几行,也别漏。

4.2 输出表

输出表分两种:Moore型和Mealy型。Moore型的输出只跟当前状态有关,Mealy型的输出还跟输入有关。

还是拿交通灯举例。Moore型输出表长这样:

当前状态 输出(红灯亮) 输出(绿灯亮) 输出(黄灯亮)
RED 1 0 0
GREEN 0 1 0
YELLOW 0 0 1

如果是Mealy型,输出表里还要多一列输入条件。比如一个序列检测器,检测到"101"时输出高电平,输出就依赖于当前状态和当前输入。

避坑指南:我曾经在Mealy型输出表里,把组合逻辑输出和时序逻辑输出混在一起写,结果仿真波形里出现了毛刺。后来我养成了一个习惯——输出表单独列,不跟状态转移表混在一起。这样代码结构清晰,调试也方便。

4.3 状态编码

状态编码,就是把状态名(比如RED、GREEN)映射成二进制数。编码方式直接影响电路的面积、速度和功耗。常用的有三种:二进制编码、格雷码和独热码。

4.3.1 二进制编码

二进制编码最直观。比如4个状态,用2位二进制数表示:

状态 二进制编码
S0 00
S1 01
S2 10
S3 11

优点:用的寄存器最少。n个状态只需要log2(n)个触发器。
缺点:状态跳转时可能有多位同时变化,容易产生毛刺。而且译码逻辑复杂,组合路径长。

我个人习惯:状态数少于8个时,优先用二进制编码。寄存器省,面积小。

4.3.2 格雷码

格雷码的特点是:相邻状态之间只有1位不同。比如:

状态 格雷码
S0 00
S1 01
S2 11
S3 10

你看,S1到S2,只有第二位从0变1,第一位不变。这样在状态连续跳转时,毛刺风险大大降低。

格雷码特别适合顺序执行的状态机,比如指令流水线、计数器型控制器。我在一个高速ADC的接口控制器里用过格雷码,状态跳转频率很高,用格雷码后时序收敛轻松了不少。

关键点:格雷码只对相邻状态跳转有优势。如果状态跳转是随机的(比如状态图里有大量非顺序跳转),格雷码的优势就不明显了。

4.3.3 独热码

独热码,每个状态对应一个独立的寄存器位。n个状态需要n个触发器,但任何时候只有1位为1。

状态 独热码
S0 0001
S1 0010
S2 0100
S3 1000

优点:译码逻辑极其简单,速度最快。每个状态只需要一个与门就能判断。
缺点:寄存器用量大。状态数一多,面积就上去了。

我记得在FPGA上做过一个状态机,状态数有16个。用二进制编码时序跑不到200MHz,换成独热码直接飙到350MHz。FPGA的寄存器资源相对丰富,独热码在FPGA上特别吃香。

注意:独热码状态机必须保证任何时候只有1位为1。如果因为干扰或bug出现了多位为1的情况,状态机就会进入非法状态。所以独热码状态机一定要加非法状态检测和自恢复逻辑。

4.4 三种编码对比

特性 二进制编码 格雷码 独热码
寄存器用量 最少(log2(n)) 最少(log2(n)) 最多(n)
组合逻辑复杂度 高 中 低
速度 慢 中 快
毛刺风险 高 低(顺序跳转) 低
适用场景 状态少、面积敏感 顺序跳转、低功耗 FPGA、高速设计

4.5 状态编码选择策略

你可能会问:到底选哪种?我的经验是:

  • 状态数 ≤ 4:二进制编码,省寄存器。
  • 状态数 5~16:看跳转方式。顺序跳转多选格雷码,随机跳转多选独热码。
  • 状态数 > 16:优先二进制编码,或者考虑分层状态机。
  • FPGA平台:独热码优先,因为FPGA寄存器多,LUT资源相对紧张。
  • ASIC平台:二进制编码优先,因为ASIC的寄存器成本高。
一个小技巧:如果你不确定选哪种,先用独热码把功能调通,再根据时序和面积报告决定是否换成其他编码。我在项目中经常这么干——先跑起来,再优化。

4.6 本章知识体系

下面这张图把状态表设计的核心脉络串起来了:

状态表设计知识体系 状态表设计知识体系 状态表设计 状态转移表 输出表 状态编码 当前状态 → 输入 → 下一状态 Moore型 / Mealy型 二进制 / 格雷码 / 独热码 二进制编码 格雷码 独热码 寄存器最少 顺序跳转低毛刺 速度最快 选择策略:状态数 + 跳转方式 + 平台资源

状态表设计是状态机从"想法"到"电路"的桥梁。状态转移表定义行为,输出表定义结果,状态编码决定硬件实现方式。这三张表搞清楚了,写RTL代码就是体力活了。