☰
跨die FPGA时序收敛实战:从频繁违例到稳定收敛的完整流程
2026/10/7 11:23:01 网站建设 项目流程

做FPGA开发也有几年了,我越来越觉得,评判一个设计有没有档次,关键就看两件事:一是能不能把功能跑通,二是能不能把时序收住。而在这两件事里,“跨die”一定是最能拉开差距的一个词。单die时代,你只要把时钟约束写清楚,让工具按默认策略跑一版,大部分路径都能收回来;但到了跨die FPGA,之前那套“顺手”几乎全部失效。SLR之间、die之间的路径延迟、时钟偏斜、温度电压不一致,都会让时序收敛变成一场硬仗。

这篇文章不打算做概念科普,而是直接把我实际项目中把一块多die FPGA从“频繁违例”打磨到“稳定收敛”的过程拆开给你看。不管你是做图像采集、高速接口还是信号处理,只要芯片是多die/SLR架构,这套思路基本都能复用。文中涉及的代码和命令以Xilinx环境为主,Intel Quartus的操作逻辑类似,关键区别我会单独说明。

1. 先弄清楚:跨die FPGA是怎么来的,难点又在哪

1.1 单颗die装不下了,于是有了多die/SLR架构

很多刚接触的人一听到“跨die”就懵:我明明用的是同一块FPGA,哪来的die?其实,所谓die就是芯片里那块真正做逻辑的裸片。传统单die FPGA,所有LUT、FF、BRAM、DSP都集中在一块die上,内部互联由统一的布线网络完成,时序模型相对简单。但工艺和容量到了一定程度后,一颗die的面积并不能无限做大,良率、光罩成本、散热全是问题。于是厂商换了一条路:把多颗die封装在一起,通过interposer、硅桥或者封装基板上的高速互联把它们连起来,对外看起来还是一颗FPGA,内部其实是多个die协同工作。

这个概念在Xilinx VU系列、VU+系列里对应的是SLR(Super Logic Region),在Intel的Stratix 10、Agilex上则是更彻底的chiplet设计。你可以把每个SLR想象成一个独立的“小FPGA”,它们片内的布线资源是完整的,但跨SLR的数据传递必须经过die-to-die接口,走一段比片内布线长得多的物理路径。我在调试时遇到的最直观现象就是:同样的逻辑,放在同一个SLR内,时序轻松收住;一旦被工具拆到两个SLR,路径延迟直接翻倍甚至更多。

所以跨die设计的第一个认知要扭转过来:你不能再把FPGA当成一整块“画布”,而是要把它当成一个由2到4个独立区域组成的集合体,让数据流尽量在区域内完成,减少跨区搬运。这就直接决定了后续所有时序收敛策略的走向。

1.2 跨die到底难在哪:时序模型完全不同

单die设计里,setup timing主要看逻辑级数和布线长度,只要约束写得好,工具通常能自动优化链路。跨die设计里多了一个让所有工程师头疼的变量:die-to-die接口延迟。这个延迟不仅数值大,而且随温度、电压的变化也更大,很难像片内布线那样精确预测。

我举个例子,假设系统时钟300MHz,周期3.33ns。单die内一条典型路径:触发器的Tcko约0.2ns,组合逻辑延迟约1.5ns,布线延迟约0.8ns,目标触发器的Tsu约0.1ns,那么slack大约还有0.73ns,算是个健康的设计。但如果把路径放到跨die场景,die-to-die接口延迟往往要增加1.5ns到3ns。就这一下,slack直接变负,时序不收敛就成了板上钉钉的事。

更麻烦的是时钟。片内时钟网络(比如BUFG)能把整个die的clock skew控制在很小的范围内,但跨die的时钟树要经过die-to-die互联,skew和jitter都明显变大。如果你还习惯性把单个高频时钟扇出到所有SLR,那你很可能同时踩中两个坑:一是时钟skew过大导致同一条路径的launch和capture不在一个节奏上,二是跨die数据路径上setup margin被接口延迟吃掉一大块。所以跨die设计的难点,本质上不是某一个点出了问题,而是数据路径、时钟路径、物理约束、工具策略四个方面都得重新适配。

2. 跨die时序收敛的底层逻辑:预算拆解与设计策略

2.1 跨die路径的时序预算怎么拆

在做任何布局和约束之前,我强烈建议先把要用的路径按延时性质分类,做一次时序预算。所谓预算,就是把周期、触发器延迟、逻辑延迟、布线延迟、跨die接口延迟、时钟偏斜全部列出来,看看每条关键路径还剩多少margin。这一步很多工程师觉得废话,但跨die设计里,预算做没做清楚,直接决定你后面是修路径还是推倒重来。

