
做数字IC后端这些年我最大的感受是Flat Flow在芯片规模面前迟早会撞墙。尤其是当你拿到一个几千万门、甚至上亿门级别的SoC跑一次全芯片place和CTS可能就需要好几天工具内存动不动飙到几百GB就算服务器扛住了团队协作和迭代效率也撑不住。这时候层次化Hierarchical设计流程就不再是加分项而是必选项。这篇东西我早就想写了把我在多个量产项目里反复折腾过的Sub-System层次、block partition策略、pin assignment细节以及那些文档里不会明说、只有踩过坑才会懂的教训一次性盘清楚。文章主要面向有一定后端基础、正准备从Flat Flow切换到Hierarchical Flow的工程师也适合刚接触大型芯片物理设计的同学用来建立整体认知。我会尽量避免空谈概念尽量把每个决策背后的逻辑和代价都讲透。1. 为什么非要用层次化设计Flat Flow的“死线”到底在哪里1.1 容量与迭代速度的双重瓶颈先算一笔账。一个典型的5nm工艺、8核应用处理器级别的die总面积大概在90到120平方毫米逻辑cell数量在8000万到1.5亿之间。用Flat Flow做place时工具需要同时维护所有cell的物理位置、时序弧、功耗和绕线资源信息内存占用通常会达到库文件大小乘以一个不小系数的状态实践中经常超过300GB甚至500GB。这还没算上CTS阶段插入的几十万个clock buffer和ECO阶段新增的cell。内存只是第一道坎更致命的是迭代速度。Flat Flow下每改一次约束、每加一条ECO都需要把整个芯片重新跑一遍place和routing。即使机器够强一次完整迭代花掉三到五天是很正常的事。在项目后期前端三天两头改RTL物理设计这边根本追不上节奏整个项目就会卡在后端收敛上。Hierarchical Flow的核心思路很简单把一个大问题分解成多个可以并行、可以独立迭代的小问题然后在顶层只做协调和接口工作而不是所有事情都搅在一起。它并没有减少总工作量但它让“迭代局部化”成为可能——某一个block子模块内部改逻辑不需要惊动整个芯片。1.2 层次化不等于简单切一刀很多第一次接触Hierarchical Flow的工程师会以为这条所谓“层次化”就是把RTL里的module边界当成物理block边界然后按图索骥地切块。实际上这种理解太过天真。RTL的module划分往往是站在功能验证角度设计的服务于代码可读性和验证复用不一定适合作为物理实现的边界。我在一个项目里就吃过这个亏。当时RTL里一个很大的DMA controller被写成一个module功能上完全没有问题但这个module内部包含了大量分散的RAM阵列和一堆分散在不同物理位置的加速器逻辑硬生生把这块区域切得七零八落。如果我们严格按照RTL边界做物理block这个block的pin数量会飙到非常夸张的数值pin密度过高导致绕线拥塞congestion而且block边界附近的时序收敛异常困难。所以在后端物理设计中block partition更准确地说是一种物理设计行为RTL层次只是参考不是金科玉律。我们需要结合floorplan、模块间数据流、功耗隔离、复用需求、以及团队解耦来综合决定怎么切。1.3 什么情况下值得上Hierarchical Flow不是所有项目都需要层次化也不是所有设计都适合层次化。基于我自己的项目经验以下几个信号出现时你基本就可以确定要上有层次的流程了规模门槛全芯片cell数超过3000万到5000万或者单die面积很大、后端迭代一次以天计不上层次化效率完全扛不住。存在明显的同构复用单元比如四核CPU集群、多组DSP、多组ISP这些sub-system物理结构高度相似完全可以做成一个物理block然后在顶层多次放置也就是常说的“multi-instantiate”或“reuse block”。团队并行需求多个工程师需要同时在同一颗芯片的不同区域工作不能互相阻塞需要清晰的物理和逻辑边界。混合信号/模拟IP集成数模混合设计里模拟版图需要全定制处理与数字后端边界必须物理隔离层次化是天然的选择。一旦决定用Hierarchical Flow接下来最关键的就是partition和pin assignment这两件事做得烂后面所有环节都会异常痛苦。2. Block Partition切好第一刀才算赢在起跑线2.1 划分的输入从数据流反推物理位置在我做过的项目里最省心的partition往往不是一个纯粹从floorplan出发的划分而是先认真看模块之间的数据流和连接关系再反推物理位置。简单说你希望“连接关系紧密的逻辑尽量被切在同一个block里”因为切开的界面需要经过pin、顶层连线、再进入另一个block这部分路径通常是时序收敛的重灾区。我一般会先做如下几件事打开RTL的hierarchy tree统计每个子模块的cell count、port数量和与外部模块的连接数量。把连接数量矩阵画出来找出天然的高内聚、低耦合区域。连接超过几百根的模块对如果物理上又要分开就要慎重考虑接口信号的绕线空间。结合前端给的预估面积在floorplan上把主要IPCPU、GPU、NPU、DDR控制器、serdes等提前摆放好再在这些anchor之间的空隙里“填空”。然后才是决定“哪个功能块做成独立block”“哪几个逻辑层次合并成一个super block”的问题。这里有一个很实用的经验block边界尽量选择在“寄存器层”上切。什么意思两条路径从一个block到另一个block如果路径的起点是发送方的reg终点是接收方的reg中间组合逻辑完全落在某一个block内部那跨block路径就只是从reg到port的延迟时序可控性最好。如果边界切在组合逻辑中间即使不是不可能收敛你也要花大量时间在顶层做balance和buffer insertion相当痛苦。2.2 Block大小的黄金比例多久收敛一次最重要很多团队在定义block size时只关心“工具能不能跑得动”而不关心“收敛速度是否令人满意”。实际上在机器资源足够的前提下block不是越大越好也不是越小越好关键是block的迭代周期和全芯片的迭代周期能否匹配上。我个人的倾向是单个物理block的面积控制在全芯片面积的1/15到1/5之间逻辑规模控制在500万到1500万cell以内。再大block内部place/CTS的一次迭代就要超过半天和全芯片的顶层迭代节奏脱节再小partition的overhead抽象模型生成、接口约束、顶层绕线资源预留占的比重反而过大不划算。以我曾经做过的某视频编解码芯片为例全芯片大概7000万cell我们切成了五个主要block两个同构的视频处理cluster、一个CPU sub-system、一个DDR/memory controller block、一个外设/IO block再加上顶层剩下的少量随机逻辑。每个block的cell规模在800万到1200万之间工程师团队刚好可以每人负责一到两个block步调非常齐整。2.3 同构模块复用省下的是三倍时间同构模块reuse block的复用是Hierarchical Flow带给团队最大的礼物之一。我们那个视频项目里有两个clusterRTL上是一模一样的vcodec core实例化两次物理上分居芯片左右两侧。我们只需要把一个block完整跑完——从floorplan到DRC/LVS全部signoff——然后通过简单的坐标变换生成第二个instance的物理数据。但这里有一个特别注意点同构block可以做physical reuse但布局位置必须保持对称性或者完全相同的朝向和电源结构。一旦你在顶层为两个cluster规划了不同的power mesh方向、或者周围有不对称的hard macro遮挡那么“一模一样”只是纸面上的实际绕线资源和pin escape方向都会变化信号完整性也会不同。遇到这种情况比较稳妥的做法是先做一次物理可行性评估把第一个block的def放到两个位置分别跑一版顶层place比较两边congestion和pin density是否一致再决定要不要做physical reuse。另一个容易遗漏的点是两个同构block的内部时序signoff可以互相借用但跨block的接口时序不行。因为接口路径的另外一侧比如DDR controller到cluster A的连接环境不同顶层时序收敛必须分别看。拿到第一个block的抽象模型后第二个block直接copy是省事的但前提是两边接口环境的约束包括虚拟端口、input/output delay完全一致。2.4 切完之后的顶层别把脏活累活全甩给顶层Partition切完之后顶层往往还剩下两类让人头疼的东西跨block的feedthrough信号一个信号从block A的port进入、穿过顶层绕线、再从block B的port出去。这些信号如果在partition阶段没有规划好顶层的绕线congestion会非常集中在某个区域尤其当信号总线很宽比如256-bit的AXI且跨了很多block的时候。顶层自己的逻辑总有那么一些零散的clock gating cell、spare cell、或者胶水逻辑不归属于任何block。如果这些逻辑太少你可能觉得无所谓但如果顶层随机逻辑超过几十万cell顶层就成了一个没有完整floorplan的“乱炖锅”CTS做起来极难收敛。我的建议是在partition阶段就应该明确一个原则——顶层只做端口连接和少量clock tree buffer不做任何大数据通路逻辑。如果前端在顶层挂了大量组合逻辑或mux逻辑push它要么下沉到某个block内部要么在顶层做一个统一的“顶层逻辑block”给它一个明确的物理区域和边界而不是让工具在整个die上自由摆放。3. Pin Assignment的完整落地细节从spec到定稿3.1 Pin Assignment的本质为数据流找一条最短路径如果说partition是切蛋糕那pin assignment就是决定“每一刀上的图案长什么样”。Pin assignment的核心目标是让每一根跨block信号在block边界上有最短、最不拥挤、且对block内部绕线干扰最小的出入口。很多初级工程师容易把pin assignment理解成“把port摆到block boundary上”这么简单。实际上pin assignment的内容远不止于此pin的位置落在boundary的哪一段、水平边还是垂直边pin的layer选择用M4还是M7是否和电源网格冲突pin的朝向朝左、朝右、朝上、朝下即pin layer上的延伸方向pin的shape是单pin还是pin cluster/bundlepin之间的spacing是否满足同层绕线和通孔需求pin对应的绕线资源预留在pin旁边留出足够的escape routepin和block内部时序路径的关系这个pin到内部时序单元的距离是否合理3.2 先决定Pin Layer再谈具体坐标在我参与的早期项目里曾经为了省事直接把所有pin都放在默认的较低金属层比如M3/M4。结果block内部的standard cell绕线本来就在低层大量跨越边界的信号也挤在低层抢道整个block边界附近的congestion简直惨不忍睹。后来我们总结出了一条比较固定的策略Pin Layer至少要选择block内部全局电源网格之上、并且最好不遮挡高层信号绕线的金属层。在当前主流工艺下M5/M6/M7甚至M8才是更合理的选择M5/M6兼顾了信号层与绕线资源的关系M7/M8虽然更宽、绕线更顺但是往往这些高层金属也是电源/地网格的主战场pin放多了会和power mesh打架。一个比较实用的做法是先让后端把block内部的电源mesh画好再在mesh间隙里找能够连续放置大量pin的“走廊”pin放在这些走廊里pin的金属层和pin的宽度都按这个层的最小宽度与最小间距设置但建议在pin两端各留出至少两个standard cell track的余量作为“escape route”。3.3 按数据流方向排布Pin的方向Pin的方向问题业内常说“pin要朝着它要去的方向”。这句话听起来像废话但实际执行起来特别容易出错。假设block A在芯片的左侧block B在右侧它们之间有一组高速数据总线那么A的output pin应当放在A block的右侧边界上并且pin的“朝向”也就是pin金属层从boundary伸出去的方向应当朝右指向B。同理B的input pin应当放在B block的左侧边界上pin伸出方向朝左指向A。如果pin放错边总线就得绕半个block才到达对端绕线长度和延迟都会显著恶化。有一种特殊情况值得单独说上下两边都有对端模块时你必须在partition阶段就把pin的归属规划好哪些信号走上方、哪些走下方。这一步如果做晚了后面的顶层绕线很难救回来。我个人会准备一张框图把每个block的四条边的pin数量预算estimated pin count per edge都列出来再把这个预算和block内部latency、congestion做一轮同步。3.4 Pin的合并、分组与bus排布技巧我们在实际项目里很少会把每个RTL port单独当成一个pin来assign更多时候是要做pin grouping同一组bus的pin尽量放在相邻track上保持大小一致、金属层一致、间距一致这样方便顶层绕线工具做bus routing也能减少不同bus之间的交叉。同名端口组里的differential pair或clock group尽量成对出现避免某一根信号夹在两条完全不同的总线之间导致coupling和SI问题。spare cell的enable信号、scan enable、test mode这类全局信号的pin尽量集中放在block某个角落而不是平均散布便于后续ECO和测试模式切换。另外我强烈建议在pin assignment阶段就打开congestion map检查一遍。你可以提前在block周围画几条假想的feedthrough lane也叫pin escape channel把pin的排放和“从pin出来后要走哪几条track出block”一起规划。如果pin区域下面的局部congestion已经偏红就应该调整pin spacing或迁移到相邻走廊。3.5 一个可复用的Pin Assignment Checklist下面是我在项目里用的、经过多轮实战打磨过的checklist每次定稿前都会对着过一遍检查项具体要求Pin Layer与电源mesh不冲突建议高层金属走廊避免M3/M4密集区Port方向每个pin的方向与目标block位置一致避免绕block长线Bus排布同bus优先顺序相邻同金属层尽量同trackPin间距满足同层via与绕线最小间距并多留1~2个track escapePower/ground pin与顶层电源mesh对齐保证PG连接可打孔关键时序路径时序关键pin尽量靠近block内部sink/source寄存器方向时钟相关pin与clock port对齐避免clock pin在block内跨大片区域Test/DFT相关pin集中摆放避免测试模式下的绕线拥塞ECO预留在pin区域附近留spare cell与spare route通道4. 层次化时序收敛抽象模型、接口时序与跨模块Debug4.1 为什么顶层时钟不能直接拉进block这是很多初次做Hierarchical Flow的工程师会犯的严重错误。Flat Flow里clock tree是从top一路长到每一级寄存器的CTS可以很自然地balance所有clock path。但在Hierarchical Flow下每个block在独立做CTS时block内部只能看到自己层面的clock port和约束它并不知道全芯片的clock tree长什么样。如果你在顶层直接把时钟模块的时钟信号一路拉到block内部的寄存器而不经过block自己的clock port和clock tree会出现几个问题首先顶层时钟到达每个block的时间不一致做CTS时根本无法balance跨block路径其次block内部做clock tree synthesis时没有包含顶层网络的实际延迟时钟偏差(clock skew)完全失控最后这种网络在物理上往往需要大量高层金属布线会干扰顶层其他信号绕线。标准做法是每个block在边界定义一组clock portblock内部自行做CTS然后在block抽象模型里给这个clock port保留一个明确的时钟延迟/transition信息。顶层只看到每个block的clock port把block抽象为“时钟从port进去到内部reg的延迟是多少”的黑盒跨block路径的时序计算基于这些抽象延迟完成。4.2 端口时序建模从简单Block Model到ILM顶层做时序分析时必须对每个block的行为做一个模型。模型越精确顶层时序可信度越高但计算代价也越大。常见的方案有三种Black box model把block当成完全黑的盒子顶层只知道端口的电容/电阻特性不知道内部cell和时序关系。这个模型只适用于顶层做早期可行性评估完全不能用于时序signoff。Block abstract / ETM (Extracted Timing Model)在block完成内部实现后用抽象库模型描述“输入到输出的延迟”、“端口到端口的时序弧”、“时钟端口到内部寄存器的setup/hold关系”。这是最主流的做法顶层的综合和物理设计都基于这个模型。但ETM是纯时序行为描述不包含内部逻辑细节因此当顶层ECO改变了接口延迟时模型需要重新生成。ILM (Interface Logic Model)这是目前大型SoC项目收敛性最好的方案。ILM保留了block边界附近一定深度的真实逻辑网表通常几级组合逻辑相关寄存器顶层在做时序分析时能真正看到接口路径的电路。ILM的仿真和STA结果比ETM更接近真实代价是抽象文件更大、顶层仿真更慢。在我的项目里跨block的高速接口DDR、PCIe、NoC几乎全部使用ILM而低速、非关键的接口则用ETM。这样既保证了收敛精度又不会让顶层仿真慢到不可接受。4.3 输入输出延迟的设置和Budgeting策略Hierarchical Flow下每个block在独立签核时要“假装”知道外部环境这个“假装”用的是set_input_delay / set_output_delay约束。但问题是这些delay值是从哪来的常见的做法是先跑一版顶层设计用比较粗糙的block抽象模型做时序分析得到每个block端口上的arrival time和required time然后把结果作为外部延时的初值反馈给各个block。block完成内部收敛后再生成新的抽象模型更新顶层时序再计算新的端口delay。如此迭代几轮直到收敛。这一套流程听起来顺理成章实际操作中最容易翻车的地方在于跨block路径的budgeting过于乐观。比如顶层给了block A的output port 2ns的available timeblock A在内部实现时也顺利收敛了但顶层把路径另一侧的block B的输入延迟调整了一点点整个跨block路径就violate了而且往往violate得很剧烈因为两侧的报文可能都接近极限。我的建议是跨block的budget要刻意留出一部分“灰色状态”空间比如把目标时钟周期的5%到10%作为跨block margin预留不要把每条路径都压到零余量。这样项目后期即使有局部微调也不至于牵一发而动全身。4.4 在顶层看到的跨模块时序问题怎么Debug当你真的在顶层看到一条跨block的violation路径时不要急着改某一个block的约束。先定位三个问题路径的起点在哪个block、终点在哪个block、接口delay是由哪一侧贡献的。常用的流程是我自己的三板斧看顶层STA report里这条path的clock path信息。如果两侧的clock skew异常偏大先去检查两个block的clock port到内部reg的抽象延迟是否一致很多时候是某个block的CTS没有做够balance。看data path在顶层部分花了多少时间。如果顶层绕线占了path delay的一半以上先去看是不是pin assignment的位置选得不好导致绕线绕了远路还是top层congestion导致工具被迫绕行。如果顶层绕线不多但path还是收不回来那就回到两侧block内部看接口路径的源寄存器到output pin、input pin到目的寄存器的delay是否都合理。哪一侧太紧就回到那一侧的block去优化。还有一个我吃了不少亏的点跨block路径的SPEF寄生参数必须包含顶层和两侧block的联合RC单独用block内部SPEF 顶层理想线延迟是不准的。尤其在高频接口比如2GHz以上上寄生电容对延迟的影响非常明显用不完整的寄生模型做出来的结果基本都是过度乐观或过度悲观的假信号。5. 电源规划、物理验证与团队交接层次化最容易翻车的隐藏环节5.1 PG连接层次化里“看不见的经络”Pin assignment通常只关心signal pin但层次化流程里power/ground连接更是重灾区。每个block内部有自己的power ring、power stripe甚至独立的power domain多电压域顶层有一个覆盖整个die的power mesh。block和顶层之间的PG连接就靠block边界上的power pinvendor pin/domain pin和顶层的power via来实现。这里面最常见的坑是block内部IR drop已经收敛但并到顶层以后IR drop就变差原因往往是block边界上的power pin数量不足、或者pin的位置没有和顶层power mesh的网格对齐。我建议在partition阶段就把block的power pin location和顶层的power mesh结构一并规划至少在block的四周每一条边上都得有足够密度的pin供顶层打via接mesh。对于大电流的block比如CPU cluster更要在顶层mesh上做一次全局IR drop仿真确认压降主要落在哪个区域、是不是因为block某条边忘留了PG pin。5.2 DRC/LVS的边界地带层次化设计的物理验证最难的不是block内部而是boundary上的那些悬空金属、重叠cut、以及跨层结构。举个例子两个block相邻它们各自的DRC都干净但拼到一起之后两边的power rail和signal pin之间可能形成新的、只在交界处才出现的间距违例。严格做法是block级做一遍完整DRC/LVS顶层也做一遍完整DRC/LVS然后把两边的违例坐标对齐人工review哪些是block边界造成的。这个活儿很费人力也是我每次项目后期最不愿干但不得不干的事情之一。有一个小技巧是在block signoff时就要求每个block在boundary上留出足够宽的空置区域不要贴着boundary放critical cell和dense wire这样两个block拼起来之后交界处自然有一条“缓冲区”DRC违例数量能大幅下降。代价是面积利用率会损失一点但和后期修DRC的返工成本相比这点面积损失完全值得。5.3 层次化数据交接清单多个工程师并行在同一个工程里工作数据交接的规范化程度直接决定项目会不会乱。我整理过一份前后端设计团队通用交接清单你可以根据自己的项目调整数据类型说明更新频率Netlistblock内部最终网表必须和顶层集成列表匹配每次ECO后更新LEF / DEFblock的物理信息、pin位置、blockage、routing信息每次floorplan/pin变化后更新Abstract View (LEF abstract lib)供顶层使用的简化物理与时序模型block signoff后生成ECO后重新生成SPEFblock内寄生参数用于顶层联合STA每轮signoff时同步输出SDC / constraintsblock接口约束与内部时钟约束随RTL/时序变化更新UPF / CPF电源 intent多电压域信息每次电源规划调整后更新GDS / OASIS最终版图数据每次需要tapeout时同步DRC / LVS report含waiver清单和top vs block的违例差异DRC/LVS clean后归档交接的时候我强烈建议搞一个“golden file”概念——任何后续修改都必须基于最新golden的版本进行并且每次交接都要附一个changelog写清楚这版改了什么、影响了哪个block、有没有导致接口时序或pin位置变化。没有changelog的庞大数据库两周之后连你自己都分不清当前def里的pin坐标是不是最终版。5.4 多人协作里的版本管理与ECO流最后聊聊版本管理。Hierarchical Flow的ECO比Flat Flow麻烦得多因为一次ECO可能只影响某一个block的内部逻辑但顶层的抽象模型和SPEF也必须跟着更新否则顶层STA就是挂在旧模型的错误分析上。我见过太多项目在ECO环节翻车就是为了赶进度block内部直接在版图上手工改cell改完就流片完全没回头更新抽象模型。结果顶层的时序报告看起来一切正常实际芯片里跨block路径早就变了。所以无论多赶每次ECO都必须把“修改block网表→重新生成抽象模型→更新顶层SPEF→跑顶层回归”这条链路走完整。宁可顶层回归多跑一个晚上也不要让模型和版图脱节。6. 我的一些个人经验和踩坑总结做层次化流程这几年如果只让我分享三条最实在的经验我会说第一Pin assignment的优先级永远高于block内部congestion优化。很多人习惯先做floorplan和place后面再挪pin结果就是pin挪一次内部绕线全部受影响。反过来先花一两天时间把pin放到位哪怕内部congestion稍微差一点后面都有很多方法去修pin位置乱了后面基本只能推倒重来。第二跨block路径的margin不要抠太死。Hierarchical Flow的时序收敛本质上是一个反复迭代的过程每一层抽象都会带来一点点延迟误差。你在一开始就把压力拉满后面任何一个小扰动都会让整个收敛过程崩溃。多留5%到10%的裕量看着保守实际上能让你省下好几轮全芯片迭代的时间。第三团队内部的沟通规范远比工具流程重要。一个block的pin坐标变了是不是同步通知了顶层负责人抽象模型更新了是不是在changelog里写了清楚这些看似琐碎的“项目管理”问题在层次化流程里会直接转化为你晚上要不要被叫起来debug的成本。我会在工作群里养成一个习惯所有pin map调整必须顶层和DRC/LVS负责人所有抽象模型更新必须顶层STA负责人。规矩很简单但坚持做下来项目的后期会顺畅很多。层次化设计不是一条一劳永逸的路它只是把原来“全芯片一把梭”的复杂度转移到了“如何切分、如何交接、如何收敛接口”这些新的维度上。希望这篇盘点能帮你在这些维度上少踩一些坑。以后有机会我再接着写abstract view生成和顶层集成ECO的更深入细节。