fpga timing constraints

    FPGA 时序约束与时序收敛:从"板子上能跑"到"永远满足时序"

    FPGA 时序实用指南:建立与保持裕量的含义、怎样写时钟和 I/O 约束、跨时钟域的处理、怎样读时序报告,以及真正能收敛时序的修法。

    ·XTELL 工程团队

    为什么"板子上能跑"还不够

    一个 FPGA 设计在某块板子、某个温度下运行正确,仍然可能是错的。如果工具从来没被告知时钟有多快、外部世界怎样送来数据,它布局布线时优化的就不是你真正的要求,设计能通过只是运气。换一次编译、换一块板、天气更热、芯片稍慢一点,它就会以看起来像随机毛刺的方式出错。时序约束,就是告诉工具"正确"意味着什么;时序收敛,则是让结果带着余量满足它的工作。

    两个重要的数:建立和保持

    每个寄存器在时钟边沿捕获输入,输入必须在边沿之前保持稳定一小段时间(建立),在边沿之后也要稳定一小段时间(保持)。工具会对寄存器之间的每条路径检查这两项。

    • 建立裕量是最长路径上剩下的时间:时钟周期,减去发送寄存器的时钟到输出延迟、逻辑延迟、布线延迟和接收寄存器的建立时间。负数表示数据到得太晚,这条路径在这个时钟频率下失败。一个编出来的例子,周期 10 ns:10 − 0.4(时钟到输出)− 6.0(逻辑)− 3.2(布线)− 0.3(建立)= 0.1 ns 的裕量。它通过了,但已经没有余地。
    • 保持裕量是最短路径上的余量:新数据不能在旧数据被安全捕获之前到达。保持违例与时钟周期无关,所以放慢时钟治不了它,工具通常靠在布线里增加延迟来修。

    报告用最差负裕量(WNS)、总负裕量(TNS)和失败的终点数量来汇总。目标是在工具建模的每一种工作条件下,每条路径的裕量都是正的。

    你必须告诉工具的事

    各大厂商使用的约束格式,都基于同一套 SDC 命令,只有细微差别,所以这些思路可以从一个工具链带到另一个。

    1. 时钟

    定义进入设计的每一个时钟及其周期。时钟管理模块(PLL 之类)的输出通常会自动推导,但要检查报告里确实有,对于你自己在逻辑里做的分频器,要自己定义生成时钟。

    2. 输入与输出

    工具看不到芯片外面。对每个接口,你要说明数据相对于时钟是怎样到达和离开的:时钟边沿与引脚上数据之间最大和最小的延迟,这来自外部器件的数据手册和板级延迟。

    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*]

    没有这些,工具会把这些引脚当作未约束,不会对它们报告任何东西,一个接口没有约束的设计,可能在报告里看起来很干净,却在系统里失败。

    3. 跨时钟域

    从一个时钟传到毫无关系的另一个时钟的信号,无法满足普通的时序,也不应该要求它满足。它们必须通过设计变得安全:单比特电平用两级触发器同步,多比特数值用握手,数据流用格雷码指针的异步 FIFO。然后告诉工具这些时钟之间没有关系,让它们不再把这些路径当成同步路径去检查:

    set_clock_groups -asynchronous \
        -group [get_clocks clk_100] \
        -group [get_clocks clk_adc]

    这是 FPGA 设计里被滥用得最多的约束。对一条实际上是同步的路径声明伪路径或异步分组,会把真实的失败藏起来,所以每一条都应当有书面理由,跨时钟域本身也应当作为一项单独的工作来评审。

    4. 例外

    多周期路径告诉工具,数据被允许用不止一个时钟周期到达,比如寄存器每四个周期才更新一次。它必须与设计真实的行为一致,包括使能逻辑。

    像工程师一样读报告

    • 先检查有没有东西没被约束:没有未约束的时钟、输入或输出,也没有被误忽略的路径。
    • 按严重程度依次看失败的路径,并把每一条从头读到尾:源寄存器和目的寄存器、逻辑级数、扇出、延迟里布线和逻辑各占多少。
    • 按原因给失败分组。五十条失败的路径,往往是同一个问题:一个慢的时钟使能、一个高扇出的复位,或者一个很大的多路选择器。
    • 在工具建模的工艺、电压、温度各个角上检查结果,并在零之上留一些余量,因为一个只有 20 ps 裕量的设计,下一次改动挪动了布局就会失败。

    真正能收敛时序的修法

    按这个顺序试,前面的代价更低。

    1. 用流水线减少逻辑级数。在很长的组合路径中间加一个寄存器。这会多一个周期的延迟,常常还要多一点面积,但它是最可靠的修法。
    2. 降低扇出。一个信号驱动几百个负载,布线延迟就很长。复制寄存器,让每个副本驱动一部分负载。
    3. 使用专用模块。乘法器、存储器和算术进位链在内置模块里比在通用逻辑里快,并把这些模块的输入和输出都寄存起来。
    4. 寄存 I/O。放在 I/O 单元里的寄存器能缩短到引脚的路径,使接口时序可预测。
    5. 用时钟使能,不用门控时钟。在逻辑里对时钟做门控,会破坏时钟网络和时序分析。请在寄存器上用使能。
    6. 引导布局。当两个频繁通信的模块被放得很远时,约束它们的区域,或者回头在设计上把它们分开。这一步要放到后期用,因为它会让结果更脆弱。
    7. 改变工具设置,比如努力程度、重定时或物理优化,前提是结构已经合理。设置能带来百分之几的改善,救不了一个设计。

    让收敛成为习惯,而不是抢救

    • 在项目一开始就写约束,每次构建都跑一遍时序报告,而不是只在最后。
    • 把新出现的时序失败,当作引入它的那次改动的 bug,在继续之前修好它。
    • 把约束和源代码一起放进版本管理,并像评审代码一样认真评审对它们的改动。
    • 目标时钟频率要比器件能力留出余量。把设计推到逻辑资源极限的时候,收敛会很慢,之后每增加一个功能都要打一仗。

    在我们的工作中

    我们在 Lattice、Microchip PolarFire 和 Xilinx 器件上点亮 FPGA 设计,包括那块光纤信号通信板:其中 Lattice FPGA 处理一个 14 位 80 MSPS 的转换器和一个 16 位 50 MSPS 的转换器。在这类设计里,接口时序就是设计本身。如果你希望把约束、跨时钟域和时序收敛,作为整个系统的一部分一起处理,请看我们的 FPGA 设计服务,以及嵌入式产品怎么选 FPGA。

    需要专业服务?

    联系我们获取定制化解决方案和报价