☰
GDS版图合并/组装:calibredrv Tcl脚本与冲突自检
2026/10/29 15:31:29 网站建设 项目流程

1. 先把需求分清楚:你要的是 merge 还是 assemble

1.1 merge 与 assemble 的根本区别

做版图的人一提“合 GDS”,十有八九没说清楚自己到底要哪一种。这两种需求的差别很大,选择错工具,往往白忙半天。

merge 是“合并库”。你手上有 A.gds、B.gds、C.gds 三个文件,每个文件里有一套 cell,彼此没有引用关系。merge 做的事是:把这三套 cell 塞进同一个 layout 数据库里,cell 的名字、层次、几何全部原样保留,只是现在它们属于同一个文件了。合并完之后,输出文件的 top cell 是哪一个是需要你自己指定的——因为这些 block 之间本来就没有父子关系,工具不会凭空帮你选一个顶。

assemble 是“组装顶层”。你手上有若干 block,每个 block 都已经是一块完整、可以被引用的硬核或者子模块,你要做的是新建一个顶 cell,把这些 block 当成 instance 摆进去,按坐标对好位置,再连上电源环、pad ring、seal ring 这些东西。assemble 之后,因为顶 cell 存在实例引用关系,所以整个文件是连通的层次结构。

区别在于:merge 之后你得到的是“一堆并列的 cell”,assemble 之后你得到的是“一棵有根的树”。很多人跑完 merge 发现输出文件的 top 不对、或者打开后一片空白,原因就是工具默认选了一个空 cell 当 top,而真正的顶层数据还在库里,于是看上去“合成了个寂寞”。

提示:如果你的目标文件要送去做全芯片 DRC/LVS,那你需要的通常是 assemble,不是 merge。只有“把散落的库合成一个库,再由别的流程去引用”才用 merge。

1.2 三个典型场景与对应做法

我把这几年遇到的合图需求归成三类,每类的做法不一样。

第一类是标准单元、IO、memory 的库合并。比如标准单元库一个文件、SRAM 一个文件、PAD 一个文件,你做全芯片验证的时候工具只吃一个 LAYOUT PATH,那就得先合起来。这类需求是纯 merge,各库互不引用,合完设不设 top 都无所谓,因为你后续是由顶层去引用它们的。做法最简单,一次性脚本跑完就行。

第二类是多来源 IP 拼顶层。A 团队给一个模拟模块,B 团队给一个数字模块,你负责拼顶。这类既要做 merge(把两边的库都装进来),又要做 assemble(建顶、摆位置)。很多人只做了前半段,然后拿一个“空顶”去跑验证,跑了半天发现面积是 0。

第三类是数据分片交付。交付方把一个版图按区域切成好几个 GDS 分开发,你收到之后要还原成一个整体。这类是纯 merge,但有个硬性约束——各分片之间的坐标必须是绝对坐标,不能各自把左下角拉到原点。如果对方切图时重新归了零,你合完就得手工平移回去,这是最容易出问题的地方。

1.3 合图前必须确认的四件事

在敲命令之前,我一般会先花五分钟把这四件事确认掉,能省掉后面两小时的排查。

确认项为什么重要怎么快速确认
每个文件的 top cell 叫什么merge 后要指定 top,名字错了就白合打开文件看层次树,或用脚本列出 cell 列表
dbu(数据库单位)是否一致不一致会引入取整误差,甚至几何变形看文件头 UNITS,或在工具里看 layout 属性
层号/层名体系是否同源不同来源的层号含义可能完全不同抽查两三个关键层,看图形对不对得上
是否有同名但内容不同的 cell会静默覆盖,最难查合并前后比对 cell 数量

这四条里,第四条是最阴的。工具不会因为“两个 cell 同名但内容不一样”就报错,它只会按某种规则留下一个,另一个直接被丢掉。你后面跑 DRC 全 clean、LVS 也过,结果流片回来发现某个 cell 的某层少了——因为合并的时候被另一个同名 cell 覆盖了。这种问题在版图上几乎不可能靠肉眼发现。

2. 方案选型:为什么我把 calibredrv 当主力

2.1 三种可选路径对比

能合 GDS 的路子不止一条,我前后试过三种,各自的适用面差别挺大。

