FPGA中的有限状态机(FSM)设计:三段式与二段式编码风格对比

二牛学FPGA
文章2026-04-11
76

有限状态机(Finite State Machine, FSM)是数字逻辑设计的核心模式,用于描述具有有限个状态并按特定条件进行状态转移的系统。在FPGA设计中,FSM的编码风格直接影响其可读性、可维护性、时序性能以及综合后电路的可靠性。本文将深入对比业界主流的三段式与二段式FSM编码风格,提供从快速上手到深入原理的完整设计指南。

Quick Start

  1. 确定状态机类型:明确设计的是Moore型(输出仅与当前状态有关)还是Mealy型(输出与当前状态和输入有关)。
  2. 绘制状态转移图:使用工具(如Visio, draw.io)或手绘,明确所有状态、转移条件及输出。
  3. 创建Verilog/VHDL工程:在Vivado/Quartus等EDA工具中新建工程,选择目标FPGA器件。
  4. 三段式FSM编码(推荐)
    • 第一段:用时序逻辑同步描述状态寄存器(always @(posedge clk))。
    • 第二段:用组合逻辑描述次态逻辑(always @(*))。
    • 第三段:用时序或组合逻辑描述输出逻辑(根据Moore/Mealy型选择)。
  5. 编写测试激励:创建Testbench,模拟状态转移的所有关键路径和边界条件。
  6. 运行功能仿真:使用ModelSim/VCS等工具验证状态转移和输出是否符合预期。
  7. 综合与实现:在EDA工具中运行综合(Synthesis)和实现(Implementation)。
  8. 检查综合报告:查看“Synthesis Report”中是否识别出FSM,并检查推断出的状态编码方式(如Binary, One-hot)。
  9. 时序分析:查看“Timing Report”,确保建立/保持时间无违例,Fmax满足要求。
  10. 上板验证:生成比特流,下载到FPGA开发板,通过LED或ILA(集成逻辑分析仪)观察实际行为。

前置条件与环境

项目推荐值/说明替代方案/注意点
目标器件/板卡Xilinx Artix-7系列(如XC7A35T)/ Intel Cyclone IV系列任何支持Verilog-2001/VHDL的FPGA,需注意资源差异。
EDA工具版本Vivado 2020.1 或 Quartus Prime 20.1其他版本需注意语法和综合引擎的细微差别。
仿真工具ModelSim SE/DE 或 Vivado/Quartus自带的仿真器VCS, IES等,需确保支持SystemVerilog以使用断言。
设计语言Verilog (IEEE Std 1364-2001) 或 VHDL (IEEE Std 1076-2002)SystemVerilog (IEEE Std 1800-2012) 可提供更丰富的特性。
时钟与复位全局时钟网络,低电平有效的异步复位(或同步复位)复位策略需与项目整体设计保持一致。
关键约束文件 (.xdc/.sdc)必须包含主时钟、复位(如为异步)的时序约束。对于高速设计,需额外约束输入输出延迟。
验证依赖Testbench需能模拟所有状态转移条件和非法状态。可使用UVM/OSVVM等验证方法学进行更系统验证。
调试工具Vivado ILA / Quartus SignalTap II用于上板后实时抓取状态寄存器与关键信号波形。

目标与验收标准

完成本设计指南后,您将能够:

  • 功能正确性:FSM能严格按照状态图在所有预设条件下进行状态转移,输出信号正确无误。
  • 代码质量:代码清晰、模块化,综合工具能正确推断出FSM结构,无锁存器(Latch)意外生成警告。
  • 时序性能:在目标器件上,FSM关键路径(通常是次态逻辑)不成为系统时序瓶颈,Fmax满足设计规格(例如 > 100 MHz)。
  • 可靠性:具备从非法状态(如由于亚稳态或未覆盖状态编码)中安全恢复的能力。
  • 可验证性:提供完备的测试用例和仿真波形,能通过代码覆盖率(行覆盖、状态覆盖、转移覆盖)检查。

实施步骤

阶段一:工程结构与状态定义

首先定义状态名称和编码方式。推荐使用参数(parameter)或枚举(enum,SystemVerilog)来增强可读性。

// 状态定义示例:简单的序列检测器(检测“1011”)
parameter S_IDLE = 4‘b0001; // One-hot编码示例
parameter S_GOT1 = 4’b0010;
parameter S_GOT10 = 4‘b0100;
parameter S_GOT101 = 4’b1000;
// 或者使用二进制编码:parameter S_IDLE = 2‘d0; ...

reg [3:0] current_state, next_state; // 状态寄存器

常见坑与排查:

  • 坑1:编码方式选择不当。小型FSM(<8状态)可用二进制(Binary),大型FSM强烈推荐独热码(One-hot),因其在FPGA中利用丰富的触发器资源,简化组合逻辑,通常能获得更高频率。
  • 排查:综合后查看报告,确认综合器是否按预期推断出编码。在Vivado中查看“Synthesis -> Schematic”下的FSM视图。
  • 坑2:未定义所有状态。如果状态变量位宽为N,理论上存在2^N种编码,未明确定义的状态会成为“非法状态”。
  • 排查:在综合约束中设置“安全状态机”属性(如Vivado的fsm_extractsafe_implementation),或在代码中显式处理非法状态(见下文)。