我常用的拆法是把路径分成三类:片内路径(寄存器到寄存器且在同一SLR内)、跨die路径(launch和capture位于不同SLR)、IO路径(到外部引脚/收发器)。片内路径的预算和单die一致,跨die路径则要额外预留die-to-die延迟,而且这个预留值不要按理想值算,最好按datasheet里的最大延迟再留20%到30%的余量。原因是die间传输对电压降和温度梯度更敏感,实测值往往比静态时序分析报出来的更大。

再往下拆,你要评估每个功能模块放在哪个SLR更合理。比如图像传感器采集进来的数据流,经过预处理、DMA搬运、DSP处理,最后输出到显示接口。每一步涉及不同的IP和存储资源,如果你不提前分配SLR,工具会自动把相关逻辑拆到离资源最近的位置,可能让关键数据通路横跨三个SLR,一条流水线被拉出好几ns延迟。预算的意义就在这里:它逼你在RTL阶段就意识到哪条路径不能跨die,哪有跨die必须用异步FIFO或者同步打拍来兜底。

2.2 从架构期就开始“设计收敛”,而不是等跑完布局再救

我见过太多跨die项目,RTL阶段完全按单die思路写,等综合布局布线出来,时序一片红,才开始加约束、插寄存器、改流水线。这种“事后补救”在单die时代还能勉强收住,在跨die时代基本是无底洞。因为工具本身不会理解你的业务数据流,它只能尽量把连接紧密的逻辑放到一起,跨die路径一旦形成,你删多少级流水都不一定救得回来。

正确的做法是在架构设计期就把“SLR/Die划分”当成一个模块级设计决策来对待。具体来说,每个大模块明确归属哪个die,模块之间的接口尽量收敛到少数几条宽总线,而不是散布大量细碎控制信号。宽总线走die间传输反而划算,因为die-to-die接口按带宽而不是按信号数量计费,逻辑上你还能打包成AXI-Stream这类流式接口,用异步FIFO天然隔离时钟域和die边界。

另外,时钟方案也要在架构期定下来。我的经验是:高频率时钟尽量在各自die内部生成,也就是每个die里用自己的PLL/MMCM,然后通过跨die的同步握手或者异步FIFO做数据交换,而不是把单个高频时钟用BUFG拉到所有die。低频慢速控制信号可以跨die传,但复位和模式配置这类信号必须做同步处理。这套做法本质上是把跨die问题从“物理路径收敛”转化为“跨时钟域设计”,而后者的方法论已经很成熟了,时序压力会小很多。

提示:跨die设计的核心不是“事后修”,而是“从一开始就不要让关键路径乱跨die”。物理约束只能帮你微调,架构决定了时序收敛的上限。

3. 实战流程:从die级布局到约束落地的完整操作

3.1 工具链与工程准备

我主要在Vivado里做这类项目,下面以它为例讲整套流程。Intel Quartus的操作思路很接近,只是命令名称不同,我会在关键步骤标注差异。

工程准备阶段有三件事必须做扎实。第一,确认芯片的SLR数量和每个SLR包含的资源,Xilinx在Device view里能看到每个SLR的坐标范围,Vivado里也可以用get_slrs命令直接列出当前器件下所有SLR。第二,把综合选项里的-flatten_hierarchy设置为rebuilt或none,保留层次结构,这样后续给模块施加物理约束时才能按模块名精确锁定。第三,综合后马上用report_utilization -slr看一眼每个SLR的资源占用,如果某个关键模块被打散了,优先通过综合属性保持层次,而不是等到布局后再切。

项目里一般会有很多个时钟域,跨die设计中时钟情况会比单die复杂得多。我建议在建工程时就把主时钟、派生时钟、异步时钟组的关系整理成一张表,后面写SDC会轻松很多。比如数据通路用300MHz、DDR控制器用200MHz、串口慢速时钟用50MHz,一上来就分清哪些时钟域之间需要同步、哪些是异步关系,能避免后续乱加约束。

3.2 第一步:die级物理规划与Pblock约束

跨die项目里我最先做的事情,不是写时序约束,而是做物理约束。说白了就是手工指定哪个模块放到哪个SLR。Vivado里最常用的手段是Pblock。你可以先创建Pblock,把某个模块的cell全部加进去,再把Pblock resize到目标SLR的范围内。示意命令如下:

# 创建Pblock并添加模块cell create_pblock pblock_axis add_cells_to_pblock [get_pblocks pblock_axis] [get_cells -hierarchical -filter {NAME =~ *axi_data_pipe*}] # 将Pblock锁定到SLR0对应区域,具体坐标以目标芯片为准 resize_pblock [get_pblocks pblock_axis] -add {SLR_X0Y0}

这里的SLR_X0Y0是Vivado里区域坐标的一部分,实际器件不同,坐标写法可能有差异。如果你不确定,可以先打开Device视图,勾选显示SLR信息,手工框选区域后用resize_pblock的操作面板生成对应命令,比手敲坐标更可靠。

Pblock刚创建时,工具可能不会把你的模块全部塞进指定区域,这时需要检查report_pblock,看有多少primitive落在区域外。区域锁定不是一锤子买卖,我通常会迭代两三轮:先按架构设计把大模块锁到die,跑一次布局,看跨die路径数量和时序情况,再细调Pblock边界或者把部分子模块挪到另一个SLR。直接一次锁死反而会让布线资源失衡,导致局部拥塞。

3.3 第二步:时钟与跨die接口约束

物理规划完成后,接下来是时序约束。跨die设计里第一个要处理的是异步时钟组。如果你按前面说的方案,让不同SLR使用独立时钟源,那么这些时钟之间必须用set_clock_groups声明异步,否则工具会默认把它们当成可分析路径,跨die的假路径和不必要的timeout约束会刷屏。

# 异步时钟域隔离:clk_a属于die0,clk_b属于die1 set_clock_groups -asynchronous -group {clk_a} -group {clk_b} # 跨die接口同步器的max_delay约束 # cdc_ff[0]在die0,cdc_ff[1]在die1,路径上通过两级同步器 set_max_delay -from [get_cells -hier -filter {NAME =~ *sync_cdc_ff[0]}] \ -to [get_cells -hier -filter {NAME =~ *sync_cdc_ff[1]}] 20

这里有个细节要注意:跨die的异步FIFO或同步器路径通常不需要严格的单周期时序收敛,它们的重点是保证数据稳定窗口和数据传输的正确性。所以用set_max_delay而不是让工具把同步器路径也按普通寄存器到寄存器路径去死磕。你可以给这类路径一个足够宽裕的约束值,比如10ns或20ns,原理是让它比die间延迟大就行,这样既不会过度约束,又不会完全放飞。

除了异步时钟组,还要检查有没有跨die的同频时钟路径。如果存在同频但相位不同的时钟跨die传输,必须核对datasheet里的die间skew值,必要时设置set_clock_uncertainty,把比片内更悲观的clock uncertainty写进去。否则静态时序分析会和实测差很远,我遇到过看起来有正slack,上位机运行一会儿就偶发错误的现象,最后发现就是clock uncertainty没加够。

3.4 第三步:跨die路径的时序修整与迭代

物理约束和时钟约束都做完之后,跑布局布线,看时序报告。跨die设计里,第一次运行时序报告全绿是极小概率事件,多数情况是少数几条关键路径红掉。这时候不要急着改逻辑,先打开时序报告里违例路径的详细信息,检查它真正的delay构成。

如果红掉的路径是片内路径,说明资源放置得不够集中,优先调整Pblock边界或把相关逻辑合并到同一SLR。如果红掉的路径是跨die路径,无非三种情况:跨die接口延迟超出预算、时序约束太严、或者跨die路径数量太多导致接口拥堵。接口拥堵的典型特征是FIFO两端逻辑都在自己die里,但违例路径都集中在同一组die-to-die接口上。这时要做的不是去修剪某条路径,而是把数据搬移分散到多个die接口,或者把部分处理挪到接收端die,减少单向传输数据量。

工具层面,Vivado里可以给Pblock设置物理综合相关的属性,比如set_property STEPS.SYNTH_DESIGN.ARGS.DIRECTIVE AlternateRoutability [current_run]这类综合策略,以及布局阶段的directive切换。跨die项目我一般会把综合和布局布线的directive都调成偏物理性的选项,比如Explore、AggressiveExplore,跑出来的结果经常比默认策略好很多。但代价是运行时间明显变长,适合在项目后期做最终收敛,不要每次迭代都用蛮力。

还有一个实用技巧:在布局完成后、布线之前,先跑一遍report_timing_summary -path_type full看跨die路径的预布局估计。Vivado的早期时序评估虽然不准,但能快速暴露物理划分的问题。我通常用这个报告决定是继续细化物理约束,还是直接进入布线。布线后如果仍然有人不过的跨die路径,再逐条查看是否存在可优化逻辑级数的地方。