方案上手成本批处理能力层次处理适用场景
calibredrv Tcl 脚本中,需要写几行 Tcl强,可循环、可判断、可记日志完整保留工程化、常态化、文件多
calibredrv 图形界面低,点几下就行弱,一次一个完整保留临时验证、排查问题
自写解析脚本高,要懂 GDS 二进制结构看自己写得多好很容易出问题不建议,除非有特殊定制

第三种我特别说一下。GDS 是二进制流格式,结构不算复杂,record 加数据类型,理论上自己写个解析器把两个文件拼起来并不难。但“能拼”和“拼对”是两回事:cell 引用索引要重建、层号要查表、属性记录要处理、path 和 boundary 的边界情况要处理、SREF 和 AREF 的重复引用要展开或保留……任何一处细节没对上,出来的文件在别的工具里就可能打不开,或者打开了但几何偏了一个 dbu。这种“看起来对、实际上差一点”的结果,比直接报错危险得多。

2.2 Tcl 批处理为什么更适合工程化

选 calibredrv 的 Tcl 接口,最大的理由不是它“能合”,而是它“能重复地、可验证地合”。

假设你手上有 40 个文件要合。手工点 40 次,第一次做完了,下周数据更新了,你还得再点 40 次,而且中间任何一次点错(漏一个、顺序错一个)都不会有人发现。换成脚本,一个文件列表加一个循环,跑完出一份日志,日志里每个文件的处理结果、每个文件的 cell 数量、最终输出的 cell 总数都记着。下次数据更新,重跑一遍,对比两份日志,差异一目了然。

这就是工程化和手工操作的本质区别:不是能不能做出来,而是能不能证明做对了。

2.3 GUI 也不是没用:它的两个不可替代场景

我并不是完全不用图形界面,有两个场景我一定是开着界面做的。

第一个是排查可疑数据。脚本跑完发现 cell 数量不对,你需要知道是哪个 cell 被覆盖了。这时候打开界面看层次树,两个版本的 cell 摆在一起比 bbox、比层数,比在命令行里 println 快得多。

第二个是确认视觉正确性。数字上的检查(bbox、cell 数、层统计)只能证明“结构对”,不能证明“长得对”。尤其是 assemble 场景,摆完位置一定要打开看一眼,看各 block 的相对位置对不对、有没有镜像翻转、有没有整体偏移一个 die 的距离。这种东西数字检查很难覆盖,但眼睛扫一遍三秒钟就能发现。

3. 最小可用:一个能跑起来的合图脚本

3.1 脚本本体与逐行解释

下面是我最常用的最小版本,够用,也好改。保存成merge_gds.tcl,用calibredrv -a merge_gds.tcl跑。

# merge_gds.tcl # 用法: calibredrv -a merge_gds.tcl # 建一个空的 layout 容器 set L [layout create] # 第一个文件用 read 装进去 layout read $L ./in/block_a.gds # 后续文件用 merge 追加 layout merge $L ./in/block_b.gds layout merge $L ./ip/macro_c.gds layout merge $L ./lib/pad_ring.gds # 指定最终输出的顶层 cell layout topcell $L top_chip # 写出去 layout write $L ./out/top_chip_merged.gds # 释放,避免下一个任务内存叠加 layout delete $L

几处值得说明的地方。

layout create建的是一个空的数据库容器,此时里面没有任何 cell。很多新手以为它建了一个“新 cell”,其实不是,它建的是“库”。真正的 cell 是后面 read/merge 进来的时候才产生的。

第一个文件用layout read而不是layout merge,是因为容器是空的,需要一个“初始化”动作。如果你对空容器直接 merge,某些版本会报错或者行为不一致。实测下来,先 read 一个再 merge 其余,是最稳的写法。

layout topcell这一步千万别省。不指定的话,工具会自己挑一个当顶层,挑的规则在不同版本、不同数据下不一定一致。我遇到过最典型的情况:merge 进来一个只有几个 text 的空 cell,工具把它当成了 top,输出文件的顶层就是那个空 cell,真正的内容全在它下面的兄弟节点里,看起来就是“合完什么都没有”。

layout delete在单次任务里看着多余,但如果你一个脚本要处理多批数据,不释放的话内存会一直往上堆,跑到第三批就被系统杀掉。

