FPGA状态机怎么写才规范?一段式二段式三段式对比

浏览量:3
时间: 2026-08-20 10:00:37

FPGA状态机编辑器状态转移图Verilog

本文概述

状态机是FPGA设计中最常用的时序逻辑设计方法,正确的状态机写法直接影响代码的可读性、可维护性和综合质量。常见写法有一段式、二段式和三段式三种。一段式把所有逻辑写在一个always块里,简单但混乱;二段式分时序和组合两个always块,结构清晰但输出可能有毛刺;三段式分状态寄存器、转移逻辑和输出逻辑三个always块,结构最清晰,输出寄存器化后无毛刺,时序最优,是工业界推荐的规范写法。状态编码常用二进制、格雷码和独热码,各有适用场景。设计状态机还要注意复位、默认状态、非法状态处理和编码风格等问题。

核心要点速览

  • 一段式:一个always块,简单但难维护,适合超简单状态机

  • 二段式:两个always块,时序存状态+组合做转移和输出

  • 三段式:三个always块,状态寄存器+转移逻辑+输出逻辑分开

  • Moore型输出只和当前状态有关,Mealy型还和输入有关

  • 状态编码:二进制省触发器、独热码省组合逻辑、格雷码翻转少

  • 推荐写法:三段式+独热码或二进制编码+完整case+默认状态

面试题:FPGA状态机有几种写法?一段式二段式三段式有什么区别?

一、面试官考察点

状态机是FPGA面试的必考题,而且往往是第一道技术题。因为状态机能反映出一个工程师的Verilog基本功和代码规范意识。面试官会从几个层面考察:知不知道有几种写法、能不能说出各自的优缺点、实际项目中习惯用哪种、能不能写出规范的三段式代码、对状态编码和毛刺问题有没有理解。

三种写法综合评分
从5个维度对比评估
代码可读性
一段式
代码可读性
二段式
代码可读性
三段式
时序质量
一段式
时序质量
二段式
时序质量
三段式
代码简洁性
一段式
代码简洁性
二段式
代码简洁性
三段式

二、回答思路

三种写法的对比

一段式状态机

  • 所有逻辑在一个always块

  • 代码量最少,最简单

  • 状态多了之后很乱,难维护

  • 时序和组合混在一起

  • 适合2-3个状态的极简状态机

  • 不推荐用于复杂设计

二段式状态机

  • 两个always块

  • 第一个:时序逻辑存当前状态

  • 第二个:组合逻辑做转移+输出

  • 结构比一段式清晰

  • 输出是组合逻辑,可能有毛刺

  • 适合中等复杂度的状态机

三段式状态机

  • 三个always块

  • 第一段:状态寄存器(时序)

  • 第二段:状态转移(组合)

  • 第三段:输出逻辑(时序或组合)

  • 结构最清晰,好维护好调试

  • 寄存器输出无毛刺,时序好

三段式状态机代码示例

三段式是工业界最推荐的写法,我详细讲一下。三段分别是:

第一段:状态寄存器。时序逻辑,负责在时钟沿更新当前状态。用非阻塞赋值。复位时进入初始状态。

第二段:状态转移逻辑。组合逻辑,根据当前状态和输入信号,判断下一个状态是什么。用阻塞赋值。这是一个大的case语句,每个状态下面判断转移条件。

第三段:输出逻辑。可以是组合逻辑也可以是时序逻辑。如果是Moore型输出且对时序要求高,建议用时序逻辑,输出打一拍,没有毛刺。如果是Mealy型或者需要立即响应,就用组合逻辑。

// 三段式状态机示例 module fsm_3seg ( input clk, input rst_n, input start, input done, output reg out_valid, output reg cnt_en ); // 状态定义 parameter IDLE = 3'd0; parameter START = 3'd1; parameter WORK = 3'd2; parameter FINISH = 3'd3; reg [2:0] curr_state; reg [2:0] next_state; // 第一段:状态寄存器(时序逻辑) always @(posedge clk or negedge rst_n) begin if(!rst_n) curr_state <= IDLE; else curr_state <= next_state; end // 第二段:状态转移(组合逻辑) always @(*) begin case(curr_state) IDLE: begin if(start) next_state = START; else next_state = IDLE; end START: next_state = WORK; WORK: begin if(done) next_state = FINISH; else next_state = WORK; end FINISH: next_state = IDLE; default: next_state = IDLE; endcase end // 第三段:输出逻辑(时序输出,无毛刺) always @(posedge clk or negedge rst_n) begin if(!rst_n) begin out_valid <= 1'b0; cnt_en <= 1'b0; end else begin case(next_state) IDLE: begin out_valid <= 1'b0; cnt_en <= 1'b0; end START: begin out_valid <= 1'b0; cnt_en <= 1'b1; end WORK: begin out_valid <= 1'b0; cnt_en <= 1'b1; end FINISH: begin out_valid <= 1'b1; cnt_en <= 1'b0; end default: begin out_valid <= 1'b0; cnt_en <= 1'b0; end endcase end end endmodule

状态编码方式对比

编码方式触发器数量组合逻辑功耗适用场景
二进制编码最少(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设计中完备性是很重要的,所有分支都要考虑到。

五、准备动作

V
手写一个三段式状态机的完整代码

找一个常见的场景比如1101序列检测、按键消抖或者简单的总线控制,写一遍三段式代码,确保语法和结构规范。

V
理解阻塞赋值和非阻塞赋值的区别

这是Verilog的基础中的基础,什么时候用=什么时候用<=,为什么,必须讲清楚。

V
总结一下状态机设计的常见问题和易错点

比如毛刺、锁存器、死状态、one-hot的非法状态、复位不充分等,每个问题要有原因有解决方法。

六、核心关键词

三段式状态机 Moore/Mealy 独热码编码 状态转移 组合逻辑输出 寄存器输出 default分支 毛刺消除


声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。仅供学习交流使用,不构成商业目的。版权归原作者所有,如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时删除。侵权投诉
相关推荐HOT
开班信息