4. 跨die设计常见问题与排查技巧实录

4.1 接口路径不长却严重违例,问题出在哪

有一种情况很迷惑:时序报告里某条跨die路径的逻辑级数只有三四级,看上去完全健康,但slack就是负得很厉害。排查这类问题,我第一件事是看路径的Route Delay。单die设计里Route Delay通常占比较小,但跨die接口路径的Route Delay可能是片内路径的好几倍,因为数据要先走到die边缘,经过接口,再从另一个die的边缘走到capture寄存器。

我在一个项目里遇到过一次,A die到B die的路径只有两级寄存器,但report_timing显示整条路径延迟超过7ns,明显不合理。后来打开Device视图高亮这条路径,发现数据从A die底部绕了大半个die才到接口。原因是Pblock没有把相关模块放到靠近die-to-die接口的位置。解决方式是调整Pblock坐标,让源模块和目标模块都贴近接口区域,路径延迟立刻降了将近一半。所以不要只看逻辑级数,要看物理位置。

另外,有的芯片die-to-die接口数量有限,如果设计里大量信号直接跨die,接口区域的布线资源会被占满,产生严重拥塞。我在排查时发现,一旦接口区域的congestion超过一定阈值,不管哪条路径,只要经过附近就会疯狂绕线。此时与其修单条路径,不如优化数据通路的打包方式,比如把32比特信号打包成AXI-Stream总线,配合异步FIFO整组传输,接口占用会小很多。

4.2 复位亚稳态:跨die最容易被忽视的坑

如果你在热搜里看到过“fpga复位信号亚稳态”这类词,说明这确实是高频痛点。跨die架构下,复位信号一旦直接从一块die的复位管理单元拉线到另一块die的触发器,大概率出问题。die间路径长、skew大,复位释放时刻在所有寄存器上根本不是同时到达,有些寄存器已经释放开始工作,有些还没有,整个状态机直接跑飞。

我踩过这个坑之后总结了一套固定打法。复位信号进入每个die时,必须在die内部先做“异步复位、同步释放”,用本地时钟打两拍,再作为这个die所有逻辑的复位。跨die的复位传递只保留原始的异步复位断言,释放统一交给各die自己处理。这样复位释放的时序在每个die内部是可控的,跨die的问题就被隔离掉了。

// 在die内部处理复位释放 reg [1:0] rst_sync; always @(posedge clk_local or posedge rst_raw_async) begin if (rst_raw_async) rst_sync <= 2'b0; else rst_sync <= {rst_sync[0], 1'b1}; end assign rst_local = rst_sync[1];

这种做法的代价是复位释放延迟增加了两个本地时钟周期,但对于绝大多数系统完全不是问题。如果你有跨die的CDC信号与复位同时出现,更要警惕一前一后的时序怪象,建议将跨die状态机的所有状态位都用格雷码或增加握手协议,保证不会因为复位路径延迟产生非法状态。

4.3 温度电压变化与多die收敛的不确定性

单die芯片内部的温度相对均匀,跨die封装里的多个die之间温度可能相差不少,尤其当某个die专跑DSP、另一个die主要做IO时。温度差大会让die间接口延迟出现明显漂移,这也是我前面强调要在时序预算里留余量的原因。做signoff的时候,不能只看典型条件下的时序报告,一定要跑多个corner,尤其是最慢corner和最差电压组合。

Intel Quartus里有专门的跨die时延分析报告,Xilinx Vivado在时序分析里会展示路径经过的SLR和die间跳转。我在项目后期养成了一个习惯:每次温度从0度到85度做高低温测试时,都把关键时钟频率和收发器误码率记录下来,对比常温数据。如果误码率或功能错误与温度强相关,十有八九是跨die路径的时序余量不足,而不是逻辑功能问题。

这时处理手段主要有两个:一是继续扩大跨die路径的max_delay约束,给同步逻辑更多裕量;二是降低跨die接口数据率,比如在FIFO写侧增加半满/半空阈值,让突发数据不会密集冲击接口。很多工程师只会在RTL里硬调,其实时序收敛是一个软硬协同的活,数据流整形和时序约束双管齐下才最有效。

4.4 从工具报表里精确定位跨die路径

跨die项目里,工程师要学会的第一件事不是写约束,而是看报表。Vivado的时序报告默认显示路径延迟占用的明细,里面有Logic Delay、Route Delay、Clock Skew等数值。如果你看到Route Delay比Logic Delay大很多,尤其是路径起点和终点不在同一个SLR时,先想到跨die。更直接的办法是在Report Timing Summary里右键路径,选择Highlight in Schematic/Device,看布局窗口里这条路径到底跨过了哪些SLR。

