GDSII 与 OASIS 版图文件格式:从第一性原理到芯片制造

ℹ 范围:从"掩模需要什么信息"出发,推导版图文件的最小数据模型;逐字节拆解 GDSII 与 OASIS;附完整的 SRAM 样例解码数据供读者复现;最后按工序说明 Fab / 掩模厂如何解析版图并完成芯片生产。

说明:本文所有字节级解码示例均附有可复现的解码脚本与完整输出。GDSII / OASIS 读写对照业界开源库 gdstk (github.com/heitzmann/gdstk)。Fab 流程为行业通用流程,具体参数以各厂 PDK 与 Tape-out 规范为准。

1. 第一性原理:版图文件要回答什么问题

1.1 制造端真正需要的输入

晶圆厂不关心电路"是什么",只关心每一道光刻"哪里透光、哪里不透光"。一颗芯片的制造输入,本质是 N 张二值平面图(每张对应一块掩模,N 在几十量级),外加把它们彼此对齐的坐标系。

制造端的硬需求由此推出的约束数据模型上的决策
图形必须精确到制造网格浮点误差不可接受;布尔运算必须可精确判定整数坐标 + 全局单位(数据库单位 DBU)
每块掩模独立制作图形必须按工艺层归类每个图形带 层号(及数据类型)
图形是"区域",不是线条需要可做面积/布尔运算的表示多边形(边界表示),线宽型走线可用 Path 简写
SRAM、标准单元大量重复平铺存储体积爆炸层次化:单元定义一次,实例只存变换
验证需要知道哪根线是哪个网络纯几何不足以做 LVS文本标注(Label)与可选属性
文件要跨工具、跨公司、跨几十年交换格式必须自描述、与平台无关定义明确的字节序列化规范

1.2 为什么不直接存位图

按光刻机最大曝光场 26 mm × 33 mm、1 nm 网格估算单层位图:

量计算结果
像素数2.6×107 × 3.3×107≈ 8.6×1014
1 bit/像素8.6×1014 / 8≈ 107 TB / 层
同一区域用多边形只存边界顶点与图形复杂度成正比,与面积无关

结论:几何必须用矢量(多边形)表示;再叠加层次复用,体积才能降到可交换的量级。位图化(栅格化)只发生在流程最末端——掩模写入机内部。

1.3 推导出的最小数据模型

概念定义GDSII 叫法OASIS 叫法
库一个文件;持有单位与全部单元Library(BGNLIB…ENDLIB)文件(START…END)
单元具名的图形容器,可被引用Structure(BGNSTR…ENDSTR)CELL
多边形闭合顶点序列 + 层/类型BOUNDARY(BOX)POLYGON / RECTANGLE / TRAPEZOID / CTRAPEZOID / CIRCLE
路径中心线 + 宽度 + 端头样式PATHPATH
实例对另一单元的引用 + 变换SREF(单个)/ AREF(阵列)PLACEMENT / PLACEMENT_TRANSFORM(+ 任意重复)
标注点 + 字符串 + 层/类型TEXTTEXT
属性附着在元素上的键值PROPATTR / PROPVALUEPROPERTY

1.4 层次 = 有向无环图(DAG)

图 1-1单元引用 DAG — 箭头标注引用类型,叶子节点为实际多边形

TOP
← 顶层单元(无父节点)
▼ SREF
IO
▼ SREF
CORE
▼ SREF
SRAM_MACRO
▼ AREF
512×256 = 131072 个实例
BITCELL
↑ 叶子:OD / PO / CO / M1 … 多边形
▼ SREF
STD_LOGIC
▼ SREF
PLL
规则
• 边 = 引用(带变换);不允许成环
• 展平 flatten(TOP) = 对每条“根 → 叶”路径,把复合变换作用到叶子多边形上,再取并集

1.5 两种格式的定位

GDSII 与 OASIS 承载同一个数据模型,差别只在序列化方式:

维度GDSII StreamOASIS
出身Calma 公司 GDS II 系统的流格式(1970 年代末),最终版本 Release 6.0(HEADER=600)SEMI P39(2004),为替代 GDSII 而设计;掩模写入子集为 SEMI P44(OASIS.MASK)
设计哲学定长、自同步、易实现信息论意义上的紧凑:去掉一切可推导的冗余
代价体积大,40 年前的位宽限制至今仍在解析器是状态机,实现复杂度高一个量级

2. 共同的几何语义

2.1 单位与网格

概念含义典型值
DBU(数据库单位)坐标整数的物理含义,所有几何都是 DBU 的整数倍1 nm(先进节点常用 0.5 nm 或更细)
用户单位工具界面显示用的单位1 µm
制造网格工艺允许的最小坐标步长,由 PDK 规定≥ DBU,且为其整数倍

GDSII 在 UNITS 记录中同时给出"1 DBU = 多少用户单位"与"1 DBU = 多少米";OASIS 在 START 中给出"每微米多少个网格单位"。单位读错 = 整张图缩放错误,这是 Fab 接收检查的第一项。

2.2 实例变换

实例把子单元坐标 p 映射到父单元坐标 p',顺序固定为:先关于 X 轴镜像 → 再缩放 → 再旋转 → 最后平移。


  p' = T + R(theta) * ( M * F(p) )

  F(x, y)  = (x, -y) if reflected else (x, y)     GDSII: STRANS bit 0x8000   OASIS: info bit F
  M        = magnification                        GDSII: MAG (real8)         OASIS: real
  R(theta) = rotation, counter-clockwise, degrees GDSII: ANGLE (real8)       OASIS: AA bits (k*90) or real
  T        = origin                               GDSII: XY                  OASIS: x, y (absolute or relative)

  Nested:  p_top = A1( A2( ... An(p_leaf) ) )     compose along the DAG path
变换类型是否保持网格对齐制造风险
平移(整数)、90° 倍数旋转、镜像是无
任意角度旋转否顶点落在网格外,需吸附(snap),产生锯齿与微小间隙
非整数放大通常否同上;多数 PDK 禁止

2.3 层号与数据类型

(layer, datatype) 二元组是图形的分类键。它不是掩模编号——两者之间隔着一张由 PDK 定义的层映射表(见 §7.4)。

字段典型用途(具体编号以 PDK 为准)
layer工艺层:有源区、阱、多晶、接触孔、各层金属/通孔、标记层
datatype同一层内的用途区分:drawing / pin / dummy(fill) / blockage / 多重曝光颜色 / OPC 排除区
texttypeTEXT 专用的分类号,常用于区分端口标注与注释

3. GDSII:逐字节拆解

3.1 唯一的结构单元:记录

GDSII 文件就是一串首尾相接的记录,没有文件头、没有索引、没有压缩。单条记录的结构如图 3-1。

图 3-1GDSII 记录结构 — 每条记录 = 4 B 定长头 + 变长载荷

字节 0–1
记录长度 L
uint16
字节 2
记录类型
字节 3
数据类型
字节 4 … L−1
载荷(L − 4 字节)
2 字节
大端序
1 字节
0x00~0xFF
1 字节
0~6
变长,共 L−4 字节;内容由记录类型决定(坐标 / 字符串 / 实数 / 位字段等)
约束:4 ≤ L ≤ 65534 且为偶数(长度含 4 B 头)→ 单个 XY 记录最多 8191 个坐标点(每点 8 B)
规则后果
长度字段 16 位单条记录 ≤ 65534 B → 单个 XY 最多 8191 个点
长度含 4 字节头且为偶数读取器只需"读 4 字节 → 读 L−4 字节"循环即可遍历全文件
全部大端序与平台无关;小端机器读取时需逐字段字节交换
文件常补零至 2048 B 的整数倍磁带时代遗留;ENDLIB 之后的 0x00 应忽略

3.2 数据类型(字节 3)