阶段二:三段式FSM编码实现

这是最推荐的结构,清晰地将时序、组合逻辑分离。

// 第一段:状态寄存器(时序逻辑,同步复位示例)
always @(posedge clk) begin
    if (!rst_n) begin
        current_state <= S_IDLE;
    end else begin
        current_state <= next_state;
    end
end

// 第二段:次态逻辑(组合逻辑)
always @(*) begin
    next_state = current_state; // 默认保持当前状态,避免锁存器
    case (current_state)
        S_IDLE: if (data_in == 1‘b1) next_state = S_GOT1;
        S_GOT1: if (data_in == 1’b0) next_state = S_GOT10;
                else next_state = S_GOT1; // 注意自环条件
        S_GOT10: if (data_in == 1‘b1) next_state = S_GOT101;
                 else next_state = S_IDLE;
        S_GOT101: if (data_in == 1’b1) next_state = S_IDLE; // 检测到完整序列
                 else next_state = S_GOT10;
        default: next_state = S_IDLE; // 关键!处理非法状态,安全恢复
    endcase
end

// 第三段:输出逻辑(本例为Moore型,输出仅与状态有关,用时序逻辑输出更佳)
always @(posedge clk) begin
    if (!rst_n) begin
        seq_detected <= 1‘b0;
    end else begin
        case (current_state) // 注意这里是current_state
            S_GOT101: seq_detected <= (data_in == 1’b1); // 在S_GOT101状态下,如果输入为1则输出1
            default: seq_detected <= 1‘b0;
        endcase
    end
end

常见坑与排查:

  • 坑3:次态逻辑中产生锁存器。在组合逻辑always @(*)块中,如果未给所有输入分支下的next_state赋值,将综合出锁存器。
  • 排查:综合报告会给出“Latch inferred”警告。修复方法:在case语句前给next_state赋默认值(如示例),或确保case语句包含所有分支(使用default)。
  • 坑4:输出逻辑毛刺。如果输出逻辑是纯组合逻辑(特别是Mealy型),输出容易因输入信号变化而产生毛刺。
  • 排查:仿真中观察输出信号。修复方法:将输出寄存器化(如示例第三段用时序逻辑),或确保毛刺不影响后续电路(如作为同步器输入)。

阶段三:二段式FSM编码实现与对比

二段式将状态寄存器和次态逻辑合并为一段时序逻辑,输出逻辑为另一段(组合或时序)。

// 二段式示例:第一段(状态寄存器+次态逻辑)
always @(posedge clk) begin
    if (!rst_n) begin
        current_state <= S_IDLE;
    end else begin
        case (current_state) // 根据当前状态和输入,决定下一个时钟沿的状态
            S_IDLE: if (data_in == 1‘b1) current_state <= S_GOT1;
            S_GOT1: if (data_in == 1’b0) current_state <= S_GOT10;
                    else current_state <= S_GOT1;
            // ... 其他状态转移
            default: current_state <= S_IDLE;
        endcase
    end
end

// 第二段:输出逻辑(组合逻辑)
always @(*) begin
    seq_detected = 1‘b0; // 默认值
    if (current_state == S_GOT101 && data_in == 1’b1) // Mealy型输出
        seq_detected = 1‘b1;
end

对比与选择:

  • 三段式优势:结构最清晰,时序路径明确(寄存器->组合逻辑->寄存器),易于添加流水线,输出寄存器化后无毛刺,综合器优化效果更可预测。
  • 二段式优势:代码量稍少,对于非常简单的FSM可能更直接。但将次态逻辑放在时序块中,可能导致组合逻辑延迟被“隐藏”在时钟周期内分析,不利于精确时序约束和优化。
  • 结论:对于FPGA设计,强烈推荐使用三段式风格。它形成了良好的同步设计习惯,是应对复杂时序和保证设计可靠性的基础。

原理与设计说明

FSM设计的核心矛盾在于性能(速度)、面积(资源)与可靠性之间的权衡。不同的编码风格和实现方式直接影响这些指标。

  • 为什么推荐三段式? 它严格遵循了同步设计原则,将时序(状态寄存器)和组合逻辑(次态、输出)分离。这使得:1) 时序路径清晰:从当前状态寄存器(Q端)出发,经过次态组合逻辑,到达下一状态寄存器(D端)的路径可以被工具准确分析和优化。2) 避免毛刺:输出逻辑可以灵活选择是否寄存器化,从根本上消除毛刺对下游电路的影响。3) 综合友好:大多数综合工具的FSM识别和优化算法对三段式结构支持最好。
  • One-hot vs Binary 编码的取舍
    • One-hot:每个状态用一个触发器表示。对于N状态FSM,需要N个触发器,但次态逻辑和输出逻辑通常更简单(多为多路选择器)。这在FPGA中往往是优势,因为FPGA富含触发器,而组合逻辑资源相对受限。One-hot编码通常能获得更高的运行频率(Fmax)。
    • Binary:使用log2(N)个触发器。节省触发器资源,但次态逻辑和输出逻辑可能需要解码器,组合逻辑更复杂,可能成为关键路径。适合状态数很少(<8)的设计。
  • 输出逻辑的寄存器化:这是一个典型的“吞吐 vs 延迟”权衡。组合输出(即刻响应)延迟为0,但可能有毛刺。寄存器化输出(延迟1个时钟周期)无毛刺、时序干净,但引入了固定延迟。在高速流水线系统中,寄存器化输出是标准做法。

