第二章:时序约束入门
各位同学,咱们今天聊聊时序约束的入门功夫。说白了,就是告诉工具你的时钟长什么样、数据什么时候来、什么时候走。我刚开始学FPGA那会儿,觉得约束可有可无,随便写两句就完事。结果呢?综合出来的电路要么跑不快,要么根本跑不对。后来我才明白——约束不是写给工具看的,是写给自己的设计看的。
2.1 创建主时钟
主时钟,就是整个设计的“心跳”。没有它,时序分析根本没法做。你想想看,工具连时钟周期都不知道,怎么判断路径是否满足时序?
创建主时钟用 create_clock 命令。语法很简单:
create_clock -name <时钟名> -period <周期(ns)> [ -waveform <上升沿 下降沿> ] [get_ports <端口名>]
举个例子。假设板子上有个100MHz的晶振,接到FPGA的 sys_clk 引脚上:
create_clock -name sys_clk -period 10.000 [get_ports sys_clk]
这里周期是10ns,对应100MHz。默认波形是50%占空比,上升沿在0ns,下降沿在5ns。如果你的时钟不是标准占空比,比如40%,那就得手动指定波形:
create_clock -name custom_clk -period 10.000 -waveform {0.000 4.000} [get_ports custom_clk]
clk_100m 比 clk1 好得多。调试时一眼就能看出是哪个时钟。
2.2 生成时钟
生成时钟,就是由主时钟通过PLL、MMCM或者分频逻辑派生出来的时钟。这类时钟不能直接用 create_clock,得用 create_generated_clock。
为什么?因为生成时钟和主时钟之间有明确的相位和频率关系。工具需要知道这个关系,才能做准确的时序分析。
语法长这样:
create_generated_clock -name <时钟名> -source <源时钟> -divide_by <分频系数> -multiply_by <倍频系数> [get_pins <输出引脚>]
举个例子。PLL输出一个200MHz的时钟,源时钟是100MHz:
create_generated_clock -name pll_clk_200m -source [get_ports sys_clk] -multiply_by 2 [get_pins pll_instance/clk_out]
再比如,用寄存器做二分频:
create_generated_clock -name div2_clk -source [get_ports sys_clk] -divide_by 2 [get_pins div2_reg/Q]
-source 必须是主时钟或者另一个已定义的生成时钟。我曾经见过有人把源时钟设成普通信号,结果时序分析全乱套了。嗯,这个坑我踩过。
2.3 输入延迟约束
输入延迟,说白了就是数据从外部芯片发出,到FPGA引脚接收,中间花了多少时间。这个时间包括:
- 外部芯片的时钟到输出延迟(Tco)
- PCB走线延迟
- 其他外部延迟
约束用 set_input_delay 命令:
set_input_delay -clock <时钟名> -max <最大延迟(ns)> -min <最小延迟(ns)> [get_ports <数据端口>]
举个例子。外部ADC输出数据,时钟是10ns周期,Tco最大2ns,最小1ns,PCB走线延迟0.5ns:
set_input_delay -clock sys_clk -max 2.5 [get_ports adc_data*]
set_input_delay -clock sys_clk -min 1.5 [get_ports adc_data*]
这里 -max 是2+0.5=2.5ns,-min 是1+0.5=1.5ns。工具会用 -max 检查建立时间,用 -min 检查保持时间。
2.4 输出延迟约束
输出延迟和输入延迟是对称的。它描述的是数据从FPGA引脚输出,到外部芯片接收,中间需要多少时间。
命令是 set_output_delay:
set_output_delay -clock <时钟名> -max <最大延迟(ns)> -min <最小延迟(ns)> [get_ports <数据端口>]
举个例子。FPGA输出数据给DAC,DAC的建立时间要求2ns,保持时间要求1ns,PCB走线延迟0.5ns:
set_output_delay -clock sys_clk -max 2.5 [get_ports dac_data*]
set_output_delay -clock sys_clk -min 1.5 [get_ports dac_data*]
这里 -max 是2+0.5=2.5ns,-min 是1+0.5=1.5ns。注意,输出延迟约束的 -max 对应外部芯片的建立时间要求,-min 对应保持时间要求。
2.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你看一遍,应该能有个整体印象。
2.6 避坑指南
最后,我把自己踩过的坑总结一下,你们遇到了能少走弯路:
- 时钟没约束全: 所有进入设计的时钟都要约束,包括异步时钟。漏掉一个,工具就不分析那条路径。
- 生成时钟用错命令: 分频出来的时钟,千万别用
create_clock,要用create_generated_clock。否则工具会当成两个独立时钟,分析结果完全不对。 - 输入/输出延迟只给一个值: -max 和 -min 都要给。只给 -max,保持时间分析会偏乐观;只给 -min,建立时间分析会偏乐观。
- 忘了加余量: 理论计算值直接拿来用,结果实际板子一测,时序差一点点。我建议至少留5%~10%的余量。
好了,这一章就到这里。约束这东西,刚开始觉得麻烦,但用顺手了就会发现——它其实是帮你理清设计思路的好工具。多练练,自然就熟了。
一句话总结: 主时钟是根,生成时钟是枝,输入/输出延迟是叶。根深才能叶茂,约束做扎实了,时序分析才能靠谱。