码类型单元宽度典型记录
0无数据0BOUNDARY、ENDEL、ENDSTR、ENDLIB
1位数组2 BSTRANS、PRESENTATION、ELFLAGS
2有符号整数2 BHEADER、BGNLIB、LAYER、DATATYPE、COLROW、PATHTYPE
3有符号整数4 BXY、WIDTH、BGNEXTN、ENDEXTN
4实数4 B规范保留,实际不用
5实数(Real8)8 BUNITS、MAG、ANGLE
6ASCII 串变长LIBNAME、STRNAME、SNAME、STRING;奇数长度补 \0

3.3 Real8:不是 IEEE 754

GDSII 的 8 字节实数沿用 IBM 大型机的十六进制浮点格式(指数 excess-64),位段如图 3-2。

图 3-2Real8 位段布局 — 不是 IEEE 754,底数 16,指数偏移 64

bit 63
S 符号
1 位
bit 62–56
E 指数
7 位
bit 55 … 0
M 尾数(小数部分)
56 位,无隐含位
值 = (−1)ˢ × (M / 2⁵⁶) × 16⁽ᴱ⁻⁶⁴⁾规格化:1/16 ≤ M/2⁵⁶ < 1
对比项Real8(GDSII)IEEE 754 double
底数162
指数7 bit,偏移 6411 bit,偏移 1023
尾数56 bit,无隐含位52 bit + 隐含位

value = (−1)S × (M / 256) × 16(E − 64)    规格化:1/16 ≤ M/256 < 1

解码用 C 代码表达(对照 gdstk gdsii.cpp gdsii_real_to_double):


double gdsii_real_to_double(uint64_t real) {
    int64_t exponent = ((real & 0x7F00000000000000) >> 54) - 256;
    double mantissa = (double)(real & 0x00FFFFFFFFFFFFFF) / 72057594037927936.0;
    double result = mantissa * exp2((double)exponent);
    return (real & 0x8000000000000000) ? -result : result;
}
对比GDSII Real8IEEE 754 double
底数162
指数7 bit,偏移 6411 bit,偏移 1023
尾数56 bit,无隐含位;十六进制规格化下首位可有至多 3 个前导 0,有效精度 53~56 bit52 bit + 1 隐含位
范围约 10−78 ~ 1075约 10−308 ~ 10308

3.4 记录类型(字节 2)

类别记录(十六进制码)
库级HEADER 00 · BGNLIB 01 · LIBNAME 02 · UNITS 03 · ENDLIB 04 · REFLIBS 1F · FONTS 20 · GENERATIONS 22 · ATTRTABLE 23 · FORMAT 36 · MASK 37 · ENDMASKS 38 · LIBDIRSIZE 39 · SRFNAME 3A · LIBSECUR 3B
单元级BGNSTR 05 · STRNAME 06 · ENDSTR 07 · STRCLASS 34
元素起始BOUNDARY 08 · PATH 09 · SREF 0A · AREF 0B · TEXT 0C · NODE 15 · BOX 2D
元素结束ENDEL 11
元素属性ELFLAGS 26 · PLEX 2F · LAYER 0D · DATATYPE 0E · TEXTTYPE 16 · NODETYPE 2A · BOXTYPE 2E · WIDTH 0F · PATHTYPE 21 · BGNEXTN 30 · ENDEXTN 31 · XY 10 · SNAME 12 · COLROW 13 · STRANS 1A · MAG 1B · ANGLE 1C · PRESENTATION 17 · STRING 19
用户属性PROPATTR 2B · PROPVALUE 2C
废弃/保留TEXTNODE 14 · SPACING 18 · UINTEGER 1D · USTRING 1E · STYPTABLE 24 · STRTYPE 25 · ELKEY 27 · LINKTYPE 28 · LINKKEYS 29 · TAPENUM 32 · TAPECODE 33 · RESERVED 35
厂商扩展Raith 电子束:0x5A(MBMS PATH)、0x62(PXXDATA)

3.5 文件语法


<file>      ::= HEADER BGNLIB [LIBDIRSIZE] [SRFNAME] [LIBSECUR] LIBNAME
                [REFLIBS] [FONTS] [ATTRTABLE] [GENERATIONS] [<format>]
                UNITS { <structure> }* ENDLIB
<structure> ::= BGNSTR STRNAME [STRCLASS] { <element> }* ENDSTR
<element>   ::= { <boundary> | <path> | <sref> | <aref> | <text> | <node> | <box> }
                { PROPATTR PROPVALUE }* ENDEL

<boundary>  ::= BOUNDARY [ELFLAGS] [PLEX] LAYER DATATYPE XY
<path>      ::= PATH [ELFLAGS] [PLEX] LAYER DATATYPE [PATHTYPE] [WIDTH]
                [BGNEXTN] [ENDEXTN] XY
<sref>      ::= SREF [ELFLAGS] [PLEX] SNAME [STRANS [MAG] [ANGLE]] XY
<aref>      ::= AREF [ELFLAGS] [PLEX] SNAME [STRANS [MAG] [ANGLE]] COLROW XY
<text>      ::= TEXT [ELFLAGS] [PLEX] LAYER TEXTTYPE [PRESENTATION] [PATHTYPE]
                [WIDTH] [STRANS [MAG] [ANGLE]] XY STRING
<node>      ::= NODE [ELFLAGS] [PLEX] LAYER NODETYPE XY
<box>       ::= BOX  [ELFLAGS] [PLEX] LAYER BOXTYPE XY

GDSII 是"开始标记 + 平铺属性 + 结束标记"的扁平语法:没有嵌套长度,层次只靠 BGNSTR/ENDSTR、元素头/ENDEL 两级括号表达。

3.6 各元素的字段语义

元素XY 内容关键字段备注
BOUNDARYn+1 个点,末点重复首点(显式闭合)LAYER、DATATYPEn ≥ 3;n+1 ≤ 8191,故顶点 ≤ 8190。超限多边形写出时须切分:gdstk 由 write_gds 的 max_points 参数控制,本项目在 gds_writer.hpp 中取 kGdsMaxPoints = 8190
PATH中心线顶点WIDTH(负值 = 绝对宽度,不随 MAG 缩放);PATHTYPEPATHTYPE:0 平头(Flush)、1 圆头(Round)、2 延伸半线宽方头(HalfWidth)、4 自定义延伸(长度由 BGNEXTN/ENDEXTN 给出)
SREF1 点:实例原点SNAME、STRANS、MAG、ANGLESNAME 可引用文件后面才定义的单元
AREF3 点:P0 原点、P1 列方向终点、P2 行方向终点COLROW(列数、行数,各 int16)第 (i,j) 个实例原点 = P0 + i·(P1−P0)/cols + j·(P2−P0)/rows
TEXT1 点:锚点TEXTTYPE、PRESENTATION、STRING不产生掩模图形;用于 LVS 端口/网络命名
BOX5 点BOXTYPE历史遗留,通常按多边形读取
NODE1~50 点NODETYPE电气节点;现代流程基本不用,OASIS 无对应

3.6.1 位字段

记录掩码含义
STRANS0x8000先关于 X 轴镜像
0x0004绝对放大(不与父级 MAG 复合)
0x0002绝对角度(不与父级 ANGLE 复合)
PRESENTATION0x0030字体号 0~3
0x000C垂直对齐:0 上、1 中、2 下
0x0003水平对齐:0 左、1 中、2 右

STRANS 的两个"绝对"标志实际很少被工具支持,gdstk 读到时会报 UnsupportedRecord。

3.6.2 库级字段

记录载荷说明
HEADERint16 版本号3、4、5、600 等;600 即 Release 6.0
BGNLIB / BGNSTR12 × int16两组"年 月 日 时 分 秒":最后修改、最后访问
UNITS2 × Real8[0] 1 DBU 折合多少用户单位;[1] 1 DBU 折合多少米
REFLIBS / FONTS44 字节定长槽 × 2 / × 4引用库名、字体文件名;可选

3.7 实例:SRAM.gds 逐字节解剖

样例文件 4096 B,含 1 个单元 "Cell"、45 个 BOUNDARY、分布在 10 个层上(1、2、3、5、6、7、8、40、50、51)。有效内容 3150 B(到 ENDLIB 为止),其后补零至 2048 B 的整数倍。

3.7.1 库头 — 逐记录走读