3.2 参数细节:text、property、case、units

上面那个脚本是“默认参数”版本,默认参数在简单数据上够用,但稍微复杂一点的数据就会丢东西。下面这几个是我每次都要显式确认的。

text(文本标签)。版图里的 text 是 LVS 做 net 命名的依据之一,尤其是模拟模块内部那些手工打的 label。有些工具的默认读取行为不带 text,或者只保留部分 text。写法上一般是在读取的时候挂一个保留标志,例如-preserveText这类选项。注意不同版本的选项名可能不一样,动手前建议在界面里的 Tcl 命令行敲一下帮助,或者用一个带 text 的小测试文件先验证一遍——这个动作只要三十秒,但能省掉后面“LVS 报了几百个未命名网络”的一天。

property(属性)。GDS 里的 PROPATTR/PROPVALUE 记录经常被忽略,但有些 IP 的属性里塞了真实信息,比如器件的匹配要求、dummy 填充的标记、某些需要特殊处理的层次。属性丢了不会立刻报错,往往要到后端某一步才出问题。处理方法同上,读取时显式要求保留属性。

case(大小写)。GDS 的 cell 名严格说是区分大小写的。但实际交付的数据里,INV_X1和inv_x1同时存在的情况并不少见,通常是不同团队命名习惯不同。如果不做统一,你会在合并后得到两个内容一致、名字只差大小写的 cell,白白多一份面积;更糟的情况是某些下游工具按不区分大小写处理,直接给你合并成一个,那就变成静默覆盖了。稳妥的做法是合并前统一转成同一套命名规范,别指望工具帮你兜底。

units(单位)。这个下一节单独讲,因为它牵扯的不是“丢不丢”,而是“几何对不对”。

3.3 批量合图的工程化写法

真实场景往往是几十个文件,而且数据会反复更新。这时候我会把文件清单外置,脚本只负责读清单、跑循环、记日志。

# merge_batch.tcl set L [layout create] set first 1 set fh [open ./filelist.txt r] set lines [split [read $fh] "\n"] close $fh set total 0 foreach line $lines { set f [string trim $line] # 支持 # 注释和空行 if {$f eq "" || [string index $f 0] eq "#"} { continue } if {![file exists $f]} { puts "ERROR: file not found -> $f" continue } if {$first} { layout read $L $f set first 0 } else { layout merge $L $f } incr total # 每合一个就报一次当前 cell 数,用于对比 puts "AFTER $f : [llength [layout cells $L]] cells" } puts "TOTAL FILES MERGED: $total" layout topcell $L top_chip layout write $L ./out/top_chip_merged.gds puts "DONE, final cells: [llength [layout cells $L]]" layout delete $L

这段代码的价值全在日志上。AFTER xxx : N cells这行,让你能一个个文件看清“合进来之后 cell 数涨了多少”。如果某个文件预期带 120 个 cell,但合完只涨了 80,说明有 40 个被同名覆盖了。这个信号非常重要,比跑完之后发现 DRC 报错的代价小得多。

filelist.txt就像下面这样:

# 基础库 ./in/std_cell.gds ./in/sram_macro.gds ./in/io_pad.gds # 自研模块 ./in/block_a.gds ./in/block_b.gds

把注释和分组写进清单,下次别人接手,或者半年后你自己回头看,都知道当时合了什么,为什么这个顺序。

注意:如果一个文件里有多个顶层 cell(比如一个库里既有标准单元又有 corner cell),merge 进来没问题,但你要清楚layout topcell设的是最终那个“壳”,不是库里的任何一个。

4. 同名 cell 冲突:最容易静默出错的地方

4.1 冲突是怎么产生的

同名 cell 冲突的成因,说穿了就那么几种,但每一种都很难在事后发现。

最常见的是同一套库的不同版本混用。数字团队上个月给了一版标准单元库,这个月修了两个 cell 更新了一版,你手上同时有新旧两个文件,合的时候忘了剔掉旧的。结果大部分 cell 一样,个别 cell 内容不同,哪一版生效取决于合并顺序和工具规则——这种“取决于顺序”的结果,是最不应该出现在交付数据里的。

