📌 本篇导读
| 知识点 | 难度 | 实战价值 | 典型场景 |
|---|---|---|---|
| FSM 三要素 | ⭐⭐ | ⭐⭐⭐⭐⭐ | 状态、输入、输出 |
| 摩尔型 vs 米利型 | ⭐⭐ | ⭐⭐⭐⭐ | 输出与时钟/输入的关系 |
| 三段式 FSM 写法 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 最稳健的编码风格 |
| 状态编码(独热/二进制/格雷) | ⭐⭐⭐ | ⭐⭐⭐⭐ | 面积与速度权衡 |
unique case 与状态转移 |
⭐⭐ | ⭐⭐⭐⭐⭐ | 消除歧义,提高安全性 |
| 复位同步器与安全状态机 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 避免上电/复位亚稳态 |
| 自启动与异常处理 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 避免挂死在非法状态 |
💡 常见误区:摩尔型组合输出误认为无毛刺;米利型输出未同步直接使用;状态转移条件优先级错误;次态逻辑漏默认赋值产生 latch;枚举未指定位宽浪费资源。
🧭 本文全部代码 综合可用 (除
ifndef SYNTHESIS部分),符合 IEEE 1800-2017。
1 FSM 的基本概念
有限状态机是时序逻辑的核心,由三部分组成:
- 状态寄存器:当前状态(时序逻辑)
- 次态逻辑:根据当前状态和输入计算下一个状态(组合逻辑)
- 输出逻辑:根据当前状态(或状态+输入)产生输出
1.1 摩尔型(Moore)vs 米利型(Mealy)
| 类型 | 输出依赖 | 特点 | 注意事项 |
|---|---|---|---|
| 摩尔型 | 仅当前状态 | 输出稳定(时序输出时无毛刺),延迟一个时钟 | 组合输出仍有毛刺,推荐时序输出 |
| 米利型 | 当前状态 + 输入 | 响应快,但组合输出可能产生毛刺 | 输出必须同步打拍后再使用 |
对比图

