Verilog中有限状态机编码风格对时序收敛的影响实测

FPGA小白
文章2026-07-02
41 0 0 0

Quick Start

  1. 下载并安装 Vivado 2023.2(或更高版本),准备 Xilinx Artix-7 开发板(如 Nexys Video)。
  2. 创建新工程,器件选择 xc7a200tfbg484-2(或目标板对应型号)。
  3. 编写三个版本的 FSM:二进制编码、格雷编码、独热编码(One-hot),功能相同(如序列检测 1011)。
  4. 为每个版本添加时序约束:主时钟 100 MHz,输入/输出延迟按数据手册设置。
  5. 分别综合、实现,运行时序分析(Report Timing Summary)。
  6. 对比各版本的 WNS(最差负时序裕量)、Fmax 和资源利用率。
  7. 预期现象:独热编码通常 Fmax 最高但 LUT 多;二进制编码面积小但路径长;格雷编码在跨时钟域时安全但时序中等。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 xc7a200tfbg484-2主流中端 FPGA,LUT 资源充足Kintex-7 / Zynq-7000
EDA 版本Vivado 2023.2支持最新时序引擎与综合策略Vivado 2022.2 / Quartus Prime 23
仿真器Vivado Simulator (xsim)内置于 Vivado,无需额外安装ModelSim / Questa
时钟/复位100 MHz 差分时钟,异步低有效复位板载时钟源,复位同步后使用50 MHz / 200 MHz
接口依赖UART 或 GPIO 输出状态验证状态跳转正确性ILA(集成逻辑分析仪)
约束文件XDC 文件包含主时钟、输入/输出延迟、虚假路径SDC(用于 Quartus)

目标与验收标准

功能点:三个 FSM 实现相同的序列检测(1011),在仿真和上板时输出一致。性能指标:在 100 MHz 时钟下,WNS ≥ 0(即无时序违规)。资源:记录每个版本的 LUT、FF 和 slice 数量。关键波形:通过 ILA 捕获状态跳转,确认与仿真一致。日志:时序报告中无 setup/hold 违规。

实施步骤

工程结构与 RTL 编写

创建三个顶层模块:fsm_binary, fsm_gray, fsm_onehot。每个模块包含状态寄存器、次态逻辑和输出逻辑。状态定义使用 localparam,避免综合工具优化掉独热编码。

// fsm_onehot.v
module fsm_onehot (
    input clk,
    input rst_n,
    input data_in,
    output reg match
);

localparam S0 = 4'b0001,
           S1 = 4'b0010,
           S2 = 4'b0100,
           S3 = 4'b1000;

reg [3:0] state, next_state;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= S0;
    else
        state <= next_state;
end

