一、三段式状态机概述
三段式状态机是 FPGA 设计中描述有限状态机(FSM)的经典方法,它将状态机的状态转移、次态逻辑和输出逻辑分别写在三个独立的 always 块中。相比于一段式、二段式,三段式结构具有代码清晰、时序明确、易于维护、避免竞争冒险等优点。
重点:三个
always块各自分工,实现时序逻辑与组合逻辑分离。
二、三段式状态机的标准写法
2.1 模块结构
module fsm_example (
input clk,
input rst_n,
input condition, // 输入条件,决定状态转移
output reg out_signal // 输出信号
);
2.2 第一段:状态寄存器(时序逻辑)
作用:当前时钟沿将 next_state 赋给 current_state,实现状态寄存。
// 第一段:状态转移(时序)
reg [1:0] current_state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
注意:
使用非阻塞赋值
<=复位应使状态回到初始状态(如
IDLE)
2.3 第二段:次态逻辑(组合逻辑)
作用:根据 current_state 和输入条件,计算 next_state。
// 第二段:次态计算(组合)
always @(*) begin
case (current_state)
IDLE : if (condition) next_state = WORK;
else next_state = IDLE;
WORK: if (done) next_state = STOP;
else next_state = WORK;
STOP: next_state = IDLE;
default: next_state = IDLE;
endcase
end
注意:
-
使用阻塞赋值
=(组合逻辑) -
敏感列表写
*代表所有输入信号(避免综合出锁存器) -
必须写
default防止综合出锁存器 -
所有可能状态分支都要明确赋值
2.4 第三段:输出逻辑(时序/组合均可)
输出逻辑可以有两种风格:时序输出(推荐,可滤除毛刺)或组合输出(速度快但有毛刺风险)。
✅ 推荐:时序输出(与状态寄存器同步)
// 第三段:输出逻辑(时序)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
out_signal <= 1'b0;
else begin
case (current_state)
WORK : out_signal <= 1'b1;
default: out_signal <= 1'b0;
endcase
end
end
组合输出写法(不推荐,除非对延迟要求极高)
always @(*) begin
case (current_state)
WORK : out_signal = 1'b1;
default: out_signal = 1'b0;
endcase
end
重点:时序输出能避免组合逻辑产生的毛刺,且方便时序约束,是 FPGA 工程实践的首选。
三、完整代码示例(以 Moore 型状态机为例)
module moore_fsm (
input clk,
input rst_n,
input start,
input done,
output reg led
);
// 状态编码(建议使用 localparam)
localparam IDLE = 2'b00;
localparam WORK = 2'b01;
localparam STOP = 2'b10;
reg [1:0] current_state, next_state;
// ----- 第一段:状态寄存器 -----
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= IDLE;
else
current_state <= next_state;
end
// ----- 第二段:次态逻辑 -----
always @(*) begin
case (current_state)
IDLE : next_state = start ? WORK : IDLE;
WORK : next_state = done ? STOP : WORK;
STOP : next_state = IDLE;
default: next_state = IDLE;
endcase
end
// ----- 第三段:输出逻辑(时序)-----
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led <= 1'b0;
else begin
case (current_state)
WORK : led <= 1'b1;
default: led <= 1'b0;
endcase
end
end
endmodule
四、重要注意事项(重点)
| 注意点 | 详细说明 |
|---|---|
| 编码方式 | 状态使用 localparam 定义,推荐独热码(one-hot)(占用触发器多但译码快、无毛刺)或格雷码(适用于高速异步时钟)。避免在代码中写硬编码的数字。 |
| 复位 | 必须有异步或同步复位,使状态机进入确定初始状态。推荐异步复位(negedge rst_n),注意复位释放需满足恢复时间。 |
| 第二段组合逻辑 | 必须使用 always @(*),条件分支必须完整(case 中加 default,if-else 中覆盖所有路径),否则综合出锁存器,导致功能错误。 |
| 第三段输出方式 | 强烈推荐时序输出(放在时钟 always 中),避免组合输出的毛刺;如果输出需要立即响应(无延迟),可以组合输出,但需要手动处理毛刺(如加滤波)。 |
| 阻塞 vs 非阻塞 | 时序逻辑(状态寄存、时序输出)用 <=;组合逻辑(次态计算、组合输出)用 =。混用会仿真出错。 |
| 状态转移条件 | 避免在一个状态内同时判断多个复杂条件,可分解成多个小状态;条件表达式应消除竞争(如输入已同步到当前时钟域)。 |
| 综合属性 | 可添加 syn_encoding 或 (* fsm_encoding = "one_hot" *) 指导综合器优化。 |
| 仿真调试 | 建议在 testbench 中打印状态名(用 $display),避免直接看二进制难以调试。 |
五、常见错误及避免方法
-
缺少
default-
现象:综合出锁存器,状态机可能卡死。
-
解决:
case语句最后加default: next_state = IDLE;
-
-
次态逻辑中使用非阻塞赋值
-
现象:仿真时 next_state 更新慢一拍,状态转移出错。
-
解决:组合逻辑一律用
=。
-
-
输出逻辑使用
current_state却放在组合块中-
现象:输出有毛刺,导致后级误触发。
-
解决:改用时序输出,或对组合输出加寄存器。
-
-
状态编码冲突
-
现象:状态机进入无效状态。
-
解决:在
default分支中让next_state回到安全状态(如IDLE),或添加状态保护逻辑。
-
-
跨时钟域处理
-
输入信号(如
start,done)若来自不同时钟域,必须先用两级寄存器同步,否则亚稳态会导致状态错误。
-
六、进阶技巧(提升可靠性)
-
状态机“一 hot”写法:可以使用
(* fsm_encoding = "one_hot" *)让综合器自动生成独热码,提高速度和抗干扰能力。 -
三段式 + 输出寄存器:即使需要组合输出,也建议在输出后加一级寄存器(即第四段),彻底消除毛刺。
-
仿真保护:在 testbench 中模拟进入非法状态,检查状态机是否能自动恢复到有效状态。
-
使用 SystemVerilog:可以用
enum定义状态,更清晰且支持自动编码。
七、总结
| 段落 | 功能 | 敏感信号 | 赋值方式 | 关键要点 |
|---|---|---|---|---|
| 第一段 | 状态寄存 | 时钟/复位 | 非阻塞 <= | 同步或异步复位 |
| 第二段 | 次态计算 | 所有输入(*) | 阻塞 = | 完整分支 + default |
| 第三段 | 输出逻辑 | 时钟/复位(时序输出)或 *(组合输出) | 非阻塞/阻塞 | 优先选时序输出避免毛刺 |
核心原则:
时序逻辑与组合逻辑严格分离,每个always块扮演唯一角色。这样写出的状态机可读性高、易调试、综合结果可靠,是工业级 FPGA 设计的最佳实践。
5万+

被折叠的 条评论
为什么被折叠?