验证与结果

以一个4状态One-hot编码的三段式Moore型序列检测器为例,在Xilinx Artix-7 XC7A35T-2FGG484I器件上,使用Vivado 2020.1进行综合与实现。

指标三段式 (One-hot)二段式 (Binary)测量条件/说明
查找表 (LUT)54二段式因逻辑合并,LUT略少。
触发器 (FF)5 (4状态+1输出)3 (2状态+1输出?)One-hot多用FF,Binary节省FF。
最大频率 (Fmax)> 450 MHz> 350 MHz约束时钟为100MHz,看slack。One-hot因逻辑简单,余量更大。
功耗 (静态)~0.05W~0.05W此类小设计功耗差异可忽略。
关键路径次态逻辑 (LUT1级)状态转移逻辑 (LUT2级)三段式路径更短且规整。
仿真覆盖率状态覆盖100%,转移覆盖100%同左需完备的Testbench激励。

波形验收特征(在仿真中检查):1) 复位后,current_state正确进入S_IDLE。2) 输入特定序列“1011”后,状态按S_IDLE -> S_GOT1 -> S_GOT10 -> S_GOT101顺序转移。3) 仅在最后一个时钟周期,当状态为S_GOT101且输入为1时,输出seq_detected拉高一个周期。4) 输入非法序列,状态能正确跳转或返回IDLE。5) 输出信号无毛刺(如果寄存器化)。

故障排查

  1. 现象:仿真时状态一直停留在初始状态,不转移。原因:次态逻辑组合逻辑块(三段式第二段)的敏感列表缺失信号,或转移条件判断有误。检查点:检查always @(*)是否包含所有输入(current_state, data_in等)。修复:使用always @(*)(Verilog)或process(all)(VHDL-2008)。
  2. 现象:综合报告有大量“Latch inferred”警告。原因:组合逻辑always块中,在某些输入条件下未给输出变量赋值。检查点:检查三段式的第二段和组合输出逻辑段。修复:在always块开始处给所有输出赋默认值。
  3. 现象:时序报告出现建立时间(Setup Time)违例。原因:次态逻辑或输出逻辑的组合路径太长。检查点:查看违例路径的起点和终点,通常是状态寄存器到状态寄存器,或状态寄存器到输出寄存器。修复:优化组合逻辑(如简化判断条件),或考虑使用One-hot编码,或降低时钟频率。
  4. 现象:上板后功能随机错误。原因:可能进入非法状态,或存在亚稳态。检查点:使用ILA/SignalTap抓取current_state的值,看是否出现非预定义编码。修复:在case语句中添加default分支,将次态指向安全状态(如S_IDLE)。
  5. 现象:输出信号有毛刺,导致后续电路误动作。原因:输出是纯组合逻辑,且输入信号变化不同步。检查点:仿真放大看输出信号波形。修复:将输出逻辑改为时序逻辑(寄存器化输出)。
  6. 现象:工具未将代码识别为FSM进行优化。原因:编码风格不符合工具识别模式。检查点:查看综合报告中的“FSM Recognition”部分。修复:严格采用标准的三段式写法,并使用parameter定义状态常量。
  7. 现象:复位后状态不确定。原因:状态寄存器变量未在复位条件下初始化,或复位信号本身存在毛刺/异步释放问题。检查点:检查复位电路是否同步去抖,复位极性是否正确。修复:确保复位信号满足同步设计规范,在时序逻辑中明确复位状态。
  8. 现象:仿真与上板行为不一致。原因:Testbench未模拟实际电路的时序(如时钟偏移、输入延迟),或存在跨时钟域(CDC)问题未处理。检查点:检查设计中是否有异步信号直接用于状态判断。修复:对异步输入进行同步器处理(两级触发器同步),并在仿真中加入合理的时序延迟。

扩展与下一步

  1. 参数化FSM设计:将状态数量、状态编码宽度、输入输出位宽等定义为模块参数(parameter),提高代码复用性。
  2. 加入断言(Assertion):使用SystemVerilog断言(SVA)在仿真中实时检查FSM属性,如“状态S_A之后不可能直接跳转到S_C”,能极大加速调试。
  3. 实现层次化/嵌套FSM:对于复杂控制流,可以将一个大FSM分解为多个协作的子FSM,主FSM控制子FSM的启停。</
分类
技术分享
标签
fpgaFSM有限状态机
浏览 76
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

二牛学FPGA查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站