Quick Start
- 打开 Vivado(2024.2 或更新版本),创建一个新工程,器件选择 XC7K325T-2FFG900。
- 在工程中新建一个 Verilog 文件,命名为
fsm_encoding_demo.v。 - 编写一个 4 状态(S0, S1, S2, S3)的简单状态机,使用独热码(one-hot)编码。
- 运行综合(Synthesis),查看综合后的资源利用率报告(LUT、FF 数量)。
- 修改状态机编码为二进制码(binary),重新综合,对比资源变化。
- 对两种编码分别运行实现(Implementation),查看时序报告(WNS、Fmax)。
- 观察并记录:独热码通常使用更多 FF、更少 LUT;二进制码使用更少 FF、更多 LUT。
- 验收点:独热码 Fmax 高于二进制码约 10%~30%(示例值,以实际综合结果为准)。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Kintex-7 (XC7K325T) | 主流中高端 FPGA,LUT/FF 资源丰富 | Artix-7 / Spartan-7(资源受限时效果更明显) |
| EDA 版本 | Vivado 2024.2 | 支持综合选项 -fsm_extraction 与 -fsm_encoding | Vivado 2018.3+ / ISE(已过时,不推荐) |
| 仿真器 | Vivado Simulator 或 ModelSim | 用于功能仿真验证状态机跳转正确性 | VCS / Questa |
| 时钟/复位 | 100 MHz 系统时钟,异步复位(高有效) | 确保时序约束真实反映设计 | 50 MHz / 200 MHz |
| 接口依赖 | 无外部接口,纯内部状态机 | 避免 I/O 时序干扰对比 | 可添加简单输出指示(如 LED) |
| 约束文件 | XDC 文件:create_clock -period 10.000 [get_ports clk] | 时钟周期 10 ns,对应 100 MHz | 按实际板卡调整 |
目标与验收标准
- 功能点:状态机在两种编码下均能正确完成 S0→S1→S2→S3→S0 循环跳转,输出信号(如
state_out)与预期一致。 - 性能指标:
- 独热码实现:WNS ≥ 0(无时序违例),Fmax ≥ 150 MHz(示例值)。
- 二进制码实现:WNS ≥ 0,Fmax ≥ 120 MHz(示例值,通常低于独热码)。
- 资源对比:
- 独热码:FF 数量 ≈ 4(每个状态 1 个 FF),LUT 数量 ≤ 8。
- 二进制码:FF 数量 ≈ 2(2 位寄存器),LUT 数量 ≤ 12。
- 验收方式:运行综合后查看“Utilization Report”中的 Slice LUTs 与 Slice Registers 数量;运行实现后查看“Timing Summary”中的 WNS 与 Fmax。
实施步骤
工程结构与关键模块
创建一个顶层模块 fsm_encoding_demo,内部实例化两个子模块:fsm_onehot 和 fsm_binary。两个子模块实现相同的状态跳转逻辑,仅在编码方式上不同。顶层模块提供时钟、复位和使能信号,并输出两个状态机的当前状态。
// fsm_encoding_demo.v - 顶层模块
module fsm_encoding_demo (
input wire clk,
input wire rst_n, // 低有效复位
input wire en, // 使能信号
output wire [3:0] state_onehot, // 独热码状态输出(4位)
output wire [1:0] state_binary // 二进制状态输出(2位)
);
// 实例化独热码状态机
fsm_onehot u_onehot (
.clk (clk),
.rst_n (rst_n),
.en (en),
.state (state_onehot)
);
// 实例化二进制码状态机
fsm_binary u_binary (
.clk (clk),
.rst_n (rst_n),
.en (en),
.state (state_binary)
);
endmodule逐行说明
- 第 1 行:注释,标明文件名和模块功能。良好的注释习惯有助于代码维护。
- 第 2 行:模块声明,端口列表开始。模块名与文件名保持一致,便于工具识别。
- 第 3 行:时钟输入,wire 类型,默认。时钟是时序逻辑的驱动源。
- 第 4 行:复位输入,低有效(rst_n 中的 n 表示 negative)。低有效复位在 FPGA 中常见,与高有效复位在综合时可能映射到不同的复位网络。
- 第 5 行:使能输入,用于控制状态机是否跳转。使能信号可以降低动态功耗。
- 第 6 行:独热码状态输出,4 位宽。4 个状态对应 4 位,每次只有 1 位为 1。
- 第 7 行:二进制状态输出,2 位宽。2 位可以表示 4 个状态。
- 第 9 行:实例化独热码状态机模块。实例名 u_onehot 应具有描述性。
- 第 10–14 行:端口连接,使用 .端口名(信号) 语法。注意顶层信号与子模块端口名称一致,避免混淆。
- 第 16 行:实例化二进制码状态机模块。
- 第 17–21 行:端口连接,与独热码实例类似。
- 第 23 行:模块结束。
独热码状态机实现
// fsm_onehot.v - 独热码状态机
module fsm_onehot (
input wire clk,
input wire rst_n,
input wire en,
output reg [3:0] state
);
// 状态定义:独热码
localparam S0 = 4'b0001,
S1 = 4'b0010,
S2 = 4'b0100,
S3 = 4'b1000;
// 状态跳转逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else if (en)
case (state)
S0: state <= S1;
S1: state <= S2;
S2: state <= S3;
S3: state <= S0;
default: state <= S0;
endcase
end
endmodule逐行说明
- 第 1 行:注释,标明模块名称和编码方式。
- 第 2 行:模块声明。
- 第 3–6 行:端口声明,与顶层模块一致。
- 第 7 行:
output reg [3:0] state,状态寄存器声明为 reg 类型,因为在 always 块中被赋值。独热码需要 4 位寄存器。 - 第 9–12 行:使用
localparam定义状态常量,独热码每个状态只有 1 位为 1。S0=4’b0001, S1=4’b0010, S2=4’b0100, S3=4’b1000。注意位宽与状态数匹配。 - 第 14 行:always 块,敏感列表包含时钟上升沿和复位下降沿(因为 rst_n 低有效)。
- 第 15 行:复位条件
if (!rst_n),当复位信号为低时执行。 - 第 16 行:复位状态为 S0(4’b0001)。
- 第 17 行:
else if (en),使能有效时才跳转,否则保持当前状态。 - 第 18–23 行:case 语句,根据当前状态决定下一状态。注意 default 分支将非法状态引导到 S0,增强鲁棒性。
- 第 25 行:模块结束。
二进制码状态机实现
// fsm_binary.v - 二进制码状态机
module fsm_binary (
input wire clk,
input wire rst_n,
input wire en,
output reg [1:0] state
);
// 状态定义:二进制码
localparam S0 = 2'b00,
S1 = 2'b01,
S2 = 2'b10,
S3 = 2'b11;
// 状态跳转逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= S0;
else if (en)
case (state)
S0: state <= S1;
S1: state <= S2;
S2: state <= S3;
S3: state <= S0;
default: state <= S0;
endcase
end
endmodule逐行说明
- 第 1–6 行:模块声明与端口,与独热码版本类似,但
state位宽为 2 位。 - 第 7 行:
output reg [1:0] state,2 位寄存器即可表示 4 个状态。 - 第 9–12 行:二进制编码:S0=2’b00, S1=2’b01, S2=2’b10, S3=2’b11。编码紧凑,但状态跳转时可能有多位同时变化(如 S1→S2:01→10,两位都变)。
- 第 14–25 行:always 块结构与独热码完全相同,仅状态位宽不同。注意 case 语句中比较的 state 是 2 位,与独热码的 4 位不同。
综合与实现设置
在 Vivado 中,综合选项可以强制指定编码方式,但为了对比公平,建议让工具自动推断。在“Synthesis Settings”中,将 -fsm_extraction 设为 one_hot 或 binary 可以强制编码。本实验中,我们让工具自动选择(auto),然后通过修改 RTL 编码来对比。
# 约束文件 top.xdc
create_clock -period 10.000 [get_ports clk]
set_property PACKAGE_PIN ... [get_ports clk] ;# 根据板卡修改
set_property IOSTANDARD LVCMOS33 [get_ports clk]逐行说明
- 第 1 行:创建时钟约束,周期 10 ns,对应 100 MHz。这是时序分析的基础。
- 第 2 行:指定时钟引脚的位置,需要根据实际板卡原理图修改。如果只是纯仿真,可以省略。
- 第 3 行:设置 I/O 标准为 LVCMOS33,与板卡电压匹配。
常见坑与排查
- 坑 1:复位极性错误。如果顶层使用高有效复位而子模块使用低有效,状态机可能无法正确复位。检查所有模块的复位极性一致。
- 坑 2:状态位宽不匹配。独热码状态位宽必须等于状态数,二进制码位宽必须 ≥ log2(状态数)。位宽错误会导致综合错误或仿真异常。
- 坑 3:缺少 default 分支。case 语句没有 default 会生成锁存器或导致综合警告。始终添加 default 分支,将非法状态引导到已知状态。
- 坑 4:使能信号未同步。如果 en 来自异步域,需要先同步再使用,否则可能导致亚稳态。本实验 en 与 clk 同源,无需同步。
验证与仿真
编写 testbench 验证两种状态机的功能。关键点:复位后状态为 S0;每个时钟上升沿且 en=1 时状态跳转;en=0 时状态保持。
// testbench.v
module tb_fsm_encoding;
reg clk, rst_n, en;
wire [3:0] state_onehot;
wire [1:0] state_binary;
fsm_encoding_demo u_dut (
.clk (clk),
.rst_n (rst_n),
.en (en),
.state_onehot (state_onehot),
.state_binary (state_binary)
);
initial begin
clk = 0;
forever #5 clk = ~clk; // 100 MHz
end
initial begin
rst_n = 0; en = 0;
#20 rst_n = 1;
#10 en = 1;
#100 en = 0;
#40 en = 1;
#80 $finish;
end
// 监控状态变化
always @(posedge clk) begin
if (en) begin
$display("Time=%0t: binary=%b", $time, state_onehot, state_binary);
end
end
endmodule逐行说明
- 第 1 行:testbench 模块,无端口。
- 第 3 行:声明激励信号为 reg 类型。
- 第 4–5 行:声明观察信号为 wire 类型。
- 第 7–13 行:实例化待测设计(DUT)。
- 第 15–18 行:时钟生成,周期 10 ns,占空比 50%。
- 第 20–25 行:复位和使能时序:先复位 20 ns,然后使能 100 ns,再关闭使能 40 ns,再使能 80 ns 后结束。
- 第 27–31 行:在每个时钟上升沿打印两个状态机的状态,用于观察是否同步跳转。
原理与设计说明
为什么独热码更快?
独热码每次状态跳转时,只有 2 位发生变化(当前状态位从 1→0,下一状态位从 0→1)。在 FPGA 中,状态寄存器的输出直接连接到下一状态译码逻辑。由于每个状态对应一个独立的触发器,译码逻辑只需要比较少数几个位(通常只需要检查当前状态位是否为 1),因此组合逻辑级数少,路径延迟小,Fmax 更高。
二进制码在状态跳转时可能有多位同时变化(例如从 01→10,两位都翻转)。译码逻辑需要比较所有状态位,组合逻辑更复杂,路径延迟更大。此外,多位同时变化时可能产生毛刺(glitch),虽然寄存器采样可以消除,但组合逻辑输出不稳定会增加动态功耗。
为什么二进制码面积更小?
二进制码使用 log2(N) 个触发器表示 N 个状态,而独热码使用 N 个触发器。对于 4 状态机,二进制码需要 2 个 FF,独热码需要 4 个 FF。状态数越多,差距越大(例如 16 状态:二进制码 4 FF,独热码 16 FF)。
但独热码的译码逻辑更简单,使用的 LUT 更少。在资源受限的设计中(如 LUT 数量是瓶颈),独热码可能反而总面积更小。实际工程中需要综合后对比。
Trade-off 总结
| 编码方式 | FF 数量 | LUT 数量 | Fmax | 适用场景 |
|---|---|---|---|---|
| 独热码 | 高(N 个) | 低 | 高 | 高速设计、状态数少(≤16) |
| 二进制码 | 低(log2(N) 个) | 高 | 低 | 面积敏感、状态数多(≥16) |
| 格雷码 | 低(log2(N) 个) | 中 | 中 | 跨时钟域传输(每次仅 1 位变化) |
对于 4~8 个状态,独热码通常是最佳选择。对于 16 个以上状态,二进制码或格雷码更优。具体选择应结合综合报告和时序约束。
验证与结果
以下是在 Vivado 2024.2 中,对 XC7K325T-2FFG900 综合实现后的示例结果(实际值以您的工程为准)。
| 指标 | 独热码 | 二进制码 | 测量条件 |
|---|---|---|---|
| Slice LUTs | 6 | 10 | 仅状态机逻辑,无其他模块 |
| Slice Registers (FF) | 4 | 2 | 状态寄存器 |
| Fmax (MHz) | 185 | 142 | 时钟约束 10 ns,WNS ≥ 0 |
| Worst Negative |

评论 0