第二种是不同来源用了同一套命名规范。比如模拟模块里有个 cell 叫inv,数字模块里也有个 cell 叫inv,两个团队各写各的,名字撞了但内容完全无关。这种在合并之后的表现非常戏剧性:某个模块的大部分图形突然“变成”了另一个模块的样子,或者某个模块干脆空了一块。

第三种是填充、dummy、decap 这类自动化生成的 cell。它们的命名往往是批量生成的(FILL_1、FILL_2……),不同工具生成的填充 cell 高度可能差几个纳米,名字却完全一样。合图时被覆盖,表现出来就是某片区域的填充高度不对,密度检查过不了。

4.2 三种处置策略与选择依据

遇到冲突,处理方式不外乎三种,选哪种取决于冲突的规模和你对数据的掌控程度。

策略一:统一重命名(加前缀)。在合并之前,先把每个来源的 cell 加一个来源前缀,inv变成ANA_inv、DIG_inv。这样彻底不会有冲突。代价是下游的 LVS 规则、netlist 里的 cell 名可能全都要跟着改,工作量取决于你的流程自动化程度。如果是一次性的项目,这个代价可能比踩坑还大。

策略二:分开合并,最后再拼顶。每个来源单独 merge 成一个中间文件,各自跑一遍 DRC 确认没问题,最后只把需要的 block 组装进顶层。这样冲突只发生在“组装”那一步,而组装时你引用的是已经验证过的 block,可控性高很多。我现在大部分项目走这条路。

策略三:接受覆盖,但必须验证。如果有把握两个同名 cell 内容完全一致(比如就是同一套库读了两遍),那覆盖了也无所谓。但“有把握”不能靠感觉,得靠比对:抽查若干个同名 cell 的 bbox 和层数,或者在合并前后各统计一次全库的图形总数,数量一致才放过。

4.3 一个可落地的冲突检测思路

不用自己去解析 GDS,用现成的接口做交叉比对就够了。思路是这样的:合并前,把每个文件单独读进一个容器,记录它的 cell 名列表和各 cell 的 bbox;合并后,把结果再读一遍,记录同样的信息。三份数据一对比,被覆盖的 cell 就浮出来了。

# 统计单个文件的 cell 与 bbox proc dump_cells {gdsfile outfile} { set L [layout create] layout read $L $gdsfile set fh [open $outfile w] foreach c [layout cells $L] { set h [layout cell $L $c] puts $fh "$c\t[layout bbox $h]" } close $fh layout delete $L }

每个文件跑一遍,得到一个cell名 -> bbox的文本表。然后把这些表按 cell 名合并去重,凡是同一个名字出现过两次以上的,就是候选冲突。之后再对这些候选逐个比 bbox,bbox 一致的可以先放过(大概率内容相同),bbox 不一致的就是必须处理的。

这个方法不完美——bbox 一样不代表内容一样,内部图形可能有差异。但它的成本极低,能筛掉 90% 的情况,剩下的靠 DRC 和 LVS 兜底。实际项目里,我还没遇到过 bbox 完全一致但内容有实质差异、最后导致流片问题的情况。

提示:这套比对脚本建议做成常备工具。合图这个动作在项目里会重复几十次,每次手工查冲突不现实,做成一条命令能省很多事。

5. 层号与单位:跨来源数据合并的两个硬约束

5.1 layer map 的写法与验证方法

层号体系不一致,是跨来源合图最常见的问题。同一个物理层,A 家的库用 10/0,B 家的库用 30/0,直接合进去,B 家的金属就跑到 A 家根本不存在的层号上去了。表现是:文件合成了,打开看,B 家的模块整个“消失”了——其实是画在了你没打开的层上。

解决办法是挂一个映射文件,在读取的时候把层号转过来。常见写法是每行一条规则,源层号、源 datatype、目标层号、目标 datatype,用空白分隔:

# src_layer src_datatype dst_layer dst_datatype 10 0 30 0 11 0 31 0 12 0 32 0

这里必须说明一点:映射文件的字段顺序和格式,在不同版本、不同工具形态下是有差异的,有的是四列数字,有的是“层号 datatype + 层名 purpose”的组合写法。你拿到手的第一件事,应该是做一个两三个层的极小测试文件,跑一遍确认映射方向没搞反。搞反了不会报错,只会把整张图翻个底朝天。

