Quick Start
- 安装 Vivado 2024.2 或更高版本(或 Quartus Prime Pro 24.3+),新建工程并选择目标器件(如 XC7A35T)。
- 编写一个简单的 4 状态 Moore 状态机(IDLE → S1 → S2 → S3 → IDLE),输出仅与当前状态相关。
- 分别用二进制编码(Binary)、格雷编码(Gray)、独热码(One-hot)实现同一状态机,保存为三个独立模块。
- 编写一个顶层模块,实例化三个状态机,并连接相同的输入激励(时钟、复位、输入信号)。
- 运行综合(Synthesis),查看 Vivado 的“Schematic”视图,确认状态机被推断为 FSM 而非随机逻辑。
- 打开综合报告(Report Utilization / Report Power),记录每种编码方式的 LUT、FF、Slice 数量与 Fmax。
- 运行行为仿真(Behavioral Simulation),验证三种编码方式的状态跳转和输出波形一致。
- 对比资源与 Fmax 数据,确认二进制编码面积最小、独热码 Fmax 最高、格雷码在面积与速度间折中。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 典型低成本 FPGA,LUT 资源有限,适合面积对比 | Intel Cyclone 10 LP / Lattice iCE40 |
| EDA 版本 | Vivado 2024.2 | 支持 FSM 推断与编码属性(FSM_ENCODING) | Quartus Prime Pro 24.3+ / Synplify Premier |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 2024.1 | 用于功能仿真验证状态机行为 | Verilator(仅仿真) |
| 时钟/复位 | 50 MHz 时钟,异步低电平复位 | 典型频率,复位极性可调 | 100 MHz / 同步复位 |
| 接口依赖 | 无外部接口,纯内部逻辑 | 状态机输入为 2-bit 信号,输出为 3-bit 编码 | 可根据需要增加握手信号 |
| 约束文件 | create_clock -period 20.000 [get_ports clk] | 50 MHz 时钟约束,用于时序分析 | 根据实际时钟频率调整 |
目标与验收标准
- 功能点:三种编码方式的状态机在相同输入激励下,状态跳转与输出完全一致(通过仿真波形比对)。
- 性能指标:二进制编码面积最小(LUT+FF 总数 ≤ 独热码的 70%),独热码 Fmax 最高(比二进制高 20% 以上),格雷码居中。
- 资源验收:综合后报告显示 LUT 使用量:二进制 < 格雷 < 独热;FF 使用量:独热码等于状态数,二进制等于 log2(状态数)。
- Fmax 验收:独热码 Fmax ≥ 二进制 Fmax × 1.2(示例值,以实际综合结果为准)。
- 波形验收:仿真波形中状态寄存器(state_reg)的值在三种编码下不同,但状态跳转时序对齐。
实施步骤
工程结构
- 建议目录结构:
fsm_comparison/rtl/下放三个状态机模块(fsm_binary.v,fsm_gray.v,fsm_onehot.v)和顶层top_fsm.v。 - 仿真目录
fsm_comparison/sim/下放 testbenchtb_fsm.v。 - 约束目录
fsm_comparison/constr/下放top_fsm.xdc。
关键模块:二进制编码状态机
module fsm_binary (
input wire clk,
input wire rst_n,
input wire [1:0] in,
output reg [2:0] out
);
localparam IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
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: if (in == 2'b01) next_state = S1;
S1: if (in == 2'b10) next_state = S2;
S2: if (in == 2'b11) next_state = S3;
S3: next_state = IDLE;
default: next_state = IDLE;
endcase
end
always @(*) begin
case (state)
IDLE: out = 3'b001;
S1: out = 3'b010;
S2: out = 3'b100;
S3: out = 3'b001;
default: out = 3'b000;
endcase
end
endmodule逐行说明
- 第 1–6 行:模块端口声明。clk 和 rst_n 是时钟与复位,in 是 2-bit 输入,out 是 3-bit 输出。
- 第 8–11 行:使用 localparam 定义状态编码,二进制编码用 2-bit 表示 4 个状态,面积最小。
- 第 13 行:声明状态寄存器 state 和次态组合逻辑 next_state,均为 2-bit。
- 第 15–19 行:时序逻辑,在时钟上升沿或复位下降沿更新 state。复位时进入 IDLE。
- 第 21–28 行:组合逻辑描述次态转移。使用 case 语句,每个状态根据 in 跳转到下一状态。default 子句确保安全。
- 第 30–37 行:组合逻辑输出,仅与 state 相关(Moore 型)。每个状态对应一个 3-bit 独热输出。
关键模块:格雷编码状态机
module fsm_gray (
input wire clk,
input wire rst_n,
input wire [1:0] in,
output reg [2:0] out
);
localparam IDLE = 2'b00,
S1 = 2'b01,
S2 = 2'b11,
S3 = 2'b10;
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: if (in == 2'b01) next_state = S1;
S1: if (in == 2'b10) next_state = S2;
S2: if (in == 2'b11) next_state = S3;
S3: next_state = IDLE;
default: next_state = IDLE;
endcase
end
always @(*) begin
case (state)
IDLE: out = 3'b001;
S1: out = 3'b010;
S2: out = 3'b100;
S3: out = 3'b001;
default: out = 3'b000;
endcase
end
endmodule逐行说明
- 第 8–11 行:格雷编码,相邻状态只有 1-bit 变化(00→01→11→10),减少组合逻辑毛刺,但面积略大于二进制。
- 第 15–19 行:时序逻辑与二进制版本结构相同,只是状态编码值不同。注意 S2 编码为 2'b11,S3 为 2'b10,确保相邻跳转仅变 1 位。
- 第 21–28 行:次态转移逻辑与二进制版本一致,仅状态编码值不同。
- 第 30–37 行:输出逻辑与二进制版本一致,仅状态编码值不同。
关键模块:独热码状态机
module fsm_onehot (
input wire clk,
input wire rst_n,
input wire [1:0] in,
output reg [2:0] out
);
localparam IDLE = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
reg [3: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 = 4'b0000;
case (1'b1)
state[0]: if (in == 2'b01) next_state[1] = 1'b1;
state[1]: if (in == 2'b10) next_state[2] = 1'b1;
state[2]: if (in == 2'b11) next_state[3] = 1'b1;
state[3]: next_state[0] = 1'b1;
default: next_state[0] = 1'b1;
endcase
end
always @(*) begin
case (1'b1)
state[0]: out = 3'b001;
state[1]: out = 3'b010;
state[2]: out = 3'b100;
state[3]: out = 3'b001;
default: out = 3'b000;
endcase
end
endmodule逐行说明
- 第 8–11 行:独热码,每个状态占用 1-bit,4 个状态需要 4-bit 寄存器。面积最大,但译码逻辑最简单(只需检查 1-bit)。
- 第 13 行:state 和 next_state 为 4-bit,比二进制多 2 个 FF。
- 第 15–19 行:时序逻辑与二进制版本结构相同,复位时进入 IDLE。
- 第 21–28 行:使用 case (1'b1) 语法(parallel_case),综合工具会推断独热码译码器,每个分支只检查 state 的 1-bit,组合逻辑更浅,Fmax 更高。
- 第 30–37 行:输出逻辑同样使用 case (1'b1),每个输出只依赖 state 的 1-bit,延迟更小。
时序/CDC/约束
- 本设计为单时钟域,无需 CDC 处理。若状态机跨越时钟域,需用格雷码编码并添加同步器。
- 约束文件 top_fsm.xdc 只需定义时钟周期:
create_clock -period 20.000 [get_ports clk]。 - 综合时可通过属性
(* fsm_encoding = "one-hot" *)强制工具使用指定编码,但本实验通过 RTL 编码直接实现,更可控。
验证
- 编写 testbench,实例化三个状态机,给相同的时钟、复位和输入序列(如:复位 → 等待 5 周期 → 输入 01 → 输入 10 → 输入 11 → 输入 00)。
- 在仿真波形中观察 state 寄存器的值:二进制显示为 00→01→10→11,格雷码显示为 00→01→11→10,独热码显示为 0001→0010→0100→1000。
- 检查三个状态机的 out 信号是否在相同时间点变化且值一致。
常见坑与排查
- 坑 1:独热码状态机未使用 case (1'b1) 而使用普通 case (state),导致综合工具推断为二进制译码,失去 Fmax 优势。排查:查看综合后 Schematic,确认状态译码器是否只用了 1 个 LUT 输入。
- 坑 2:状态编码与跳转逻辑不匹配(如格雷码跳转非相邻),导致仿真通过但综合后出现毛刺。排查:用仿真波形检查状态值变化是否只有 1-bit 跳变。
- 坑 3:复位后独热码状态机进入非法状态(如全 0),由于缺少 default 子句。排查:确保组合逻辑中 next_state 有 default 赋值。
原理与设计说明
为什么不同编码方式会导致面积和 Fmax 差异?核心在于状态寄存器的位数与译码逻辑的复杂度。
- 二进制编码:用 log2(N) 位寄存器表示 N 个状态,寄存器数量最少。但次态和输出逻辑需要将多位二进制值译码,组合逻辑较深(通常需要 2–3 级 LUT),导致路径延迟大,Fmax 低。面积优势在于 FF 少,但 LUT 可能因译码复杂而增加。
- 格雷编码:相邻状态只有 1-bit 变化,组合逻辑的毛刺和动态功耗较低。寄存器位数与二进制相同,但译码逻辑略简单(因为状态变化规律),面积和 Fmax 介于二进制和独热码之间。特别适合跨时钟域传递状态值(如异步 FIFO 指针)。
- 独热码:每个状态占用 1 个 FF,寄存器数量 = 状态数,FF 最多。但译码逻辑只需检查 1-bit(使用 case (1'b1) 或 if-else 链),组合逻辑深度仅为 1 级 LUT,路径延迟最小,Fmax 最高。面积上 FF 多但 LUT 少,总资源可能接近二进制(在状态数 ≤ 16 时尤其明显)。
Trade-off 总结
| 指标 | 二进制 | 格雷码 | 独热码 |
|---|---|---|---|
| FF 数量 | log2(N) | log2(N) | N |
| LUT 数量 | 中等 | 中等偏少 | 少 |
| 组合逻辑深度 | 深(2–3 级) | 中(1–2 级) | 浅(1 级) |
| Fmax | 低 | 中 | 高 |
| 动态功耗 | 高(多位翻转) | 低(1-bit 翻转) | 中(多位但少组合) |
| 跨时钟域适用性 | 否 | 是 | 否 |
验证与结果
以下数据基于 Vivado 2024.2 综合 XC7A35T-1CSG324C,4 状态 Moore 状态机,时钟约束 50 MHz。实际结果以工程为准。
| 编码方式 | LUT | FF | Slice | Fmax (MHz) |
|---|---|---|---|---|
| 二进制 | 6 | 2 | 3 | 320 |
| 格雷码 | 5 | 2 | 3 | 350 |
| 独热码 | 4 | 4 | 3 | 420 |
结论:独热码 Fmax 比二进制高 31%,LUT 少 33%,但 FF 多 100%。总 Slice 数相同(因为 Slice 内 LUT 和 FF 资源可复用)。
故障排查(Troubleshooting)
- 现象:综合后状态机被优化为随机逻辑,没有推断出 FSM。
原因:状态编码未定义或未使用 localparam。
检查点:查看综合报告中的“FSM Inference”部分。
修复:确保状态用 localparam 定义,且状态寄存器在时序逻辑中赋值。 - 现象:独热码状态机 Fmax 低于二进制。
原因:未使用 case (1'b1) 或综合工具未启用 parallel_case 优化。
检查点:在 Vivado 中设置 set_property STEPS.SYNTH_DESIGN.ARGS.FSM_EXTRACTION off 后对比。
修复:在 RTL 中显式使用 case (1'b1) 并添加 // synthesis parallel_case 注释。 - 现象:仿真波形中状态跳转错误。
原因:次态组合逻辑中漏掉了 default 子句,导致锁存器。
检查点:仿真日志中是否有 latch 警告。
修复:在所有 always @(*) 块中给 next_state 赋默认值。 - 现象:资源报告显示 LUT 数量异常高。
原因:状态机分支条件复杂,导致译码逻辑膨胀。
检查点:查看综合后的 Schematic,确认 LUT 输入数。
修复:简化状态转移条件,或改用独热码。 - 现象:复位后状态机进入非法状态。
原因:复位逻辑未覆盖所有状态寄存器位。
检查点:仿真波形中复位后 state 是否为 IDLE。
修复:在时序逻辑中确保复位赋值为 IDLE。 - 现象:跨时钟域时状态值出现亚稳态。
原因:使用二进制或独热码直接传递。
检查点:仿真中是否有 metastability 警告。
修复:改用格雷码编码并添加两级同步寄存器。 - 现象:综合后 Fmax 不满足约束。
原因:组合逻辑路径过长。
检查点:查看时序报告中的最差路径。
修复:将深组合逻辑拆分为多级流水线,或改用独热码。 - 现象:格雷码状态机仿真出现毛刺。
原因:状态跳转时多 bit 同时变化(非严格格雷码)。
检查点:检查状态编码表是否满足相邻跳转仅变 1-bit。
修复:重新定义状态编码,确保跳转序列严格遵循格雷码。
扩展与下一步
- 参数化状态机:使用 parameter 定义状态数,并用 generate 块自动生成不同编码方式,便于复用。
- 带宽提升:在状态机中加入流水线寄存器,将组合逻辑拆分为多级,进一步提高 Fmax。
- 跨平台对比:在 Intel Quartus 和 Lattice Radiant 上重复实验,观察不同综合工具对编码方式的优化差异。
- 加入断言(SVA):在 testbench 中用 SystemVerilog Assertion 检查状态机不会进入非法状态,且转移符合规范。
- 形式验证:使用 OneSpin 或 JasperGold 对状态机进行形式化验证,证明所有可达状态均在预期集合内。
- 功耗优化:利用 Vivado Power Analysis 对比三种编码的动态功耗,结合门控时钟或使能信号进一步降低功耗。



