Quick Start
- 准备Vivado 2024.2(或更高版本)工程,目标器件选xc7a35ticsg324-1L(Artix-7)。
- 创建三个Verilog模块:fsm_onehot.v(独热码)、fsm_binary.v(二进制码)、fsm_gray.v(格雷码),实现相同状态机(8状态,条件转移)。
- 编写顶层测试文件,例化三个FSM并驱动相同输入序列。
- 运行行为仿真,确认三个FSM输出一致(状态转换正确)。
- 运行综合(synth_design),查看综合后资源报告(LUT、FF)与最大频率(Fmax)。
- 对比资源与Fmax:独热码通常LUT多但Fmax高;二进制码LUT少但Fmax低;格雷码居中。
- 运行实现(place_design、route_design),检查时序收敛情况。
- 记录结果至表格,完成对比。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | xc7a35ticsg324-1L (Artix-7) | 典型中低端FPGA,资源有限,对比明显 | xc7k325t (Kintex-7) 或 xczu9eg (Zynq UltraScale+) |
| EDA版本 | Vivado 2024.2 | 2026年主流版本,综合策略成熟 | Vivado 2025.x 或 Quartus Prime Pro 24.x |
| 仿真器 | Vivado Simulator (xsim) | 内置于Vivado,方便快捷 | ModelSim/QuestaSim 2024.2 |
| 时钟 | 100 MHz(周期10 ns) | 典型系统时钟,便于时序分析 | 50 MHz 或 200 MHz |
| 复位 | 异步复位,高有效 | 与Xilinx原语匹配 | 同步复位(需额外逻辑) |
| 接口依赖 | 无外部接口 | 纯内部逻辑对比 | 可添加UART/GPIO输出状态 |
| 约束文件 | create_clock -period 10.000 [get_ports clk] | 必须指定时钟约束 | 多时钟域需额外约束 |
目标与验收标准
- 功能点:三个FSM在相同输入序列下状态输出一致(仿真通过)。
- 性能指标:记录综合后LUT/FF使用数、最大频率(Fmax,以Vivado报告为准)。
- 资源对比:独热码LUT数应比二进制码多30%~80%,FF数相近。
- 时序对比:独热码Fmax应比二进制码高10%~30%(因组合逻辑级数少)。
- 验收方式:Vivado综合后报告(utilization_synth.rpt 和 timing_summary.rpt)中的具体数值。
实施步骤
1. 工程结构与RTL设计
- 创建Vivado工程,添加三个Verilog源文件:fsm_onehot.v, fsm_binary.v, fsm_gray.v。
- 每个模块实现一个8状态(S0~S7)的状态机,状态转移条件相同:输入din为0时进入下一状态,为1时回到S0。
- 输出dout为当前状态值(独热码输出8位,二进制/格雷码输出3位,需统一位宽以便对比)。
- 使用always块描述状态寄存器和次态逻辑,组合逻辑输出。
// fsm_onehot.v
module fsm_onehot (
input clk,
input rst_n,
input din,
output reg [7:0] dout
);
parameter [7:0] S0 = 8'b0000_0001;
parameter [7:0] S1 = 8'b0000_0010;
parameter [7:0] S2 = 8'b0000_0100;
parameter [7:0] S3 = 8'b0000_1000;
parameter [7:0] S4 = 8'b0001_0000;
parameter [7:0] S5 = 8'b0010_0000;
parameter [7:0] S6 = 8'b0100_0000;
parameter [7:0] S7 = 8'b1000_0000;
reg [7:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
state <= next_state;
end
always @(*) begin
case (state)
S0: next_state = din ? S0 : S1;
S1: next_state = din ? S0 : S2;
S2: next_state = din ? S0 : S3;
S3: next_state = din ? S0 : S4;
S4: next_state = din ? S0 : S5;
S5: next_state = din ? S0 : S6;
S6: next_state = din ? S0 : S7;
S7: next_state = din ? S0 : S0;
default: next_state = S0;
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
dout <= 8'b0;
else
dout <= state;
end
endmodule逐行说明(fsm_onehot.v)
- 第1~6行:模块端口声明,clk和rst_n为时钟和复位,din为输入,dout为8位输出。
- 第8~15行:用parameter定义8个独热码状态,每个状态只有1位为1,其余为0。独热码编码方式。
- 第17行:声明state(当前状态)和next_state(次态)寄存器,均为8位。
- 第19~23行:时序逻辑,在时钟上升沿或复位下降沿更新state。复位时进入S0。
- 第25~36行:组合逻辑,根据当前state和din计算next_state。case语句覆盖所有状态,default处理未定义状态。
- 第38~43行:输出寄存器dout,在时钟沿更新为当前state。此处dout直接输出状态编码。
// fsm_binary.v
module fsm_binary (
input clk,
input rst_n,
input din,
output reg [7:0] dout
);
parameter [2:0] S0 = 3'd0, S1 = 3'd1, S2 = 3'd2, S3 = 3'd3,
S4 = 3'd4, S5 = 3'd5, S6 = 3'd6, S7 = 3'd7;
reg [2:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
state <= next_state;
end
always @(*) begin
case (state)
S0: next_state = din ? S0 : S1;
S1: next_state = din ? S0 : S2;
S2: next_state = din ? S0 : S3;
S3: next_state = din ? S0 : S4;
S4: next_state = din ? S0 : S5;
S5: next_state = din ? S0 : S6;
S6: next_state = din ? S0 : S7;
S7: next_state = din ? S0 : S0;
default: next_state = S0;
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
dout <= 8'b0;
else
dout <= {5'b0, state}; // 扩展为8位输出
end
endmodule逐行说明(fsm_binary.v)
- 第1~6行:模块端口声明,与独热码模块相同。
- 第8~9行:用parameter定义8个二进制编码状态,每个状态用3位二进制数表示。二进制码编码方式。
- 第11行:state和next_state声明为3位,比独热码节省5个FF。
- 第13~17行:时序逻辑,更新state。
- 第19~30行:组合逻辑,与独热码相同,但case的比较对象是3位二进制数。
- 第32~37行:输出dout,将3位state扩展为8位(高位补0),以便与独热码模块输出位宽一致。
// fsm_gray.v
module fsm_gray (
input clk,
input rst_n,
input din,
output reg [7:0] dout
);
parameter [2:0] S0 = 3'b000, S1 = 3'b001, S2 = 3'b011, S3 = 3'b010,
S4 = 3'b110, S5 = 3'b111, S6 = 3'b101, S7 = 3'b100;
reg [2:0] state, next_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else
state <= next_state;
end
always @(*) begin
case (state)
S0: next_state = din ? S0 : S1;
S1: next_state = din ? S0 : S2;
S2: next_state = din ? S0 : S3;
S3: next_state = din ? S0 : S4;
S4: next_state = din ? S0 : S5;
S5: next_state = din ? S0 : S6;
S6: next_state = din ? S0 : S7;
S7: next_state = din ? S0 : S0;
default: next_state = S0;
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
dout <= 8'b0;
else
dout <= {5'b0, state};
end
endmodule逐行说明(fsm_gray.v)
- 第1~6行:模块端口声明。
- 第8~9行:用parameter定义8个格雷码状态,相邻状态只有1位不同。格雷码编码方式。
- 第11行:state和next_state为3位。
- 第13~17行:时序逻辑。
- 第19~30行:组合逻辑,case语句比较格雷码值。
- 第32~37行:输出扩展为8位。
2. 仿真验证
- 编写testbench,例化三个模块,驱动相同时钟、复位和din序列(如:din先为0连续8个周期,再为1一个周期,重复)。
- 运行仿真,观察dout波形:独热码输出应为8位,二进制/格雷码输出应为3位扩展后的值。
- 确认状态转换正确:例如,din=0时,状态从S0→S1→...→S7→S0循环;din=1时,状态回到S0。
3. 综合与实现
- 在Vivado中分别将三个模块设为顶层(或使用综合选项中的“flatten_hierarchy”为none以保持模块边界)。
- 运行综合,打开综合后报告,记录每个模块的LUT、FF使用数和WNS(最差负时序裕量)。
- 运行实现,检查时序是否收敛。
常见坑与排查
- 坑1:综合工具自动优化状态机编码。Vivado默认会尝试将状态机优化为二进制码,导致对比失效。需在综合设置中关闭FSM优化:在Vivado Tcl控制台运行
set_property STEPS.SYNTH_DESIGN.ARGS.FSM_EXTRACTION off [get_runs synth_1]。 - 坑2:输出位宽不一致导致对比困难。确保三个模块输出位宽一致(如都扩展为8位),否则资源对比不公平。
- 坑3:复位状态不一致。确保所有模块复位后都进入S0,且S0编码在所有编码方式中对应最低值(独热码为0x01,二进制为0,格雷码为000)。
原理与设计说明
为什么独热码Fmax更高? 独热码每个状态只有1位为1,次态逻辑只需比较该位是否为1(或使用与门),组合逻辑级数少。例如,从S0到S1的转移只需判断state[0]和din,无需多级译码。二进制码需将3位state与常数比较,产生3位比较器,组合逻辑深度更大,导致路径延迟增加,Fmax降低。
为什么独热码LUT更多? 独热码使用8位寄存器(FF),而二进制/格雷码仅3位。次态逻辑中,独热码的case语句会综合出8选1多路器,每个输入需一个LUT;二进制码的case语句综合出3位比较器,LUT更少。但独热码的译码逻辑更简单,因此LUT多但Fmax高。
格雷码的优势:格雷码相邻状态仅1位变化,在跨时钟域或异步信号同步时能减少亚稳态风险。但在同一时钟域内,其性能与二进制码接近,Fmax略高于二进制码(因译码逻辑略简单)。
Trade-off总结:
| 编码方式 | FF使用 | LUT使用 | Fmax | 适用场景 |
|---|---|---|---|---|
| 独热码 | 高(N个状态需N个FF) | 较高 | 高 | 高速设计、状态数少(<16) |
| 二进制码 | 低(log2(N)个FF) | 低 | 低 | 资源受限、状态数多(>16) |
| 格雷码 | 低 | 低 | 中 | 跨时钟域、低功耗(减少翻转) |
验证与结果
以下为在Vivado 2024.2、xc7a35ticsg324-1L器件下,关闭FSM优化后的综合结果(示例值,实际以工程为准):
| 编码方式 | LUT数 | FF数 | Fmax (MHz) | WNS (ns) |
|---|---|---|---|---|
| 独热码 | 24 | 16 | 285 | 0.023 |
| 二进制码 | 14 | 11 | 210 | 0.045 |
| 格雷码 | 15 | 11 | 225 | 0.038 |
测量条件:时钟约束100 MHz,综合策略为Vivado默认,关闭FSM优化。Fmax由Vivado时序报告中的最大频率给出(基于WNS计算)。
故障排查(Troubleshooting)
- 现象:综合后三个模块资源相同 → 原因:Vivado自动优化了FSM编码。检查点:确认FSM_EXTRACTION已关闭。修复:在Tcl中运行
set_property STEPS.SYNTH_DESIGN.ARGS.FSM_EXTRACTION off [get_runs synth_1]后重新综合。 - 现象:仿真中状态不按预期转移 → 原因:复位信号极性错误。检查点:确认rst_n为低有效,且testbench中复位时序正确。修复:调整testbench复位序列。
- 现象:时序不收敛(WNS为负) → 原因:时钟频率过高或组合逻辑路径过长。检查点:查看关键路径报告,定位最长路径。修复:降低时钟频率或优化状态机逻辑(如使用独热码)。
- 现象:独热码LUT数异常高 → 原因:状态数过多(如超过16),独热码FF数线性增长,导致LUT也增加。检查点:评估状态数是否适合独热码。修复:改用二进制码或格雷码。
- 现象:格雷码Fmax低于预期 → 原因:综合工具未识别格雷码序列,仍按二进制优化。检查点:查看综合报告中的状态编码。修复:手动指定状态编码(使用syn_encoding属性)。
- 现象:输出dout出现毛刺 → 原因:组合逻辑输出未寄存。检查点:确认dout是否在always块中时序赋值。修复:将dout改为寄存器输出(如代码中所示)。
- 现象:资源对比时FF数相同 → 原因:输出扩展逻辑引入了额外FF。检查点:确认输出寄存器是否独立于状态寄存器。修复:将输出dout直接赋值为state(不额外寄存),但需注意时序。
- 现象:Vivado报告中的Fmax为0 → 原因:未添加时钟约束。检查点:确认XDC文件中包含create_clock。修复:添加约束。
扩展与下一步
- 参数化状态机:使用parameter定义状态数,通过generate语句自动选择编码方式,提高代码复用性。
- 带宽提升:在高速设计中,可结合流水线技术,在状态机前/后插入寄存器,进一步提高Fmax。
- 跨平台对比:在Quartus Prime或ISE中重复实验,对比不同综合工具对编码方式的优化差异。
- 加入断言:在testbench中使用SVA(SystemVerilog Assertions)检查状态转换的合法性,如“状态不能非法跳转”。
- 形式验证:使用OneSpin或Vivado Formal验证状态机等价性,确保不同编码方式的功能一致。
- 低功耗优化:格雷码在状态翻转时仅1位变化,可降低动态功耗。可测量实际功耗对比。
参考与信息来源
- Xilinx UG901 (Vivado Design Suite User Guide: Synthesis) – 关于FSM编码和综合属性。
- Clifford E. Cummings, "State Machine Coding Styles for Synthesis" (SNUG 2002) – 经典论文。
- Vivado 2024.2 在线帮助文档 – FSM_EXTRACTION属性说明。
- IEEE Std 1364-2005 (Verilog HDL) – 语言参考手册。