验证方法很简单但很有效:映射前后各统计一次层数量。假设源文件有 8 个层,映射规则覆盖了全部 8 个(或者是故意只映射其中几个),那输出的层统计应该跟预期对得上。如果输出层的总数明显多于或少于预期,说明有层没被规则命中,被原样带过去了。

5.2 dbu 不一致怎么办

dbu 是版图数据库的精度单位。常见的有 1nm(0.001um)和 5nm(0.005um)两档,老一点的工艺还有 10nm 的。两个不同精度来源的文件合并,会发生什么?

合并工具通常会把所有几何换算到更细的那个精度上,比如 5nm 数据库里的坐标 100 换算到 1nm 数据库里变成 500,这个换算本身是精确的,没问题。但反过来的情况就麻烦了:1nm 数据库里的坐标 503,要放进 5nm 数据库,除不尽,只能取整,得到 500 或者 505。每一个坐标都可能有 1~2 个 dbu 的误差。

这个误差单独看很小,5nm 工艺里也就一根线宽的几十分之一。但如果你的设计里有等间距的阵列(比如 memory 的位线、pad 的排布),误差会累积。最典型的后果是:merge 之后跑 DRC 突然多出几百个间距违例,全部集中在某个模块内部,间距值只差 1~3nm。

我的处理原则是:能不混就不混。如果必须混,先把粗精度的文件升级到细精度,在细精度下做完所有合并和编辑,最后再整体降级。升级是精确的,降级才丢精度,把丢精度的动作压到最后一步,至少中间所有的检查和修改都是干净的。

判断当前 dbu 的方法:在工具里看文件属性,或者用一个只读的小脚本打印一下,再或者直接看二进制文件头的 UNITS 记录。三种方法都能得到答案,选最顺手的那个。

6. 合完之后怎么验:三层自检法

6.1 结构层自检

合完的文件不能直接往下走,要先做结构检查。这一步只看“骨架对不对”,不看内容。

检查项判定标准不合格意味着
top cell 名字跟你预期的完全一致topcell 设错或设漏
总 cell 数各文件 cell 数之和,减去已知重复有静默覆盖
顶层 bbox覆盖所有 block 的 bbox 并集有数据丢失或位置偏移
层总数与合并前的层并集一致层映射漏了
text 数量与合并前之和接近text 没保留

这五项里面,最容易被跳过的是“层总数”。因为大部分人只关心图形在不在,不太关注层列表。但层数量少了一两个,往往意味着某个来源的某一层完全没进来,那部分图形就这么没了,而你的 DRC 照样 clean——因为 DRC 只检查它认识的层,不认识的层不存在正好不报错。

6.2 引用完整性自检

第二层检查是看引用关系。版图里一个 cell 引用另一个 cell,如果被引用的那个不存在,有的工具会自动创建一个空的占位 cell 顶上去。结果是文件结构完全合法,打开也不报错,就是那个位置什么都没有。

这种问题在 merge 场景下特别容易发生:你合了 A 和 B,B 引用了 C,但 C 在第三个文件里,你忘了合进来。工具不报错,它默默给你造了个空 C。

查的办法很直接:把所有 cell 名拉一个集合,遍历每个 cell 的引用列表,看有没有不在集合里的。有就是缺。这个检查在脚本里几行就能写完,但能挡住一类最难查的问题。

# 收集所有被引用但未定义的 cell set all [layout cells $L] set defined [dict create] foreach c $all { dict set defined $c 1 } foreach c $all { set h [layout cell $L $c] foreach ref [layout refs $h] { if {![dict exists $defined $ref]} { puts "MISSING REF: $c -> $ref" } } }

不同版本的引用查询接口不一定叫这个名,思路是通用的:拿到“被谁引用”的名单,跟“已定义”的名单求差集。差集非空就是数据不全。

6.3 用一个小 DRC 检查重叠与错位

第三层是物理检查,也就是真的跑一次验证。

最省事的做法是:把合并后的文件和合并前的各个 block 分别跑同一个 DRC deck,比较违例数量。如果合并后的违例数大致等于各 block 之和(加上少量边界处的正常违例),说明合并没有引入新问题;如果合并后突然多出几百个,八成是位置错位或者重叠。

想更精准一点,可以做按区域筛选。全芯片 DRC 跑完之后,把违例按坐标范围分桶统计,重点看落在 block 接缝处那些坐标上的违例。这些位置的违例才是合图引入的,块内部的违例是设计本身的。这个动作看起来麻烦,实际上就是把 DRC 结果数据库按坐标过滤一遍,脚本化之后很快。

还有一种粗但极其有效的检查:算面积。把每个 block 的 bbox 面积加起来,跟合并后的总 bbox 面积比。如果合并后的面积比各块之和还小,说明有两个块被摆到了同一个位置,重叠了。这个方法不需要任何工具支持,拿几个数字一算就知道,我在赶时间的时候经常先用这招扫一遍。

注意:面积法只能发现“整体重叠”,发现不了“错位一微米”这种情况。它是初筛,不是终检。

7. 常见问题速查与踩坑记录

7.1 速查表

下面这张表是我这几年攒下来的,遇到现象直接查,能省掉从头想的时间。

现象最可能的原因处理办法
合并后打开一片空白top cell 指到了一个空 cell手动指定顶层,或检查是否合进来一个只有 text 的 cell
cell 数量比预期少同名覆盖用 dump_cells 比对,冲突的加前缀
某模块的图整个消失层号不一致,画在了别的层上检查层映射,统计层总数
DRC 突然多出大量间距违例dbu 不一致导致的取整误差统一精度,或在细精度下完成全部编辑
LVS 报大量未命名网络text 未保留读取时显式保留 text,重跑
某区域填充高度异常填充 cell 同名覆盖比对填充 cell 的 bbox 和层数
输出文件在别的工具打不开写文件时选项不兼容或数据本身有非法记录换一种格式输出,或用原工具校验一遍
脚本跑到一半被系统杀掉内存未释放,多个容器叠加每个任务结束调用 delete,或分批跑
位置整体偏了一个 dieassemble 时坐标没减 bbox 左下角安置前先取 bbox,用左下角做基准
引用关系里有空 cell遗漏合入某个被引用的文件做引用完整性检查,求差集

7.2 几条只有踩过才知道的经验

第一条:先合小文件,再合大文件。这不是必须的,但在排查阶段很有用。小文件合并快,出问题容易定位;大文件放到最后,前面结构对了再往里加,出问题范围小。

第二条:中间结果一定要落盘。有些项目我要求每一个 block 单独 merge 出一个中间文件并保留下来。这样最后组装的顶出了问题,可以直接拿中间文件去定位是哪个 block 的问题,而不用重新合一遍全部数据。多占几十 G 磁盘,换回来的是排查时间。

第三条:不要在同一步里既改层号又改单位又合文件。三个动作叠在一起,出问题了你根本不知道是哪一步导致的。分开做,每步验证一次,虽然多敲几条命令,但每次出问题只需要查一个变量。

第四条:别人的 GDS 到手先自己跑一遍读-写往返。把文件读进来,什么都不改,直接写出去,然后用工具打开对比。如果往返一次之后 bbox 变了、层数变了、text 少了,说明这个文件在读取环节就有损耗,后面所有基于它的操作都不可信。这个动作只要几十秒,我现在的习惯是收到任何外来数据的第一步就是做它。

第五条:日志比结果重要。合图这件事,成功的输出是一个文件,但真正让你敢把它交出去的,是那份记录了“合了哪些、每个合完多少 cell、总共多少 cell、top 是谁”的日志。尤其在多人协作的项目里,别人问你“你确定都合进去了吗”,你能拿出日志,比嘴上说“应该没问题”有用得多。

我个人在这些年的实际使用中体会最深的一点是:合图这个动作本身的技术难度很低,几条命令的事,难的是证明自己合对了。所有真正花时间的部分,都在前面那五分钟的确认和后面那半小时的验证上。跳过这两步,图照样能合出来,也能过 DRC 能过 LVS,然后问题会在最不该出现的时候出现。所以现在我的习惯是,脚本可以五分钟写完,验证环节一定要留够时间,这部分投入从来不亏。

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

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

立即咨询