偏移原始字节记录解码
000 06 00 02 00 05HEADERL=6, type=0x00, dtype=2(int16), payload=00 05 → 版本 5
600 1C 01 02 00 74 00 07 00 19 00 03 00 2D 00 35 00 74 00 07 00 19 00 03 00 2D 00 35BGNLIBL=28, type=0x01, dtype=2, 12×int16: 修改时间 2016-07-25 03:45:53, 访问时间同上
3400 0C 02 06 53 52 41 4D 2E 67 64 73LIBNAMEL=12, dtype=6(ASCII): "SRAM.gds"
4600 14 03 05 3E 41 89 37 4B C6 A7 F0 39 44 B8 2F A0 9B 5A 54UNITSL=20, dtype=5(real8): [0]=0.001 µm/DBU, [1]=1×10−9 m/DBU → DBU = 1.0 nm

3.7.2 第一个多边形(8 边形、斜边)— 完整记录序列

偏移字节记录字段值
66–9300 1C 05 02 …BGNSTR单元时间戳
94–10100 08 06 06 43 65 6C 6CSTRNAME"Cell"
102–10500 04 08 00BOUNDARY(无数据记录)
106–11100 06 0D 02 00 01LAYER1
112–11700 06 0E 02 00 00DATATYPE0
118–19300 4C 10 03 FF FF FE F2 …XYL=76, dtype=3(int32), 9 点(8 边形 + 闭合点)
194–19700 04 11 00ENDEL(无数据记录)

3.7.3 XY 坐标展开(第一个多边形,全部 9 点)

#DBU 整数 (x, y)物理坐标 (µm)说明
0−270, 40−0.270, 0.040起点
1−295, −40−0.295, −0.040左下斜边
2−295, −182−0.295, −0.182↓竖直
3−345, −182−0.345, −0.182←水平
4−345, −40−0.345, −0.040↑竖直
5−370, 40−0.370, 0.040左上斜边
6−370, 182−0.370, 0.182↑竖直
7−270, 182−0.270, 0.182→水平(回到 x 起点)
8−270, 40−0.270, 0.040闭合点 = 第 0 点

3.7.4 第二个多边形(矩形)— 完整记录序列

偏移字节记录字段值
198–20100 04 08 00BOUNDARY
202–20700 06 0D 02 00 01LAYER1
208–21300 06 0E 02 00 00DATATYPE0
214–25700 2C 10 03 FF FF FF 38 …XYL=44, 5 点: (−200,−78)→(−50,−78)→(−50,182)→(−200,182)→(−200,−78)
258–26100 04 11 00ENDEL
本元素总计 64 B(BOUNDARY 4 + LAYER 6 + DATATYPE 6 + XY 44 + ENDEL 4)

3.7.5 全文件记录统计

记录类型出现次数
HEADER / BGNLIB / LIBNAME / UNITS / ENDLIB各 1
BGNSTR / STRNAME / ENDSTR各 1
BOUNDARY45
LAYER45
DATATYPE45
XY45
ENDEL45
合计233

3.7.6 全部 45 个多边形的层分布

layerdatatype数量层用途(典型 PDK 含义)
1016有源区 (OD / DIFF)
207N 阱 (NWELL)
302P 型注入 (PIMP)
501多晶硅 (PO / POLY)
605接触孔 (CO / CONT)
705第一层金属 (M1 / Metal1)
801第一层通孔 (V1 / Via1)
4004N 型注入 (NIMP)
5001标记 / 识别层
5103另一标记层

3.8 GDSII 的结构性局限

局限根因工程影响
体积大坐标恒 4 B、绝对坐标、闭合点重复、每元素重写层号先进节点 OPC 后数据量常达数百 GB 级
XY ≤ 8191 点16 位记录长度大多边形须切分,增加顶点与边界
坐标 int32±2.1×109 DBUDBU 越细,可表达范围越小(0.25 nm DBU 时约 ±0.54 m,仍够用)
layer/datatype 原为 0~255早期规范现代工具普遍扩展到 0~32767(int16)甚至无符号 16 位
无索引纯顺序流读一个单元也要扫全文件;无法并行读
只有引用能阵列化AREF 只能阵列化单元引用重复的多边形(如通孔阵列)只能逐个存或另建单元
无完整性校验无校验和字段传输损坏需依赖外部 MD5/SHA

4. OASIS:逐字节拆解

4.1 设计思路:逐项消灭 GDSII 的冗余

GDSII 冗余OASIS 机制典型收益
每条记录 4 B 头1 B 记录号,长度由内容自推导小元素头开销降 75%
整数恒 2/4 B变长整数(7 bit/字节)小数值 1 B
绝对坐标首点 + 增量;XYRELATIVE 模式下位置也相对上一元素增量远小于绝对值
曼哈顿多边形存 x、y 两个分量点列表类型 0/1:每点只存一个分量;末点隐含矩形 2 个数
矩形存 5 点RECTANGLE:宽、高(正方形只存一个)—
每元素重写层号、宽度等模态变量:省略即沿用上一个值同层连续图形省掉层号
只能阵列化引用任意元素可带重复(12 种模式:矩阵、单行、显式偏移、斜格、散点…)通孔阵列 1 条记录
引用名每次写全名称表 + 整数索引长单元名只存一次
剩余统计冗余CBLOCK:DEFLATE 压缩任意记录段再降一截
无索引、无校验END 中的表偏移、单元偏移属性、CRC32/校验和随机访问、并行读、完整性验证

4.2 文件骨架


"%SEMI-OASIS\r\n"                                     magic, 13 bytes
START  version="1.0"  unit  offset-flag  [6 x (flag, offset)]   if offset-flag == 0
{ PAD | PROPERTY | name-record }*
{ CELL_REF_NUM(index) | CELL(name)
      { XYABSOLUTE | XYRELATIVE | PLACEMENT | PLACEMENT_TRANSFORM | TEXT
      | RECTANGLE | POLYGON | PATH | TRAPEZOID* | CTRAPEZOID | CIRCLE
      | PROPERTY | LAST_PROPERTY | XELEMENT | XGEOMETRY | CBLOCK | PAD }*
}*
name-record ::= CELLNAME | TEXTSTRING | PROPNAME | PROPSTRING | LAYERNAME | XNAME
                (may appear before, between or after cells)
END  [6 x (flag, offset)]  padding(b-string)  validation-scheme  [signature 4B]
     -> END record is padded to exactly 256 bytes

4.3 基本编码

4.3.1 整数(Varint)

类型编码例
unsigned-integer小端 7 bit 分组,每字节最高位 = "后面还有"300 = 0b1_0010_1100 → AC 02
signed-integer先把 (|v|<<1) | sign 作为无符号数编码−200 → 401 → 91 03;+150 → 300 → AC 02

4.3.2 实数(首字节为类型标记)

类型含义后续内容
0 / 1正 / 负整数unsigned
2 / 3正 / 负倒数 1/nunsigned n
4 / 5正 / 负分数 n/dunsigned n, unsigned d
6IEEE float324 B 小端
7IEEE float648 B 小端

写出器应优先用精确的整数/倒数/分数形式;只有无法精确表示时才退化为浮点。本文样例就是反例:1e-6 / 1e-9 在 IEEE 754 双精度下算得 999.9999999999999 而非 1000,gdstk 只能写成 9 字节的 float64,而不是 1 字节的整数(见 §4.11.1)。

4.3.3 字符串

类型允许字符用途
a-string可打印 ASCII + 空格(0x20–0x7E)文本标注
b-string任意字节二进制属性值、END 填充
n-string可打印且无空格(0x21–0x7E),长度 > 0单元名、属性名

三者编码相同:unsigned 长度 + 原始字节。

4.3.4 位移量(Delta)

