1. 先弄明白:到底什么样的设计才值得上Hierarchical Flow
在数字IC设计这行待久了,你会发现一个特别有意思的现象:很多团队一听到"Hierarchical Flow"就觉得这是个高端玩意儿,好像只有做CPU、GPU的大厂才配用。结果就是,要么设计规模明明不大却硬上层级化,把自己折腾得半死;要么设计已经堆到几千万门了还在用扁平化硬扛,最后在布局布线阶段叫苦不迭。
从我自己的实践经验来看,Hierarchical Flow说白了就一句话:把一个大设计拆成多个模块,分别做综合和物理实现,最后在顶层把结果拼起来。这个思路本身不难理解,难的是判断什么时候该拆、拆到什么粒度、以及拆完之后怎么把各个模块的时序和物理信息无缝对接起来。
先说说什么时候你真的需要层级化。我用一个比较粗的标准来判断:
- 设计规模在1000万门以上,或者单核频率要求很激进,时序收敛压力巨大;
- 设计中有多个功能差异明显的大块,比如CPU core、GPU单元、NPU加速器、各类接口IP等,天然具备模块化边界;
- 团队的并行工作量够,可以把不同模块分给不同的人或小组并行推进;
- 跑一次平坦化布局布线的时间已经长到影响迭代效率,比如超过一两天,这时候拆开做能够显著缩短迭代周期。
反过来说,如果你的设计规模不大,各模块之间交互又极度紧密,层级化反而会给你添乱。我见过有人把一个只有两三百万门的设计硬拆成五个模块,结果光是处理模块间的IP边界时序就花了两周,最后做出来时序还不如直接平坦化跑得好。所以第一步不是急着动手,而是冷静评估你的设计到底适不适合层级化。
从逻辑综合到布局布线的完整Hierarchical Flow,大体上可以分成四个阶段:层次划分与约束准备、逻辑综合、物理实现(布局布线)、全芯片集成与签核。每个阶段都有各自的坑,而且很多坑是嵌套的——你在综合阶段埋下的雷,到了布局布线阶段才会炸。
接下来我按这个路径,把我踩过的、看见别人踩过的坑,一条一条拎出来说。
2. 逻辑综合阶段的层次划分:拆得对,后面才顺
2.1 划分原则:功能内聚、时序解耦、规模均匀
逻辑综合阶段,第一个关键决策就是怎么划分层次。这个决策直接影响后面布局布线的成败,很多人在这上面吃亏。
理想的分块原则,应该同时满足三个条件:
第一是功能内聚。每个模块内部尽量是功能完整的一块,模块之间的数据流相对简单。比如说,一个CPU子系统的取指单元、译码单元、执行单元,它们之间交互非常紧密,应该放在同一个物理模块里;而CPU core与GPU之间通过总线交互,数据流相对规则,这种边界就适合做层级切分。
第二是时序解耦。模块之间的边界应该尽量"干净"——翻译成人话就是,不要让关键路径横跨模块边界。如果一条关键路径从模块A出发,穿过模块B,再回到模块A,这种交叉耦合会让顶层时序分配变得极其痛苦。理想情况下,模块内部的时序应该占80%以上,模块间路径保持在20%以内。
第三是规模均匀。每个模块的规模不要相差太悬殊。如果一个模块占了全芯片60%的面积,另外一个模块只占5%,那大模块会变成整个flow的瓶颈——所有人都等它跑完才能做顶层集成。
在工具实现层面,综合阶段就要设定好每个模块的compile_ultra -gate_clock选项(如果有时钟门控),并且对每个子模块分别做约束。这里有个很容易忽略的点:子模块综合时使用的时钟周期假设,必须与顶层最终的时钟约束一致。我见过不少团队在模块级综合时用了一个相对宽松的约束,结果到顶层集成时发现模块内部路径的时序余量根本不够,只能回头重新综合。
2.2 顶层网表与子模块网表:谁来管哪些逻辑?
层级化综合完成之后,你会得到一份顶层网表和若干子模块网表。顶层网表里,每个子模块被实例化,它们内部的逻辑都用don't touch的方式保护起来。
这里有个常见误区:顶层综合时要不要把子模块的约束也重复约束一遍?我的经验是,顶层的约束只需要覆盖顶层的逻辑和模块间路径,不需要重复子模块内部的完整约束。如果你在顶层把子模块内部的约束也带上了,不但浪费时间,还可能因为约束叠加导致不期望的时序违例。
把这个问题说细一点:逻辑综合工具在优化顶层时,会把子模块当作黑盒处理,只看到模块端口的时序弧(timing arc)。所以你在顶层只需要约束好:
- 模块输入端口的输入延迟(input delay);
- 模块输出端口的输出延迟(output delay);
- 顶层时钟、生成时钟及时钟组(clock groups)关系;
- 模块间的伪路径或多周期路径(如果有)。
这些约束来源,就是你在子模块综合时得到的模块级接口时序。很多团队会专门导出一份接口时序报告(interface timing report),作为顶层约束的参考。这个做法非常值得推广,因为接口时序报告里的路径delay值,才是真正反映模块物理实现后接口时序的可靠数据。
2.3 多模式多角标场景下的综合策略
数字IC设计到了先进工艺节点,多模式多角标(MMMC,Multi-Mode Multi-Corner)已经是标配。在Hierarchical Flow里,MMMC的复杂度会进一步放大,因为你每个子模块都要做一遍MMMC综合,顶层还要再做一遍。
我在实际项目中遇到过最头疼的情况是:子模块在慢工艺角下综合收敛了,但在快工艺角下hold time却出了大量violation。原因很简单——模块级综合时hold fix的力度通常不如顶层完整,很多hold violation要到顶层集成阶段才会暴露。
解决这个问题的思路是:在子模块综合阶段就主动预留一些hold margin。比如时钟树综合之前的setup target留5%的余量,hold target也提前放开,让综合工具在优化时把一部分hold问题顺手处理掉。这样虽然会让子模块面积稍微变大,但换来的是顶层集成阶段少很多折腾,整体时间反而是省下来的。
3. 模块接口时序建模:ILM和ETM到底怎么选
3.1 两种接口模型的本质区别
到了物理实现阶段,你需要把综合后的子模块网表送到布局布线工具里去做floorplan和时钟树综合。问题来了:**顶层和子模块怎么协同工作?**这时候就涉及到接口时序建模。
业界最常用的两种模型是:
ILM(Interface Logic Model,接口逻辑模型):保留模块边界附近的逻辑,包括所有寄存器、以及从端口到寄存器、寄存器到端口的组合逻辑。内部远离端口的逻辑被简化掉。ILM中包含的实际物理信息更多,时序精度更高。
ETM(Extracted Timing Model,提取时序模型):把整个模块抽象成一个带时序弧的"黑盒",只保留端口的时序行为,内部逻辑完全不保留。
从使用场景来说,ILM适合模块内部逻辑和边界逻辑之间耦合度较高、需要较高精度时序分析的情况;ETM则适合追求顶层运行速度和内存占用、对精度要求相对宽松的情况。
我个人的习惯是:在顶层布局规划(floorplan)阶段用ETM快速迭代,在最终签核阶段换成ILM做高精度验证。这两个阶段各取所长,既保证了迭代速度,又不牺牲最终精度。但注意,ETM的生成质量非常依赖提取时对接口条件的设置——如果输入转换时间(input slew)和环境负载(load)设置不合理,ETM提取出的时序弧参数会与实际差异很大,到了签核阶段就会翻车。
3.2 接口时序约束的常见“坑”
接口时序约束是Hierarchical Flow里翻车率最高的地方,我把常见的坑列一列:
坑一:模块输入端口约束使用理想时钟而非传播时钟。
在模块级实现时,工具会默认把时钟当作理想时钟来处理,也就是认为时钟到达每个触发器的时间是相同的。但到了实际物理实现中,时钟树是有延迟的,这就是时钟传播延迟(clock latency)。如果你在生成ILM或ETM时没有正确传播时钟信息,那么顶层分析模块接口时序时,会把时钟延迟算错,导致实际时序和预期偏差很大。
解决方式是必须在生成接口模型时使能时钟传播选项,让工具把时钟树延迟信息包含到模型里。
坑二:接口路径的虚假延迟。
模块输出端口到外部负载之间的延迟,取决于外部物理连线长度和负载电容。如果你在模块级实现时给输出负载设了一个过小的值,模块输出级的驱动优化就会不够;反之设得过大,又会过度优化、浪费面积功耗。
正确的做法是使用实际估算的线负载模型或基于floorplan的寄生参数预估(比如根据预估连线长度计算RC值),而不是简单拍脑袋给一个固定负载值。
坑三:模块间路径没有进行时钟偏移约束。
当时钟树综合完成后,相邻模块的时钟偏移(clock skew)如果比较大,模块间路径的实际时序余量会比理想分析小很多。所以顶层集成阶段务必要检查跨模块路径的clock skew,必要时在模块级约束里就预留足够的skew margin。
3.3 实际使用:ILM/ETM生成的步骤建议
以我习惯的流程为例,模块级物理实现完成后,生成接口模型大致分这几步:
- 在模块级布局布线的工具里,检查模块内部时序收敛情况,确保模块内部没有未修复的violation;
- 禁用时钟树综合(CTS)之后,提取带时钟延迟的网表信息;
- 设置好输入端口slew、输出端口负载,确保与实际环境一致;
- 生成ILM或ETM模型文件;
- 在顶层使用这些模型替换原来的黑盒,重新做时序分析。
这里特别提醒一下第4步。生成完模型后,一定要做一次模型质量检查——把模型与实际网表的时序比对一遍,确认误差在可接受范围内。我见过有人直接拿模型就跑顶层,结果到了最后LVS阶段发现模型与实际网表对不上,整个顶层数据全部要重新反标,那种返工代价真的高得离谱。
4. 布局布线阶段:整芯片规划和封装之后,麻烦才刚刚开始
4.1 顶层Floorplan的层次化处理:分区必须留出“呼吸空间”
到了布局布线阶段,Hierarchical Flow的难度会成倍上升。因为在模块级,你可以相对独立地做floorplan和布局布线,但到了顶层,你必须在物理上把每个模块的实际位置定下来,同时还要保证模块之间的连线能够顺利布通。
顶层floorplan有几个关键点需要注意:
首先是模块形状和宽高比。模块的真实面积和利用率之间有个平衡。我见过有人早期估算模块面积时太乐观,结果模块实际布线完成率不够,出现大量的绕线拥塞,最后不得不重新规划floorplan。合理的做法是,在模块面积估算时预留10%-15%的布线通道余量——尤其是数据通路密集的模块,这个余量值得给足。
其次是模块周边要有足够的I/O pin分布空间。很多人做floorplan时只盯着面积,没考虑I/O pin的分布是否均匀。如果一组总线接口的pin全部集中在模块的一侧,且非常密集,肯定会造成局部拥塞。我实践中的经验是,让工具在放置pin时尽量打散,同时预留好pin与pin之间的间距,宁可多占一点绕线资源,也不要让pin堆在一起变成瓶颈。
第三是硬宏和模拟IP的摆放。有模拟IP的设计里,数字模块和模拟模块之间必须留出隔离带,这不仅是为了避免噪声耦合,也是为了布线资源的均匀分布。任何有高精度模拟IP的设计,我都会建议在floorplan阶段就用物理距离把数字和模拟隔开——这个原则在Hierarchical Flow里照样成立,而且由于有多个数字层次,隔离带的规划更要提前。
4.2 模块间布线:你绕不开的拥塞热点
层级化布局布线里,模块间布线的拥塞是一个无解但必须管理的课题。模块内部的布线,因为逻辑密度相对可控,拥塞通常不多;但模块之间的总线连线,尤其是穿过某个模块"头顶"的跨模块连线,往往会在物理实现阶段成为拥塞热点。
我在一个具体的SoC项目里遇到过这样的情况:三段式总线从CPU模块出发,穿过GPU模块到达NPU模块,这个"穿行"路径刚好经过GPU内部的密集逻辑区域,结果GPU模块布线时怎么绕都绕不通,严重影响了时序收敛。
后来怎么解决的?我们在顶层floorplan时调整了GPU模块的形状,把中间"让"出一条走线走廊,允许总线以最短路径穿过,同时把GPU内部的布局密度适当降低。就这样一个改动,整颗芯片的布线完成率和时序余量都有了明显改善。
所以,在做模块级floorplan时,一定要把顶层跨模块连线的走向和数量考虑进去。你可以在顶层floorplan阶段先快速跑一版全局布线(global routing),把主要拥塞区域和线网走向看清楚,然后回头调整模块的形状、位置和I/O pin分布。这个"先global后local"的思路,是减少返工最高效的方式。
4.3 时钟树综合的分层处理策略
时钟树综合(CTS)是Hierarchical Flow中另一个最容易出问题的环节。在扁平化流程里,CTS是整个时钟网络一次性构建的,skew相对容易控制。但在层级化流程里,CTS通常是模块内部各做各的,模块之间的时钟路径则在顶层做连接。
这里有个关键点:模块内部CTS的时钟树质量,直接影响顶层CTS的可行性。如果每个模块内部都把时钟树做得特别深、特别细,树根latency很大,那顶层连接时就会面临大量的skew调整困难。
我推荐的做法是,模块内部CTS时就要考虑"顶层友好"的原则:
- 模块内部时钟树的最大延迟要控制在合理范围内,不要做得太深;
- 在模块边界预留好时钟输入端口,让顶层时钟树能把时钟送入模块;
- 模块内部不同时钟域的边界区域,提前做好**时钟域交叉(CDA)**的约束,避免顶层集成时出现意想不到的clock gating check失败。
另外还有个细节:模块级CTS时通常会把时钟当作ideal处理,但到了顶层,你必须在完整的RC寄生参数下重新做时钟树分析。这时候模块内部的时钟latency和skew都会变化。如果模块级CTS没有留够余量,顶层CTS完成后很容易出现内部路径的hold violation,这个时候回头改模块内部CTS,代价就大了。
5. 集成与签核阶段:那些“差一点就炸了”的实战场景
5.1 跨模块关键路径的时序收敛:一条路径一个方案
顶层集成阶段最常见的场景,就是跨模块关键路径的时序分析。
我在以往的项目中总结了一套处理流程,按照这个流程走,大部分路径问题都能在早期发现:
- 顶层集成后,先做全芯片时序分析,把所有跨模块路径按余量排序,找出最差的几十条路径;
- 对每条路径,判断延迟的主要构成——是模块内部路径延迟过大,还是模块间连线延迟过大;
- 如果是模块内部延迟大,回到模块级去优化(改进逻辑综合约束或物理实现);
- 如果是模块间连线延迟大,看能否调整模块的位置或I/O pin位置来缩短连线长度;
- 如果都试过了还不行,考虑在这些路径上插入流水线寄存器,把长路径拆成两段——这是最后手段,但往往是最高效的。
我印象很深的一次:一个跨模块路径总是差0.2ns左右,模块内部怎么优化都降不下来。后来仔细分析发现,路径上有个多路选择器(MUX)的逻辑层级过多,而且恰好分布在模块边界两侧。我们在边界插入了一级流水寄存器,把路径拆成两段,分量都降到了一半以下,整个芯片的时序瞬间就收敛了。当然,插入寄存器会改变流水级数,需要同步调整功能验证环境——这一步千万别漏。
5.2 功耗与电压降(IR Drop)在层级化里的特殊性
先进工艺节点下,功耗密度居高不下,IR Drop分析在Hierarchical Flow里也有它的特殊性。
标准的IR Drop分析是基于完整的电源网络(power grid)来做的。在层级化流程里,如果你在模块级单独做IR Drop分析,由于模块没有完整的电源网络——顶层电源网络还没有连下来——分析结果实际上是不完整、不准确的。
正确的做法是:在顶层把电源网络铺好,再把模块放置在物理位置上,建立完整电源网络后做全局IR Drop分析。这时候重点检查模块顶部电源pad与模块内部标准单元供电点之间的电压变化——如果压降超过阈值,首先要考虑的是在顶层增加电源pad或垂直供电通道,然后才是调整模块内部的电源网络密度。
这里分享一个我踩过的坑:有一次模块级IR Drop分析显示内部压降只有2%,完全在范围内,结果顶层的完整分析却报出5%的压降。原因就在于模块级分析时没包含顶层电源网络的电阻,实际工作电流模块附近的本地IR Drop被低估了。从那以后,我每次做模块级IR Drop分析都会明确标注"仅用于早期评估,最终以顶层完整分析为准"。
5.3 形式验证与ECO流程:层级化的隐藏成本
签核阶段的逻辑等价性检查(LEC),在层级化流程里也有特殊讲究。
通常的做法是:模块级LEC用来验证综合前后的等价性,顶层LEC用来验证模块级网表+顶层网表与综合前顶层RTL的等价性。这个过程中,很容易出问题的环节是:模块级ECO修改了网表,但没有同步更新到顶层。
ECO在层级化流程里是一个"双刃剑"——它让模块级修改变得相对简单,但也容易造成顶层网表和模块网表的不一致。我的原则是:任何ECO操作都必须有清晰的版本管理记录,并且在顶层LEC时把ECO涉及的逻辑变换显式包含进去。很多人为了省事,只在模块级跑了LEC,顶层LEC没有覆盖到ECO修改的部分,结果到了后端发现逻辑不等价无法修复,那种绝望真的是刻骨铭心。
实际操作中,我建议团队建立一套"ECO-only path"的专项检查流程:ECO列表出来后,由后端负责人确认哪些ECO影响顶层逻辑,哪些只影响模块内部逻辑,然后在顶层LEC环境里创建一个专门的setup,把ECO后的模块网表和ECO前的模块网表进行等价性对比。只有这样,ECO才不会成为埋在后端流程里的一颗地雷。
6. 从实践中来:三个真实跳坑场景复盘
6.1 案例一:模块接口时序两套报告,自相矛盾
有次在做一颗SoC的时序收敛,我拿到模块级和顶层的接口时序报告,发现同一段路径在两个报告中的延迟值差了整整30%。查了半天,原因是模块级生成接口模型时,输入端口的slew值用的是默认的较慢转换时间,而顶层实际驱动的slew要快得多。
这个问题的教训很直接:接口模型的输入条件和顶层实际环境必须保持一致。从那以后,我在流程里加了一个步骤——从顶层时序报告中找一段模块的驱动路径,倒推模块端口处的实际slew,再用这个slew去生成接口模型。虽然步骤多了一点,但接口模型与顶层的一致性大幅提升。
6.2 案例二:一个看似无害的模块复用引发了芯片级拥塞
另一个项目里,我们从上一代设计中直接复用了一个接口模块,模块内部逻辑完全没改,只是在顶层把模块的朝向旋转了一个角度。结果就是旋转之后,模块的I/O pin分布与顶层网络连接方向不匹配,导致绕线绕出了一个巨大的包,周边区域的拥塞率飙升到95%以上。
后来我们把模块物理方向调回来,并且手动调整了几个关键bus的pin位置,拥塞立刻降到了75%。这个案例说明,模块复用不只是物理信息的复用,还包括与顶层的接口适配。任何复用的IP,在顶层floorplan里都要重新检查I/O pin的分布和网络连接方向,不能因为"上代芯片能跑通就默认这次也没问题"。
6.3 案例三:时钟树skew导致的功能性错误
最后一个案例涉及一个很隐蔽的时钟问题。模块A和模块B各自有内部时钟树,顶层把它们连到同一个PLL输出。模块级分析时,两棵树都没有问题,但顶层集成后,模块A的时钟与模块B的时钟之间出现了接近1ns的skew。
这条路径刚好是一条跨模块的握手信号路径,数据从模块A发出,在模块B的时钟沿被采样。由于skew过大,采样时刻提前,导致数据还没稳定就被采到了,功能验证跑某些边界case时开始随机失败。
当时我们在顶层CTS后发现skew超标,立刻对这两个模块的时钟树进行了重新平衡,把skew控制到了100ps以内,功能验证重新通过。从那以后,我在团队里明确要求:所有存在跨模块握手信号的设计,CTS之后必须单独分析跨模块skew,不能只看模块内部的skew报告。
7. 按经验落地的检查清单与关键配置建议
说了这么多坑,最后把我自己的实践沉淀成一份可直接对照的检查清单。每次做Hierarchical Flow项目前,我会把这个清单过一遍,能避免大部分可预见的坑。
| 阶段 | 检查项 | 常见失败信号 | 预防/解决方案 |
|---|---|---|---|
| 层次划分 | 模块规模是否均匀 | 某模块面积占比超过50% | 重新平衡模块边界 |
| 层次划分 | 时序耦合是否可控 | 跨模块路径占比超过20% | 调整分区,收敛跨模块交互 |
| 逻辑综合 | 子模块约束与顶层一致 | 模块级时序在顶层恶化 | 统一时钟约束和条件设置 |
| 逻辑综合 | 接口时序报告是否生成 | 顶层约束无依据 | 每个子模块导出接口时序报告 |
| 物理实现 | 模块面积估算余量 | 布线完成率不足 | 预留10%-15%布线余量 |
| 物理实现 | I/O pin分布均匀性 | 局部拥塞热点 | 打散pin,加宽走线走廊 |
| 物理实现 | 模块间CTS质量 | 跨模块skew超标 | 单独分析跨模块skew |
| 集成签核 | 接口模型与实际一致 | 时序分析偏差大 | 反标实际输入slew/负载 |
| 集成签核 | LEC覆盖ECO变更 | 逻辑等价性失败 | ECO专项等价性检查 |
| 集成签核 | 完整IR Drop分析 | 单元供电压降超标 | 顶层电源网络铺好后分析 |
除了这个清单,还有一类配置想强调一下:日志和报告的管理。层级化流程的日志分散在各个模块,出问题时你要能快速定位——是综合阶段的问题、CTS阶段的问题,还是顶层集成阶段的问题?我建议团队从一开始就统一日志命名和报告输出路径,并在顶层集成阶段保留一份完整的、带时间戳的flow日志。定位问题的时候,这份日志比任何猜测都靠谱。
8. 写在最后的一点个人体会
做Hierarchical Flow这几年,最大的感触是:这个流程不是一个纯靠工具自动化就能跑通的流程,它需要人在每个环节做出审慎判断——在哪一层切分逻辑、用什么模型做接口时序、模块放到哪里、时钟怎样布、ECO怎么管。这些判断没有标准答案,只能在实践中逐步积累感觉。
对我个人而言,最有用的一个习惯是:每次项目结束后,把踩过的坑和解决方案整理成一份"Hierarchical Flow Notes"。这份笔记不追求理论高度,纯粹是记录"什么情况下会出现什么问题、我当时是怎么发现和解决的"。新项目启动时翻一翻,很多坑就提前避开了。
如果你正准备在项目里推Hierarchical Flow,我建议从一个小规模的试点模块开始,先跑通整个流程、验证好方法论,再扩展到全芯片。不要一上来就把整个设计拆得七零八落——流程没有磨合好之前,拆得越细,返工的痛感越强。等团队把层级化的节奏找到了,你会发现它能带来的并行效率和收敛速度,会明显超过它所增加的管理成本。