---
title: Verilog 状态机编码方式对 FPGA 资源利用率的影响对比：设计指南与实验验证
url: "https://www.fpgalearn.com/51481.html"
kind: article
id: 51481
excerpt: Quick Start在 Vivado 中新建工程，器件选择 XC7A35T-1CSG324C（Artix-7 系列示例）。创建三个 Verilog 模块，分别…
published_at: 2026-06-14
author: FPGA小白
tags: Verilog, 状态机, 编码方式
word_count: 4211
source: "https://www.fpgalearn.com"
---

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

> 标签：Verilog、状态机、编码方式
> Quick Start在 Vivado 中新建工程，器件选择 XC7A35T-1CSG324C（Artix-7 系列示例）。创建三个 Verilog 模块，分别…

> Quick Start在 Vivado 中新建工程，器件选择 XC7A35T-1CSG324C（Artix-7 系列示例）。创建三个 Verilog 模块，分别…

## 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 LUTs | Slice Registers | Fmax (MHz) |
| --- | --- | --- | --- |
| 二进制 | 6 | 2 | 320 |
| 格雷 | 6 | 2 | 330 |
| 独热 | 4 | 4 | 380 |
测量条件：关闭自动 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
## 附录
无
