Verilog 中三段式状态机 vs 两段式:2026 年设计风格选择

FPGA小白
文章2026-07-14
26 0 0 0

Quick Start

  1. 打开 Vivado 2024.2(或更高版本),创建新工程,器件选择 XC7A35T-1CSG324C(Artix-7 系列)。
  2. 新建一个 Verilog 源文件 fsm_3seg.v,编写一个摩尔型三段式状态机:一个 always 块用于状态寄存器更新(时序逻辑),一个 always 块用于次态组合逻辑,一个 always 块用于输出组合逻辑。
  3. 再新建一个 Verilog 源文件 fsm_2seg.v,编写同样的摩尔型状态机,但使用两段式:一个 always 块用于状态寄存器更新(时序逻辑),一个 always 块同时包含次态组合逻辑和输出组合逻辑。
  4. 编写一个顶层模块 top.v,实例化两个状态机,并连接相同的输入(时钟、复位、输入信号)。
  5. 编写一个 testbench tb_top.v,提供 100 MHz 时钟,复位后施加相同的输入序列(例如:IDLE → S1 → S2 → S3 → IDLE),运行仿真 1 us。
  6. 观察仿真波形:对比两个状态机的输出是否完全一致(应在每个时钟沿对齐),并记录资源占用(LUT + FF)与最大频率(Fmax)。
  7. 在 Vivado 中运行综合(Synthesis),查看资源报告;再运行实现(Implementation),查看时序报告中的 Fmax。
  8. 如果输出不一致,检查组合逻辑中的敏感信号列表是否遗漏(三段式常见遗漏 state_next)。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 (XC7A35T)主流低端 FPGA,资源有限,便于对比资源差异Intel Cyclone V / Lattice ECP5
EDA 版本Vivado 2024.22026 年主流版本,支持 SystemVerilog 与综合优化Vivado 2023.x / Quartus Prime Pro 23+
仿真器Vivado Simulator (xsim)内置于 Vivado,零配置ModelSim / Questa / Verilator
时钟100 MHz(周期 10 ns)典型系统时钟,便于时序分析50 MHz / 200 MHz
复位异步复位,高电平有效与大多数开发板一致同步复位 / 低电平有效
接口依赖无外部接口纯 RTL 仿真与综合对比可连接 UART / GPIO 验证
约束文件XDC 约束:时钟周期 10 ns,输入输出延迟默认仅用于时序分析,无需物理引脚使用 create_clock 命令

目标与验收标准

  • 功能正确性:三段式与两段式状态机在相同输入序列下,输出波形完全一致(每个时钟沿对齐)。
  • 资源对比:三段式状态机通常比两段式多消耗 0~5% 的 LUT(因输出逻辑独立),但 FF 数量相同。
  • 时序性能:三段式状态机的 Fmax 应不低于两段式,甚至略高(因输出逻辑与次态逻辑分离,减少组合路径长度)。
  • 仿真验收:在 testbench 中通过 $display 打印状态转换序列,确认无毛刺或延迟。
  • 综合验收:Vivado 综合后无警告(尤其是关于 latch 或 incomplete sensitivity list 的警告)。

实施步骤

1. 工程结构与代码组织

  • 在 Vivado 中创建工程,添加 3 个源文件:fsm_3seg.v、fsm_2seg.v、top.v。
  • 使用相同的参数化状态编码(例如:localparam IDLE=2’d0, S1=2’d1, S2=2’d2, S3=2’d3)。
  • 顶层模块 top.v 仅做实例化,不添加额外逻辑。

2. 关键模块:三段式状态机(fsm_3seg.v)

module fsm_3seg (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       in,
    output reg  [1:0] out
);

localparam IDLE = 2'd0;
localparam S1   = 2'd1;
localparam S2   = 2'd2;
localparam S3   = 2'd3;

reg [1:0] state_reg, state_next;

// 第一段:状态寄存器更新(时序逻辑)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state_reg <= IDLE;
    else
        state_reg <= state_next;
end

// 第二段:次态组合逻辑
always @(*) begin
    state_next = state_reg;  // 默认保持
    case (state_reg)
        IDLE: if (in) state_next = S1;
        S1:   if (in) state_next = S2;
        S2:   if (in) state_next = S3;
        S3:   state_next = IDLE;
        default: state_next = IDLE;
    endcase
end

// 第三段:输出组合逻辑
always @(*) begin
    out = 2'b00;
    case (state_reg)
        IDLE: out = 2'b00;
        S1:   out = 2'b01;
        S2:   out = 2'b10;
        S3:   out = 2'b11;
        default: out = 2'b00;
    endcase
end

endmodule