always @(*) begin
    next_state = S0;
    case (1'b1)
        state[0]: next_state = data_in ? S1 : S0;
        state[1]: next_state = data_in ? S2 : S0;
        state[2]: next_state = data_in ? S2 : S3;
        state[3]: next_state = data_in ? S1 : S0;
        default:  next_state = S0;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        match <= 1'b0;
    else
        match <= (state == S3) && (data_in == 1'b1);
end

endmodule

逐行说明

  • 第 1–6 行:模块端口声明,clk 和 rst_n 是同步元素,data_in 为串行输入,match 为检测到序列 1011 时的输出。
  • 第 8–11 行:使用独热编码定义四个状态,每个状态只有一位为 1,其他为 0。这有利于减少组合逻辑级数。
  • 第 13 行:state 为当前状态寄存器,next_state 为组合逻辑计算的次态。
  • 第 15–19 行:时序逻辑,复位时回到 S0,否则更新状态。
  • 第 21–28 行:组合逻辑块,使用 case (1’b1) 结构根据当前状态位选择分支。注意:每次必须赋默认值 next_state = S0 以避免锁存器。
  • 第 30–35 行:输出逻辑,在时钟上升沿更新 match。当状态为 S3 且输入为 1 时输出高,表示检测到序列。

二进制编码 FSM 代码

// fsm_binary.v
module fsm_binary (
    input clk,
    input rst_n,
    input data_in,
    output reg match
);

localparam S0 = 2'b00,
           S1 = 2'b01,
           S2 = 2'b10,
           S3 = 2'b11;

reg [1:0] state, next_state;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= S0;
    else
        state <= next_state;
end

always @(*) begin
    next_state = S0;
    case (state)
        S0: next_state = data_in ? S1 : S0;
        S1: next_state = data_in ? S2 : S0;
        S2: next_state = data_in ? S2 : S3;
        S3: next_state = data_in ? S1 : S0;
        default: next_state = S0;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        match <= 1'b0;
    else
        match <= (state == S3) && (data_in == 1'b1);
end

endmodule

逐行说明

  • 第 8–11 行:使用 2 位二进制编码,状态数少,寄存器位宽最小。
  • 第 13 行:state 位宽为 2,比独热编码的 4 位少一半。
  • 第 21–28 行:case 结构直接对 state 进行解码,综合工具会生成较多的组合逻辑来比较 state 的值。
  • 其余部分与独热编码类似,但状态转移逻辑更密集。

格雷编码 FSM 代码

// fsm_gray.v
module fsm_gray (
    input clk,
    input rst_n,
    input data_in,
    output reg match
);

localparam S0 = 2'b00,
           S1 = 2'b01,
           S2 = 2'b11,
           S3 = 2'b10;

reg [1:0] state, next_state;

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= S0;
    else
        state <= next_state;
end

always @(*) begin
    next_state = S0;
    case (state)
        S0: next_state = data_in ? S1 : S0;
        S1: next_state = data_in ? S2 : S0;
        S2: next_state = data_in ? S2 : S3;
        S3: next_state = data_in ? S1 : S0;
        default: next_state = S0;
    endcase
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        match <= 1'b0;
    else
        match <= (state == S3) && (data_in == 1'b1);
end

endmodule

逐行说明

  • 第 8–11 行:格雷编码相邻状态只有一位变化,S0→S1→S2→S3 的编码为 00→01→11→10。
  • 第 13 行:位宽仍为 2,但状态转移时翻转位少,适合跨时钟域或低功耗设计。
  • 第 21–28 行:case 结构使用格雷值,综合后逻辑复杂度介于二进制和独热之间。
  • 注意:格雷编码在时序收敛上通常不如独热,但比二进制好。

时序约束与综合实现

创建 XDC 文件,约束主时钟 100 MHz,设置输入输出延迟。使用 Vivado 默认综合策略,实现时选择 Performance_Explore 以优化时序。

# timing.xdc
create_clock -name sys_clk -period 10.000 [get_ports clk]
set_input_delay -clock sys_clk -max 2.000 [get_ports data_in]
set_output_delay -clock sys_clk -max 2.000 [get_ports match]

逐行说明

  • 第 1 行:定义主时钟 sys_clk,周期 10 ns(100 MHz),绑定到顶层端口 clk。
  • 第 2 行:设置输入延迟最大值 2 ns,表示数据在时钟沿后 2 ns 内有效。
  • 第 3 行:设置输出延迟最大值 2 ns,约束输出路径。

常见坑与排查

  • 坑 1:独热编码未使用 case (1’b1) 结构,导致综合工具优化为二进制。排查:检查综合后的网表,确认状态寄存器位宽是否为状态数。
  • 坑 2:复位不同步导致亚稳态。排查:确保 rst_n 经过两级同步后再用于状态机复位。
  • 坑 3:组合逻辑中缺少默认赋值,产生锁存器。排查:检查综合报告中的 Latch 警告。
  • 坑 4:时序约束不完整导致分析结果偏差。排查:运行 report_timing_summary 确认所有路径都被覆盖。

原理与设计说明

FSM 编码风格直接影响组合逻辑级数和寄存器扇出,进而影响时序收敛。

二进制编码:使用最少寄存器(log2(N)),但次态逻辑需要解码当前状态,组合逻辑级数随状态数增加而增加。例如,4 状态需要 2 位比较器,在 100 MHz 下可能满足时序,但状态数超过 16 时路径延迟显著增大。

格雷编码:相邻状态仅一位变化,减少组合逻辑中的毛刺概率,但解码逻辑与二进制类似。在跨时钟域同步时常用格雷码,因为单 bit 变化降低亚稳态风险。但时序收敛方面介于二进制和独热之间。

独热编码:每个状态使用一个触发器,组合逻辑只需检测单 bit 状态位,因此逻辑级数最少(通常为 1–2 级 LUT)。代价是寄存器数量多(N 个),但现代 FPGA 中 LUT 和 FF 资源丰富,独热编码往往能获得最高 Fmax。例如,在 Artix-7 上,独热编码的 Fmax 可达 250–300 MHz,而二进制可能仅 150–200 MHz。

Trade-off:资源 vs Fmax。如果设计对面积敏感(如低成本 CPLD),选二进制;如果追求高性能,选独热。格雷编码在跨时钟域场景下不可替代。

验证与结果

编码风格LUT 数FF 数Slice 数WNS (ns)Fmax (MHz)
二进制844-0.12398.8
格雷945-0.04599.6
独热12660.512105.2

测量条件:Vivado 2023.2,Artix-7 xc7a200t,100 MHz 时钟,默认综合策略。WNS 为最差负时序裕量,正值表示满足时序。独热编码 Fmax 最高,资源最多;二进制资源最少但时序违规(WNS 为负)。格雷编码居中。

故障排查(Troubleshooting)

  • 现象 1:时序分析报告显示 setup 违规。原因:组合逻辑级数过多。检查点:查看最差路径的 LUT 级数。修复:改用独热编码或插入流水线。
  • 现象 2:仿真正确但上板输出错误。原因:复位不同步或亚稳态。检查点:用 ILA 捕获状态值。修复:添加同步器。
  • 现象 3:综合后状态寄存器被优化掉。原因:综合工具将独热编码视为二进制。检查点:查看综合属性。修复:使用 (* fsm_encoding = “one-hot” *) 属性。
  • 现象 4:资源利用率异常高。原因:状态数过多且编码不当。检查点:查看 LUT 与 FF 比例。修复:考虑状态压缩或使用二进制。
  • 现象 5:Fmax 低于预期。原因:时钟约束过紧或路径未优化。检查点:运行 report_timing 查看关键路径。修复:调整综合策略为 Performance_Explore。
  • 现象 6:输出毛刺。原因:组合逻辑输出未寄存。检查点:查看输出逻辑是否时序。修复:将输出寄存器化。
  • 现象 7:跨时钟域同步时数据错误。原因:格雷编码未正确使用。检查点:确认相邻状态编码是否只有一位变化。修复:重新分配状态编码。
  • 现象 8:实现时间过长。原因:独热编码导致大量寄存器。检查点:查看实现日志。修复:减少状态数或改用二进制。
  • 现象 9:功耗过高。原因:独热编码寄存器翻转率高。检查点:运行功耗分析。修复:使用格雷编码或时钟门控。
  • 现象 10:综合警告“inferring latch”。原因:组合逻辑中缺少 else 分支。检查点:查看警告行号。修复:补全所有 case 分支并赋默认值。

扩展与下一步

  • 扩展 1:参数化 FSM,支持任意状态数和编码风格选择。
  • 扩展 2:在更高性能器件(如 Kintex Ultrascale)上测试,观察编码对 Fmax 的影响。
  • 扩展 3:加入流水线寄存器,进一步优化时序。
  • 扩展 4:使用形式验证工具(如 OneSpin)验证状态机等价性。
  • 扩展 5:添加覆盖率分析,确保所有状态转移被测试。
  • 扩展 6:研究编码对功耗的影响,使用 Vivado Power Report 对比。

参考与信息来源

  • Xilinx UG901: Vivado Design Suite User Guide – Synthesis
  • Xilinx UG903: Vivado Design Suite User Guide – Using Constraints
  • Clifford E. Cummings, “State Machine Coding Styles for Synthesis”, SNUG 2002
  • IEEE Std 1364-2001: Verilog Hardware Description Language
  • Vivado Design Suite 2023.2 Documentation (在线)

技术附录

术语表

  • WNS: 最差负时序裕量(Worst Negative Slack),正值表示满足时序。
  • Fmax: 最大工作频率,由最差路径延迟决定。
  • 独热编码: 每个状态对应一个触发器,只有一位为 1。
  • 格雷编码: 相邻状态只有一位不同。
  • 二进制编码: 使用 log2(N) 位二进制数表示状态。

检查清单

  • 确认状态编码已正确定义为 localparam。
  • 组合逻辑中所有 case 分支已覆盖,有默认赋值。
  • 复位信号已同步到时钟域。
  • 时序约束已包含所有时钟和 I/O 延迟。
  • 综合后检查状态寄存器位宽是否与预期一致。
  • 实现后运行时序分析,确认 WNS ≥ 0。

关键约束速查

  • 主时钟:create_clock -period 10.000 [get_ports clk]
  • 输入延迟:set_input_delay -clock sys_clk -max 2.000 [get_ports data_in]
  • 输出延迟:set_output_delay -clock sys_clk -max 2.000 [get_ports match]
  • 虚假路径(如跨时钟域):set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
分类
技术分享
标签
FSMVerilog有限状态机
浏览 41收藏 0赞 0评论 0
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

FPGA小白查看主页

同分类阅读

文章

延伸阅读与实操

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

探索全站