FPGA 时序实用指南:建立与保持裕量的含义、怎样写时钟和 I/O 约束、跨时钟域的处理、怎样读时序报告,以及真正能收敛时序的修法。
一个 FPGA 设计在某块板子、某个温度下运行正确,仍然可能是错的。如果工具从来没被告知时钟有多快、外部世界怎样送来数据,它布局布线时优化的就不是你真正的要求,设计能通过只是运气。换一次编译、换一块板、天气更热、芯片稍慢一点,它就会以看起来像随机毛刺的方式出错。时序约束,就是告诉工具"正确"意味着什么;时序收敛,则是让结果带着余量满足它的工作。
每个寄存器在时钟边沿捕获输入,输入必须在边沿之前保持稳定一小段时间(建立),在边沿之后也要稳定一小段时间(保持)。工具会对寄存器之间的每条路径检查这两项。
报告用最差负裕量(WNS)、总负裕量(TNS)和失败的终点数量来汇总。目标是在工具建模的每一种工作条件下,每条路径的裕量都是正的。
各大厂商使用的约束格式,都基于同一套 SDC 命令,只有细微差别,所以这些思路可以从一个工具链带到另一个。
定义进入设计的每一个时钟及其周期。时钟管理模块(PLL 之类)的输出通常会自动推导,但要检查报告里确实有,对于你自己在逻辑里做的分频器,要自己定义生成时钟。
工具看不到芯片外面。对每个接口,你要说明数据相对于时钟是怎样到达和离开的:时钟边沿与引脚上数据之间最大和最小的延迟,这来自外部器件的数据手册和板级延迟。
create_clock -name clk_100 -period 10.000 [get_ports clk_100]
# 来自外部器件的数据,使用数据手册里的延迟
set_input_delay -clock clk_100 -max 3.0 [get_ports data_in*]
set_input_delay -clock clk_100 -min 0.5 [get_ports data_in*]
# 送往外部器件的数据
set_output_delay -clock clk_100 -max 2.0 [get_ports data_out*]
set_output_delay -clock clk_100 -min -0.5 [get_ports data_out*]
没有这些,工具会把这些引脚当作未约束,不会对它们报告任何东西,一个接口没有约束的设计,可能在报告里看起来很干净,却在系统里失败。
从一个时钟传到毫无关系的另一个时钟的信号,无法满足普通的时序,也不应该要求它满足。它们必须通过设计变得安全:单比特电平用两级触发器同步,多比特数值用握手,数据流用格雷码指针的异步 FIFO。然后告诉工具这些时钟之间没有关系,让它们不再把这些路径当成同步路径去检查:
set_clock_groups -asynchronous \
-group [get_clocks clk_100] \
-group [get_clocks clk_adc]
这是 FPGA 设计里被滥用得最多的约束。对一条实际上是同步的路径声明伪路径或异步分组,会把真实的失败藏起来,所以每一条都应当有书面理由,跨时钟域本身也应当作为一项单独的工作来评审。
多周期路径告诉工具,数据被允许用不止一个时钟周期到达,比如寄存器每四个周期才更新一次。它必须与设计真实的行为一致,包括使能逻辑。
按这个顺序试,前面的代价更低。
我们在 Lattice、Microchip PolarFire 和 Xilinx 器件上点亮 FPGA 设计,包括那块光纤信号通信板:其中 Lattice FPGA 处理一个 14 位 80 MSPS 的转换器和一个 16 位 50 MSPS 的转换器。在这类设计里,接口时序就是设计本身。如果你希望把约束、跨时钟域和时序收敛,作为整个系统的一部分一起处理,请看我们的 FPGA 设计服务,以及嵌入式产品怎么选 FPGA。
联系我们获取定制化解决方案和报价