逐行说明(三段式)

  • 第 1 行:模块声明,定义时钟、复位、输入和输出端口。输出为 reg 类型,因为它在组合逻辑 always 块中被赋值。
  • 第 5-8 行:使用 localparam 定义状态编码,避免使用 `define 造成的全局污染。
  • 第 10 行:声明 state_reg(当前状态寄存器)和 state_next(次态组合信号)。state_reg 是触发器输出,state_next 是组合逻辑。
  • 第 12-16 行:第一段 always 块,时序逻辑,敏感列表为时钟上升沿和复位下降沿。复位时 state_reg 回到 IDLE,否则更新为 state_next。
  • 第 18-26 行:第二段 always 块,组合逻辑(@(*)),计算次态。默认保持当前状态,根据输入 in 跳转。注意:state_next 在 case 外赋默认值,避免生成 latch。
  • 第 28-35 行:第三段 always 块,组合逻辑,输出仅由 state_reg 决定(摩尔型)。每个 case 分支都显式赋值,避免 latch。

3. 关键模块:两段式状态机(fsm_2seg.v)

module fsm_2seg (
    input  wire       clk,
    input  wire       rst_n,
    input  wire       in,
    output reg  [1:0] out
);

localparam IDLE = 2'd0;
localparam S1   = 2'd1;
localparam S2   = 2'd2;
localparam S3   = 2'd3;

reg [1:0] state_reg;

// 第一段:状态寄存器更新(时序逻辑)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state_reg <= IDLE;
    else
        state_reg <= state_next;  // 注意:state_next 是组合信号,需提前声明
end

// 第二段:次态与输出组合逻辑(合并)
always @(*) begin
    state_next = state_reg;  // 默认保持
    out = 2'b00;
    case (state_reg)
        IDLE: begin
            out = 2'b00;
            if (in) state_next = S1;
        end
        S1: begin
            out = 2'b01;
            if (in) state_next = S2;
        end
        S2: begin
            out = 2'b10;
            if (in) state_next = S3;
        end
        S3: begin
            out = 2'b11;
            state_next = IDLE;
        end
        default: begin
            out = 2'b00;
            state_next = IDLE;
        end
    endcase
end

endmodule

逐行说明(两段式)

  • 第 1 行:模块声明与三段式相同,输出仍为 reg 类型。
  • 第 5-8 行:状态编码与三段式一致,便于对比。
  • 第 10 行:只声明了 state_reg,但第 18 行使用了 state_next。实际上需要在第 10 行后补充声明 reg [1:0] state_next;,否则编译报错。这是一个常见遗漏点。
  • 第 12-16 行:第一段 always 块,时序逻辑,与三段式完全相同。
  • 第 18-42 行:第二段 always 块,组合逻辑,同时计算次态和输出。注意:state_next 和 out 的默认赋值必须写在 case 之前,否则未覆盖的分支会生成 latch。
  • 第 20-21 行:默认赋值,确保所有信号都有初始值。
  • 第 23-41 行:case 分支中,每个分支都同时更新 out 和 state_next,避免 latch。

4. 时序与约束

  • 创建 XDC 约束文件 top.xdc,内容:create_clock -period 10.000 -name sysclk [get_ports clk]。
  • 不需要设置输入输出延迟(纯内部逻辑对比)。
  • 运行综合后,检查时序报告中的 Setup Slack 和 Hold Slack。

5. 验证:Testbench 与仿真

module tb_top;

reg clk, rst_n, in;
wire [1:0] out_3seg, out_2seg;

top u_top (
    .clk(clk),
    .rst_n(rst_n),
    .in(in),
    .out_3seg(out_3seg),
    .out_2seg(out_2seg)
);

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

initial begin
    rst_n = 0;
    #20 rst_n = 1;
    #10 in = 1;
    #20 in = 0;
    #30 in = 1;
    #20 in = 0;
    #50 $finish;
end

initial begin
    $monitor("Time=%0t, in=%b, out_3seg=%b, out_2seg=%b", $time, in, out_3seg, out_2seg);
end

endmodule

逐行说明(Testbench)

  • 第 1 行:testbench 模块,无端口。
  • 第 3 行:声明激励信号和输出线网。
  • 第 5-11 行:实例化 top 模块,连接信号。
  • 第 13-15 行:时钟生成,周期 10 ns(100 MHz)。
  • 第 17-23 行:复位和输入激励序列,模拟简单状态转换。
  • 第 25-27 行:使用 $monitor 打印每个时间点的输出,便于对比。

6. 常见坑与排查

  • 坑 1:两段式中 state_next 未声明。检查:编译报错“Identifier state_next not declared”。修复:在 reg 声明区添加 reg [1:0] state_next;。
  • 坑 2:三段式输出 always 块敏感列表遗漏 state_reg。检查:Vivado 综合警告“Incomplete sensitivity list”。修复:使用 @(*) 自动包含所有信号。
  • 坑 3:两段式中输出赋值不全,导致 latch。检查:综合报告显示“Latch inferred”。修复:确保 case 的每个分支和 default 分支都赋值 out。
  • 坑 4:复位极性错误。检查:仿真中状态机不复位。修复:确认开发板复位电平,调整 rst_n 极性。

原理与设计说明

为什么三段式优于两段式(2026 年视角)?

  • 代码可维护性:三段式将状态跳转逻辑与输出逻辑分离,修改输出时不会影响状态机核心,减少引入 bug 的概率。两段式将两者混合,代码耦合度高。
  • 时序性能:三段式的输出逻辑独立,综合工具可以将其优化为独立的组合路径,不会与次态逻辑争抢逻辑资源。两段式将输出和次态逻辑合并,可能导致 LUT 级联变长,降低 Fmax。
  • 仿真调试:三段式中,state_next 和 state_reg 是独立信号,可以在波形中同时观察当前状态和下一状态,便于调试。两段式只有 state_reg,调试时无法直接看到次态。
  • 综合工具优化:现代综合工具(Vivado 2024+)对三段式有更好的识别能力,可以自动推断出状态机并在 FSM 优化阶段进行独热码或格雷码重编码。两段式虽然也能识别,但输出逻辑的混合可能降低优化效果。
  • 资源对比:三段式通常不会增加 FF 数量,但可能多消耗 1~2 个 LUT(因为输出逻辑独立)。在 XC7A35T 上,这种差异可以忽略不计(< 0.1% 资源)。

两段式何时仍然可用?

  • 非常简单的状态机(状态数 ≤ 4,输出与状态一一对应):两段式的代码量更少,阅读更直观。
  • 资源极度受限(例如 CPLD 或超低端 FPGA):每节省一个 LUT 都有意义,两段式可能减少 1~2 个 LUT。
  • 团队编码规范强制要求:某些老项目可能沿用两段式风格,保持一致性比引入新风格更重要。

验证与结果

指标三段式两段式测量条件
LUT 消耗87Vivado 2024.2 综合,Artix-7,4 状态摩尔机
FF 消耗22同上
Fmax(示例值)350 MHz340 MHz时序约束 10 ns,最差 PVT 条件下
仿真输出一致性与两段式完全对齐与三段式完全对齐100 MHz 时钟,相同输入序列
综合警告数00代码正确编写时

说明:以上资源与 Fmax 数值为示例值,基于典型 4 状态摩尔型状态机。实际值因状态数、输出逻辑复杂度、器件型号而异,请以实际工程报告为准。

故障排查(Troubleshooting)

  • 现象:仿真输出为 X 或 Z → 原因:未初始化状态寄存器或复位未生效。检查:确认复位信号在仿真开始时有效,且 state_reg 被赋初值。
  • 现象:综合报错“Latch inferred” → 原因:组合逻辑中未覆盖所有分支。检查:确保 case 语句有 default 分支,且每个分支都赋值了所有输出。
  • 现象:两段式编译报错“state_next not declared” → 原因:忘记声明 state_next 信号。检查:在 reg 声明区添加 reg [1:0] state_next;。
  • 现象:三段式输出延迟一个时钟周期 → 原因:输出 always 块误用了时序逻辑(posedge clk)。检查:输出 always 块应使用 @(*),而非 @(posedge clk)。
  • 现象:状态机跳转到非法状态 → 原因:状态编码未覆盖所有可能值,或复位未生效。检查:使用 localparam 定义所有状态,并在 default 分支中回到 IDLE。
  • 现象:Fmax 远低于预期 → 原因:输出逻辑过于复杂或级联太多。检查:将输出逻辑拆分为流水线,或使用三段式分离输出路径。
  • 现象:资源报告显示大量 LUT 用于状态机 → 原因:状态编码为二进制,综合工具未优化。检查:在综合属性中启用 FSM 优化(Vivado: set_property FSM_EXTRACT 1 [current_design])。
  • 现象:仿真中状态机不跳转 → 原因:输入信号未在时钟沿附近变化。检查:testbench 中 in 信号应在时钟沿之后变化,避免建立时间违例。
  • 现象:两段式输出出现毛刺 → 原因:组合逻辑输出直接连接到外部,未经过 FF。检查:如果输出需要同步到时钟域,应在输出后添加一级寄存器。
  • 现象:Vivado 综合警告“FSM encoding not recognized” → 原因:状态机写法不规范,综合工具无法识别为 FSM。检查:确保状态寄存器使用 reg 类型,且状态跳转逻辑在 always 块中。

扩展与下一步

  • SystemVerilog 改进:使用 enum 类型定义状态,使用 always_comb 和 always_ff 增强可读性,并让综合工具更好地检查。
  • <!– /
    • 参数化状态机:使用 parameter 定义状态数和编码方式(二进制、独热码、格雷码),并在三段式基础上封装为可复用 IP。
    • 输出流水线:如果输出逻辑复杂(例如多周期计算),可以在三段式基础上增加一级输出寄存器,变成四段式,进一步提升 Fmax。
    • 跨时钟域状态机:将状态机的输入/输出通过双触发器同步器跨时钟域,学习 CDC 设计方法。
    • 形式验证:使用 SymbiYosys 或 JasperGold 对状态机进行形式化验证,证明其不会进入非法状态。
    • SystemVerilog 改进:使用 enum 类型定义状态,使用 always_comb 和 always_ff 增强可读性,并让综合工具更好地检查。
    • <!– /
    分类
    技术分享
    标签
    Verilog三段式状态机两段式状态机
    浏览 26收藏 0赞 0评论 0
    分享:

    相关推荐

    同频道 · 相近分类

    暂无相关推荐

    作者

    FPGA小白查看主页

    同分类阅读

    文章

    延伸阅读与实操

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

    探索全站