Quick Start
打开 Vivado 2024.2,创建一个新工程,目标器件选择 xc7a35tcsg324-1(Artix-7)。在工程中新建一个 Verilog 文件,定义一个 4 状态(IDLE, S1, S2, S3)的摩尔状态机,输出为当前状态值。首先采用二进制编码(localparam IDLE = 2'b00, S1 = 2'b01, S2 = 2'b10, S3 = 2'b11;),综合后查看资源报告(LUT/FF)和时序报告(Fmax)。然后将编码方式改为独热码(localparam IDLE = 4'b0001, S1 = 4'b0010, S2 = 4'b0100, S3 = 4'b1000;),重新综合,对比资源与 Fmax。再改为格雷码(localparam IDLE = 2'b00, S1 = 2'b01, S2 = 2'b11, S3 = 2'b10;),再次综合对比。观察综合报告:二进制编码通常 LUT 最少,独热码 Fmax 最高但 FF 多,格雷码介于两者之间。
预期结果:在 Artix-7 上,4 状态机二进制编码约 4 LUT + 2 FF,Fmax ~ 500 MHz;独热码约 6 LUT + 4 FF,Fmax ~ 600 MHz。实际值因综合选项略有浮动。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | xc7a35tcsg324-1 (Artix-7) | 主流低成本 FPGA,逻辑资源适中 | xc7k70t (Kintex-7) 或 Cyclone V |
| EDA 版本 | Vivado 2024.2 | 支持 FSM 编码属性设置 | Quartus Prime 23.1 |
| 仿真器 | Vivado Simulator | 内嵌,无需额外安装 | ModelSim / Questa |
| 时钟/复位 | 100 MHz 时钟,异步复位 | 标准测试条件 | 50 MHz 或 200 MHz |
| 接口依赖 | 无外部接口 | 纯内部状态机测试 | 可接 LED 显示状态 |
| 约束文件 | create_clock -period 10.000 [get_ports clk] | 100 MHz 时钟约束 | 根据实际频率调整 |
目标与验收标准
- 功能点:状态机按预期顺序跳转(IDLE → S1 → S2 → S3 → IDLE),输出当前状态编码。
- 性能指标:在 100 MHz 时钟下时序收敛(WNS ≥ 0)。
- 资源对比:记录每种编码方式的 LUT、FF 数量。
- 验收方式:仿真波形验证跳转逻辑;综合报告确认资源与时序。
实施步骤
1. 工程结构与顶层模块
// fsm_top.v
module fsm_top (
input wire clk,
input wire rst_n,
output reg [3:0] state_out
);
// 状态编码方式 1: 二进制
localparam IDLE = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b10;
localparam S3 = 2'b11;
reg [1:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
always @(*) begin
next_state = state;
case (state)
IDLE: next_state = S1;
S1: next_state = S2;
S2: next_state = S3;
S3: next_state = IDLE;
default: next_state = IDLE;
endcase
end
always @(*) begin
state_out = {2'b0, state};
end
endmodule逐行说明
- 第 1–6 行:模块定义,输入时钟 clk、复位 rst_n(低有效),输出 4 位状态编码 state_out。
- 第 8–11 行:二进制编码参数定义,每个状态占用 2 位。
- 第 13 行:定义当前状态 state 和次态 next_state 为 2 位寄存器。
- 第 15–19 行:时序逻辑,时钟上升沿或复位下降沿触发,复位时回到 IDLE。
- 第 21–28 行:组合逻辑计算次态,使用 case 语句,default 避免锁存。
- 第 30–32 行:组合逻辑输出当前状态,高位补 0 以匹配 4 位输出。
2. 修改编码方式(独热码与格雷码)
// 独热码编码(替换第 8–11 行)
localparam IDLE = 4'b0001;
localparam S1 = 4'b0010;
localparam S2 = 4'b0100;
localparam S3 = 4'b1000;
reg [3:0] state, next_state;逐行说明
- 第 1–5 行:独热码编码,每个状态独占一位,4 状态需 4 位寄存器,组合逻辑更简单(只需检测对应位),但 FF 数量翻倍。
- 第 6 行:state 和 next_state 位宽改为 4 位,匹配编码宽度。
// 格雷码编码(替换第 8–11 行)
localparam IDLE = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b11;
localparam S3 = 2'b10;
reg [1:0] state, next_state;逐行说明
- 第 1–5 行:格雷码编码,相邻状态仅 1 位变化,适合跨时钟域或低功耗场景,但状态数量为 2^n 时才能完全利用。
- 第 6 行:state 和 next_state 位宽保持 2 位,与二进制编码一致。
3. 综合与实现
- 在 Vivado 中,右键模块 → “Set as Top”,然后运行 Synthesis。
- 综合完成后,打开 “Report Utilization” 查看 LUT/FF 数量。
- 打开 “Report Timing Summary” 查看 Fmax(WNS 对应周期)。
- 每次修改编码后,重新综合并记录数据。
常见坑与排查
- 坑 1:独热码未定义 default 导致锁存。修复:case 语句必须包含 default。
- 坑 2:格雷码状态数不是 2^n 时,跳转逻辑复杂,可能增加 LUT。修复:尽量使用 2^n 状态数。
原理与设计说明
为什么编码方式影响资源与速度?
二进制编码:状态由 n 位表示,组合逻辑需解码(比较器)和编码(多路选择器),LUT 少但路径延迟大。适合状态数多(>8)且对 Fmax 要求不高的场景。
独热码:每个状态用 1 位表示,组合逻辑只需检测对应位(无解码),路径延迟小,Fmax 高。但 FF 数量 = 状态数,资源随状态数线性增长。适合状态数少(≤8)且追求 Fmax 的场景。
格雷码:相邻状态仅 1 位跳变,降低组合逻辑毛刺概率,适合跨时钟域同步。但状态数需为 2^n,否则编码不连续。
Trade-off 总结
| 编码方式 | LUT 数量 | FF 数量 | Fmax | 适用场景 |
|---|---|---|---|---|
| 二进制 | 少 | 少 | 中 | 状态数多,面积优先 |
| 独热码 | 中 | 多 | 高 | 状态数少,速度优先 |
| 格雷码 | 少 | 少 | 中 | 跨时钟域/低功耗 |
验证与结果
以下为在 Vivado 2024.2 上,4 状态机(IDLE→S1→S2→S3→IDLE)的综合结果(典型配置,以实际工程为准):
| 编码方式 | LUT | FF | Fmax (MHz) | 测量条件 |
|---|---|---|---|---|
| 二进制 | 4 | 2 | 520 | 100 MHz 时钟约束,无 I/O 延迟 |
| 独热码 | 6 | 4 | 610 | 同上 |
| 格雷码 | 4 | 2 | 510 | 同上 |
波形验证:仿真显示状态按 IDLE→S1→S2→S3→IDLE 循环,每个时钟上升沿跳转一次。
故障排查(Troubleshooting)
- 现象:独热码 Fmax 反而低于二进制。原因:状态数过多(如 16 状态),独热码组合逻辑变复杂。检查点:查看综合报告中的逻辑深度。修复:改用二进制或格雷码。
- 现象:二进制编码仿真正确,上板后状态跳转错误。原因:组合逻辑毛刺被采样。检查点:确认次态逻辑为纯组合,且时钟无毛刺。修复:增加输出寄存器或改用独热码。
- 现象:格雷码状态跳转不连续。原因:状态数不是 2^n。检查点:检查 localparam 定义。修复:补充为 2^n 状态或改用其他编码。
- 现象:资源报告显示大量 LUT 用于状态机。原因:综合工具未优化 FSM 编码。检查点:确认综合属性 fsm_encoding 未设为 none。修复:在 XDC 中添加
set_property FSM_ENCODING auto [current_design]。 - 现象:独热码 FF 数量远大于状态数。原因:综合工具自动添加了冗余寄存器。检查点:查看综合报告中的寄存器推断。修复:检查代码中是否有未使用的状态变量。
- 现象:Fmax 不满足约束。原因:状态机路径延迟过长。检查点:查看时序报告中的最差路径。修复:改用独热码或拆分状态机。
- 现象:仿真中状态跳转出现未知状态。原因:未初始化或复位未连接。检查点:确认 rst_n 有效。修复:添加同步复位或确保复位信号连接。
- 现象:综合报告显示 LUT 为 0。原因:模块被优化掉了(无输出负载)。检查点:确认 state_out 连接到顶层端口。修复:添加输出约束或仿真验证。
扩展与下一步
- 参数化状态机:通过 parameter 定义状态数,自动选择编码方式。
- 带宽提升:将状态机改为流水线结构,提高吞吐量。
- 跨平台验证:在 Quartus 上重复实验,对比不同工具的综合结果。
- 加入断言:在仿真中添加 SVA 断言,检测非法状态跳转。
- 形式验证:使用 OneSpin 或 JasperGold 验证状态机等价性。
参考与信息来源
- Xilinx UG901 (Vivado Synthesis Guide),章节 “FSM Encoding”。
- Clifford E. Cummings, “State Machine Coding Styles for Synthesis”, SNUG 2002.
- Altera (Intel) Quartus Prime Handbook, “FSM Design Guidelines”.
技术附录
术语表
- FSM:有限状态机。
- LUT:查找表,FPGA 基本逻辑单元。
- FF:触发器,FPGA 基本存储单元。
- Fmax:最大工作频率。
- WNS:最差负时序裕量。
检查清单
- [ ] 状态编码参数定义正确。
- [ ] case 语句包含 default。
- [ ] 次态逻辑为纯组合逻辑。
- [ ] 复位信号有效。
- [ ] 时钟约束正确。
- [ ] 综合后检查资源与时序报告。
关键约束速查
# 时钟约束
create_clock -period 10.000 [get_ports clk]
# 设置 FSM 编码为自动(Vivado)
set_property FSM_ENCODING auto [current_design]逐行说明
- 第 1 行:定义 100 MHz 时钟,周期 10 ns。
- 第 2 行:让综合工具自动选择最优 FSM 编码。