类型方向编码用于
1-delta单轴signed-integer曼哈顿点列表(type 0/1)、梯形腰部偏移
2-delta4 方向 (E/N/W/S)unsigned:低 2 bit 方向,其余为长度曼哈顿任意序点列表(type 2)
3-delta8 方向 (含 NE/NW/SW/SE)unsigned:低 3 bit 方向,其余为长度八角点列表(type 3)
g-delta 形式 18 方向unsigned:bit0=0,bit1–3 方向,>>4 为长度任意角度点列表(type 4/5)、重复向量
g-delta 形式 2任意方向unsigned:bit0=1,bit1 = x 符号,>>2 为 |x|;再跟一个 signed y同上

4.4 记录类型表

ID记录ID记录
0PAD17PLACEMENT(90° 倍数旋转)
1START18PLACEMENT_TRANSFORM(任意角度/放大)
2END19TEXT
3 / 4CELLNAME(隐式 / 显式编号)20RECTANGLE
5 / 6TEXTSTRING21POLYGON
7 / 8PROPNAME22PATH
9 / 10PROPSTRING23–25TRAPEZOID(AB / A / B)
11 / 12LAYERNAME(几何层 / 文本层)26CTRAPEZOID(26 种预定义)
13CELL_REF_NUM(按索引引用单元)27CIRCLE
14CELL_NAME(按名称引用单元)28 / 29PROPERTY / LAST_PROPERTY
15XYABSOLUTE30 / 31XNAME
16XYRELATIVE32 / 33XELEMENT / XGEOMETRY
34CBLOCK(DEFLATE 压缩块)

4.5 info 字节:每条元素记录的"存在位图"

几何、引用、文本记录紧跟一个 info 字节,每一位表示某个字段本条是否显式给出;未给出的字段取对应模态变量的当前值。

记录bit7bit6bit5bit4bit3bit2bit1bit0
RECTANGLES 正方形W 宽H 高XYR 重复D 类型L 层
POLYGON00P 点列表XYRDL
PATHE 端头W 半宽PXYRDL
TRAPEZOIDO 竖向WHXYRDL
CTRAPEZOIDT 形状码WHXYRDL
CIRCLE00r 半径XYRDL
TEXT0C 显式文本N 按索引XYRT 文本类型L 文本层
PLACEMENTC 显式单元N 按索引XYRAA k×90°F 镜像
PLACEMENT_TRANSFORMCNXYRM 放大A 角度F
PROPERTYUUUU 值个数(15=另读)V 沿用C 显式名N 按索引S 标准属性

字段在字节流中的出现顺序与位顺序不一定相同(例如 POLYGON 的字段顺序为 L → D → P → X → Y → R),以规范为准。

4.6 模态变量:OASIS 解析器的核心状态

模态变量被谁读写
xy-mode(绝对/相对)XYABSOLUTE / XYRELATIVE 设置;影响所有位置字段
placement-x / placement-yPLACEMENT*
geometry-x / geometry-y所有几何记录共享
text-x / text-yTEXT
layer / datatype所有几何记录共享
textlayer / texttypeTEXT
geometry-w / geometry-hRECTANGLE、TRAPEZOID、CTRAPEZOID
polygon-point-list / path-point-listPOLYGON / PATH
path-halfwidth / start-extension / end-extensionPATH
ctrapezoid-type / circle-radiusCTRAPEZOID / CIRCLE
repetition所有可重复记录共享(重复类型 0 = 沿用)
placement-cell / text-stringPLACEMENT* / TEXT
last-property-name / last-value-listPROPERTY
时机规则
文件开始、每个 CELL 开始xy-mode = 绝对,三组位置 = (0, 0);其余模态变量变为"未定义"
引用未定义的模态变量文件非法,解析器应报错
相对模式本条位置 = 上一条同类(几何/引用/文本各自独立)元素位置 + 本条增量

推论:OASIS 记录不能脱离上下文单独解析——要知道一个多边形在哪一层,必须从它所在 CELL 的开头顺序执行到它。这也是单元成为最小随机访问单位的原因。

4.7 点列表类型

类型适用每点编码多边形闭合
0曼哈顿,先水平1-delta,水平/垂直交替最后两条边隐含,不写
1曼哈顿,先垂直1-delta,垂直/水平交替同上
2曼哈顿,任意顺序2-delta最后一条边隐含
3八角(0°/45°/90°…)3-delta同上
4任意角度g-delta(相对首点逐点累加)同上
5任意角度g-delta,增量为二阶差分同上

4.8 重复(Repetition)

任何几何、引用、文本都可携带重复,副本数 = 1(原件)+ 偏移数。计数字段统一存"总数 − 2"。

类型形状字段
0沿用上一个无
1正交矩阵cols−2, rows−2, dx, dy
2 / 3单行 / 单列n−2, 间距
4 / 5X 不等距 / 带网格倍数n−2, [grid], 逐个间距
6 / 7Y 不等距 / 带网格倍数同上
8斜格矩阵n−2, m−2, 两个 g-delta 向量
9斜向单行n−2, 一个 g-delta 向量
10 / 11任意散点 / 带网格倍数n−2, [grid], 逐个 g-delta

4.9 名称表与前向引用

名称表(CELLNAME、TEXTSTRING、PROPNAME、PROPSTRING、LAYERNAME)可以出现在文件末尾——gdstk 就把所有表写在单元之后。因此读取器要么先用 END 中的表偏移跳过去读表,要么先存索引、读到 END 后统一回填名字。

4.10 CBLOCK 与 END

CBLOCK 存储 raw DEFLATE(RFC 1951,无 zlib header,对应 inflateInit2(…, -15))压缩的普通记录序列;典型写法是一个单元体一个 CBLOCK。CBLOCK 不可嵌套。

END 记录恰好 256 B,包含 6 张名称表的文件偏移、b-string 填充和校验方案(0=无、1=CRC32、2=checksum32)。读取器可直接 seek 到距文件尾 256 B 处读取。

4.11 实例:SRAM.oas 逐字节解剖

由 SRAM.gds 经 gdstk 转换而来(write_oas,DEFLATE 级别 6,config_flags=0:不检测矩形、不写标准属性、不写校验)。文件 698 B。

4.11.1 顶层记录走读

偏移原始字节记录解码
0–1225 53 45 4D 49 2D 4F 41 53 49 53 0D 0Amagic"%SEMI-OASIS\r\n"
1301START记录 ID 1
14–1703 31 2E 30版本"1.0"(a-string:长度 3 + 3 字节内容)
18–2607 FF FF FF FF FF 3F 8F 40unit类型 7 (float64),值 ≈ 999.9999999999999(为何不是整数 1000 见 §4.3.2)
2701offset-flag1 = 表偏移在 END 里
28–290D 00CELL_REF_NUM单元名索引 0(对应后面的 CELLNAME 表第 0 项)
30–3522 00 FB 04 90 03CBLOCKcomp-type=0(DEFLATE),uncomp=635 B,comp=400 B
36–435(400 B)DEFLATE 数据解压后为 45 个 POLYGON 记录
436–44103 04 43 65 6C 6CCELLNAME_IMPLICIT索引 0 = "Cell"
44202END记录 ID 2
443–44501 B4 03CELLNAME 表flag=1(strict), offset=436 (0x01B4)
446–45501 00×5其余 5 表均 flag=1,offset=0(表不存在)
456–696EF 01+239×00填充 b-string长度 239
69700校验方案0(无校验)
END 合计 256 B ✓(偏移 442–697:记录号 1 + CELLNAME 表 3 + 其余 5 表 10 + 填充 241(长度 2 + 内容 239)+ 校验方案 1)

4.11.2 解压后的前两个多边形 — 逐字节解码

多边形 #0(8 顶点斜边,对应 GDS 层 1):

字节含义详细解码
15记录 ID 21 = POLYGON
3Binfo = 00111011bit0=1(L), bit1=1(D), bit3=1(Y), bit4=1(X), bit5=1(P) → layer, datatype, x, y, point-list 均显式
01layer = 1unsigned-integer, 1 字节
00datatype = 0unsigned-integer, 1 字节
04点列表类型 = 4"arbitrary"(有斜边,用 g-delta)
07点数 = 7unsigned-integer, 1 字节
67g-delta #1形式 2: 0x67=103, bit0=1→形式2, bit1=1→x为负, x=103>>2=25→−25; y=…
7 个 g-delta 完整解码:(−25,−80), (0,−142), (−50,0), (0,142), (−25,80), (0,142), (100,0)
9D 04x 位置signed-integer → −270
50y 位置signed-integer → 40
首点 (x,y)=(−270,40),绝对顶点序列: (−270,40), (−295,−40), (−295,−182), (−345,−182), (−345,−40), (−370,40), (−370,182), (−270,182) → 回到原点
本元素 25 B;同一多边形在 GDSII 中 96 B

