Verilog 状态机编码方式对 FPGA 资源利用率的影响对比:设计指南与实验验证

FPGA小白
文章2026-06-14
114 0 0 0

Quick Start

  • 在 Vivado 中新建工程,器件选择 XC7A35T-1CSG324C(Artix-7 系列示例)。
  • 创建三个 Verilog 模块,分别实现二进制编码(Binary)、格雷编码(Gray)和独热编码(One-Hot)的状态机,功能相同:4 状态循环(IDLE → S1 → S2 → S3 → IDLE)。
  • 为每个模块编写独立的顶层文件,实例化状态机模块并连接时钟(100 MHz)和复位(异步低有效)。
  • 添加约束文件(.xdc),指定时钟周期为 10 ns,所有 I/O 引脚分配虚拟引脚(避免上板冲突)。
  • 分别对三个顶层模块运行综合(Synthesis),打开综合报告。
  • 对比三个综合报告中的“Register”数量、“LUT”数量以及“Fmax”估算值。
  • 预期现象:二进制编码使用最少的寄存器(2 个),独热编码使用最多的寄存器(4 个),但独热编码的组合逻辑(LUT)更少,Fmax 更高;格雷编码介于两者之间。
  • 若资源差异不明显,可增加状态数至 16 或 32 以放大对比效果。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T-1CSG324C主流中低端 FPGA,LUT+FF 资源约 4 万Intel Cyclone IV / V 或 Lattice ECP5
EDA 版本Vivado 2024.1综合与实现工具,支持自动编码推断Vivado 2023.x / ISE 14.7(旧器件)
仿真器Vivado Simulator 或 ModelSim SE-64 2023.4用于功能验证,确保状态机行为正确Questa / VCS / IUS
时钟/复位100 MHz 时钟,异步低有效复位典型同步设计基础50 MHz / 200 MHz,同步复位亦可
接口依赖无外部接口,纯内部状态机避免 I/O 约束影响资源统计可添加简单输出指示
约束文件create_clock -period 10.000 [get_ports clk]必须指定时钟周期,否则 Fmax 不准确set_false_path 对异步复位

目标与验收标准

  • 功能点:三种编码的状态机在相同输入序列下输出完全一致(通过仿真波形验证)。
  • 资源指标:综合后报告中的 Register(FF)数量、LUT 数量、Slice 数量。
  • 性能指标:时序报告中的最大工作频率(Fmax),单位 MHz。
  • 验收方式:打开 Vivado 综合报告 → Report Utilization → 记录资源数;Report Timing Summary → 记录 Worst Negative Slack (WNS) 并换算 Fmax = 1/(Tclk – WNS)。
  • 关键波形:仿真中 state 信号(以二进制、格雷、独热形式显示)按预期循环,无毛刺或非法状态。

实施步骤

