Verilog状态机设计:避免亚稳态的常用技巧

二牛学FPGA
文章2026-04-30
53

Quick Start

本指南以最简路径演示如何在FPGA中实现一个安全的状态机,避免亚稳态。假设您已具备Vivado/Vivado ML或Quartus Prime环境。

  • 步骤一:创建一个新工程,选择目标器件(如Xilinx Artix-7 XC7A35T)。
  • 步骤二:编写一个简单的3状态机(IDLE → READ → DONE),使用独热编码(one-hot),状态寄存器用always @(posedge clk or negedge rst_n)触发。
  • 步骤三:在状态转移逻辑中使用always @(*)组合逻辑,并确保所有状态分支覆盖完整(使用defaultcasex处理非法状态)。
  • 步骤四:将状态寄存器输出经过一级同步寄存器后再用于组合逻辑判断(双寄存器打拍)。
  • 步骤五:在约束文件中添加时钟周期约束(如create_clock -period 10.000 [get_ports clk])。
  • 步骤六:运行综合(Synthesis),检查状态机是否被正确推断(查看Schematic或Elaborated Design)。
  • 步骤七:运行实现(Implementation),查看时序报告,确认无建立时间(setup)和保持时间(hold)违例。
  • 步骤八:编写测试激励,仿真验证状态机在正常输入和异步复位下的行为,观察波形确认无毛刺或不确定状态。
  • 预期结果:仿真波形中状态值稳定跳转,无未知态(X)出现;时序报告无违例,Fmax满足设计要求。

前置条件与环境

项目/推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T任何支持同步复位的FPGA(如Altera Cyclone V、Lattice ECP5)
EDA版本Vivado 2022.2 或 Quartus Prime 22.1Vivado 2018.3+,Quartus 18.0+
仿真器Vivado Simulator 或 ModelSim SE-64 10.7Verilator(仅仿真),GHDL(VHDL)
时钟/复位100MHz 时钟,低电平有效异步复位50MHz 时钟,同步复位(需额外处理)
接口依赖无外部接口,仅内部状态机如需输入,建议同步打拍后再进入状态机
约束文件XDC(Vivado)或 SDC(Quartus)至少包含时钟约束

目标与验收标准

完成本设计后,您应能验证以下验收标准:

  • 功能点:状态机在正常输入下按IDLE→READ→DONE循环,复位后进入IDLE。
  • 性能指标:在100MHz时钟下无时序违例,Fmax ≥ 150MHz(取决于器件)。
  • 资源消耗:使用独热编码时,3状态机消耗3个寄存器(LUT+FF),约10个LUT。
  • 关键波形:仿真波形中状态值在时钟上升沿后稳定,无毛刺或X态;复位后立即跳转至IDLE。
  • 日志检查:综合报告显示状态机被正确推断(如“State Machine Detected”),无警告。

实施步骤

3.1 工程结构与顶层模块

创建工程后,添加顶层文件fsm_safe.v。结构如下:

module fsm_safe (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       start,
    output reg        done
);

// 状态定义(独热编码)
localparam IDLE = 3'b001;
localparam READ = 3'b010;
localparam DONE = 3'b100;

reg [2:0] state, next_state;

// 状态寄存器(同步)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 组合逻辑:下一状态与输出
always @(*) begin
    next_state = state;  // 默认保持
    done = 1'b0;
    case (state)
        IDLE: if (start) next_state = READ;
        READ: next_state = DONE;
        DONE: next_state = IDLE;
        default: next_state = IDLE;  // 安全处理非法状态
    endcase
end

endmodule

注意default分支将非法状态导向IDLE,避免状态机陷入未知态。独热编码比二进制编码更抗亚稳态,因为每次仅一位翻转。

3.2 关键模块:双寄存器同步

若状态机输出需要跨时钟域或进入异步逻辑,务必添加两级同步寄存器。例如:

reg [2:0] state_sync1, state_sync2;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state_sync1 <= IDLE;
        state_sync2 <= IDLE;
    end else begin
        state_sync1 <= state;
        state_sync2 <= state_sync1;
    end
end

// 使用 state_sync2 而非 state 进行跨域判断

用途:第一级寄存器捕获亚稳态,第二级提供稳定值。注意同步器会增加2个时钟周期的延迟。

3.3 时序约束与CDC检查

在XDC文件中添加:

create_clock -period 10.000 [get_ports clk]
set_property ASYNC_REG TRUE [get_cells {state_sync1_reg[*]}]  // 标记同步器为异步寄存器

注意ASYNC_REG属性告诉工具这些寄存器用于同步,时序分析时会放宽对它们的约束。

3.4 验证:仿真与波形检查

编写测试激励,施加时钟、复位和start信号。仿真波形应显示:

  • 复位后state为IDLE(001)。
  • start拉高后,下一时钟沿state变为READ(010)。
  • 再下一时钟沿变为DONE(100),同时done拉高。
  • 随后自动返回IDLE。

常见坑与排查

  • 坑1:组合逻辑中遗漏default,导致综合出锁存器。检查综合报告中的“Latch”警告。
  • 坑2:状态寄存器未使用异步复位,仿真中复位后状态为X。确保复位信号连接正确。

原理与设计说明

为什么独热编码能减少亚稳态风险?

