GDSII版图交付全解析:二进制结构、导出与自检清单 凌晨两点收到流片前数据被退回的邮件附件里只有一句版图数据存在结构性问题请重新导出这大概是每个做后端的人最熟悉的噩梦开场。GDSII这个格式说起来老得掉牙——它诞生于上世纪七十年代末比很多读者的年龄都大可直到今天全球几乎所有晶圆代工厂的流片入口认的第一个文件仍然是它。很多人以为GDSII只是版图工具里另存为的一个选项点一下导出来就完事直到第一次真正把数据送进掩模厂才发现这里面的门道比想象中多得多。这篇内容我打算把GDSII从二进制层面拆到交付层面讲清楚它到底是什么、文件里每一段字节在表达什么、导出时哪些环节最容易出问题、送到代工厂之前该做哪些自检。适合刚接触版图交付的工程师、需要理解数据链路的数字前端同学以及任何想把导出一个GDS这件事真正做对的人。读完之后你至少应该能在不看文档的情况下判断一个GDS文件是不是导对了、导全了。1. 一次数据退回让我重新正视这个老格式1.1 退回通知背后通常藏着什么那次退回的起因现在想起来很基础项目里用了一个别人交接过来的IP单元我在导出顶层版图时勾选了保留层次结构结果这个IP内部有几层嵌套引用在不同工具里被解释得不一致代工厂那边的检查脚本读出来的层次树和我这边完全对不上。表面上看是两个工具都成功打开了文件实际上它们对同一段SREF记录的处理路径不同一个把它当成扁平引用一个保留了中间层级。这件事让我意识到一个很朴素的事实GDSII是一份二进制契约不是一张图片。工具能打开它不代表它被正确解读了能跑通DRC也不代表它送到掩模厂还能跑通。很多团队对GDSII的认知停留在版本兼容层面觉得只要工具能导出、能导入就万事大吉。但真正的风险在于GDSII是一个语义相对宽松、描述能力有限的格式它没有强制的设计规则约束也不携带完整的层次语义和属性信息。同一个几何图形可以用一个BOUNDARY描述也可以用多个PATH拼出来甚至可以拆成几个小多边形再靠布尔运算合并。工具之间对这些等价表达的容忍度不一样最后到掩模数据准备MDP环节任何一点歧义都会被放大。1.2 它为什么还没被OASIS取代道理其实不复杂。OASIS在压缩率、属性表达、大文件处理上确实比GDSII强一大截一个几GB的GDS压成OASIS往往能小一个数量级。但GDSII有一个无法忽视的优势生态惯性。从版图编辑、DRC/LVS、到掩模写入设备整条链路几十年积累下来的工具、脚本、检查规则默认输入都是GDSII。代工厂的工艺设计套件里layer map、DRC deck、填充脚本全部按GDSII的记录结构写死。换格式意味着整条链路重做验证这个成本没有任何一家愿意为了一点存储空间去承担。所以现实的做法是内部设计流程里OASIS可以作为中间交换格式用来传输和存储大版图但最终交付给代工厂的那一份绝大多数情况下仍然要求是GDSII。这就决定了你必须对GDSII有足够深的理解不能把它当成黑盒。1.3 一个GDSII文件里究竟装了什么简单说它装的是一个库Library库里包含若干个结构Structure也可理解为单元Cell每个结构里包含若干元素Element——多边形、路径、文本、以及对其他结构的引用。除此之外还有少量元信息库名、单位定义、修改时间、版本号。这里有个容易混淆的点GDSII没有顶层这个概念。它只是一个库文件哪个结构是顶层是由使用者根据引用关系推断出来的——被引用次数为零的那个结构就是顶层。这个特性直接导致了一个常见问题如果你导出时不小心把某个子单元也当成了独立输出或者反过来把顶层错误地嵌套进了别的结构代工厂读到的顶层可能和你以为的不一样。层级组成常见问题Library库名、单位、版本单位定义不一致导致尺寸整体偏差Structure若干Element顶层判定错误、层次嵌套异常Element几何、引用、文本多边形不闭合、引用变换错误Record二进制字段字节序、数据类型误读提示判断顶层不要靠工具界面的高亮要靠引用关系——写个脚本统计所有SNAME引用没被任何结构引用的才是真正的顶层。2. 拆开二进制记录是怎么编码的2.1 记录、数据类型、数据三元组构成的流GDSII是纯粹的流式格式整个文件就是一串**记录Record**首尾相接没有全局索引没有偏移表。每条记录的结构固定为三部分长度字段2字节无符号整数表示整条记录的总字节数包含长度字段自身记录类型1字节标识这条记录是什么HEADER、BOUNDARY、XY等数据类型1字节标识后面数据的解释方式整数、实数、ASCII、位数组数据区长度减去4之后的剩余字节关键点在于长度字段包含它自己那2字节。这一点在写解析器时特别容易出错我见过不少人第一版解析脚本就栽在这里把长度当成数据区长度去读结果整个流错位越读越乱。正确做法是读到长度L后实际数据区长度是L-4下一条记录从当前位置加L开始。数据类型那1字节也很重要它决定了数据区怎么解释。常见的有0x022字节有符号整数、0x034字节有符号整数、0x058字节IEEE 754实数、0x06ASCII字符串。同一个记录类型数据区可能是整数也可能是字符串全看数据类型字段。比如STRNAME记录数据类型是0x06数据区就是一串ASCII末尾补零对齐到偶数长度。2.2 一个最小合法文件的记录序列要理解结构最好的办法是看一个能通过检查的最小文件长什么样。它必须包含以下记录顺序不能乱HEADER (0x00) 版本号2字节整数如 600 表示 v6.0.0 BGNLIB (0x01) 库修改时间访问时间各6个2字节整数 LIBNAME (0x02) 库名字符串 UNITS (0x03) 两个8字节实数数据库单位/用户单位、数据库单位/米 BGNSTR (0x05) 结构创建时间修改时间 STRNAME (0x06) 结构名字符串 ...元素记录... ENDSTR (0x07) 结构结束 ...更多结构... ENDLIB (0x04) 库结束注意顺序里没有结构数量这种字段结构的数量完全靠BGNSTR/ENDSTR成对出现来隐式表达。ENDLIB是文件的终点读到它就应该停。如果ENDLIB之后还有数据规范上说应该忽略但有些严格的检查工具会报错。我一般建议导出后截断到ENDLIB别留尾巴。版本号这一项值得单独提一句。HEADER里的版本号是2字节整数v6.0.0写作600v3.0写作3。现在绝大多数工具默认导出600也就是v6。但有些老设备或者特定工艺的掩模写入工具只认v3这时候需要在导出时降版本。降版本不是简单改个数——v3和v6在AREF的坐标点数量上有差异v3要求5个点v6要求3个点粗暴改版本号会导致读取方按错误规则解析。正确做法是用工具的另存为v3功能让它按目标版本的规则重新编码。2.3 UNITS记录那个决定一切的比例尺UNITS是整份文件里最容易被低估的一条记录。它包含两个8字节实数第一个用户单位对应多少个数据库单位第二个数据库单位对应多少米举个例子最常见的一组值是0.001和1e-9。翻译过来就是数据库单位是1纳米1e-9米1个用户单位通常是微米等于1000个数据库单位0.001反过来是1000。这意味着文件里所有XY坐标都是整数单位是数据库单位。你看到一个坐标值1000实际尺寸就是1微米。这里有个经典的坑如果两个工具对UNITS的解释不一致整张版图会整体缩放1000倍或者0.001倍。我见过一次案例导入方把第一个值当成了数据库单位/用户单位而不是用户单位/数据库单位结果版图缩成了指甲盖大小。检查方法很简单导入后量一个已知尺寸的图形——比如标准单元的高度——如果差了两个数量级以上先在UNITS上找原因别急着怀疑几何数据。注意UNITS的两个值必须严格符合规范定义不能凭直觉填。第一个值是用户单位除以数据库单位的商第二个值是数据库单位的米数。填反了不会报错但会毁掉整份数据。2.4 坐标为什么是整数负数怎么存所有XY坐标都是4字节有符号整数采用的是二进制补码表示。这一点在写解析器或者做低层比对时非常关键。比如一个坐标值是 -1000在文件里不是简单的一个负号加数字而是补码形式读出来要按有符号整数解释。为什么用整数因为浮点数在不同平台上的舍入行为不一致版图对几何精度要求极高用整数加固定比例尺的方式能保证跨平台、跨工具的一致性。所有坐标都是数据库单位的整数倍这也是为什么版图里要求吸附到网格——网格本质上就是数据库单位的一个整数倍约束。解析的时候每个XY记录里的数据区是一串连续的有符号整数每4字节一个数量由长度字段决定。BOUNDARY至少要有4个坐标点3个顶点加一个闭合点PATH至少2个点。如果读出来的点数量不对基本可以断定是长度字段算错了。2.5 手读一个文件头十六进制实战光讲理论不如动手看一眼。下面是一段真实文件的开头十六进制我们来逐段读00 06 00 02 01 2C 00 1C 01 02 0E 10 07 E4 0E 10 07 E4 ...第一段00 06 00 02 01 2C长度字段是0x0006即6字节记录类型0x00HEADER数据类型0x022字节整数数据区是0x012C即300。版本号300对应v3.0。接着00 1C 01 02 ...长度0x001C即28字节记录类型0x01BGNLIB数据类型0x02。数据区应该放12个2字节整数6个修改时间字段加6个访问时间字段共24字节4加24正好28对上了。按照这个节奏你能把整个文件的记录流一条条读出来。写个几十行的Python脚本就能做到再用一个成熟库的输出做交叉验证两三次就能建立起对格式的肌肉记忆。3. 图形对象BOUNDARY、PATH、SREF、AREF的真实用法3.1 BOUNDARY多边形闭合这件事没你想的那么随便BOUNDARY是版图里最核心的图形元素代表一个闭合多边形。一条BOUNDARY记录后面要跟LAYER、DATATYPE、XY等记录。LAYER和DATATYPE各是2字节整数决定这个图形落在哪个工艺层、哪个数据类型上。DATATYPE在很多工艺里用来区分同层的不同用途比如普通图形、填充图、切割道标记等填错会被DRC当成非法图形。XY必须首尾闭合第一个点和最后一个点坐标完全相同。规范里没有强制要求工具必须检查这一点但下游的掩模处理工具和某些DRC引擎会默认闭合导致要么多算一条边要么直接报错。另外多边形不能自交。自交多边形在数学上是病态的——它没有明确的内部区域不同工具对它的面积计算、布尔运算结果都不一样。工具里画图时看着没问题导出后可能就是一个自交图形所以导出前一定要跑一遍多边形合法性检查。还有一点零面积多边形。两个点重合、三个点共线这类退化图形在视觉上不可见但会占据记录空间还可能让某些工具的内部数据结构出错。清理掉它们不会影响任何功能纯赚。3.2 PATH宽度、端点、扩展方式PATH是用一条中心线加一个宽度来描述图形的适合做走线、guard ring、切割道之类的东西。它至少需要两个坐标点宽度由WIDTH记录给出。关键在于端点类型PATHTYPEPATHTYPE含义典型用途0方形端端点在中心线终点处截断普通互连线1圆形端端点向外延伸半个宽度需要圆角过渡的走线2方形端端点向外延伸半个宽度需要保证覆盖长度的走线4变宽配合BGNEXTN/ENDEXTN使用特殊 tapered 结构PATHTYPE选错是个隐蔽的坑。比如你画了一段宽度2微米的走线期望它在端点处正好截断在坐标点上结果用成了type 2实际图形会向外多出1微米。这在密度检查或者间距检查时可能刚好擦边违规。我一般建议除非有明确理由默认用type 0行为最可预测。需要精确覆盖的时候再单独处理端点。变宽路径type 4用得少但一旦用了BGNEXTN和ENDEXTN记录必须成对出现否则读取方不知道两端怎么扩展。这种表达在GDSII里本身就不是强项复杂形状建议直接画成多边形别硬用PATH。3.3 SREF与AREF引用变换是错误高发区SREF是单次引用AREF是阵列引用。两者都是把另一个结构搬到当前位置来用。SREF的结构是SNAME指定被引用的结构名可选STRANS/MAG/ANGLE描述变换最后跟一个XY点表示放置位置。这里最容易出问题的是变换顺序。GDSII规定先做镜像STRANS里的位标志再做缩放MAG最后旋转ANGLE。有的工具在内部用一个4x4矩阵做变换导出时按自己的习惯排顺序结果和读取方不一致。我遇到过一次一个带90度旋转加镜像的单元在我这边看着正常代工厂那边镜像方向反了整个IP的引脚位置全错。AREF更复杂。它需要COLROW记录给出列数和行数然后用三个坐标点定义整个阵列区域第一个点是阵列原点第二个点定义列方向上的跨度第三个点定义行方向上的跨度。列间距和行间距是从这三个点反算出来的。如果三个点共线或者退化间距就算不出来整个阵列会塌掉。AREF的第三个点在早期版本v3里还有个第五点表示列向量的补充版本不一致会导致解析差异——这也是为什么前面强调不要粗暴改版本号。AREF记录序列示例 SNAME 被引用单元名 COLROW 列数 行数 XY 点1 点2 点3 (v6)3.4 TEXT和属性被丢弃的那些信息TEXT在GDSII里主要是标注用途记录文字内容、位置、大小、对齐方式。它的本质是给人看的不参与几何运算。所以很多代工厂在掩模数据处理时会把TEXT整个丢掉。如果你把关键信息比如某个区域的特殊说明只写在TEXT里很可能在某一环丢失。属性PROPATTR/PROPVALUE用来给元素挂自定义键值对但在GDSII里支持非常有限而且解析路径不统一。我的经验是不要指望GDSII携带业务属性。需要传递的工程信息走独立的配套文件比如网表、层信息表、README里的说明别塞进GDS的属性里。4. 导出环节图层映射、层次处理与精度控制4.1 Layer Map数字对不上的第一现场版图工具里图层通常用名字标识比如M1、POLY、VIA1但GDSII里只认数字对layer, datatype。这两者之间的映射由一张Layer Map表决定。这张表通常由工艺设计套件给出格式是一行一行M1 drawing 31 0 M1 pin 31 1 POLY drawing 5 0 VIA1 drawing 51 0问题往往出在三个地方一是工具里的图层名和表里的名字大小写、空格不完全匹配匹配不上就落到默认值二是同一图层名对应多个用途drawing/pin/label导出时选错用途图形落到了错误的数据类型上三是换工艺节点时忘了换表。我的做法是每次换工艺或者换项目先单独导出一个只有几根线的测试结构用脚本把里面的layer/datatype全部列出来和Layer Map逐条对照确认无误再导正式数据。这个动作只要十分钟能挡掉绝大多数低级错误。检查项正确做法常见错误图层名匹配大小写、空格严格一致部分匹配落到默认层用途选择drawing层不含pin/label多用途混在一层表版本与当前工艺节点对应沿用旧节点映射表4.2 扁平化还是保留层次导出时的这个选项很多人随手一选其实影响很大。保留层次结构的好处是文件体积小、便于定位问题、便于后续修改坏处是引用关系复杂任何一层引用变换出错都会传导到顶层而且不同工具对层次的处理差异大。扁平化把所有引用展开成独立的几何好处是语义明确、任何工具读出来都一样坏处是文件体积可能涨几倍而且丢失了层次信息后续改图很难。我的建议内部回归和版本归档用保留层次的版本交付代工厂用扁平化版本。代工厂最关心的是你能不能用不需要理解你的层次设计。扁平化能最大限度地消除工具间解释差异。代价是文件大但现在的传输手段完全扛得住。需要留一个中间版本就是把可疑的IP单独扁平化、其余保留层次。这种做法能平衡体积和可靠性但前提是你清楚哪些IP是可疑的——首次合作的项目建议全扁平化稳妥优先。4.3 精度、网格与DRC容差的关系版图坐标是数据库单位的整数倍这个最小单位就是网格。常见配置是1纳米。DRC规则里的容差也按这个单位写比如最小间距0.1微米就是100个数据库单位。这里的关键是不要产生网格外的坐标——如果一个图形边长是1.0005微米在1纳米网格下它就有半个单位的余量不同工具舍入方向不同可能变成1000或者1001。这种误差单点看不出累积到大版图上就是一堆DRC误报。导出版本前把整张版图吸附到网格上多余的小数位一律清掉。大多数工具都有snap to grid功能导出时勾上。有个例外是某些特殊工艺的填充图形故意用亚网格精度控制密度计算这种要在工艺方明确要求下才做不能自己拍脑袋。5. 送到掩模厂之前一份长在踩坑里的自检清单5.1 结构完整性从引用关系反推顶层前面说过GDSII没有显式顶层。检查方法是写脚本遍历所有SREF/AREF记录里的SNAME统计每个结构被引用的次数。被引用次数为0的结构就是顶层。正常情况下应该恰好有一个。如果有多个说明导出时把内部子单元也单独输出了或者有孤立结构没删干净。如果一个是0个说明存在循环引用——结构A引用BB又引用A这种在GDSII里是非法且危险的某些工具会直接卡死。同时检查每个SREF引用的SNAME是否能在库里找到对应的结构定义。悬空引用引用了不存在的结构在宽松的读取器里会被静默忽略图形凭空消失在严格读取器里直接报错。两种情况都很难受。5.2 版图与网表的交叉核对这一步经常被跳过但它能挡住一类致命问题。方法是从网表里提取所有顶层端口pin/port的名字和坐标从版图里提取所有pin层图形的位置逐个比对。名字对不上、位置偏了、数量不一致都属于硬件连接层面的错误。我习惯用脚本做这件事网表侧导出一份端口名-坐标清单版图侧用工具API导出另一份两个文件按名字排序后逐行diff。有一次这样查出一个pin被放到了错误的边上LVS是能通过的因为连接关系正确但封装打线会打偏。这种问题如果到封装厂才暴露返工成本极高。5.3 文件体积和版本兼容的最后确认交付前看一眼文件体积。一个几百万门的SOC版图扁平化后几十GB是合理的如果一个简单模块导出来只有几百KB那多半是图层没选上或者元素被过滤掉了。反过来一个本该很小的结构导出后GB级可能是某个填充脚本失控或者重复引用没合并。版本方面确认代工厂要求的具体版本号并在导出后用一个独立的GDSII阅读器别用导出的那个工具再打开一次看能不能正常读、图层对不对、数量级对不对。这一步叫换工具验证是最便宜也最有效的一道防线。6. 与掩模数据准备的接口细节6.1 代工厂真正吃进去的是什么很多人以为代工厂拿到的就是设计师导出的那份GDSII实际上中间还要经过掩模数据准备MDP这一环。MDP会把版图做一系列处理逻辑运算比如把某些层做AND/OR生成新层、尺寸微调光学邻近效应修正OPC、添加辅助图形SRAF、拆分层次、格式转换。也就是说你交付的GDSII只是原料最终写入掩模机的数据是经过二次加工的。理解这一点很重要因为它解释了很多我这边看着没问题的困惑。比如某个图形在你自己工具里是合法的但经过逻辑运算后产生了一个自交图形MDP就报错退回。所以自检不能只停留在能打开、能跑DRC的层面还要站在MDP的视角想这个几何经过布尔运算后还合法吗6.2 常见退回原因和对应修复退回原因根因修复方式层次结构不规范顶层判定失败或存在循环引用扁平化后重新导出图形自交/退化编辑残留的无效多边形跑多边形清理脚本单位定义不一致UNITS记录填反或使用了非常规值统一为1nm数据库单位图层映射错误Layer Map与工艺不匹配用测试结构逐层核对文件截断导出中断或ENDLIB缺失重新导出并验证末尾这些原因里我觉得最值得强调的是单位定义和图层映射两项因为它们不报错。一个单位填错的文件能被所有工具正常打开只是尺寸不对一个图层映射错的文件DRC也能过只是图形落在了错误的层上。它们靠能打开是发现不了的只能靠主动核对。6.3 沟通上的几个实操经验第一交付前主动问清楚代工厂对格式的具体要求版本号、是否要求扁平化、数据库单位、是否接受TEXT、AREF的版本兼容性。这些问题在项目启动时问一次记录下来形成模板以后每个项目套用能省掉大量往返。第二保留一份交付清单文档每次交付时逐项打勾。清单内容从历史退回原因里长出来越用越厚是团队最值钱的资产之一。第三出问题的时候别急着改数据重发先要一份对方读取文件的日志。日志里通常会写明是第几条记录、哪个结构、什么类型的问题。拿着日志定位比盲目重导快十倍。我个人在实际操作中的体会是GDSII这个格式本身不难难的是它牵扯的环节太多每一环都有各家的实现差异。把它当成一份需要双方严格对齐的契约而不是一个可以随手另存的文件很多问题在源头就能避免。