我常用的排查流程是先report_clock_interaction看跨时钟域路径数量,再report_timing_summary -max_paths 100排出所有违例路径,按起点所在的Pblock分组。如果违例路径集中出现在两个die的边界区域,那就明确是die-to-die布局问题,优先做物理调整。如果违例路径分散在各个SLR内部,反而说明是整体逻辑延迟过长,考虑插流水或优化组合逻辑深度。

注意:Vivado的默认时序报告里,跨die路径未必会直接显示“crossed SLR”字样。你需要在路径报告里看Source和Destination的Site坐标,如果它们的SLR编号不同,就能确认它是一条跨die路径。养成这个习惯,能省下大量排查时间。

5. 和高速IO、图像处理等场景的衔接经验

5.1 高速接口场景下的die间数据搬运

跨die FPGA大量用于高速接口场景,比如LVDS接收、MIPI、SRIO、PCIe等等。芯片面积一大,既要做协议栈又要做数据缓存,还要和主控逻辑交互,跨die几乎无法避免。我的经验是,所有高速数据进入FPGA后,第一时间应该落到靠近收发器所在die的FIFO/BRAM里,完成数据格式转换或位宽拼接,然后以burst方式搬运到另一个die的处理区块。

这样做的核心原因是:收发器的硬核资源通常固定在特定die上,如果你让数据一进来就横穿多个die去做对齐、做校验,那跨die路径会遍布整个设计,时序根本没法收敛。我在设计里会指定“采集侧die”和“处理侧die”,中间的数据通路用AXI-Stream + 异步FIFO,总线位宽尽量大一点。例如300MHz时钟下用64bit甚至128bit的FIFO总线,把数据突发压低,跨die接口压力会小非常多。

如果你做MIPI或者LVDS接收,还要特别注意字节对齐、通道对齐逻辑尽量放在同一个die内完成。这类逻辑需要频繁比较各个通道的延迟状态,跨die做的话通道间skew会变得非常难以控制。我看到不少做图像采集的同行在跨die项目上卡住,最后发现根本不是算法问题,而是把对齐逻辑打散到了不同die,导致通道间差距超过协议容忍范围。

5.2 图像处理链路中的跨die布局原则

图像处理相关的关键词在热搜里扎堆出现,像“fpga图像采集”“fpga双线性插值”“fpga实现rgb转tmds”等等。图像处理链路的典型特征是数据流连续、吞吐量大、算法模块多。跨die项目里最容易犯的错,是把整条图像流水线按模块顺序一个模块占一个die,A做灰度、B做滤波、C做缩放,数据从左到右横穿整个芯片,结果每级之间都跨die,整条链路时序红成一片。

更合理的做法是,把图像处理链路按照“存储密集”和“逻辑密集”来划分。存储密集的部分(如行缓存、帧缓存、DMA写DDR)放在靠近BRAM/DDR控制器资源的die,逻辑密集的部分(如滤波、缩放、边缘检测)放在另一个die,然后让整块逻辑在一个die内跑完算法,只把最终结果送出去。这样跨die路径只在链路的边界出现,数量少且可控。

如果你非要把一些算法模块放在不同die,那就必须在它们之间插入足够深度的FIFO,并且让每个模块内部尽量无反馈回路。图像算法里常见的回环结构(比如迭代滤波、递归滤波)特别不适合跨die,因为反馈路径跨die会形成很长的循环,收敛难上加难。我通常的做法是把这类算法用流水线展开,或者把反馈状态留在本die内的BRAM,跨die只传加速后的数据流。这套原则做透了,图像处理链路在跨die器件上依然能跑得很稳。

6. 一点我自己的体会

最后说点系统性的东西。跨die设计做了几个项目之后,我心里的技术优先级已经非常明确:架构设计时先画die级数据流图,RTL设计时把跨die接口全部做成流式接口加同步逻辑,物理约束明确锁模块,时序约束里优先隔离异步时钟域和设置跨die max_delay。只要这四层做好了,后面的工具迭代就只是微调,而不是失控的拉锯战。

很多工程师一开始会把跨die当成一个纯工具问题,觉得“跑个布局、加几条约束就完事”,但真正踩过几次坑之后你会发现,决定收敛的从来不是某一条命令,而是你愿不愿意在设计早期就把die边界当成第一等公民来看。时序分析报表只是最后的成绩单,真正的功课全在前面。希望这篇从概念到实战的总结,能让你少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询