阶段一:工程结构与 RTL 设计

  • 创建 Vivado 工程,添加三个 Verilog 源文件:fsm_binary.v、fsm_gray.v、fsm_onehot.v。
  • 每个模块端口一致:input clk, input rst_n, output reg [3:0] out。
  • 使用 localparam 定义状态编码,避免直接使用 `define。
  • 状态机采用三段式写法(状态寄存器、次态组合逻辑、输出组合逻辑),便于综合工具优化。
  • 创建三个顶层模块 top_binary.v 等,仅实例化对应 fsm 并连接时钟复位。

关键模块代码:二进制编码状态机

module fsm_binary (
    input wire clk,
    input wire rst_n,
    output reg [3:0] out
);

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

reg [1: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;
    case (state)
        IDLE: next_state = S1;
        S1:   next_state = S2;
        S2:   next_state = S3;
        S3:   next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

always @(*) begin
    out = 4'b0000;
    case (state)
        IDLE: out = 4'b0001;
        S1:   out = 4'b0010;
        S2:   out = 4'b0100;
        S3:   out = 4'b1000;
        default: out = 4'b0000;
    endcase
end

endmodule

逐行说明

  • 第 1 行:模块名 fsm_binary,端口列表包含时钟、复位和 4 位输出。
  • 第 6–9 行:使用 localparam 定义二进制编码状态值,占用 2 位寄存器。
  • 第 11 行:声明 state(当前状态)和 next_state(次态)为 2 位 reg。
  • 第 13–17 行:时序逻辑,时钟上升沿或复位下降沿触发,异步复位到 IDLE。
  • 第 19–27 行:次态组合逻辑,使用 case 语句描述状态转移;default 防止锁存器。
  • 第 29–37 行:输出组合逻辑,根据当前状态产生独热输出(方便观察)。
  • 第 35 行:default 子句给输出赋默认值,避免综合出锁存器。

关键模块代码:格雷编码状态机

module fsm_gray (
    input wire clk,
    input wire rst_n,
    output reg [3:0] out
);

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

reg [1: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;
    case (state)
        IDLE: next_state = S1;
        S1:   next_state = S2;
        S2:   next_state = S3;
        S3:   next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

always @(*) begin
    out = 4'b0000;
    case (state)
        IDLE: out = 4'b0001;
        S1:   out = 4'b0010;
        S2:   out = 4'b0100;
        S3:   out = 4'b1000;
        default: out = 4'b0000;
    endcase
end

endmodule

逐行说明

  • 第 6–9 行:格雷编码状态定义,相邻状态仅一位变化,如 00→01→11→10。
  • 第 11 行:仍使用 2 位寄存器,与二进制相同。
  • 第 13–17 行:时序逻辑与二进制版本完全一致。
  • 第 19–27 行:次态逻辑与二进制相同,因为状态转移顺序一致,只是编码不同。
  • 第 29–37 行:输出逻辑与二进制相同,确保功能等价。
  • 格雷编码的优势在于跨时钟域传递状态时减少亚稳态概率,但本实验不涉及 CDC。

关键模块代码:独热编码状态机

module fsm_onehot (
    input wire clk,
    input wire rst_n,
    output reg [3:0] out
);

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

reg [3: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 = 4'b0000;
    case (state)
        IDLE: next_state = S1;
        S1:   next_state = S2;
        S2:   next_state = S3;
        S3:   next_state = IDLE;
        default: next_state = IDLE;
    endcase
end

always @(*) begin
    out = 4'b0000;
    case (state)
        IDLE: out = 4'b0001;
        S1:   out = 4'b0010;
        S2:   out = 4'b0100;
        S3:   out = 4'b1000;
        default: out = 4'b0000;
    endcase
end

endmodule

逐行说明

  • 第 6–9 行:独热编码,每个状态独占一位,共需 4 位寄存器。
  • 第 11 行:state 和 next_state 声明为 4 位 reg。
  • 第 13–17 行:时序逻辑与前面相同。
  • 第 20 行:次态组合逻辑中,next_state 先赋默认值 0,避免锁存器。
  • 第 21–28 行:case 语句,每次只给对应状态位赋值 1,其余位为 0。
  • 第 29–37 行:输出逻辑与之前一致。
  • 独热编码的次态逻辑只需译码一位,组合逻辑更简单,但寄存器数量随状态数线性增长。

阶段二:综合与实现

  • 在 Vivado 中为每个顶层模块分别设置“Hierarchy”为顶层,运行综合。
  • 综合后打开 Report Utilization,记录 Slice LUTs、Slice Registers 数量。
  • 打开 Report Timing Summary,查看 WNS,计算 Fmax = 1/(10 ns – WNS)。
  • 若 WNS 为正,则 Fmax > 100 MHz;若为负,则实际 Fmax 低于 100 MHz。
  • 重复上述步骤,对比三个结果。

常见坑与排查

  • 坑 1:综合工具自动优化编码方式。Vivado 默认会根据状态数自动选择编码方式(如独热)。需在综合设置中关闭自动编码推断:在 Vivado 综合设置中将“-fsm_extraction”设为“off”或指定编码方式。否则即使代码写二进制,综合后也可能被替换。
  • 坑 2:资源统计包含顶层连线。顶层模块可能因引脚约束引入额外 I/O buffer,影响资源计数。建议将状态机模块单独设为顶层,或使用 virtual I/O 避免。
  • 坑 3:状态数太少时差异不明显。4 状态时二进制与独热的寄存器差仅 2 个,LUT 差异可能被其他逻辑淹没。建议扩展至 8、16 或 32 状态。
  • 坑 4:未使用 default 子句导致锁存器。case 语句缺少 default 会综合出锁存器,增加 LUT 并降低 Fmax。务必添加 default 赋值。

原理与设计说明

状态机编码方式直接影响 FPGA 内部寄存器和查找表(LUT)的用量,进而影响最大工作频率。核心 trade-off 在于:寄存器数量 vs 组合逻辑复杂度。

二进制编码(Binary)

使用最少的寄存器(log2(N) 个,N 为状态数)。但次态和输出逻辑需要译码所有状态位,组合逻辑随状态数增加而显著膨胀,导致路径延迟增大,Fmax 下降。适用于状态数少(≤8)且资源紧张的场景。

格雷编码(Gray)

寄存器数与二进制相同,但相邻状态仅一位变化,在跨时钟域传输时可降低亚稳态概率。组合逻辑复杂度与二进制接近,Fmax 略优。适合需要 CDC 或低功耗设计的场景(减少翻转率)。

独热编码(One-Hot)

使用 N 个寄存器(N 为状态数),寄存器用量最大。但次态逻辑只需检查当前状态的一位,组合逻辑极简,路径延迟短,因此 Fmax 最高。Xilinx 官方推荐在状态数 ≥ 8 时使用独热编码,因为 FPGA 的 LUT 通常有 4–6 个输入,独热编码的译码逻辑可完全映射到单个 LUT 内。代价是寄存器消耗大,但 FPGA 寄存器资源通常比 LUT 充裕。

关键对比总结

编码方式寄存器数组合逻辑Fmax适用场景
二进制log2(N)高低状态数少,资源受限
格雷log2(N)中中CDC、低功耗
独热N低高状态数多,追求 Fmax

验证与结果

以下数据基于 Vivado 2024.1 综合 Artix-7 XC7A35T-1CSG324C,4 状态状态机,时钟约束 100 MHz。实际数值以您的工程为准,此处仅为示例。

编码方式Slice LUTsSlice RegistersFmax (MHz)
二进制62320
格雷62330
独热44380

测量条件:关闭自动 FSM 提取(-fsm_extraction off),仅统计状态机内部资源,不含顶层 I/O。Fmax 通过 WNS 换算:Fmax = 1/(Tclk – WNS),其中 Tclk = 10 ns。

结论:独热编码以 2 倍寄存器代价换取了约 18% 的 Fmax 提升(380 vs 320 MHz),且 LUT 减少 33%。当状态数增加至 16 时,差异会更显著。

故障排查(Troubleshooting)

  • 现象:综合后资源与预期不符 → 原因:Vivado 自动优化了编码方式。检查点:综合设置中 -fsm_extraction 是否为 off。修复:设置为 off 或指定编码。
  • 现象:Fmax 很低(< 100 MHz) → 原因:组合逻辑路径过长。检查点:查看 Timing Report 中最大延迟路径,是否包含多级 LUT 或进位链。修复:增加流水线或改用独热编码。
  • 现象:仿真中状态机进入非法状态 → 原因:未使用 default 子句或复位不完整。检查点:case 语句是否覆盖所有状态,复位是否有效。修复:添加 default 并确保复位将 state 置为 IDLE。
  • 现象:资源报告中出现大量 LUTRAM → 原因:综合工具将状态寄存器推断为分布式 RAM。检查点:综合报告中的“RAM”类别。修复:在代码中显式使用 always @(posedge clk) 描述寄存器。
  • 现象:独热编码的寄存器数不等于状态数 → 原因:综合工具合并了未使用的状态位。检查点:状态定义是否完整,是否有未用状态。修复:确保所有状态都被 case 覆盖。
  • 现象:格雷编码与二进制资源完全相同 → 原因:状态数太少,组合逻辑差异被优化掉。检查点:增加状态数至 16 或 32。修复:重新对比。
  • 现象:综合报告显示“FSM inferred”但编码方式未知 → 原因:Vivado 自动推断。检查点:综合日志中搜索“FSM”。修复:添加综合属性 (* fsm_encoding = “one_hot” *) 等。
  • 现象:上板后输出错误 → 原因:复位信号未正确连接或异步复位毛刺。检查点:复位信号是否经过同步器。修复:对异步复位做两级同步。
  • 现象:功耗过高 → 原因:独热编码寄存器翻转率高。检查点:使用 Vivado Power Report。修复:在低功耗场景改用格雷编码。
  • 现象:跨时钟域传输时状态出错 → 原因:二进制编码多位同时变化导致亚稳态。检查点:CDC 路径是否使用格雷编码。修复:改用格雷编码或添加握手信号。

扩展与下一步

  • 参数化状态机:使用 parameter 定义状态数和编码方式,通过 generate 或宏选择不同编码,便于复用。
  • 带宽提升:在状态机中引入流水线,将输出寄存器化,进一步提高 Fmax。
  • 跨平台对比:在 Intel Cyclone V 或 Lattice ECP5 上重复实验,比较不同厂商 LUT 结构对编码方式的影响。

参考

  • Xilinx UG901: Vivado Design Suite User Guide: Synthesis
  • Xilinx UG949: Vivado Design Suite User Guide: Methodology
  • Clifford E. Cummings, “State Machine Coding Styles for Synthesis”, SNUG 2002

附录

无

分类
技术分享
标签
Verilog状态机编码方式
浏览 114收藏 0赞 0评论 0
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

FPGA小白查看主页

同分类阅读

文章

延伸阅读与实操

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

探索全站