有限状态机(Finite State Machine, FSM)是数字逻辑设计的核心模式,用于描述具有有限个状态并按特定规则在这些状态间转换的系统。在Verilog HDL中,FSM的编码风格直接决定了设计的可读性、可维护性、综合后的资源与性能表现,以及时序收敛的难易程度。本文旨在系统对比一段式、两段式与三段式FSM编码方法,提供从快速上手到深入理解其原理与影响的完整实施指南,帮助工程师根据具体设计需求选择最合适的编码范式。
快速上手指南
本部分提供一个基于三段式FSM的快速实现流程,帮助你快速建立概念并运行第一个状态机设计。
- 步骤一:确定状态机模型。明确状态集合(例如:IDLE, READ, WRITE, DONE)以及触发状态转换的输入条件。
- 步骤二:创建Verilog模块文件。例如,创建名为
fsm_demo.v的源文件。 - 步骤三:描述状态寄存器(时序逻辑)。使用一个独立的
always @(posedge clk or posedge rst)块,在时钟上升沿或复位信号有效时更新当前状态寄存器(state_reg)。 - 步骤四:描述次态逻辑(组合逻辑)。使用另一个
always @(*)块,内部通过case(state_reg)语句,根据当前状态和输入信号计算下一个状态(next_state)。 - 步骤五:描述输出逻辑。使用第三个
always @(*)块。对于摩尔(Moore)型输出,仅基于case(state_reg)赋值;对于米利(Mealy)型输出,则需结合case(state_reg)与输入信号进行判断。 - 步骤六:定义状态编码。推荐使用
parameter或`define声明状态常量,例如:parameter IDLE = 2'b00, READ = 2'b01;。 - 步骤七:编写测试平台。创建一个简单的Testbench,生成时钟、复位信号以及符合状态转换条件的输入激励。
- 步骤八:进行行为仿真。使用仿真工具(如ModelSim, VCS)运行仿真,观察状态转换序列和输出波形是否符合设计预期。
- 步骤九:执行逻辑综合。使用综合工具(如Vivado, Quartus)对设计进行综合,查看报告中的资源占用(LUTs, Registers)和时序分析(Setup/Hold Time)。
- 步骤十:对比不同风格。尝试将设计改为两段式(合并次态与输出逻辑)和一段式(所有逻辑在一个always块中),重新综合并对比网表结构与时序报告的差异。
前置条件与环境
- 掌握Verilog HDL基础语法,理解always块、阻塞/非阻塞赋值。
- 安装并配置好FPGA开发工具链(任一即可),例如:Xilinx Vivado、Intel Quartus Prime,或仿真工具如ModelSim。
- 了解同步数字电路的基本概念,如时钟、复位、时序路径。
目标与验收标准
通过本指南,你将能够:
- 正确实现:独立编写出功能正确的三段式、两段式和一段式FSM Verilog代码。
- 清晰区分:明确说出三种编码风格在代码结构、综合结果和适用场景上的核心区别。
- 合理选择:针对给定的设计需求(如对面积、速度、代码清晰度的侧重),能给出编码风格选择的合理解释。
- 完成验证:通过仿真波形验证状态转换与输出逻辑的正确性,并通过综合报告初步评估设计质量。
实施步骤详解
以下对三种编码风格的实施细节进行展开说明。
1. 三段式FSM(推荐)
这是最清晰、最易于维护和调试的风格,将状态转移的时序部分、次态判断的组合部分、输出产生的组合部分严格分离。
- 第一个always块(时序):仅负责在时钟边沿将
next_state锁存到state_reg。这确保了状态转换严格同步于时钟,避免了异步逻辑可能带来的毛刺和时序问题。 - 第二个always块(组合):使用
case语句描述状态转移图。其敏感列表应包含所有影响次态的变量(使用always @(*)可自动推断)。注意避免生成锁存器(Latch),确保所有条件分支都有明确的next_state赋值。 - 第三个always块(组合/时序):描述输出。摩尔输出仅与当前状态有关;米利输出与当前状态和输入有关。若输出需要避免毛刺,也可用时序逻辑实现(输出寄存器化),但这会引入一个时钟周期的延迟。
2. 两段式FSM
将上述三段式中的次态逻辑和输出逻辑合并到一个组合always块中,状态寄存器仍独立为一个时序always块。
- 优点:代码量稍减。对于输出逻辑简单的状态机,结构更紧凑。
- 缺点与风险:合并后,组合逻辑块可能变得复杂,可读性下降。更重要的是,米利型输出的毛刺问题会被带入,因为输出直接由快速变化的组合逻辑产生。在时钟频率较高或路径较长时,可能影响下游电路的稳定性。
3. 一段式FSM
将所有逻辑(状态转移、输出)塞进一个时序always块中,在时钟边沿判断条件并直接更新状态和输出。
- 优点:代码最为简短,且输出天然是寄存器输出,无毛刺。
- 核心缺点与机制分析:严重牺牲可读性与可维护性。状态转移、多种输出、复杂条件判断混杂在一起,调试困难。从综合角度看,它迫使综合工具将复杂的组合逻辑(次态和输出计算)与寄存器打包在同一个时序路径内,可能限制电路的最高工作频率,因为组合逻辑的延迟直接加在了时钟到输出的路径或状态回环路径上。此外,它难以清晰地区分摩尔和米利输出模型。
原理与设计说明
编码风格的选择本质上是对逻辑进行划分与寄存的决策。三段式风格严格遵循了同步设计原则:
- 时序路径明确:从状态寄存器出发,经过次态组合逻辑,再回到状态寄存器,构成一个清晰的时序环,便于静态时序分析(STA)。
- 组合输出可控:输出的组合逻辑独立成块,方便根据需要将其寄存器化以消除毛刺或缩短关键路径。
- 综合友好:清晰的结构有助于综合工具进行更好的优化和映射,结果更可预测。
相比之下,一段式风格模糊了这些边界,将设计负担转移给了工程师(在代码中管理混乱)和综合工具(在模糊的约束下进行优化),增加了后期调试和时序收敛的风险。
验证与结果分析
完成编码后,应通过以下步骤验证:
- 仿真验证:在Testbench中构造覆盖所有状态和转移路径的测试用例。重点观察:复位后是否进入初始状态;每个状态在特定输入下是否正确跳转;输出值(特别是米利输出)是否在正确的时刻发生变化(组合输出立即变,寄存器输出下一个时钟变)。
- 综合结果对比:将三种风格的代码分别综合到同一FPGA器件。对比综合报告:
1. 资源使用:三者使用的查找表(LUT)和触发器(FF)数量通常接近,但一段式可能因为逻辑打包方式不同而有细微差别。
2. 时序性能:关注“最差负时序裕量(Worst Negative Slack, WNS)”。三段式设计因其路径清晰,通常更容易达到更高的Fmax,或是在相同频率下具有更好的时序裕量。一段式设计的组合逻辑延迟可能成为关键路径的瓶颈。 - 网表查看:使用综合后的原理图查看器,观察三段式代码生成的网表通常能清晰地看到状态寄存器、次态逻辑云、输出逻辑云三个部分,而一段式代码则混合成一个大的逻辑块。
故障排查
- 状态机“卡死”在某一个状态:检查次态逻辑的
case语句是否覆盖了所有可能的状态值(使用default分支),并确保在每个状态分支下,对next_state都进行了赋值。 - 仿真中出现未预期的锁存器(Latch):在组合always块中,如果存在某些输入条件下输出变量或
next_state未被赋值,综合工具就会推断出锁存器。确保在所有可能的条件分支下,组合逻辑的输出都有明确值。 - 输出有毛刺:这是两段式FSM中米利输出的典型问题。解决方案是:1)将米利输出改为摩尔输出(如果系统允许);2)将输出逻辑用时序always块实现,对输出进行寄存,牺牲一个时钟周期的延迟来换取稳定。
- 时序违例:如果状态机路径成为关键路径,可以尝试:1)输出寄存器化;2)检查状态编码,使用格雷码(Gray Code)或独热码(One-Hot Code)来减少状态跳转时翻转的位数,从而降低组合逻辑复杂度。
扩展与下一步
- 状态编码优化:深入研究二进制编码、格雷码、独热码(One-Hot)对面积和速度的影响。独热码常用于FPGA设计,因其状态比较逻辑简单,但占用触发器较多。
- 使用SystemVerilog增强:采用SystemVerilog的
enum枚举类型定义状态,使代码更安全、更易读,并减少编码错误。 - FSM与数据路径分离:学习“FSMD(有限状态机与数据路径)”设计模式,将控制逻辑(FSM)与数据处理逻辑分离,这是构建复杂数字系统的基础。
- 形式化验证应用:对于安全关键的设计,可探索使用形式化验证工具来数学化地证明状态机属性的正确性,而不仅仅是仿真测试。
参考与信息来源
- Samir Palnitkar, 《Verilog HDL: A Guide to Digital Design and Synthesis》.
- Clifford E. Cummings, “The Fundamentals of Efficient Synthesizable Finite State Machine Design using NC-Verilog and BuildGates” (SNUG论文).
- Xilinx UG901 (Vivado Design Suite User Guide: Synthesis) 中关于FSM编码的章节。
- IEEE Standard for SystemVerilog (IEEE Std 1800-2017).
技术附录
代码片段示例(三段式摩尔型FSM):
module fsm_moore (
input wire clk,
input wire rst_n,
input wire start,
input wire done,
output reg [1:0] status
);
// 状态定义
parameter S_IDLE = 2'b00;
parameter S_BUSY = 2'b01;
parameter S_DONE = 2'b10;
reg [1:0] state_reg, state_next;
// 1. 状态寄存器(时序逻辑)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state_reg <= S_IDLE;
else
state_reg <= state_next;
end
// 2. 次态逻辑(组合逻辑)
always @(*) begin
state_next = state_reg; // 默认保持当前状态
case (state_reg)
S_IDLE: if (start) state_next = S_BUSY;
S_BUSY: if (done) state_next = S_DONE;
S_DONE: state_next = S_IDLE;
default: state_next = S_IDLE;
endcase
end
// 3. 输出逻辑(摩尔型,组合逻辑)
always @(*) begin
case (state_reg)
S_IDLE: status = 2'b00;
S_BUSY: status = 2'b01;
S_DONE: status = 2'b10;
default: status = 2'b00;
endcase
end
endmodule(注:实际应用中,输出逻辑根据需求也可用时序逻辑实现,以消除毛刺或满足时序要求。)