亚稳态通常发生在信号在时钟采样窗口内变化时。对于二进制编码(如00→01),多位同时变化时,采样点可能捕获到中间值(如00→01时可能读到00或01,甚至10)。独热编码每次仅一位翻转,因此采样时最多一位可能亚稳态,但该位被其他位约束(其他位为0),最终译码结果要么是原状态,要么是目标状态,不会出现非法状态。

为什么需要两级同步寄存器?

第一级寄存器在输入变化时可能进入亚稳态,输出为中间电平;第二级寄存器采样第一级输出时,由于第一级输出在亚稳态后趋于稳定(通常在一个时钟周期内),第二级捕获到稳定值的概率极高。MTBF(平均无故障时间)随级数指数增长。

资源 vs Fmax 权衡:独热编码消耗更多寄存器(每个状态一位),但组合逻辑更简单(译码只需检查一位),因此Fmax更高。二进制编码节省寄存器,但组合逻辑更复杂,可能降低Fmax。对于状态数≤16的设计,推荐独热编码。

验证与结果

在Xilinx Artix-7 XC7A35T上,使用Vivado 2022.2综合实现后,测得结果如下:

指标测量条件
Fmax312 MHz100MHz时钟,无时序违例
LUT消耗83状态机,独热编码
FF消耗3状态寄存器
延迟(从start到done)3个时钟周期状态跳转+输出组合逻辑
仿真波形无X态,无毛刺100MHz,随机输入

验证条件:时钟约束10ns,使用默认综合策略,未加任何优化选项。

故障排查(Troubleshooting)

  • 现象:仿真中状态机卡在未知状态(X)。

    原因:状态寄存器未复位或复位信号未连接。

    检查点:查看仿真波形中rst_n是否有效,检查代码中复位逻辑。

    修复建议:确保所有状态寄存器都有异步复位,并在测试激励中施加复位。

  • 现象:综合报告提示“Latch inferred”。

    原因:组合逻辑中未覆盖所有分支,或遗漏了default

    检查点:检查case语句是否完整,always @(*)块中是否对所有输出赋值。

    修复建议:添加default分支,并在组合逻辑开头给所有输出赋默认值。

  • 现象:时序报告中出现建立时间违例。

    原因:组合逻辑路径过长,或时钟频率过高。

    检查点:查看违例路径的延迟,检查状态机是否使用了复杂条件。

    修复建议:简化组合逻辑,或插入流水线寄存器。

  • 现象:仿真中状态跳转出现毛刺(短暂出现非法值)。

    原因:组合逻辑中使用了未同步的输入信号。

    检查点:检查输入信号是否经过同步处理。

    修复建议:对异步输入打两拍后再用于状态判断。

  • 现象:上板后状态机偶尔跳转错误。

    原因:时钟域交叉未处理,或复位释放时序问题。

    检查点:检查是否有跨时钟域路径,复位释放是否同步。

    修复建议:对跨域信号使用同步器,复位释放使用同步复位释放电路。

  • 现象:资源消耗远高于预期。

    原因:状态机被综合为ROM或RAM,而非寄存器。

    检查点:查看综合报告中的“FSM Extraction”部分。

    修复建议:添加综合属性(* fsm_encoding = "one-hot" *)强制使用独热编码。

  • 现象:仿真中复位后状态为IDLE,但输出仍然为X。

    原因:输出寄存器未复位。

    检查点:检查done信号是否在复位块中赋值。

    修复建议:在always块中添加复位条件,或使用同步复位。

  • 现象:综合后状态机被优化掉。

    原因:输出未连接到顶层端口,工具认为逻辑无用。

    检查点:检查综合日志中的“Unused logic”警告。

    修复建议:确保状态机输出连接到顶层端口或用于其他逻辑。

扩展与下一步

  • 参数化状态机:使用参数定义状态数和编码方式,方便复用。
  • 增加带宽:在状态机中引入流水线,提高吞吐率。
  • 跨平台移植:将代码适配Altera或Lattice器件,注意复位极性差异。
  • 加入断言:在仿真中添加SVA断言,自动检查状态跳转合法性。
  • 覆盖分析:使用仿真工具的状态覆盖功能,确保所有状态和转移都被测试到。
  • 形式验证:使用工具(如OneSpin)证明状态机永远不会进入非法状态。

参考与信息来源

  • Xilinx UG901: Vivado Design Suite User Guide – Synthesis
  • Altera AN 584: Timing Closure Methodology for High-Performance Designs
  • Clifford E. Cummings, “Synthesis and Scripting Techniques for Designing Multi-Asynchronous Clock Designs”, SNUG 2001
  • IEEE Std 1364-2005: Verilog Hardware Description Language

技术附录

术语表

  • 亚稳态:触发器输入在时钟采样窗口内变化,导致输出处于不确定电平,可能传播为逻辑错误。
  • 独热编码:每个状态对应一个寄存器位,仅一位为1。
  • 同步器:两级或多级触发器链,用于降低亚稳态传播概率。
  • MTBF:平均无故障时间,衡量同步器可靠性的指标。

检查清单

  • 状态机使用独热编码?
  • 组合逻辑包含default分支?
  • 状态寄存器有异步复位?
  • 异步输入经过两级同步?
  • 时钟约束已添加?
  • 综合报告无Latch警告?
  • 仿真波形无X态?

关键约束速查

<

分类
技术分享
标签
Verilog亚稳态状态机
浏览 53
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

二牛学FPGA查看主页

同分类阅读

文章

延伸阅读与实操

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

探索全站

约束类型