
本文概述
状态机是FPGA设计中最常用的时序逻辑设计方法,正确的状态机写法直接影响代码的可读性、可维护性和综合质量。常见写法有一段式、二段式和三段式三种。一段式把所有逻辑写在一个always块里,简单但混乱;二段式分时序和组合两个always块,结构清晰但输出可能有毛刺;三段式分状态寄存器、转移逻辑和输出逻辑三个always块,结构最清晰,输出寄存器化后无毛刺,时序最优,是工业界推荐的规范写法。状态编码常用二进制、格雷码和独热码,各有适用场景。设计状态机还要注意复位、默认状态、非法状态处理和编码风格等问题。
核心要点速览
一段式:一个always块,简单但难维护,适合超简单状态机
二段式:两个always块,时序存状态+组合做转移和输出
三段式:三个always块,状态寄存器+转移逻辑+输出逻辑分开
Moore型输出只和当前状态有关,Mealy型还和输入有关
状态编码:二进制省触发器、独热码省组合逻辑、格雷码翻转少
推荐写法:三段式+独热码或二进制编码+完整case+默认状态
一、面试官考察点
状态机是FPGA面试的必考题,而且往往是第一道技术题。因为状态机能反映出一个工程师的Verilog基本功和代码规范意识。面试官会从几个层面考察:知不知道有几种写法、能不能说出各自的优缺点、实际项目中习惯用哪种、能不能写出规范的三段式代码、对状态编码和毛刺问题有没有理解。
二、回答思路
三种写法的对比
一段式状态机
所有逻辑在一个always块
代码量最少,最简单
状态多了之后很乱,难维护
时序和组合混在一起
适合2-3个状态的极简状态机
不推荐用于复杂设计
二段式状态机
两个always块
第一个:时序逻辑存当前状态
第二个:组合逻辑做转移+输出
结构比一段式清晰
输出是组合逻辑,可能有毛刺
适合中等复杂度的状态机
三段式状态机
三个always块
第一段:状态寄存器(时序)
第二段:状态转移(组合)
第三段:输出逻辑(时序或组合)
结构最清晰,好维护好调试
寄存器输出无毛刺,时序好
三段式状态机代码示例
三段式是工业界最推荐的写法,我详细讲一下。三段分别是:
第一段:状态寄存器。时序逻辑,负责在时钟沿更新当前状态。用非阻塞赋值。复位时进入初始状态。
第二段:状态转移逻辑。组合逻辑,根据当前状态和输入信号,判断下一个状态是什么。用阻塞赋值。这是一个大的case语句,每个状态下面判断转移条件。
第三段:输出逻辑。可以是组合逻辑也可以是时序逻辑。如果是Moore型输出且对时序要求高,建议用时序逻辑,输出打一拍,没有毛刺。如果是Mealy型或者需要立即响应,就用组合逻辑。
状态编码方式对比
| 编码方式 | 触发器数量 | 组合逻辑 | 功耗 | 适用场景 |
|---|---|---|---|---|
| 二进制编码 | 最少(log2N个) | 多 | 较高 | 状态少、资源紧张的设计 |
| 格雷码 | 少(同二进制) | 中 | 低 | 状态顺序变化、低功耗设计 |
| 独热码 | 多(N个) | 少 | 中 | 状态多、速度要求高、FPGA |
| 约翰逊码 | 中(N/2个) | 少 | 中 | 移位型状态机、计数器 |
FPGA里触发器资源比较多,独热码往往是性价比最高的选择,因为组合逻辑少,速度快,而且综合工具也很容易优化。状态数量越多,独热码的优势越明显。当然如果状态特别多,比如32个以上,独热码占用的触发器也太多了,这时候可以用格雷码或者二进制。
Moore型和Mealy型的区别
状态机按输出类型分为Moore型和Mealy型。Moore型的输出只和当前状态有关,状态不变输出就不变。Mealy型的输出不仅和当前状态有关,还和输入有关,输入变了输出可能马上变,不用等状态跳变。
Moore型的优点是输出稳定,没有输入变化直接带来的毛刺,时序好分析。缺点是响应慢一拍,输入变化要等下一个时钟周期才能体现在输出上。Mealy型的优点是响应快,输入变化马上就能反映到输出。缺点是输出可能有毛刺,因为输入直接连到组合逻辑输出。而且综合时序更难分析。
实际设计中,能用Moore型就尽量用Moore型,简单稳定。如果确实需要输入直接影响输出的快速响应,才用Mealy型。很多状态机是混合型的,一部分输出是Moore型一部分是Mealy型。
状态机设计规范(推荐做法)
优先使用三段式写法,结构清晰便于维护
状态定义用parameter,不要直接用数字,可读性差
case语句必须有default分支,防止进入死状态
组合逻辑用always @(*),不要写敏感信号列表,容易漏
时序逻辑用非阻塞赋值<=,组合逻辑用阻塞赋值=
输出建议寄存器化,消除毛刺,改善时序
状态变量命名要清晰,curr_state/next_state是通用写法
状态命名要有意义,IDLE/SEND/WAIT/FINISH比S0/S1/S2好得多
状态不多时用独热码,利用FPGA触发器资源多的优势
复杂状态机可以画状态转移图再写代码,避免逻辑遗漏
三、追问环节
追问1:为什么二段式状态机的输出容易有毛刺?怎么解决?
因为二段式的输出是组合逻辑,直接由当前状态和输入组合出来。状态转移的时候,各个触发器的翻转不可能完全同时,中间会经过一些中间状态,虽然时间很短但确实存在,这就导致组合逻辑输出可能出现短暂的毛刺。解决方法就是三段式,把输出再打一拍寄存器输出,这样毛刺就被滤掉了。代价是输出延迟一个时钟周期,但大多数情况下这不是问题。或者也可以在输出端加约束,确保组合逻辑的延迟足够小,在一个周期内稳定下来,但这治标不治本。
追问2:状态机如果进入了非法状态怎么办?
状态机进入非法状态有几种可能:上电初始化不确定、干扰导致状态寄存器翻转、设计bug导致跳转到了没定义的状态。后果就是状态机卡死,再也跳不出来。解决方法有几个:第一,case语句一定要加default分支,把所有没定义的状态都引导到一个已知状态,比如IDLE或者专门的ERROR状态。第二,用独热码编码的话,可以加一个状态检测逻辑,如果检测到同时有多个状态位为1(独热码的非法状态),就复位状态机。第三,对于高可靠设计,可以考虑两级或者三级容错设计,比如ECC编码的状态寄存器。实际应用中,default分支+可靠复位基本够用了。
追问3:一段式状态机为什么不推荐?
一段式就是把状态寄存器、转移条件、输出逻辑全都写在一个时序always块里。状态少的时候比如两三个状态,写起来确实省事。但状态一多,代码就变成一大坨,很难读很难维护。而且因为所有逻辑都混在一起,修改某一个状态的输出可能影响到别的状态,容易出bug。从综合角度来看,一段式也不利于综合工具优化,因为综合工具要从一大坨逻辑里提取出状态机结构,不如三段式那么明确。还有一个问题是一段式的输出都是寄存器输出,没问题,但如果某些输出需要立即响应就不好处理了。总的来说,一段式只适合特别简单的状态机,稍微复杂一点都推荐用三段式。
追问4:独热码状态机有什么优缺点?
独热码的优点有几个:第一,组合逻辑简单,判断是不是某个状态只需要看一位,译码逻辑少,速度快;第二,状态转移时只有两位翻转(一进一出),动态功耗相对可控;第三,FPGA里触发器多组合逻辑少,独热码正好匹配FPGA的资源结构,综合效果通常很好;第四,调试方便,抓波形看哪一位是1就知道在哪个状态。缺点也有:第一,触发器用得多,状态特别多时浪费资源;第二,如果发生SEU(单粒子翻转),可能出现多个状态同时有效的非法状态,需要额外的容错设计;第三,面积上不如二进制编码紧凑。总的来说,FPGA设计里状态数在几十个以内的话,独热码通常是首选。
四、失分表达
"我平时就写一段式,简单好用,能实现功能就行"
只追求能跑,不追求质量和可维护性,说明缺乏工程化思维和规范意识。企业里的代码是要团队协作的,可读性和可维护性非常重要。
"状态编码用二进制最省资源,最好"
在FPGA里不一定,触发器多的情况下独热码综合出来可能频率更高、总面积也没大多少。说"最好"说明缺乏具体分析,什么都一概而论。
"case不加default也没事,反正不会走到"
这是非常不好的习惯,不仅可能产生锁存器,还会留下安全隐患。FPGA设计中完备性是很重要的,所有分支都要考虑到。
五、准备动作
手写一个三段式状态机的完整代码
找一个常见的场景比如1101序列检测、按键消抖或者简单的总线控制,写一遍三段式代码,确保语法和结构规范。
理解阻塞赋值和非阻塞赋值的区别
这是Verilog的基础中的基础,什么时候用=什么时候用<=,为什么,必须讲清楚。
总结一下状态机设计的常见问题和易错点
比如毛刺、锁存器、死状态、one-hot的非法状态、复位不充分等,每个问题要有原因有解决方法。
六、核心关键词
三段式状态机 Moore/Mealy 独热码编码 状态转移 组合逻辑输出 寄存器输出 default分支 毛刺消除

扫码关注




