多边形 #1(矩形,对应 GDS 层 1):

字节含义详细解码
15 3B 01 00POLYGON, info, layer=1, datatype=0同前
00点列表类型 = 0"manhattan_horizontal"(曼哈顿,先水平,用 1-delta)
02点数 = 2类型 0:Δx, Δy(第 3、4 点隐含,不写)
AC 021-delta Δx = +150signed-integer: 300>>1=150
88 041-delta Δy = +260signed-integer: 520>>1=260
91 03x = −200signed-integer: 401>>1=200, LSB=1→负
9D 01y = −78signed-integer: 157>>1=78, LSB=1→负
矩形:(−200,−78) × 150 × 260。本元素 14 B;同一矩形在 GDSII 中 64 B

4.11.3 全部 45 个 OASIS 多边形解码汇总

以下是通过解析脚本解出的全部 45 个多边形的层、类型、点数和字节大小。完整解码数据见 §9.5。

#layerdatatype点列表类型delta 数字节首点 (DBU)
0104 (arbitrary)725(−270, 40)
1100 (manhattan)214(−200, −78)
2100213(50, 77)
3104725(270, −40)
…(共 45 个;各记录字节数之和 635 B,与 CBLOCK 声明的解压尺寸一致)

各多边形按层统计与 GDS 完全一致:layer 1—16 个、layer 2—7 个、layer 3—2 个、layer 5—1 个、layer 6—5 个、layer 7—5 个、layer 8—1 个、layer 40—4 个、layer 50—1 个、layer 51—3 个。几何无损,层与坐标完全对应。

5. GDSII 与 OASIS 对比

5.1 结构对比

维度GDSIIOASIS
记录头4 B(长度 + 类型 + 数据类型)1 B 记录号
整数定长 int16/int32,大端变长 7 bit 分组
实数Real8(底 16,excess-64)整数/倒数/分数/IEEE float32/64
坐标绝对,int32增量 + 模态位置;格式本身不限位宽(实现多用 int64)
上下文无状态,每条记录可独立理解有状态,依赖模态变量
基本几何BOUNDARY、PATH、BOXRECTANGLE、POLYGON、PATH、TRAPEZOID、CTRAPEZOID、CIRCLE
重复仅 AREF12 种重复,适用于任意元素
名字内联 ASCII名称表 + 索引
压缩无内建 CBLOCK(DEFLATE)
索引/随机访问无END 表偏移 + S_CELL_OFFSET
完整性无CRC32 / checksum32
多边形顶点上限8190无格式上限

5.2 体积实测

对象GDSIIOASIS比例
一个矩形64 B14 B4.6 : 1
一个 8 顶点斜边多边形96 B25 B3.8 : 1
单元体(45 个多边形)3080 B635 B(未压缩)/ 400 B(DEFLATE)4.9 : 1 / 7.7 : 1
整个文件4096 B(含 946 B 填充)698 B(含 256 B END)5.9 : 1

样例极小,固定开销(END 256 B)占比高。真实全芯片数据中,OASIS 文件通常比 GDSII 小一个数量级左右。

5.3 互转的信息损失

转换路径几何丢失/改变
GDSII → OASIS无损BGNLIB/BGNSTR 时间戳、HEADER 版本号无处存放;NODE 丢弃;BOX 变 POLYGON
OASIS → GDSII通常无损CIRCLE 须多边形化(引入近似误差);非引用元素上的重复须展开或另建单元;超 8190 顶点须切分

6. 解析器如何工作

6.1 GDSII 读取:两遍式流解析

对应于 gdstk read_gds (library.cpp)


loop:
    read 4 bytes -> L(u16 BE), rec, dtype ;  require L >= 4
    read L-4 bytes payload ; byte-swap by dtype (2/4/8-byte units)
    switch rec:
      UNITS     : factor = db_in_meters / target_unit
      BGNSTR    : cell = new Cell
      STRNAME   : cell.name = payload ; library.cells += cell
      BOUNDARY/BOX : cur = new Polygon            -> cell.polygons
      PATH      : cur = new Path                  -> cell.paths
      SREF/AREF : cur = new Reference(mag=1)      -> cell.refs
      TEXT      : cur = new Label                 -> cell.labels
      LAYER/DATATYPE/TEXTTYPE/WIDTH/... : set field on cur
      XY        : polygon -> points * factor (drop repeated closing point at ENDEL)
                  ref     -> origin; if AREF: derive column/row vectors from P1, P2
      ENDEL     : cur = null
      SNAME     : cur.target_name = payload       # may be FORWARD reference
      ENDLIB    : build map name -> cell ; resolve every ref.target_name ; done
      other     : warn "unsupported record", skip
要点说明
第二遍只解析引用SNAME 可能指向后面才出现的单元,必须在 ENDLIB 后统一建名字表回填
坐标立即乘 factorDBU 整数 → 物理单位;Fab 内部工具通常保持整数不转浮点
未知记录可跳过长度自描述,跳过不影响后续——这是 GDSII 健壮性的来源
按层过滤可在读时完成ENDEL 时检查 (layer, datatype) 不在白名单即丢弃,大幅省内存

6.2 OASIS 读取:状态机 + 延迟名字解析

对应于 gdstk read_oas (library.cpp)


check 14 bytes == "%SEMI-OASIS\r\n" + START(0x01)
version = string ; require "1.0"
unit    = real   ; factor = 1/unit ; precision = 1e-6 * factor
offset_flag = uint ; if 0: read 12 uints (table offsets in START)
modal = { xy_absolute: true, positions: (0,0), others: undefined }

loop: rec = 1 byte
    CELL_REF_NUM/CELL : new cell ; reset positions & xy-mode
    XYABSOLUTE/XYRELATIVE : set xy-mode
    RECTANGLE/POLYGON/PATH/TRAPEZOID/... :
        info = 1 byte
        for each field in record order:
            if info bit set: read field, update modal variable
            use modal variable value            # error if still undefined
        position = absolute ? value : modal_pos + value
        if R bit: read repetition (type 0 = reuse modal repetition)
    PROPERTY      : attach to the most recent element/cell/name record
    CELLNAME/...  : append to name tables
    CBLOCK        : inflate raw DEFLATE -> switch input to decompressed buffer
    END           : resolve cell/text/property indices via name tables ; resolve refs ; done
要点说明
输入透明切换CBLOCK 解压后从内存缓冲取字节,状态机无感知
属性挂载靠"最近对象"指针PROPERTY 没有显式归属字段,挂在最近完成的元素或名称记录上
大文件并行化先读 END(文件尾 256 B)→ 名称表偏移 → CELLNAME 及其 S_CELL_OFFSET → 各线程从各自偏移解析单元

7. Fab 如何解析版图并完成芯片生产

7.1 全流程总览

图 7-1芯片制造全流程 — 从版图文件到封装成品,12 个阶段跨越 4 个团队

设计公司
[0] 交付 GDS/OAS + TOF
▼
Fab / MDP 团队
1. 接收
完整性校验
→
2. 解析
单位/网格
→
3. 层映射
布尔运算
→
4. 拼版
划片道/标记
→
5. 签核复查
DRC/LVS
→
6. Dummy Fill
密度均衡
→
7. RET/OPC
SRAF/ILT
→
8. OPC 验证
ORC/LFD/MRC
→
9. MDP
碎化/MPC
▼
掩模厂
10. 掩模制造:写入 → 检测 → 修补 → 贴保护膜
▼
晶圆厂
11. 晶圆制造:光刻 / 刻蚀 / 注入 / CMP,逐层循环 N 次
→
12. 测试与封装:WAT / CP / 切割 / 封装 / FT
■ 前处理■ RET/OPC 迭代■ MDP + 掩模■ 最终产出
阶段输入输出数据量变化
[1]–[6]层次化 GDS/OAS 设计层层次化掩模层数据小幅增加(Frame、Fill)
[7]–[8]掩模层目标图形OPC 后掩模图形(通常 OASIS)顶点数显著膨胀,层次部分被破坏
[9]OPC 后多边形写入机专用格式再膨胀;按掩模扁平化
[10]写入机格式物理掩模数据 → 实物
[11]–[12]掩模组晶圆 → 芯片—

