---
title: Verilog中状态机编码优化：2026年综合工具对格雷码的偏好
url: "https://www.fpgalearn.com/48985.html"
kind: article
id: 48985
excerpt: Quick Start安装 Vivado 2024.2 或更高版本（推荐 2025.1+，以支持最新的 FSM 编码优化策略）。创建新工程，目标器件选择 Xil…
published_at: 2026-06-05
author: FPGA小白
tags: Verilog, 状态机, 编码优化
word_count: 2176
source: "https://www.fpgalearn.com"
---

# Verilog中状态机编码优化：2026年综合工具对格雷码的偏好

> 标签：Verilog、状态机、编码优化
> Quick Start安装 Vivado 2024.2 或更高版本（推荐 2025.1+，以支持最新的 FSM 编码优化策略）。创建新工程，目标器件选择 Xil…

> Quick Start安装 Vivado 2024.2 或更高版本（推荐 2025.1+，以支持最新的 FSM 编码优化策略）。创建新工程，目标器件选择 Xil…

## Quick Start
1. 安装 Vivado 2024.2 或更高版本（推荐 2025.1+，以支持最新的 FSM 编码优化策略）。
1. 创建新工程，目标器件选择 Xilinx Artix-7（xc7a35tcsg324-1）或更新系列（如 Kintex-7 / Virtex-7 / Versal 均可）。
1. 编写一个 4 状态（S0~S3）的有限状态机，状态编码使用格雷码（Gray code），并添加 `syn_encoding = "gray"` 综合属性。
1. 编写一个对比用的二进制编码（Binary code）版本，使用 `syn_encoding = "sequential"`。
1. 在综合设置中，将 FSM 编码策略设为 `Gray`（Vivado 综合属性：`-fsm_extraction gray`）。
1. 运行综合，检查综合报告中的 FSM 编码信息（Report → Report FSM Extraction）。
1. 对比两种编码实现后的 LUT 数量、触发器数量、最大频率（Fmax）和动态功耗（使用 Report Power）。
1. 预期结果：格雷码版本在状态转换频繁时，触发器切换次数更少，动态功耗降低 10%~30%；综合工具会自动推断并推荐格雷码编码。
## 前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
| --- | --- | --- | --- |
| 器件/板卡 | Xilinx Artix-7 (xc7a35tcsg324-1) | 典型中低端 FPGA，资源有限，适合对比编码效果 | Kintex-7 / Spartan-7 / Versal (需适配约束) |
| EDA 版本 | Vivado 2025.1 | 2026 年主流版本，FSM 综合引擎已优化格雷码推断 | Vivado 2024.2 / 2026.1 (预览版) |
| 仿真器 | Vivado Simulator (xsim) | 内建，无需额外安装 | ModelSim / Questa / VCS |
| 时钟/复位 | 100 MHz 单端时钟，异步低电平复位 | 标准时序约束 | 差分时钟 / 同步复位（不推荐） |
| 接口依赖 | 无外部接口 | 纯内部状态机，仅观察内部信号 | 可添加 LED 输出用于上板验证 |
| 约束文件 | create_clock -period 10.000 [get_ports clk] | 必须添加，否则综合工具无法优化时序 | 根据实际时钟频率调整 |
## 目标与验收标准
- **功能正确**：状态机按预期顺序（S0→S1→S2→S3→S0）循环，无毛刺或误跳转。
- **资源对比**：格雷码版本与二进制版本相比，LUT 数量差异 ≤ 5%，触发器数量相同（状态寄存器位数相同）。
- **Fmax 对比**：格雷码版本 Fmax 不低于二进制版本，且通常略高（因组合逻辑更少）。
- **功耗对比**：在相同激励下（状态切换频率 50%），格雷码版本动态功耗降低 ≥ 15%（以 Vivado Report Power 为准）。
- **综合报告**：Vivado 综合日志应显示“FSM Encoding: Gray”或类似信息，表明工具自动采用格雷码。
## 实施步骤
### 1. 工程结构与 RTL 编写
创建两个顶层模块：`fsm_gray`（格雷码）和 `fsm_binary`（二进制），用于对比。以下给出格雷码版本的完整 RTL。
```
// fsm_gray.v
// 4-state Gray-coded FSM with syn_encoding attribute
// Target: Xilinx Vivado 2025.1+

module fsm_gray (
    input  wire       clk,
    input  wire       rst_n,
    output reg  [1:0] state_out  // 格雷码状态输出
);

    // 状态定义：格雷码（相邻状态仅1位变化）
    localparam S0 = 2'b00;
    localparam S1 = 2'b01;
    localparam S2 = 2'b11;
    localparam S3 = 2'b10;

    reg [1:0] state, next_state;

    // 综合属性：强制使用格雷编码（可选，工具通常自动推断）
    (* syn_encoding = "gray" *)
    reg [1:0] state_syn;

    // 时序逻辑：状态更新
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= S0;
        else
            state <= next_state;
    end

    // 次态逻辑：简单循环
    always @(*) begin
        case (state)
            S0: next_state = S1;
            S1: next_state = S2;
            S2: next_state = S3;
            S3: next_state = S0;
            default: next_state = S0;
        endcase
    end

    // 输出（直接连接状态寄存器）
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state_out <= 2'b00;
        else
            state_out <= state;
    end

endmodule
```
### 逐行说明
- **第 1~4 行**：模块声明与端口列表。clk 为输入时钟，rst_n 为异步低电平复位，state_out 为 2 位输出，表示当前状态。
- **第 7~10 行**：使用 `localparam` 定义 4 个状态，编码为格雷码：S0=00, S1=01, S2=11, S3=10。相邻状态（如 S0→S1）仅 1 位变化，这是格雷码的核心特性。
- **第 12 行**：声明状态寄存器 `state` 和次态信号 `next_state`，均为 2 位。
- **第 15~16 行**：使用 `(* syn_encoding = "gray" *)` 综合属性，显式要求 Vivado 将 `state_syn` 视为格雷码编码。注意此处 `state_syn` 仅用于属性绑定，实际状态寄存器仍为 `state`；在 Vivado 中，属性可作用于整个 always 块或模块，但更推荐直接作用于状态变量。
- **第 19~23 行**：时序 always 块，在时钟上升沿或复位下降沿触发。复位时将状态设为 S0。
- **第 26~33 行**：组合逻辑 always 块，根据当前状态计算下一状态。case 语句覆盖所有状态，default 确保安全。
- **第 36~41 行**：输出寄存器，将 `state` 同步输出到 `state_out`，避免组合输出毛刺。
### 2. 二进制编码对比模块
```
// fsm_binary.v
// 4-state Binary-coded FSM for comparison

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

    localparam S0 = 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 <= S0;
        else
            state <= next_state;
    end

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

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state_out <= 2'b00;
        else
            state_out <= state;
    end

endmodule
```
### 逐行说明
- **第 7~10 行**：状态编码为二进制顺序：S0=00, S1=01, S2=10, S3=11。相邻状态（如 S1→S2）有 2 位变化，这是二进制编码的典型特征。
- **其余行**：结构与格雷码版本完全相同，仅状态编码不同。这样设计可确保对比只反映编码差异，不受其他逻辑影响。
### 3. 综合设置与 FSM 编码策略
在 Vivado 中，通过 Tcl 命令或 GUI 设置 FSM 编码策略：
```
# Tcl 命令：设置全局 FSM 编码为格雷码
set_property STEPS.SYNTH_DESIGN.ARGS.FSM_EXTRACTION gray [get_runs synth_1]

# 或者使用综合属性（在 RTL 中已添加）
# 运行综合后，检查日志：
# grep "FSM Encoding" vivado.log
# 应看到：FSM Encoding: Gray
```
### 逐行说明
- **第 2 行**：`set_property` 命令设置综合步骤参数 `FSM_EXTRACTION` 为 `gray`。这会覆盖工具默认的自动选择行为，强制使用格雷码。
- **第 5~6 行**：综合完成后，在 Vivado 日志中搜索“FSM Encoding”可确认实际使用的编码方式。
### 4. 仿真验证
编写 testbench 驱动时钟和复位，观察状态转换波形。
```
// tb_fsm.v
// Testbench for both FSM modules

module tb_fsm;

    reg clk, rst_n;
    wire [1:0] gray_out, binary_out;

    fsm_gray    u_gray  (.clk(clk), .rst_n(rst_n), .state_out(gray_out));
    fsm_binary  u_binary(.clk(clk), .rst_n(rst_n), .state_out(binary_out));

    initial begin
        clk = 0;
        forever #5 clk = ~clk;  // 100 MHz
    end

    initial begin
        rst_n = 0;
        #20 rst_n = 1;
        #200 $finish;
    end

    initial begin
        $monitor("Time=%0t gray=%b binary=%b", $time, gray_out, binary_out);
    end

endmodule
```
### 逐行说明
- **第 5 行**：声明测试信号。clk 和 rst_n 为 reg 类型，gray_out 和 binary_out 为 wire 类型。
- **第 7~8 行**：实例化两个 FSM 模块，对比输出。
- **第 10~13 行**：生成 100 MHz 时钟（周期 10 ns）。
- **第 15~18 行**：复位时序：前 20 ns 保持复位，然后释放，运行 200 ns 后结束。
- **第 20~22 行**：使用 `$monitor` 打印状态变化，便于观察。
### 5. 常见坑与排查
- **坑 1：综合属性未生效**——检查 RTL 中 `syn_encoding` 是否拼写正确（Vivado 支持 `syn_encoding`，但某些版本也接受 `fsm_encoding`）。建议同时设置全局策略。
- **坑 2：状态编码位数不足**——4 个状态需要至少 2 位寄存器。若使用 one-hot 编码（4 位），触发器数量会翻倍，但功耗可能更低（视切换频率而定）。
- **坑 3：综合报告未显示 FSM 信息**——确保在综合设置中启用了 FSM 提取（默认开启）。若使用 `syn_encoding` 属性，工具可能仍按自动策略优化；此时应检查日志中是否有“FSM Encoding: Gray”字样。
- **坑 4：功耗对比不明显**——格雷码的优势在状态切换频繁时更显著。若测试激励中状态变化较少，差异可能很小。建议使用随机输入或高频切换序列。
## 原理与设计说明
### 为什么格雷码能降低功耗？
在 CMOS 电路中，动态功耗的主要来源是信号翻转时对负载电容的充放电。公式为：`P_dynamic = 0.5 × C_load × V_DD² × f_sw × α`，其中 `α` 是翻转活动因子（toggle rate）。格雷码的核心优势在于：相邻状态之间 **只有 1 位发生变化**，因此状态寄存器的平均翻转次数（即 `α`）比二进制编码（平均 2 位变化）减少约 50%。对于 4 状态机，二进制编码从 S1→S2 时 2 位翻转，而格雷码始终只有 1 位翻转。
此外，格雷码的次态逻辑通常更简单：因为相邻状态仅差 1 位，组合逻辑中需要比较的比特数更少，LUT 深度可能降低，从而略微提升 Fmax。
### 2026 年综合工具对格雷码的偏好
截至 2026 年，主流 FPGA 综合工具（Vivado、Synplify、Quartus Prime）的 FSM 提取引擎已非常智能。在默认设置下，工具会分析状态转换图，自动选择最优编码方式。对于大多数控制型状态机（转换图稀疏、状态数 ≤ 16），工具倾向于选择格雷码或 one-hot 编码，而非二进制。这是因为：
- 格雷码在功耗和时序上通常优于二进制；
- One-hot 编码在状态数较少时（≤8）可减少组合逻辑，但触发器数量多；
- 二进制编码在状态数较多时（≥16）可节省寄存器，但组合逻辑更复杂。
Vivado 2025.1 引入了基于机器学习的 FSM 编码推荐（需 License），可针对特定设计自动选择功耗最优的编码方式。但手动指定 `syn_encoding = "gray"` 仍然是可靠的做法，尤其当设计对功耗有严格约束时。
### Trade-off：资源 vs Fmax vs 功耗
| 编码方式 | 触发器数 | 组合逻辑（LUT） | Fmax | 动态功耗 | 适用场景 |
| --- | --- | --- | --- | --- | --- |
| 二进制 | log2(N) | 中等 | 中等 | 高（翻转多） | 状态数多（≥16），对功耗不敏感 |
| 格雷码 | log2(N) | 较低 | 较高 | 低 | 状态数少（≤16），功耗敏感 |
| One-hot | N | 低 | 高 | 中等（翻转少但寄存器多） | 状态数少（≤8），时序关键 |
注意：上表为典型趋势，实际结果取决于状态转换图的复杂度和综合工具的优化能力。建议在具体设计中实测对比。
## 验证与结果
| 指标 | 二进制编码 | 格雷码编码 | 差异（格雷 vs 二进制） |
| --- | --- | --- | --- |
| LUT 数量 | 4 | 3 | 减少 25% |
| 触发器数量 | 2 | 2 | 相同 |
| Fmax（MHz） | 312 | 357 | 提升 14% |
| 动态功耗（mW）@100MHz | 0.85 | 0.62 | 降低 27% |
| 状态切换平均翻转位数 | 1.5 | 1.0 | 减少 33% |
**测量条件**：Vivado 2025.1，Artix-7 xc7a35tcsg324-1，时钟 100 MHz，输入激励为连续循环状态切换（每个时钟周期状态变化一次）。功耗数据来自 Vivado Report Power，仅含动态功耗（静态功耗已扣除）。上述数值为示例配置，实际结果以具体工程与数据手册为准。
## 故障排查（Troubleshooting）
- **现象：综合报告显示“FSM Encoding: Sequential”而非“Gray”** → 原因：RTL 中 `syn_encoding` 属性拼写错误或未生效。检查点：确认属性写在状态寄存器声明之前，且 Vivado 版本支持该属性。修复：改用 Tcl 命令强制设置 `FSM_EXTRACTION gray`。
- **现象：仿真中状态跳转错误（如 S0→S2）** → 原因：次态逻辑中 case 语句未覆盖所有状态，或 default 处理不当。检查点：验证所有状态转换路径。修复：添加 full_case 综合属性（`// synthesis full_case`）或显式列出所有状态。
- **现象：功耗对比差异小于 5%** → 原因：测试激励中状态切换频率过低，或时钟频率太低。检查点：确认 testbench 中每个时钟周期都触发状态变化。修复：使用高频时钟（如 200 MHz）和连续切换序列。
- **现象：Fmax 反而下降** → 原因：格雷码的次态逻辑在某些情况下可能更复杂（如状态转换图不规则）。检查点：查看综合后的关键路径报告。修复：尝试 one-hot 编码，或手动优化次态逻辑。
- **现象：综合时间过长** → 原因：FSM 提取引擎在状态数较多时（>64）可能变慢。检查点：减少状态数或使用更简单的编码。修复：对于大型状态机，考虑使用二进制编码并手动优化。
- **现象：Vivado 报告“FSM extraction disabled”** → 原因：综合设置中禁用了 FSM 提取。检查点：查看综合策略。修复：在 Tcl 中运行 `set_property STEPS.SYNTH_DESIGN.ARGS.FSM_EXTRACTION gray [get_runs synth_1]`。
- **现象：上板后状态机不工作** → 原因：复位信号未正确连接或时钟抖动。检查点：使用 ILA 抓取状态信号。修复：确保复位信号稳定，时钟源干净。
- **现象：综合后资源占用异常高** → 原因：状态机被综合为分布式逻辑而非专用 FSM。检查点：查看综合报告中的 FSM 部分。修复：添加 `(* fsm_encoding = "gray" *)` 属性（某些工具支持）。
## 扩展与下一步</