结论:
- 摩尔组合输出:与状态同时变化,但有毛刺风险
- 摩尔时序输出(用
next_state):与状态同步,无毛刺 - 米利同步输出:比输入晚1个时钟,无毛刺
1.2 复位类型选择
| 复位类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 异步复位 | 不依赖时钟,上电即复位;不占用组合逻辑 | 复位释放时接近时钟沿产生亚稳态 | FPGA 设计(配合复位同步器) |
| 同步复位 | 无亚稳态风险;便于时序分析 | 依赖时钟,上电无时钟时无法复位 | ASIC、高可靠性设计 |
异步复位同步释放(推荐用于所有异步复位设计):
systemverilog
module rst_sync (
input logic clk,
input logic rst_n_async, // 外部异步复位
output logic rst_n_sync // 同步后的复位
);
logic rst_n_ff1;
always_ff @(posedge clk or negedge rst_n_async) begin
if (!rst_n_async) begin
rst_n_ff1 <= 1'b0;
rst_n_sync <= 1'b0;
end
else begin
rst_n_ff1 <= 1'b1;
rst_n_sync <= rst_n_ff1;
end
end
endmodule
2 三段式 FSM 编码风格(工业标准)
2.1 模板:摩尔型 + 时序输出(无毛刺)+ unique case
systemverilog
module fsm_moore_optimized (
input logic clk,
input logic rst_n_sync, // 已同步的复位信号
input logic din,
output logic dout
);
// 状态编码:独热码(FPGA最优),兼容主流工具
(* fsm_encoding = "one_hot", syn_encoding = "onehot" *)
typedef enum logic [2:0] {
S_IDLE = 3'b001,
S_GOT1 = 3'b010,
S_GOT11 = 3'b100
} state_t;
// 实际项目状态数多于3个时,按需扩展
state_t cur, nxt;
// 第一段:状态寄存器(同步复位)
always_ff @(posedge clk) begin
if (!rst_n_sync)
cur <= S_IDLE;
else
cur <= nxt;
end
// 第二段:次态逻辑(组合,必须默认赋值 + unique case)
always_comb begin
nxt = cur; // 默认保持,避免 latch
unique case (cur)
S_IDLE: if (din) nxt = S_GOT1;
S_GOT1: if (din) nxt = S_GOT11; else nxt = S_IDLE;
S_GOT11: if (!din) nxt = S_IDLE; else nxt = S_GOT11;
default: nxt = S_IDLE; // 自启动
endcase
end
// 第三段:时序输出(无毛刺,使用 next_state)
always_ff @(posedge clk) begin
if (!rst_n_sync)
dout <= 1'b0;
else
dout <= (nxt == S_GOT11); // 示例:状态 S_GOT11 时输出1
end
// 仿真断言:检测非法状态(独热码检查)
`ifndef SYNTHESIS
assert property (@(posedge clk) disable iff (!rst_n_sync)
$onehot0(cur)) else $error("[%0t] 非法状态:%b", $time, cur);
`endif
endmodule
2.2 米利型输出同步模板
systemverilog
// 米利型组合输出 + 同步打拍
logic out_comb;
always_comb begin
out_comb = 1'b0;
if (cur == S_DATA && din_valid) // 依赖输入
out_comb = 1'b1;
end
always_ff @(posedge clk or negedge rst_n_sync) begin
if (!rst_n_sync)
out_sync <= 1'b0;
else
out_sync <= out_comb;
end
2.3 混合状态机(工程常用)
systemverilog
// 同时产生摩尔型稳定输出和米利型快速响应输出
always_comb begin
// 摩尔部分:仅依赖当前状态
led_on = (cur == S_ACTIVE);
// 米利部分:依赖状态+输入
intr_comb = (cur == S_DATA && din_last);
end
3 状态编码与工具指令
| 编码方式 | 特点 | 触发器数 | 组合复杂度 | 适用场景 |
|---|---|---|---|---|
| 二进制 | 顺序编码,N→log₂N | 最少 | 较高 | ASIC、面积优先 |
| 格雷码 | 相邻仅1bit变化 | 较少 | 中等 | 异步FIFO指针 |
| 独热码 | 每状态1位 | 最多 | 最简单 | FPGA、高速设计 |
3.1 综合指令(兼容主流工具)
systemverilog
// Xilinx Vivado, Synopsys DC, Intel Quartus 通用写法
(* fsm_encoding = "one_hot", syn_encoding = "onehot" *)
typedef enum logic [3:0] {
S_IDLE = 4'b0001,
S_START = 4'b0010,
S_DATA = 4'b0100,
S_STOP = 4'b1000
} state_t;
3.2 独热码优势原理
- FPGA 基本单元为 LUT + 触发器,触发器资源丰富,独热码占用更多触发器但大幅简化次态逻辑。
- 次态判断只需检查 1 位,路径延迟极短,有利于时序收敛。
- 典型适用:状态数 ≤ 32,时钟频率 > 200MHz。
4 自启动与异常处理
4.1 代码级安全措施
systemverilog
always_comb begin
nxt = cur; // 必须的默认赋值
unique case (cur)
// 正常状态转移
default: nxt = S_IDLE; // 捕获非法状态
endcase
end
4.2 工具级安全状态机
- Vivado :综合选项
-safe_fsm,自动将非法状态跳转到复位状态。 - DC :
set_fsm_safe命令。 - 使用时无需修改 RTL,但会增加面积。
4.3 独热码非法状态显式检测
systemverilog
logic fsm_error;
always_comb begin
unique case (1'b1)
cur[0]: fsm_error = 1'b0;
cur[1]: fsm_error = 1'b0;
cur[2]: fsm_error = 1'b0;
cur[3]: fsm_error = 1'b0;
default: fsm_error = 1'b1; // 多位为1或全0
endcase
end
always_ff @(posedge clk or negedge rst_n_sync) begin
if (!rst_n_sync)
fsm_rst <= 1'b0;
else if (fsm_error)
fsm_rst <= 1'b1; // 触发软复位
else
fsm_rst <= 1'b0;
end
5 完整设计实例:1101 序列检测器(重叠)
5.1 设计代码
systemverilog
module seq_detector_1101 (
input logic clk,
input logic rst_n_sync,
input logic din,
output logic dout
);
typedef enum logic [4:0] {
S_IDLE = 5'b00001,
S_GOT1 = 5'b00010,
S_GOT11 = 5'b00100,
S_GOT110 = 5'b01000,
S_MATCH = 5'b10000
} state_t;
state_t cur, nxt;
always_ff @(posedge clk) begin
if (!rst_n_sync)
cur <= S_IDLE;
else
cur <= nxt;
end
always_comb begin
nxt = cur;
unique case (cur)
S_IDLE: if (din) nxt = S_GOT1; else nxt = S_IDLE;
S_GOT1: if (din) nxt = S_GOT11; else nxt = S_IDLE;
S_GOT11: if (!din) nxt = S_GOT110; else nxt = S_GOT11;
S_GOT110: if (din) nxt = S_MATCH; else nxt = S_IDLE;
S_MATCH: if (din)
nxt = S_GOT11;
else
nxt = S_IDLE;
default: nxt = S_IDLE;
endcase
end
always_ff @(posedge clk) begin
if (!rst_n_sync)
dout <= 1'b0;
else
dout <= (nxt == S_MATCH);
end
endmodule
5.2 Testbench(带自动比对 & 下降沿驱动)
systemverilog
module tb_seq_detector;
logic clk, rst_n_sync, din, dout;
logic dout_ref; // 参考模型
// 实例化 DUT
seq_detector_1101 u_dut (.*);
// 参考模型:移位寄存器 + 重叠检测
logic [3:0] sr;
always_ff @(posedge clk or negedge rst_n_sync) begin
if (!rst_n_sync)
sr <= 4'b0;
else
sr <= {sr[2:0], din};
end
assign dout_ref = (sr == 4'b1101);
// 时钟:10ns 周期
always #5 clk = ~clk;
// 测试激励
initial begin
clk = 0; rst_n_sync = 0; din = 0;
#10 rst_n_sync = 1;
// 测试序列:1,1,0,1 -> 匹配
@(negedge clk) din = 1;
@(negedge clk) din = 1;
@(negedge clk) din = 0;
@(negedge clk) din = 1; // 第一个匹配
// 重叠测试:继续发送 1,0,1,1,0,0...
@(negedge clk) din = 1;
@(negedge clk) din = 0;
@(negedge clk) din = 1; // 重叠匹配(1101 中的 1 作为新序列首)
@(negedge clk) din = 1;
@(negedge clk) din = 0;
@(negedge clk) din = 0;
@(negedge clk) din = 0;
#20 $finish;
end
// 自动比对(在时钟上升沿后采样)
always @(posedge clk) begin
#1; // 等待输出稳定
if (rst_n_sync && (dout !== dout_ref)) begin
$error("[%0t] 错误!dout=%0d, 期望=%0d", $time, dout, dout_ref);
end
else if (rst_n_sync) begin
$display("[%0t] 正确 dout=%0d", $time, dout);
end
end
endmodule

6 常见错误与编码建议(10条必读)
- 次态逻辑忘记默认赋值 → 产生 latch。必须写
nxt = cur;。 - 仅靠
default防 latch →default只负责自启动,不能替代默认赋值。 - 输出逻辑未赋默认值 → 组合输出也会产生 latch。必须写
out = 1'b0;。 - 枚举未指定位宽 → 默认 32bit,浪费资源。写
enum logic [2:0] {...}。 - 米利型输出直接使用 → 有毛刺且异步,打一拍再使用。
- 测试激励与时钟沿对齐 → 违反建立时间,用
@(negedge clk)驱动输入。 - 使用
always @(*)而非always_comb→always_comb更安全,自动初始化。 - 次态逻辑用非阻塞赋值 → 组合逻辑必须用
=。 - 异步复位无同步释放 → 会产生亚稳态,使用复位同步器。
- 状态机无
default或自启动 → 一旦进入非法状态就挂死,必须处理。
7 工程调试技巧
7.1 FPGA 调试信号选取(ILA)
current_state→ 观察实际状态转移next_state→ 检查次态计算- 关键输入(
din,valid等) - 关键输出(
dout,interrupt等)
7.2 状态机划分原则
- 一个状态机负责一个独立功能,状态数 ≤ 20。
- 复杂系统拆分为多个子状态机,通过握手信号通信。
- 状态命名体现操作,如
S_TX_START,S_RX_WAIT_DATA。
📢 关于文章 :原创分享,转载需注明出处。
🔔 点击关注,第一时间收到后续内容推送(第9篇:验证基础与简单Testbench)。
📚 参考文档 :IEEE Standard for SystemVerilog