7.2 [0] 设计方交付物

交付物内容Fab 用途
版图文件GDSII 或 OASIS,单一顶层单元全部后续处理的输入
Tape-out 表单(TOF)工艺/PDK 版本、顶层单元名、芯片尺寸、掩模层清单、光罩类型、是否 MPW决定做哪些掩模、用哪套规则
层映射/层使用说明用到的 (layer, datatype) 列表核对漏层、多层
签核报告DRC、LVS、天线、密度、ERC 结果及豁免(waiver)Fab 复查的比对基线
校验和MD5 / SHA 值确认传输完整、版本正确

7.3 [1]–[2] 数据接收、解析与归一化

#检查项方法不通过的后果
1文件完整校验和比对;OASIS 另验 CRC32/checksum传输损坏、版本错
2格式合法GDSII:记录长度 ≥ 4、偶数、语法次序;OASIS:magic、版本、模态变量无未定义使用、CBLOCK 可解压、END 恰 256 B解析失败或静默错读
3单位与网格UNITS / START 与 PDK 规定一致;所有顶点落在制造网格上整体缩放错误(最严重);off-grid 吸附产生毛刺
4顶层单元DAG 中无父节点的单元唯一,且与 TOF 中名称一致拿错顶层,做出另一颗芯片
5引用闭合所有引用(SNAME / 单元索引)都能解析;无环缺图形;展开死循环
6单元名冲突与 Fab 要合并的 IP、Frame 单元不重名合并时被静默替换
7变换合法只允许 90° 倍数旋转、镜像、MAG = 1(多数 PDK)off-grid
8多边形合法不自交、非零面积、无重复点、无锐角尖刺布尔运算与 OPC 异常
9层清单实际出现的 (layer, datatype) ⊆ PDK 层表且与 TOF 一致漏做掩模或混入非法层
10尺寸顶层包围盒 = 声明的芯片尺寸且 ≤ 曝光场拼版冲突

7.4 [3] 层映射:设计层 → 掩模层

设计层是"设计意图",掩模层是"每次曝光的图形"。两者由 PDK 规定的布尔运算与尺寸调整生成。

掩模层(示意)生成规则(示意)说明
有源区 ODDIFF.drawing OR DIFF.dummy设计图形与填充合并
N 阱 NWNWELL.drawing直接映射
多晶 POPOLY.drawing OR POLY.dummy先进节点另有 PO cut 掩模
N+ 注入 NPNIMP.drawing, 或由 (OD NOT PIMP) SIZE +x 自动生成注入层常由工艺规则推导
阈值调节注入VT 标记层 AND 栅区域标记层本身不上掩模
M1 颜色 A / BM1 按 datatype(颜色)拆分多重曝光:一层设计 → 多张掩模
钝化开口PAD.drawing SIZE −y尺寸偏置补偿
每张掩模还需确定含义
Tone / 极性图形在掩模上是遮光(暗)还是透光(亮)——Clear/Dark field
镜像掩模朝向(Right/Wrong reading),由光刻机光路决定
倍率DUV/EUV 扫描机为 4× 缩小,掩模图形为晶圆图形的 4 倍
掩模类型二元铬掩模、衰减相移(MoSi)、EUV 反射式掩模等

7.5 [4] Frame:拼版与辅助结构

结构位置作用
密封环(Seal ring)die 四周阻挡切割裂纹与水汽
划片道(Scribe line)die 之间切割通道,承载测试/对准结构
PCM / WAT 测试键划片道测晶体管、电阻、电容等参数
对准标记划片道 / 版面边缘每层曝光时光刻机对准前层
套刻标记(Overlay)划片道测量层间对准误差
CD 量测结构划片道CD-SEM 测线宽
掩模条码、名称、版本版面边缘掩模追溯
拼版参数典型值
晶圆面最大曝光场26 mm × 33 mm(High-NA EUV 为其一半)
掩模面图形区4 倍:104 mm × 132 mm
掩模基板6025:6 英寸 × 6 英寸 × 0.25 英寸石英
MPW(多项目晶圆)多个客户 die 共享一个 Frame,按格位排布

7.6–7.7 [5]–[6] 签核复查与 Dummy Fill

检查输入目的
DRC完整 Frame 后的数据用 Fab 内部最新规则复跑;检查 die 与 Frame 交界
LVS(可选)版图 + 网表确认版图与电路一致;依赖 TEXT 标注确定端口
天线检查金属/栅面积比防止刻蚀充电击穿栅氧
密度检查按窗口统计各层覆盖率为 CMP 与刻蚀均匀性做准备

Dummy Fill:在空白处按规则插入不连电的填充图形,使每个统计窗口密度落在标准区间内,防止 CMP 凹陷或过刻。填充图形通常放在专门的 datatype,便于 LVS 忽略。

7.8 [7] RET / OPC:让图形"印得出来"

技术做法对数据的影响
规则 OPC按查表规则加偏置、线端锤头(hammerhead)、角部衬线(serif)顶点数小幅增加
模型 OPC边切为小段,按光刻/光刻胶/刻蚀模型迭代移动至仿真轮廓与目标一致顶点数大幅增加;同一单元在不同环境下 OPC 结果不同,层次被打散
SRAF(亚分辨率辅助图形)在孤立图形旁加入不成像的细小图形,改善焦深新增大量小多边形
ILT(反演光刻)把掩模当作待优化的像素/曲线场,直接求最优解曲线图形,数据量最大
多重曝光拆分LELE:着色拆成多张掩模;SADP/SAQP:芯轴+切割掩模一层 → 多张掩模

OPC 后的数据几乎都用 OASIS 交付——这是 OASIS 在产业中真正普及的场景。

7.9–7.10 [8]–[9] OPC 后验证与 MDP

步骤内容
ORC / LFD多焦距/剂量条件下仿真,生成 PV-band,找桥连、断线等热点;热点回馈 OPC 重做或退回设计修改
MRC检查掩模本身的最小线宽/间距、碎小台阶(jog)、细条(sliver)
Fracture(碎化)多边形切为 VSB 可曝光的矩形/三角/梯形;控制 shot 数(决定写入时间)、避免过窄 sliver
MPC / PEC掩模工艺修正 + 电子束邻近效应修正 + 雾化/负载效应修正
格式转换输出写入机格式:VSB11/12(NuFlare)、JEOL52(JEOL)、OASIS.MASK(SEMI P44);多束写入机取多边形/曲线并在机内实时栅格化
Job deck描述掩模版面:图形数据位置、阵列方式、条码/标记、tone、镜像、倍率
数据回读验证把写入格式反渲染,与 OPC 后数据做 XOR 比对,确认无丢失

7.11 [10] 掩模制造

工序内容与版图数据的关系
空白基板石英+遮光层(铬/MoSi)+光刻胶;EUV 为 Mo/Si 多层反射膜+吸收层由 TOF 中的掩模类型决定
写入电子束(关键层)或激光(非关键层)按 MDP 数据曝光直接执行碎化后的 shot
显影、刻蚀、去胶把胶图形转到遮光层—
量测CD、套准(registration)、相位/透过率量测点位来自数据中的量测结构
缺陷检测Die-to-Database:MDP 数据渲染为参考图,与掩模实拍图逐点比对版图数据第二次被"执行"
修补电子束诱导沉积/刻蚀、纳米机械修补按缺陷坐标定位
可印性验证AIMS 空间像测量:模拟光刻机成像判断残留缺陷是否会印到晶圆—
贴保护膜Pellicle 距掩模面数毫米的透明薄膜,使落尘处于离焦面—
出货进入掩模库,按层绑定工艺路线掩模 ID ↔ 层 ↔ 版本

