---
title: 2026年6月：Verilog中FSM优化降低功耗的实用方法
url: "https://www.fpgalearn.com/52306.html"
kind: article
id: 52306
excerpt: Quick Start步骤1：准备 Vivado 2024.1 或更高版本（或 Quartus Prime Pro 23.4），新建一个空工程，器件选择 Xil…
published_at: 2026-06-17
author: FPGA小白
tags: FSM, Verilog, 功耗优化
word_count: 7064
source: "https://www.fpgalearn.com"
---

# 2026年6月：Verilog中FSM优化降低功耗的实用方法

> 标签：FSM、Verilog、功耗优化
> Quick Start步骤1：准备 Vivado 2024.1 或更高版本（或 Quartus Prime Pro 23.4），新建一个空工程，器件选择 Xil…

> Quick Start步骤1：准备 Vivado 2024.1 或更高版本（或 Quartus Prime Pro 23.4），新建一个空工程，器件选择 Xil…

## Quick Start
- 步骤1：准备 Vivado 2024.1 或更高版本（或 Quartus Prime Pro 23.4），新建一个空工程，器件选择 Xilinx Artix-7 XC7A35T（示例）。
- 步骤2：编写一个典型的 Moore 型 FSM（三段式），状态数 8 个，状态编码使用 Binary（顺序编码）。
- 步骤3：在 Vivado 中运行综合（Synthesis），打开综合后的功耗报告（Power Report），记录动态功耗（Dynamic Power）和信号翻转率（Toggle Rate）。
- 步骤4：将状态编码改为 One-Hot（独热码），重新综合并对比功耗。
- 步骤5：在原始 Binary 编码 FSM 中，增加门控时钟（Clock Gating）逻辑：当状态不发生变化时，关闭状态寄存器的时钟使能。
- 步骤6：再次综合并对比功耗，观察动态功耗下降比例（预期 15%–30%，以实际工程为准）。
- 步骤7：在仿真中验证功能正确性：状态跳转、输出与原始 FSM 一致。
- 步骤8：记录资源占用（LUT/FF）变化，评估功耗-面积 trade-off。
## 前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
| --- | --- | --- | --- |
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 28nm 工艺，功耗特征典型 | Intel Cyclone V / Lattice ECP5 |
| EDA 版本 | Vivado 2024.1 | 支持功耗分析（Power Report） | Quartus Prime Pro 23.4 / Synplify |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 2023.4 | 用于功能验证 | VCS / Xsim |
| 时钟/复位 | 时钟 50 MHz，异步复位高有效 | 典型频率，便于观察翻转率 | 其他频率需重新计算功耗 |
| 接口依赖 | 无外部接口，纯逻辑验证 | 便于隔离测量 | 可挂载简单输入输出 |
| 约束文件 | XDC 约束：时钟周期 20 ns，输入输出延迟默认 | 确保综合正确 | SDC 约束（Quartus） |
## 目标与验收标准
- 功能点：FSM 状态跳转逻辑正确，输出与原始设计一致（通过仿真波形比对）。
- 性能指标：动态功耗降低 ≥15%（在相同测试向量下，对比优化前后 Power Report）。
- 资源占用：LUT 增加不超过 10%，FF 数量不变或略有增加（门控时钟引入额外寄存器）。
- Fmax：优化后 Fmax 不低于原始设计的 90%（避免因组合逻辑增加导致时序恶化）。
- 验收方式：在 Vivado 中运行综合后功耗分析，记录 Dynamic Power 值；运行时序报告确认 setup/hold 无违例。
## 实施步骤
### 阶段一：工程结构与基础 FSM 设计
创建一个 Vivado 工程，顶层模块名为 `fsm_demo`。包含一个 8 状态 Moore FSM，状态转换条件由外部输入 `sel[1:0]` 控制。状态编码使用 Binary（顺序编码：`3'b000` 到 `3'b111`）。
```
// fsm_binary.v – Binary 编码 FSM (三段式)
module fsm_binary (
    input wire clk,
    input wire rst_n,
    input wire [1:0] sel,
    output reg [2:0] state_out
);

    localparam [2:0] S0 = 3'b000,
                     S1 = 3'b001,
                     S2 = 3'b010,
                     S3 = 3'b011,
                     S4 = 3'b100,
                     S5 = 3'b101,
                     S6 = 3'b110,
                     S7 = 3'b111;

    reg [2: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 = (sel == 2'b00) ? S1 : S0;
            S1: next_state = (sel == 2'b01) ? S2 : S1;
            S2: next_state = (sel == 2'b10) ? S3 : S2;
            S3: next_state = (sel == 2'b11) ? S4 : S3;
            S4: next_state = (sel == 2'b00) ? S5 : S4;
            S5: next_state = (sel == 2'b01) ? S6 : S5;
            S6: next_state = (sel == 2'b10) ? S7 : S6;
            S7: next_state = (sel == 2'b11) ? S0 : S7;
            default: next_state = S0;
        endcase
    end

    // 输出逻辑 (Moore)
    always @(*) begin
        state_out = state;
    end

endmodule
```
### 逐行说明
- 第 1 行：模块声明，输入时钟、复位、选择信号，输出当前状态。
- 第 6-13 行：使用 `localparam` 定义 8 个状态，Binary 编码从 000 到 111。
- 第 15 行：声明当前状态 `state` 和次态 `next_state` 寄存器。
- 第 18-23 行：时序逻辑，时钟上升沿或复位下降沿触发，复位到 S0。
- 第 26-37 行：组合逻辑，根据当前状态和 `sel` 计算次态。每个状态只有一种条件跳转，否则保持。
- 第 40-43 行：输出逻辑，直接赋值当前状态。
- 综合后，状态寄存器使用 3 个 FF，次态逻辑使用少量 LUT。
### 阶段二：状态编码优化 – One-Hot 编码
将状态编码改为 One-Hot：每个状态对应一个独立的寄存器位，只有一位为高。虽然寄存器数量增加（8 个 vs 3 个），但组合逻辑更简单，且翻转率降低（每次只有 2 位翻转：旧位变 0，新位变 1，而 Binary 编码可能多位翻转）。
```
// fsm_onehot.v – One-Hot 编码 FSM
module fsm_onehot (
    input wire clk,
    input wire rst_n,
    input wire [1:0] sel,
    output reg [7:0] state_out
);

    localparam [7:0] S0 = 8'b0000_0001,
                     S1 = 8'b0000_0010,
                     S2 = 8'b0000_0100,
                     S3 = 8'b0000_1000,
                     S4 = 8'b0001_0000,
                     S5 = 8'b0010_0000,
                     S6 = 8'b0100_0000,
                     S7 = 8'b1000_0000;

    reg [7: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 = 8'b0;  // 默认全0
        case (1'b1)  // 使用 case 的独热码写法
            state[0]: next_state = (sel == 2'b00) ? S1 : S0;
            state[1]: next_state = (sel == 2'b01) ? S2 : S1;
            state[2]: next_state = (sel == 2'b10) ? S3 : S2;
            state[3]: next_state = (sel == 2'b11) ? S4 : S3;
            state[4]: next_state = (sel == 2'b00) ? S5 : S4;
            state[5]: next_state = (sel == 2'b01) ? S6 : S5;
            state[6]: next_state = (sel == 2'b10) ? S7 : S6;
            state[7]: next_state = (sel == 2'b11) ? S0 : S7;
            default: next_state = S0;
        endcase
    end

    always @(*) begin
        state_out = state;
    end

endmodule
```
### 逐行说明
- 第 8-15 行：One-Hot 编码，每个状态独享一个 bit，S0 为 bit0，S7 为 bit7。
- 第 17 行：状态寄存器位宽 8 位。
- 第 19-24 行：时序逻辑同 Binary 版本。
- 第 26 行：次态组合逻辑，使用 `case (1'b1)` 语法，综合工具会推断为独热码多路选择器。
- 第 27-35 行：每个 case 项检查 `state` 的对应位是否为 1，然后计算次态。
- 输出逻辑不变。
- 综合后：寄存器从 3 个增加到 8 个，但组合逻辑 LUT 减少（因为不需要解码 Binary 到独热）。
### 阶段三：门控时钟优化
门控时钟（Clock Gating）是降低动态功耗的有效手段：当状态不变化时，关闭状态寄存器的时钟使能，减少不必要的寄存器翻转。在 FPGA 中，通常使用时钟使能（Clock Enable, CE）实现，而非真正的门控时钟（因为 FPGA 内部有专用 CE 引脚）。
```
// fsm_gated.v – 带时钟使能的 Binary FSM
module fsm_gated (
    input wire clk,
    input wire rst_n,
    input wire [1:0] sel,
    output reg [2:0] state_out
);

    localparam [2:0] S0 = 3'b000, S1 = 3'b001, S2 = 3'b010, S3 = 3'b011,
                     S4 = 3'b100, S5 = 3'b101, S6 = 3'b110, S7 = 3'b111;

    reg [2:0] state, next_state;
    wire state_change;

    // 状态变化检测：当前状态与次态不同时，产生使能
    assign state_change = (state != next_state);

    // 带时钟使能的状态寄存器
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            state <= S0;
        else if (state_change)
            state <= next_state;
        // 如果 state_change 为 0，state 保持不变（无翻转）
    end

    // 次态逻辑同 Binary 版本
    always @(*) begin
        case (state)
            S0: next_state = (sel == 2'b00) ? S1 : S0;
            S1: next_state = (sel == 2'b01) ? S2 : S1;
            S2: next_state = (sel == 2'b10) ? S3 : S2;
            S3: next_state = (sel == 2'b11) ? S4 : S3;
            S4: next_state = (sel == 2'b00) ? S5 : S4;
            S5: next_state = (sel == 2'b01) ? S6 : S5;
            S6: next_state = (sel == 2'b10) ? S7 : S6;
            S7: next_state = (sel == 2'b11) ? S0 : S7;
            default: next_state = S0;
        endcase
    end

    always @(*) begin
        state_out = state;
    end

endmodule
```
### 逐行说明
- 第 14 行：定义 `state_change` 线网，当 `state` 与 `next_state` 不同时为高。
- 第 17-23 行：时序逻辑增加 `else if (state_change)` 条件，只有状态需要变化时才更新寄存器。
- 当 `state_change` 为 0 时，`state` 保持原值，寄存器不翻转，动态功耗降低。
- 次态逻辑和输出逻辑与 Binary 版本相同。
- 综合后，每个状态寄存器会使用 FF 的 CE 引脚，不额外增加逻辑（Xilinx 7 系列 FF 自带 CE）。
### 阶段四：综合与功耗分析
- 在 Vivado 中，分别将三个模块设为顶层，运行综合。
- 打开综合后的设计（Open Synthesized Design），点击 Reports → Report Power，使用默认的翻转率（Toggle Rate）设定（通常 12.5%）。
- 记录 Dynamic Power 值。对于 Binary 版本，典型值约 2.5 mW（50 MHz，Artix-7）；One-Hot 版本约 2.0 mW；门控版本约 1.8 mW。
- 注意：这些数值仅为示例，实际值取决于综合策略、器件型号和测试向量。
### 常见坑与排查
- 坑 1：门控时钟的 `state_change` 信号如果由组合逻辑产生，可能引入毛刺，导致状态误更新。解决方案：在 `state_change` 后加一级寄存器同步，或使用 `always @(posedge clk)` 采样。
- 坑 2：One-Hot 编码的 FSM 在复位时，必须确保只有一位为高，否则状态机进入非法状态。解决方案：复位时赋值为 S0（8’b0000_0001），并在次态逻辑中加入非法状态恢复（如 default 回到 S0）。
- 坑 3：功耗分析时，如果测试向量不翻转（例如 sel 一直为 0，状态一直保持 S0），功耗差异不明显。解决方案：编写一个 testbench，使 sel 周期性变化，确保状态机遍历多个状态。
## 原理与设计说明
FSM 功耗主要由动态功耗构成，动态功耗公式为：`P_dynamic = 0.5 * C * V^2 * f * α`，其中 `α` 为翻转率（Toggle Rate）。降低 `α` 或减少负载电容 `C` 是优化关键。
**为什么 One-Hot 编码能降低功耗？**Binary 编码每次状态跳转可能有多位翻转（例如从 3’b011 到 3’b100 翻转 3 位），而 One-Hot 每次只有 2 位翻转（旧位变 0，新位变 1）。虽然寄存器数量增加，但每个寄存器的负载电容较小，且整体翻转次数减少，因此动态功耗降低。
**为什么门控时钟有效？**当状态机停留在同一状态时，状态寄存器的值不需要更新。门控时钟通过 CE 信号禁止寄存器翻转，使 `α` 降为 0。在状态机长时间保持的场景（如空闲状态），功耗降低显著。
**Trade-off 分析：**One-Hot 编码以寄存器换 LUT，适合寄存器资源丰富的 FPGA；门控时钟增加少量组合逻辑（state_change 比较器），但几乎不增加面积（利用 FF 自带 CE）。Binary 编码面积最小但功耗最高。实际选择需根据状态机停留时间分布：如果状态频繁跳转，One-Hot 优势减弱；如果长时间空闲，门控时钟效果最佳。
## 验证与结果
| 优化方法 | 寄存器 (FF) | LUT | 动态功耗 (mW) | Fmax (MHz) |
| --- | --- | --- | --- | --- |
| Binary (原始) | 3 | 8 | 2.5 | 250 |
| One-Hot | 8 | 5 | 2.0 | 280 |
| Binary + 门控时钟 | 3 | 9 | 1.8 | 245 |
测量条件：Vivado 2024.1，Artix-7 XC7A35T，时钟 50 MHz，测试向量使状态机每 5 个时钟周期跳转一次（翻转率约 20%）。动态功耗为综合后 Power Report 值。Fmax 通过 Timing Report 获得。
结论：门控时钟方案功耗最低（降低 28%），One-Hot 次之（降低 20%）。One-Hot 的 Fmax 最高，因为组合逻辑更浅。
## 故障排查（Troubleshooting）
- **现象：门控时钟后状态机不跳转。** 原因：`state_change` 信号在组合逻辑中产生，但被综合工具优化掉了。检查点：查看综合后的原理图，确认 CE 引脚连接。修复：在 `state_change` 赋值前加 `always @(posedge clk)` 打一拍。
- **现象：One-Hot FSM 进入非法状态（多个 bit 为 1）。** 原因：复位或组合逻辑毛刺。检查点：仿真中观察 state 信号。修复：在次态逻辑中加入 `default` 分支回到 S0，或使用 `casez` 处理。
- **现象：功耗优化后 Fmax 下降。** 原因：门控时钟引入了额外的组合逻辑延迟。检查点：查看 Timing Report 中最差路径。修复：在 `state_change` 路径上插入一级流水线寄存器。
- **现象：功耗报告显示动态功耗未降低。** 原因：测试向量翻转率过低或过高。检查点：确认 Power Report 中的 Toggle Rate 设定。修复：在约束文件中设置准确的翻转率，或使用 SAIF 文件反标。
- **现象：综合工具自动将 Binary 编码转为 One-Hot。** 原因：Vivado 的 FSM 综合选项（`-fsm_extraction`）默认为 auto。检查点：查看综合 log 中的 FSM 编码信息。修复：在综合属性中设置 `fsm_extraction = one_hot` 或 `binary` 以强制指定。
- **现象：门控时钟导致仿真时状态跳转延迟一个周期。** 原因：`state_change` 在组合逻辑中产生，但状态寄存器更新需要 CE 有效。检查点：仿真波形中对比 `state` 和 `next_state`。修复：这是正常行为，功能正确即可。
- **现象：资源占用比预期多。** 原因：One-Hot 编码的 case 语句被综合为优先级编码器而非并行多路器。检查点：查看综合后的 LUT 数量。修复：使用 `case (1'b1)` 并确保所有分支互斥，或使用 `parallel_case` 综合属性。
- **现象：功耗优化后输出信号出现毛刺。** 原因：门控时钟导致状态变化时间点偏移。检查点：在仿真中观察输出。修复：在输出逻辑中加入寄存器输出（时序输出）。
## 扩展与下一步
- 扩展 2：结合时钟门控与 One-Hot 编码，进一步降低功耗（需评估面积开销）。
<!– /wp:
- 扩展 1：参数化状态机，通过 `parameter` 控制状态数、编码方式，便于复用。
- 扩展 2：结合时钟门控与 One-Hot 编码，进一步降低功耗（需评估面积开销）。
<!– /wp:
