版图做完,DRC清干净,LVS过,最后导出GDS交给工艺厂——这条链路我走过很多遍,但每次带新人,问题几乎都出在同一个地方:那个后缀为.gds的二进制文件里到底装了什么,没人说得清。GDSII版图格式从八十年代定型至今,仍然是绝大多数流片流程里的“交换货币”,可它既不是文本也不是数据库,用记事本打开就是一堆乱码,出了问题只能靠猜。这篇东西想做的事很直接:把GDSII从记录结构到交付检查完整拆一遍,讲清楚每个字段为什么存在、导出时哪些参数会咬人、流片前该盯哪几个点。适合刚接触版图交付的工程师、需要自己做数据检查的版图设计者,也适合那些被“文件导进去尺寸不对”折腾过的朋友。
1. 先把GDSII当成一种数据容器来理解
1.1 二进制记录流:为什么它不能被文本编辑器读懂
GDSII最容易被误解的一点,是很多人以为它像网表或者脚本那样“看得见”。实际上它是一个纯粹的二进制记录流,文件从头到尾就是一条接一条的记录首尾相连,没有任何分隔符、没有换行、没有缩进。每条记录的头部固定四个字节:前两个字节是无符号整数,表示这条记录的总长度(包含这四个字节本身);第三个字节是记录类型,第四个字节是数据类型。长度字段决定了后面要读多少字节的数据,读完这一条紧接着读下一条,直到遇到ENDLIB为止。这种设计在八十年代存储资源紧张的年代非常合理——紧凑、顺序读取快、不需要索引,但它带来的副作用是:文件里任何一个字节错位,后面的解析全乱。我见过有人用文本编辑器“修”GDS文件里的一个字符串,保存之后整个文件在工具里打不开,原因就是编辑器偷偷改了行尾符或者编码。所以第一条铁律:GDSII永远只用工具处理,不要手改。
1.2 库、结构、元素、记录:四层嵌套关系
理解GDSII的结构,最好把它想成一个套娃。最外层是库(Library),一个.gds文件通常就是一个库,库有自己的名字和单位定义。库里面装着若干个结构(Structure),也就是我们常说的cell,每个cell有自己的名字。cell里面装着元素(Element),元素就是多边形、路径、文本、引用这些东西。元素再往下才是具体的记录(Record),比如一个多边形元素由BOUNDARY、LAYER、DATATYPE、XY、ENDEL这几条记录组成。这个四层关系决定了GDSII天生支持层次化:一个标准单元画一次,在顶层用SREF引用几百次,文件体积不会膨胀。反过来,如果你把所有层次打平(flatten),文件可能从几兆涨到几百兆,DRC跑起来也慢得多。所以每次有人问我“为什么我的GDS这么大”,我第一反应就是去看他导出时是不是勾了“打平所有层次”。
1.3 UNITS记录与坐标精度:一个小数决定版图尺寸
UNITS记录是整个文件里最不起眼、但杀伤力最大的地方。它包含两个8字节浮点数:第一个表示数据库单位相对于用户单位的大小,第二个表示数据库单位对应多少米。举个最常见的配置:用户单位是1微米,数据库单位是1纳米,那么第二个数写1e-9,第一个数写1e-9除以1e-6等于1e-3。听起来很绕,但记住一个结论就够了:第一个数写错,版图尺寸就错。假设你设计了一条10微米宽的金属线,数据库单位1纳米,坐标里存的是10000。如果导出时第一个数误写成1e-6,接收方会认为一个坐标单位只有1皮米,这条线在人家工具里就变成0.01微米。反过来写成1e-1,线就变成1000微米。这种错误在跨团队协作里特别常见,因为不同PDK给的推荐值不一样,有人习惯1e-3,有人习惯5e-3甚至1e-2。我的做法是:导出前把UNITS两个值抄在交付说明里,接收方拿到文件第一件事就是核对这两个数,比核对任何图形都优先。
1.4 版本号与兼容性:HEADER里那个600的含义
HEADER记录只有两个字节的数据,存的是版本号,目前主流是600,代表GDSII第六版。有些老工具或者特殊流程会写3,代表更早的版本。版本号本身不影响图形解析,但会影响某些记录是否被识别——比如BOX记录是第六版才有的,如果文件头写3却带了BOX,严格的解析器可能会报错或者忽略。实际工作里我很少主动改版本号,但遇到过一类情况:某些老旧掩模厂的数据接收系统只认版本3,导出的文件版本是600,对方直接拒收。这时候要么在导出设置里调低版本,要么用转换工具降版。降版有风险,BOX和某些属性会丢,所以更稳妥的做法是提前问清楚接收方支持什么版本,而不是等被打回来再改。
2. 逐条拆解GDSII的核心记录
2.1 库级记录:从HEADER到ENDLIB的骨架
一个合法的GDS文件,库级记录的顺序基本固定:HEADER打头,接着BGNLIB、LIBNAME、UNITS,然后是一堆结构定义,最后ENDLIB收尾。BGNLIB的数据是12个2字节整数,前6个是最后修改时间(年、月、日、时、分、秒),后6个是最后访问时间。这两个时间戳在很多工具里会显示成“文件信息”,有人拿它判断版本新旧,但我要提醒一句:时间戳可以被工具改写,不能当成版本管理的依据。LIBNAME存的是库名,ASCII字符串,长度不固定,但很多工具对库名有字符限制,带空格、斜杠、中文的库名在某些解析器里会出问题。我习惯用纯英文加下划线,长度控制在32字符以内,省得给自己找麻烦。ENDLIB是文件结束标志,如果解析到文件末尾还没遇到它,说明文件被截断了——这是传输过程中最常见的事故,后面会细说。
2.2 结构级记录:BGNSTR、STRNAME与ENDSTR
每个cell的定义从BGNSTR开始,数据同样是12个2字节的时间戳,然后是STRNAME,存cell名字。这里有个细节值得说:GDSII标准对cell名字的限制是32个字符以内,虽然很多现代工具放宽了,但跨工具交换时还是按32字符来控制最保险。STRNAME之后是这个cell里所有元素的记录,最后以ENDSTR结束。cell的名字在同一个库里必须唯一,大小写敏感——这一点在跨平台协作时坑过不少人,Windows上某些工具会把名字转成大写,到了Linux环境下引用就断了。我的处理习惯是:所有cell名统一小写加下划线,避免任何大小写歧义。另外,引用别人的cell时名字里不要带点号、斜杠、冒号这类字符,有些解析器会把它们当成路径分隔符处理。
2.3 几何元素记录:BOUNDARY、PATH、BOX各自的适用场景
BOUNDARY是多边形,数据是LAYER、DATATYPE、XY三组记录,XY存的是闭合点序列,标准要求最后一个点等于第一个点,形成一个闭环。有些工具导出时会省略重复的最后一个点,接收方也能认,但严格的DRC工具可能会报“未闭合多边形”,所以导出后最好抽查几个多边形确认闭合。PATH是路径,核心是WIDTH(宽度)和PATHTYPE(端点类型),PATHTYPE为0是方形端点,1是圆形端点,2是半宽延伸,4是自定义延伸。PATH在画金属走线、电阻条时很省事,但它有个隐患:路径宽度如果是负数,表示绝对宽度,不受后续缩放影响,这个特性在不同工具里的解释不完全一致,跨工具传递时容易出问题,我一般建议能转成多边形就转成多边形。BOX是第六版引入的,用来表示封装区域或者某些特殊层,很多老流程不认它,导出前最好确认接收方是否支持。
2.4 引用与变换:SREF、AREF和那几个变换位
SREF是单个引用,数据是SNAME(被引用的cell名)加XY(引用原点)。AREF是阵列引用,除了SNAME还要有COLROW(列数和行数)和三个XY点:第一个是阵列原点,第二个定义列方向(原点到第二个点的距离除以列数就是列间距),第三个定义行方向。这里最容易错的是间距的理解——很多人以为第二个点就是“下一列的位置”,实际上它是“所有列排完之后的终点”,工具会用总距离除以列数得到间距。变换方面,STRANS记录里几个bit控制镜像、绝对放大、绝对角度,MAG是放大倍数,ANGLE是旋转角度。镜像加旋转的组合在不同工具里实现顺序可能不同,我吃过一次亏:在A工具里镜像后再旋转90度,导到B工具里变成了先旋转再镜像,图形位置整体偏了。从那以后,凡是用到镜像加旋转的组合,我都在导出后重新导入做一次比对,确认位置一致再交付。
2.5 文本与属性:TEXT记录和它背后的信息传递
TEXT记录用来放标注、标签、pin名,数据包括LAYER、TEXTTYPE、PRESENTATION(对齐方式)、XY、STRING。文本在版图里看着不起眼,但在流片流程里作用不小:有些工艺要求用特定层的文本标记芯片标识、版本号、对准标记名称,掩模厂的数据处理系统会读这些文本做自动识别。文本还有个常见问题是对齐方式,PRESENTATION里的水平和垂直对齐位如果设置不对,文本在接收方工具里会整体偏移半个字宽,虽然不影响图形,但做版图比对时会报出一堆“文本位置差异”。属性记录PROPATTR和PROPVALUE是给元素挂自定义键值对的,很多PDK用它传递器件参数,但并非所有工具都保留属性,跨工具转换时属性丢失是常态。如果属性对后续流程重要,导出前一定要做一次往返测试。
3. 从版图设计到GDS导出的实操要点
3.1 层号映射表:整个流程里最容易出事的一环
层号映射是导出环节的头号事故源。设计环境里层是用名字标识的,比如“METAL1”“VIA1”“POLY”,但GDS里只有数字,层号加数据类型两个数字。这中间的对应关系由映射表决定,而映射表往往由工艺方提供,不同工艺、不同版本还不一样。我见过最典型的事故是:项目从旧工艺迁到新工艺,映射表换了但导出脚本没改,结果金属1的图形全跑到金属2的层上,DRC报了几千个错,查了一整天才定位到映射表。防这类问题的办法不复杂:把映射表当成代码来管理,放进版本控制,每次导出后跑一个检查脚本,统计每个层上的图形数量,和预期值比对。下面这张表是我习惯用的记录格式,实际使用时按工艺方给的map文件填写:
| 设计层名 | GDS层号 | 数据类型 | 用途说明 | 常见错误 |
|---|---|---|---|---|
| POLY | 5 | 0 | 多晶硅栅 | 与扩散层号写反 |
| DIFF | 6 | 0 | 有源区 | 数据类型误填1 |
| METAL1 | 10 | 0 | 第一层金属 | 层号少写一位 |
| VIA1 | 20 | 0 | 第一层通孔 | 与接触孔混淆 |
| TEXT | 63 | 0 | 标注文本 | 被当成图形层做DRC |
注意:上表里的层号只是示例,实际必须严格按工艺方提供的映射文件来填,任何自行推测都可能造成批量错误。
3.2 保留层次还是打平:两种策略的取舍
导出时要不要打平层次,这个问题没有标准答案,要看下游怎么用。保留层次的好处很明显:文件小、DRC可以分级跑、LVS能对应到具体单元、出问题好定位。坏处是引用关系复杂,尤其是跨团队拼版的时候,别人的cell名和你的cell名撞了,或者引用了不存在的cell,文件就会报“未定义结构”。打平的好处是简单粗暴,一个文件里全是多边形,谁都能读;坏处是体积膨胀、DRC时间成倍增加、版图可读性差。我的经验是分阶段处理:设计阶段和内部验证阶段保留层次,交付给工艺方之前再按对方要求决定是否打平。有些掩模厂的数据处理系统对层次嵌套深度有限制,超过一定层数会报错,这种信息一定要提前问,别等文件传过去被打回来。
3.3 导出参数设置:精度、单位、文本、属性四项必查
导出对话框里的参数看着多,真正要盯的就四项。第一是数据库单位,按工艺方要求填,常见的1纳米或者5纳米,填完核对UNITS记录。第二是用户单位,通常1微米,和数据库单位要匹配。第三是文本和属性是否导出,有些流程需要文本做标识,有些流程嫌文本干扰DRC,这个要按下游要求来。第四是是否包含空cell、是否合并重复图形,空cell在某些工具里会导致LVS报错,合并重复图形能减小体积但会改变层次结构。我习惯在导出后立刻用查看工具打开,随机点几个图形看坐标和层号,再和设计环境里的对应位置比对。这个动作花不了两分钟,但能拦下大部分低级错误。
3.4 一次导出翻车的完整记录
说个我自己踩过的坑。某次一个模拟模块交付,导出后DRC全过,LVS也过,文件发给工艺方,对方回来说“顶层单元缺失”。查了半天发现,我的顶层cell名字里带了一个连字符,导出工具把它当成了特殊字符,自动截断了名字,导致引用关系断裂。更麻烦的是,我本地的DRC工具对此很宽容,直接忽略了那个引用,所以本地验证完全看不出问题。后来定了个规矩:所有cell名只允许小写字母、数字、下划线,长度不超过32字符,导出后用一个独立脚本扫描所有SNAME,确认每个被引用的名字都能在文件里找到对应的STRNAME。这个检查脚本很简单,但救过我好几次。同类问题还有中文路径导致的文件写入不完整、网络盘传输导致的文件截断,解决思路都一样:导出后做一次结构性自检,别只看DRC结果。
4. 流片交付阶段:GDSII要过哪些关
4.1 物理验证:DRC、LVS、ERC和天线检查
GDS导出只是起点,真正决定能不能流片的是物理验证。DRC查设计规则,间距、宽度、包围、密度,这一关最耗时间,也最容易因为单位或者层号问题产生假错。LVS查版图和原理图的一致性,它依赖GDS里的文本标签和端口定义,如果文本层没导出或者标签位置偏了,LVS会报大量“器件不匹配”。ERC查电气规则,比如衬底接触是否足够、电源地是否短路。天线效应检查查的是等离子体工艺中的电荷积累,长金属线直接连到栅极会有风险,需要跳层或者加二极管。这四类检查里,DRC和LVS是必过的硬指标,ERC和天线检查按工艺要求来。我的习惯是每次改完版图先跑一遍快速DRC,只查关键规则,通过后再跑全量,避免每次等半小时。
4.2 版图合并与顶层集成
多个模块拼成一个芯片,这一步叫顶层集成,坑比单个模块多得多。要处理的东西包括:焊盘环、密封环、划片道、对准标记、测试结构、电源地网络。焊盘和密封环通常由工艺方提供固定单元,直接引用就行,但要注意它们的层号和你的设计层号是否一致。划片道宽度、对准标记位置这些参数工艺方有明确规定,填错可能导致后续封装切割出问题。顶层集成时最容易忽略的是密度平衡:大面积空白区域如果不加填充图形,化学机械抛光会不平整,影响良率。填充图形的层号、尺寸、间距都要按工艺方给的规则来,不能自己随手画。我见过有人为了通过密度检查,在芯片上随便铺了一层金属块,结果引入了寄生电容,电路指标全偏了。
4.3 从GDS到掩模数据:中间还隔着好几步
设计方交付的GDS并不是直接拿去写掩模的。掩模厂拿到数据后要做一系列处理:图形拆分(把大图形切成电子束能曝光的梯形)、光学邻近效应修正、掩模数据准备、作业文件生成。这些步骤会改变图形的形状和数量,所以设计方交付的GDS和最终掩模上的图形不是一一对应的。理解这一点很重要,因为它解释了一个常见困惑:为什么我交付的GDS里某个图形是矩形,掩模上却变成了带拐角的形状。设计方要做的,是确保交付数据本身干净、层次清晰、层号正确,剩下的交给掩模厂。有些工艺还要求交付前做一次“掩模规则检查”,查的是图形最小尺寸、最小间距是否在掩模制作能力范围内,这个检查通常由工艺方提供规则文件。
4.4 交付清单:一张表把该查的都查一遍
流片交付最怕漏项,我习惯用一张清单逐项打勾。下表是精简版,实际项目可以按工艺方要求增删:
| 检查项 | 检查内容 | 通过标准 |
|---|---|---|
| 文件完整性 | 能否正常打开,有无截断 | 解析到ENDLIB无报错 |
| 单位一致性 | UNITS两个值与工艺要求一致 | 与PDK文档逐位核对 |
| 层号映射 | 每层图形数量与预期一致 | 统计脚本输出对比 |
| 引用完整性 | 所有SNAME都有对应STRNAME | 扫描无缺失 |
| 顶层结构 | 顶层cell包含完整芯片 | 查看工具确认边界 |
| 文本层 | 芯片标识、版本号存在 | 指定层上有对应文本 |
| 密度检查 | 各层密度在工艺范围内 | DRC密度规则通过 |
| 文件校验 | MD5或哈希值记录 | 交付前后一致 |
注意:清单不是形式,每一项都要有实际动作支撑。我见过团队把清单打印出来贴在墙上,但没人真的去跑统计脚本,结果层号错误一直留到掩模厂才发现。
5. 常见问题排查实录
5.1 尺寸放大一千倍:单位问题的典型表现
单位错误的表现很有规律:如果导入后图形整体放大或缩小了整数倍,基本都是UNITS记录的问题。放大1000倍通常是把1e-3写成了1e-6,缩小1000倍反过来。排查方法很直接:在导入方工具里量一条已知长度的线,比如设计里10微米的线,量出来是10000微米,倍数就是1000,回头看UNITS第一个值。修复方式分两种:如果文件还没交付,重新导出并核对参数;如果文件已经交付,可以在导入方工具里用缩放功能修正,但修正后一定要重新跑DRC,因为缩放会引入坐标量化误差。预防办法前面说过,导出前把两个值抄进交付说明,接收方第一件事核对。
5.2 图形缺失与层号漂移:两类问题的不同排查路径
图形缺失和层号漂移看起来像一回事,排查路径完全不同。图形缺失通常是引用断裂或者记录被截断:先查文件大小是否和源文件一致,再查所有SNAME是否有对应定义,最后查是否有工具不支持的记录类型导致解析中断。层号漂移则是映射表或者数据类型的问题:先统计每层图形数量,和设计环境对比,找出多出来或者少掉的层,再回头查映射表。有一种特殊情况:某些工具在导出时会把DATATYPE为0的层省略不写,接收方默认按0处理,看起来没问题,但如果你的设计里同一层号有多个DATATYPE,省略就会导致图形合并到一层。这种问题只能靠导出后统计来发现。
5.3 文件体积失控与2GB限制
GDSII的记录长度字段只有两个字节,单条记录最大65535字节,这本身不是大问题,因为大图形可以拆成多条XY记录。真正的限制来自很多工具的内部实现:文件偏移用32位有符号整数,导致单个文件不能超过2GB。版图一大,尤其是打平之后,很容易触到这个上限,表现是导出到一半报错,或者导出的文件末尾被截断。解决办法有三个:保留层次减少冗余、把大图形拆成多个cell分别导出再合并、改用OASIS格式。前两个是权宜之计,第三个是趋势,但要看接收方是否支持。我现在的习惯是,预估打平后超过500MB的版图,直接按层次化导出,并且提前和工艺方确认文件大小限制。
5.4 跨工具往返:GDSII和OASIS该怎么选
GDSII和OASIS的取舍,核心看三件事:文件大小、接收方支持、特性需求。OASIS在压缩和重复图形处理上优势明显,同样版图体积可能只有GDSII的几分之一,而且没有2GB的硬限制。但OASIS的解析复杂度高,一些老工具和老流程只认GDSII。我的判断标准是:如果下游是主流掩模厂或者新版物理验证工具,优先OASIS;如果下游是老旧系统或者不确定,老老实实GDSII。两者之间的转换工具很多,但转换不是无损的,属性、某些文本对齐方式可能在转换中丢失,所以转换后必须做一次结构比对。下面这张表是我自己总结的对比,供参考:
| 对比项 | GDSII | OASIS |
|---|---|---|
| 文件体积 | 较大 | 明显更小 |
| 单文件上限 | 常见2GB限制 | 基本无硬限制 |
| 工具支持 | 几乎所有工具 | 新工具支持好,老工具可能不支持 |
| 属性保留 | 有限 | 更灵活 |
| 解析复杂度 | 低 | 较高 |
| 适用场景 | 兼容性优先 | 大版图、新流程 |
6. 我自己的工具链与脚本习惯
6.1 用几十行Python做一次GDS快速体检
交付前做结构自检,不需要复杂的工具,几十行Python就够。核心思路是按记录流顺序读,统计记录类型、收集cell名、检查引用完整性。下面这段代码只做最基础的解析,目的是让你看清楚记录流是怎么读的:
import struct from collections import Counter def scan_gds(path): names, refs, layers = set(), set(), Counter() with open(path, 'rb') as f: while True: head = f.read(4) if len(head) < 4: break length, rtype, dtype = struct.unpack('>HBB', head) data = f.read(length - 4) if rtype == 0x06: # STRNAME names.add(data.rstrip(b'\x00').decode('ascii', 'ignore')) elif rtype == 0x12: # SNAME refs.add(data.rstrip(b'\x00').decode('ascii', 'ignore')) elif rtype == 0x0D: # LAYER layers[struct.unpack('>h', data)[0]] += 1 missing = refs - names print('cells:', len(names)) print('missing refs:', missing if missing else 'none') print('layer hits:', dict(sorted(layers.items()))) scan_gds('demo.gds')这段代码的意义不在于功能多强,而在于它把“文件里到底有什么”变成了可量化的输出。层号统计能直接暴露映射错误,引用缺失能提前发现断链,比打开图形界面一个个点要快得多。实际项目里我会再扩展几项:统计坐标范围确认边界、统计每个cell的元素数量找异常、对比两次导出的层统计差异。
6.2 版本管理与命名规范
GDS文件不适合做文本diff,所以版本管理要靠命名和哈希。我的命名规则是“项目名_模块名_日期_版本号.gds”,比如“demo_core_20250101_v3.gds”,所有交付文件旁边配一个同名.txt,记录导出时间、数据库单位、层映射表版本、MD5值。MD5的作用是确认传输前后文件一致,尤其是通过文件服务器或者邮件传递的时候,截断和损坏非常常见。还有一点:不要用“final”“最终版”这种命名,项目里最终版永远有下一版,用日期加递增版本号最可靠。层映射表也按同样方式管理,版本号写进交付说明,避免接收方用错表。
6.3 几条踩坑换来的经验
第一条,导出后永远重新导入一次做比对,不要相信“导出成功”的提示。第二条,跨团队协作时,先要对方的层映射表和单位要求,再开始导出,顺序反了就是返工。第三条,文本层不要随手关,很多流程靠它做识别,关了之后排查问题会变得非常困难。第四条,文件传输尽量用带校验的方式,收到文件先跑一遍结构自检再打开图形界面。第五条,遇到“本地能开、对方打不开”的情况,优先怀疑文件截断和字符编码,而不是图形本身有问题。这些经验没什么技术含量,但每一条都是实际交付里用时间换来的。