7.12 [11] 晶圆制造:单层循环

步骤内容版图数据在此的体现
1. 前处理/涂胶清洗、涂底、旋涂光刻胶、前烘—
2. 对准扫描机定位前层对准标记Frame 中的对准标记
3. 曝光步进扫描,4× 缩小,逐场曝光整片晶圆掩模 = 该层几何
4. 后烘/显影形成光刻胶图形—
5. 量测CD-SEM 测线宽,套刻机台测层间偏差量测结构、套刻标记
6. 图形转移刻蚀(成形)或离子注入(掺杂)胶图形决定作用区域
7. 去胶/清洗——
8. 薄膜/平坦化沉积(CVD/PVD/ALD/电镀)、CMPDummy Fill 保证 CMP 均匀

7.13 掩模层在工艺中的顺序(逻辑工艺示意)

段典型掩模形成的结构
FEOLOD(有源区)→ NW/PW(阱)→ VT 注入 → PO(栅)/PO cut → LDD/Halo → NP/PP(源漏)→ SAB(硅化物阻挡)晶体管
MEOLCO / 局部互连器件到第一层金属
BEOLM1 → V1 → M2 → … → 顶层金属(多重曝光层每层多张掩模)互连
封装PAD 开口、RDL、凸点对外连接

掩模总数:成熟节点数十张,先进逻辑节点可达 60–80 张以上;工艺步骤数以千计,周期数月。

7.14 [12] 测试、切割与封装

工序内容与版图的关系
WAT探针测划片道上的 PCM 测试键测试键来自 Frame
CP(中测)探针卡逐 die 测功能,标记坏片探针位置 = PAD 坐标
减薄、切割沿划片道切开划片道位置来自 Frame
封装贴片、键合/倒装、塑封PAD/凸点布局
FT(终测)封装后全功能、全温度测试—

7.15 版图文件的每个要素去了哪里

文件要素在制造链中的去向
UNITS / START unit[2] 网格校验;所有后续坐标的物理含义
顶层单元[2] 确定芯片;[4] 放入 Frame
单元与引用(SREF/AREF/PLACEMENT/重复)[5]–[7] 层次化 DRC/OPC 加速;[9] 按需展平
(layer, datatype)[3] 经布尔运算映射为掩模层;决定图形落在哪张掩模
多边形 / 路径[7] OPC 目标 → [9] 碎化为 shot → [10] 掩模上的铬/吸收层图形 → [11] 晶圆结构
TEXT 标注[5] LVS 端口与网络命名;一般不上掩模
属性多为工具元数据;Fab 流程通常忽略
时间戳 / 库名仅追溯用

8. 总结

问题答案
版图文件的本质一组按层分类、整数网格上的多边形,加上把它们组织成 DAG 的单元引用
GDSII 的本质"4 字节头 + 定长载荷"的无状态记录流;简单、健壮、冗余大
OASIS 的本质同一数据模型的紧凑编码:变长整数、增量坐标、模态变量、通用重复、名称表、块压缩、尾部索引
选择依据交换与兼容:GDSII;OPC 后、超大规模、需要并行读取:OASIS
Fab 对文件的处理校验 → 解析 → 层映射 → 拼版 → 复查 → 填充 → OPC → 验证 → 碎化 → 写掩模 → 逐层光刻
最致命的错误单位/网格、顶层单元、层映射错误——不会让工具报错,却会让整套掩模报废

9. 附录

9.1 gdstk 核心架构

gdstk(github.com/heitzmann/gdstk)是用 C++ 实现、带 Python 绑定的 GDSII/OASIS 读写库。核心类结构见图 9-1,读写调用链见图 9-2。

图 9-1gdstk 核心类结构 — Library 持有 Cell,Cell 持有多种元素

«根对象» Library
− name : string
− unit : double (m/DBU)
− precision : double
− cells : Array<Cell>
+ read_gds(filename)
+ read_oas(filename)
+ write_gds(filename)
+ write_oas(filename)
+ top_level() : Cell*[]
+ new_cell(name) : Cell*
1 : N
→
Cell
− name : string
− polygons : Array<Polygon>
− paths : Array<FlexPath>
− references : Array<Reference>
− labels : Array<Label>
(DAG:只存向下引用,不存父指针)
→
Polygon
Reference
Label
FlexPath
RobustPath
各元素的关键字段
元素关键字段 / 说明
Polygonlayer, datatype, point_array (Array<Vec2>) + area(), bounding_box(), filter()…
Referencetype(SREF/AREF), cell_name, origin, rotation, mag, x_reflection, columns, rows + get_bounding_box()
Labellayer, texttype, origin, text, rotation, mag, presentation, anchor
FlexPathspine(Array<Vec2>), end_type, end_extensions, bend_type, join_type, elements[] = half_width + offset
RobustPath独立类(不继承 FlexPath):宽度/偏移可为函数,急弯与自交处理更鲁棒;写出时转为多边形或 Path

图 9-2gdstk 读写数据流 — 四种 I/O 路径的函数调用链

写出
write_gds
→
library.cpp
max_points(本项目取 8190)
→
.gds 字节流
write_oas
→
library.cpp(状态机)
oasis.cpp(基础编码)
→
.oas 字节流
config_flags:检测矩形/梯形、写标准属性、CRC32 / checksum32 校验
deflate_level:0–9(默认 6),CBLOCK 压缩级别
读入
.gds 字节流
→
gdsii.cpp(记录读取器)
gdsii_read_record()
→
library.cpp(状态机)
read_gds()
Real8 编解码:
gdsii_real_to_double()
gdsii_real_from_double()
.oas 字节流
→
oasis.cpp(变长解码)
read_unsigned/signed/real…
→
library.cpp(状态机)
read_oas()
解码函数:
oasis_read_unsigned_integer()
oasis_read_signed_integer()
oasis_read_real()
oasis_read_gdelta()
oasis_read_repetition()
inflate(CBLOCK)

9.2 关键源码位置(github.com/heitzmann/gdstk)

主题源文件
GDSII 记录类型枚举、数据类型常量include/gdstk/gdsii.hpp
Real8 编解码(GDSII 实数 ↔ IEEE double)src/gdsii.cpp(gdsii_real_to_double / gdsii_real_from_double)
GDSII 记录读取器(字节 → 记录)src/gdsii.cpp(gdsii_read_record)
GDSII 完整读写状态机src/library.cpp(read_gds / write_gds)
OASIS 记录/点列表/重复类型常量include/gdstk/oasis.hpp
OASIS 基础编码(varint、实数、delta、点列表、重复)src/oasis.cpp
OASIS 完整读写状态机 + CBLOCK inflatesrc/library.cpp(read_oas / write_oas)
写出参数(多边形点数上限、DEFLATE 级别、config_flags)include/gdstk/library.hpp(write_gds 的 max_points;write_oas 的 deflate_level / config_flags)
格式说明文档doc/manual/ch05_formats.rst、ch09_internals.rst

9.3 GDSII 记录级解码脚本

以下 Python 脚本将任意 .gds 文件解析为记录级 JSON。它对每条记录输出偏移、类型名、数据类型和解码后字段,可直接验证本文 §3.7 中所有字节声明。

gds_records.py — GDSII 记录级解码器


"""GDSII 原始记录级解析器 — 逐条记录解码为 JSON。"""
import json, struct

RECORD_NAMES = {
    0x00:"HEADER",0x01:"BGNLIB",0x02:"LIBNAME",0x03:"UNITS",0x04:"ENDLIB",
    0x05:"BGNSTR",0x06:"STRNAME",0x07:"ENDSTR",0x08:"BOUNDARY",0x09:"PATH",
    0x0A:"SREF",0x0B:"AREF",0x0C:"TEXT",0x0D:"LAYER",0x0E:"DATATYPE",
    0x0F:"WIDTH",0x10:"XY",0x11:"ENDEL",0x12:"SNAME",0x13:"COLROW",
    0x15:"NODE",0x16:"TEXTTYPE",0x17:"PRESENTATION",0x19:"STRING",
    0x1A:"STRANS",0x1B:"MAG",0x1C:"ANGLE",0x1F:"REFLIBS",0x20:"FONTS",
    0x21:"PATHTYPE",0x22:"GENERATIONS",0x26:"ELFLAGS",0x2B:"PROPATTR",
    0x2C:"PROPVALUE",0x2D:"BOX",0x2F:"PLEX",0x30:"BGNEXTN",0x31:"ENDEXTN",
    0x36:"FORMAT",
}

def decode_real8(raw):
    v=struct.unpack(">Q",raw)[0]
    s=-1.0 if v&0x8000000000000000 else 1.0
    e=((v>>56)&0x7F)-64
    m=v&0x00FFFFFFFFFFFFFF
    return 0.0 if m==0 else s*(m/float(1<<56))*(16.0**e)

def parse(path):
    blob=open(path,"rb").read()
    doc={"header":None,"libname":None,"units":None,"structures":[]}
    cur_struct,cur_elem=None,None
    pos=0
    while pos<len(blob):
        if pos+4>len(blob): break
        L,rtype,dtype=struct.unpack(">HBB",blob[pos:pos+4])
        if L<4: break
        payload=blob[pos+4:pos+L]
        name=RECORD_NAMES.get(rtype,f"UNKNOWN_0x{rtype:02X}")
        data=None
        if dtype==0x02:   data=[struct.unpack(">h",payload[i:i+2])[0] for i in range(0,len(payload),2)]
        elif dtype==0x03: data=[struct.unpack(">i",payload[i:i+4])[0] for i in range(0,len(payload),4)]
        elif dtype==0x05: data=[decode_real8(payload[i:i+8]) for i in range(0,len(payload),8)]
        elif dtype==0x06: data=payload.decode("ascii",errors="replace").rstrip("\x00")
        elif dtype==0x01: data=[struct.unpack(">H",payload[i:i+2])[0] for i in range(0,len(payload),2)]
        # dispatch to library/cell/element fields (简化版,完整版见 gdstk)
        if name=="HEADER": doc["header"]={"version":data[0]}
        elif name=="LIBNAME": doc["libname"]=data
        elif name=="UNITS": doc["units"]={"db_in_user":data[0],"db_in_meter":data[1]}
        elif name=="BGNSTR": cur_struct={"name":None,"elements":[]}
        elif name=="STRNAME": cur_struct["name"]=data
        elif name=="ENDSTR": doc["structures"].append(cur_struct); cur_struct=None
        elif name in ("BOUNDARY","PATH","SREF","AREF","TEXT","NODE","BOX"):
            cur_elem={"type":name}
        elif name=="ENDEL": cur_struct["elements"].append(cur_elem); cur_elem=None
        elif cur_elem:
            if name=="LAYER": cur_elem["layer"]=data[0]
            elif name=="DATATYPE": cur_elem["datatype"]=data[0]
            elif name=="XY": cur_elem["points"]=[[data[i],data[i+1]] for i in range(0,len(data),2)]
            elif name=="SNAME": cur_elem["sname"]=data
            elif name=="WIDTH": cur_elem["width"]=data[0]
            elif name=="MAG": cur_elem["mag"]=data[0]
        pos+=L
    return doc

if __name__=="__main__":
    import sys
    doc=parse(sys.argv[1])
    print(json.dumps(doc,ensure_ascii=False,indent=2))

9.4 OASIS CBLOCK 多边形解码脚本

以下 Python 脚本解压 SRAM.oas 的 CBLOCK 并逐条解码全部 POLYGON 记录(含 g-delta 运算)。输出的层、坐标、delta 与每条记录字节数可逐项核对 §4.11。

oas_polygons.py — OASIS CBLOCK 多边形解码器


"""Decode all POLYGON records from an OASIS CBLOCK body."""
import json, zlib, sys

data = open(sys.argv[1], "rb").read()

def uint(buf, p):
    v, shift = 0, 0
    while True:
        b = buf[p]; p += 1; v |= (b & 0x7F) << shift; shift += 7
        if not b & 0x80: return v, p

def sint(buf, p):
    v, p = uint(buf, p)
    return (-(v >> 1) if v & 1 else v >> 1), p

DIRS = {0:(1,0),1:(0,1),2:(-1,0),3:(0,-1),4:(1,1),5:(-1,1),6:(-1,-1),7:(1,-1)}

pos = data.index(b"\x22\x00", 30) + 2  # find CBLOCK
usize, pos = uint(data, pos)
csize, pos = uint(data, pos)
body = zlib.decompress(data[pos:pos+csize], -15)

polygons = []
q = 0
while q < len(body):
    start = q
    rec = body[q]; q += 1
    if rec != 21: break  # only POLYGON in this file
    info = body[q]; q += 1
    entry = {"info": f"{info:08b}"}
    if info & 0x01: entry["layer"], q = uint(body, q)
    if info & 0x02: entry["datatype"], q = uint(body, q)
    if info & 0x20:
        ptype = body[q]; q += 1
        cnt, q = uint(body, q)
        deltas = []
        for _ in range(cnt):
            if ptype in (0, 1):
                d, q = sint(body, q); deltas.append({"val": d})
            elif ptype == 4:
                v, q0 = uint(body, q); q = q0
                if v & 1 == 0:
                    dx, dy = DIRS[(v>>1)&7]; m = v>>4
                    deltas.append({"dx":dx*m,"dy":dy*m,"form":1})
                else:
                    xs = -(v>>2) if v&2 else v>>2
                    y, q = sint(body, q0)
                    deltas.append({"dx":xs,"dy":y,"form":2})
        entry["point_list"] = {"type": ptype, "count": cnt, "deltas": deltas}
    if info & 0x10: entry["x"], q = sint(body, q)
    if info & 0x08: entry["y"], q = sint(body, q)
    entry["bytes"] = q - start
    polygons.append(entry)

print(json.dumps({"polygon_count": len(polygons), "polygons": polygons},
      ensure_ascii=False, indent=2))

9.5 样例文件解码数据(完整 45 个多边形)

以下是通过 §9.4 脚本从 SRAM.oas 解出的全部 45 个多边形的层、类型、点数和首点坐标。与 GDS 源文件中 45 个 BOUNDARY 的层/坐标完全一致,确认几何无损。

#layerdatatype点列表类型delta 数首点 (x, y)
0104 (g-delta)7(−270, 40)
1100 (1-delta)2(−200, −78)
21002(50, 77)
31047(270, −40)
41002(−460, −10)
51002(−460, −270)
61002(390, −10)
71002(−460, 160)
81002(−560, −10)
91002(−460, −190)
101002(440, −110)
111002(480, −110)
121002(400, −270)
131002(450, −270)
141002(250, 90)
151002(200, −90)
162002(−600, 340)
172002(−600, 340)
182002(−400, 120)
192002(−400, 120)
202002(−400, 120)
212002(−380, 20)
222002(−380, 20)
233002(−530, 260)
243002(−530, 260)
255002(−220, −160)
266002(−210, −220)
276002(−210, 160)
286002(−210, 160)
296002(−210, 160)
306002(−210, 60)
317002(−200, −80)
327002(50, 80)
337002(50, −250)
347002(−200, 180)
357002(50, 180)
368002(−320, 200)
3740002(−520, 240)
3840002(−520, 240)
3940002(−320, 120)
4040002(−320, 120)
4150002(−400, −300)
4251047(200, −470)
4351002(−200, −470)
4451002(−300, −260)

层统计与 GDS 源完全一致(layer 1—16 个、2—7 个、3—2 个、5—1 个、6—5 个、7—5 个、8—1 个、40—4 个、50—1 个、51—3 个),总计 45 个;各记录字节数之和 635 B,与解压尺寸一致。

posted @ 2026-10-19 17:02  起飞的乌龟  阅读(46)  评论(0)    收藏  举报