资讯动态

Python脚本生成GDSII光刻掩模:从参数化设计到流片前自检

发布时间:2026/10/15 14:42:48 来源:尧图企业网站定制
简介面向光刻掩模与芯片版图设计场景gdsCAD 是基于 Python 的 GDSII 布局文件创建与操作工具包适合科研人员、芯片设计工程师及希望自动化版图流程的开发者。它既能编写脚本批量生成布局也支持交互式调整尤其在包含多个增量微调对象的复杂设计中表现出色并借助 matplotlib 将几何图元到完整布局全部可视化。压缩包共 60 个文件体积约 452KB其中 9 个 Python 模块core、shapes、templates 等构成核心功能10 个 RST 文档说明 API 与使用方式8 张 PNG 图片展示示例效果另含配置、字体、许可证等辅助文件。目前已有 1933 人学习下载对入门光刻掩模脚本设计的读者具有较高参考价值。资源内附可运行的对齐标记示例、字形数据与完整目录结构便于读者观察图形对象、Cell 和 Layout 的分层关系从而快速上手并扩展出自定义的掩模生成脚本。1. 写在流片前夜为什么用Python脚本生成GDSII光刻掩模某次流片前夜我把一套MEMS对准标记的间距从5µm改成4.5µm图形化版图工具里改到第三个对齐线就全乱了。换成gdsCAD——一个简单但功能强大的Python软件包专门创建GDSII格式光刻掩模——操作变成改一行变量重跑脚本。它只有Cell、图形、布尔运算、导出几层接口却能覆盖从单层标记到多层传感器整张掩模。适合不想被大型EDA拖着走的工艺开发者也适合需要参数化、批量生成版图的人。下面从GDSII原理讲起按最小脚本、阵列、批量生成到交付前自检把能直接复现的代码和坑位一次给齐。2. GDSII格式不是黑匣子层级结构、记录流与单位模型很多人第一次用gdsCAD时把它当成“画图工具”来学总在找按钮。实际上gdsCAD做的事情是把Python对象翻译成GDSII文件而GDSII本质上是一串紧凑的二进制记录。不理解这层对应关系遇到“文件生成了但查看器里是空的”“层号对了但图形跑偏”这类问题时会非常被动。2.1 GDSII的层级逻辑LIBRARY、CELL、BOUNDARY与SREFGDSII文件按记录流组织常见记录从HEADER开始中间是BGNLIB、LIBDIR真正干活的是CELL、BOUNDARY、SREF、AREF、TEXT这些记录。一个CELL可以被多个SREF引用SREF本身不复制几何数据只记录引用了哪个Cell、放在哪个坐标、转了多少度。这就是为什么同一单元放一万个位置文件体积也不会线性膨胀。gdsCAD的类基本和这些记录一一对应gc.Cell对应CELLgc.Rectangle、gc.Polygon、gc.Circle导出为BOUNDARYgc.CellReference导出为SREF。调试的时候我会在导出后拿解析工具直接看记录类型缺哪一层就查是哪一环没生成。比如发现BOUNDARY记录个数为0那问题一定出在“图形对象没有add进Cell”这一步。有个容易忽略的点GDSII里不存在“布尔运算后的复杂对象”所有几何在文件里都是闭合多边形的顶点坐标列表。你在gdsCAD里做差集、并集最终导出的仍然是拆解后的边界。所以“多少个矩形”不等于“多少条BOUNDARY”带孔的图形会被拆成多条边界记录。2.2 为什么用Python而不是图形界面画掩模图形化版图工具当然能画但有两个场景会让人非常难受一是尺寸批量修改几十个图形按公式整体缩放手动改容易漏二是生成规则阵列或曲线轮廓比如环形光栅、螺旋电感、渐变宽度导线手画几乎不可能脚本几百毫秒就出结果。脚本化还有一个隐藏收益——可追溯。掩模版本迭代时只要保存了生成脚本就能知道上一版到底改了哪个参数。图形化工具里改过的文件两个版本之间逐个比对几何属性那份痛苦经历过的都懂。我一般会把每个掩模版本对应的脚本存进同一个目录脚本用日期命名后续不管是自己回溯还是交给同事复核都比翻文件时间戳靠谱。gdsCAD在这类需求里算轻量且够用的选择。大型EDA的设计套件不是不能用但启动慢、授权重只是生成几层掩模的话性价比很低纯手写GDS二进制又不现实中间需要一层封装。gdsCAD正好卡在这个位置接口少而稳定能出标准GDSII也能配合shapely这类几何库做复杂计算。2.3 gdsCAD的单位模型你其实在跟整数打交道GDSII规范里所有坐标都是整数呈现在文件里的是“数据库单位DBU”的倍数。导出时有两个参数控制换算unit是用户单位对应的米数precision是文件里最小坐标单位对应的米数。gdsCAD的默认组合是unit1e-6、precision1e-9也就是用户单位1µm、DBU 1nm。实际使用中DBU决定了你版图能表达的最小尺寸。下面这个表是常见组合DBU最小可表达尺寸典型适用场景1e-91nm大部分成熟工艺坐标够精细5e-95nm某些掩模厂的最小网格要求1e-810nm粗网格设计文件体积更小选DBU时不能只看精度还要看掩模厂的设计规则。有的厂要求坐标是5nm或10nm的整数倍你用1nm网格画完导出时数据没问题但进厂前要被对方“修整”修整过程可能引入与预期不符的图形改动。建议画图之前先查清楚接收方的最小网格再决定gdsCAD导出参数。3. 第一份掩模动手跑Cell、矩形、布尔运算与导出参数原理理清之后直接上手。这一章从最简脚本开始逐步加入多边形、布尔运算和导出配置。所有代码都可以在Jupyter Notebook或普通Python脚本里直接跑依赖只有gdsCAD和shapely。3.1 最小可运行脚本一个50×20µm的对准标记import gdsCAD.core as gc # 创建主单元名字会写进GDSII文件 cell gc.Cell(ALIGN_TL) # 画一个矩形两个角坐标单位为微米 bar gc.Rectangle((-25, -10), (25, 10), layer1) cell.add(bar) # 创建库并将单元加入 lib gc.GdsLibrary() lib.add(cell) # 导出GDSII文件 lib.save_gds(align_marks.gds, unit1e-6, precision1e-9)这段代码的逻辑是三层先有Cell再把几何对象add进Cell最后把Cell挂到Library并保存。gc.Rectangle接收左下角和右上角两个坐标元组坐标系原点在版图中心还是角落取决于你自己的设计习惯gdsCAD不会自动调整。layer1表示该矩形属于1号层层号是数字具体对应哪层工艺由掩模厂的映射表决定。save_gds的两个参数前面说过unit1e-6表示用户坐标1单位等于实际1µmprecision1e-9表示DBU是1nm。如果你希望文件更小可以把precision放大到1e-8但前提是工艺网格允许。3.2 多边形与圆两条最常用的路径import gdsCAD.core as gc cell gc.Cell(SHAPES) # 多边形顶点列表按顺序排列 pts [(0, 0), (20, 0), (20, 15), (10, 25), (0, 15)] poly gc.Polygon(pts, layer2) cell.add(poly) # 圆gdsCAD用圆弧段逼近 circle gc.Circle((0, 0), radius10, layer3) cell.add(circle)gc.Polygon的顶点列表就是GDSII里BOUNDARY的XY记录顺序要保持一致否则可能出现自相交的非法多边形。这里有个细节GDSII不支持真圆所有圆都是多边形逼近。gc.Circle默认按一段角度生成圆弧半径越大、逼近段数越多导出的坐标点就越多。图形化工具里看不出差别但文件大小和后续仿真加载时间会受影响。从计算精度角度我习惯先把顶点算好再传给gc.Polygon而不是依赖gc.Circle的自动参数。比如要生成一个带120个点的圆环先用三角函数算出坐标列表再包成Polygon这样能精确控制点数也方便后续做顶点级操作。3.3 布尔运算AND、OR、NOT把图形拼出节奏gdsCAD本身没有把二维布尔运算做成核心功能这点官方文档也不强调。实际做差集、交集时我的常见做法是把多边形先交给shapely处理再把结果转换回gdsCAD对象。这个方法比在版图工具里一点点挖空要稳得多。import gdsCAD.core as gc from shapely.geometry import Polygon as ShapelyPolygon # 定义两个矩形的shapely几何 a ShapelyPolygon([(0, 0), (100, 0), (100, 50), (0, 50)]) b ShapelyPolygon([(40, -10), (140, -10), (140, 60), (40, 60)]) # 差集用a挖掉b res a.difference(b) # 把shapely结果转回gdsCAD def add_shapely_polygon(cell, geom, layer): # exterior是闭合环最后一点和第一点重复要去掉 pts list(geom.exterior.coords)[:-1] cell.add(gc.Polygon(pts, layerlayer)) cell gc.Cell(DIFF_DEMO) add_shapely_polygon(cell, res, layer5)shapely返回的多边形坐标顺序是规范的转回gdsCAD时唯一要处理的是把闭合环的重复点去掉。需要注意如果布尔运算结果是多个多边形比如a被b切成两半res会变成MultiPolygon这时候直接取exterior会报错。处理办法是遍历res.geoms逐个转换。这个坑后面避坑章节还会展开。做完布尔运算后最好用shapely的buffer(0)处理一次它能修复部分自相交、退化边问题。这一步在图形化工具里是“自动修复”按钮在脚本里就要显式调用。3.4 导出GDSunit与precision参数不要瞎填lib gc.GdsLibrary() lib.add(cell) lib.save_gds(design_out.gds, unit1e-6, precision1e-9)导出参数的选择直接影响掩模厂解析结果。下面是参数组合的含义表参数默认值含义建议unit1e-6用户坐标1单位对应的米数保持1e-6µmprecision1e-9DBU对应的米数查清工艺网格后定layer无图层号按工艺映射文件填提示导出前先去掩模厂拿设计规则文件DRM确认最小网格和层号映射再定参数。参数错了轻则坐标放大1000倍重则整版报废。4. 参数化生成掩模用函数、SREF阵列与CSV批量铺满版图单层图形跑通之后真正的效率提升来自参数化和批量生成。这一章解决“改一个数整张掩模跟着变”的需求也是脚本化版图相比图形工具最明显的优势。4.1 用函数造PCell改一个参数重出整个掩模import gdsCAD.core as gc def alignment_mark(cx, cy, width50, gap10, layer1): 生成一个十字对准标记返回Cell half width / 2 cell gc.Cell(fALIGN_{cx}_{cy}) # 水平条和垂直条拼出十字 h_bar gc.Rectangle((cx - half, cy - gap), (cx half, cy gap), layerlayer) v_bar gc.Rectangle((cx - gap, cy - half), (cx gap, cy half), layerlayer) cell.add(h_bar) cell.add(v_bar) return cell # 使用示例在坐标(100, 200)处生成一个默认尺寸的对准标记 cell alignment_mark(100, 200)这就是“参数化单元PCell”的雏形。函数接收坐标、宽度、间隙、层号返回一个完整Cell。需要调整标记尺寸时只改width或gap重跑脚本整张掩模同步更新不用再手动逐个拖拽。参数命名上我建议直接对应物理尺寸比如width是十字总宽gap是竖条宽度的一半这样半年后回来看脚本还能看懂。函数内部用half width / 2做换算所有子图形都基于中心点计算比硬编码绝对坐标更抗改。4.2 用SREF做阵列坐标、旋转与镜像import gdsCAD.core as gc # 先做一个基础单元 unit_cell gc.Cell(UNIT) unit_cell.add(gc.Rectangle((0, 0), (10, 10), layer1)) # 顶层单元引用上面那个基础单元 top gc.Cell(TOP_2X2) # 平移引用 top.add(gc.CellReference(unit_cell, origin(20, 0))) # 旋转90度后的引用 top.add(gc.CellReference(unit_cell, origin(40, 0), rotation90)) # 带镜像的引用 top.add(gc.CellReference(unit_cell, origin(60, 0), mirrorTrue))gc.CellReference生成的就是GDSII里的SREF记录。好处是重复单元只存一份几何定义引用本身只占几条记录文件体积小修改基础单元时所有引用自动更新。rotation单位是度逆时针为正mirrorTrue会做镜像翻转。这里提醒一句SREF的旋转和镜像作用于整个Cell包括里面包含的文字。具体坑在第5章展开。4.3 从CSV读坐标批量生成十秒生成几百个图形实际项目里图形位置往往来自仿真结果或外部计算比如传感器阵列、测试图形布局。这时不再手写坐标而是从CSV批量读取。import csv import gdsCAD.core as gc cell gc.Cell(SENSOR_MATRIX) with open(positions.csv) as f: reader csv.DictReader(f) for row in reader: cx float(row[x]) cy float(row[y]) size float(row.get(size, 10)) # 以(cx, cy)为中心生成size×size的矩形 r gc.Rectangle((cx - size/2, cy - size/2), (cx size/2, cy size/2), layer3) cell.add(r)CSV的列名和脚本里的字段要对齐常见错误是DictReader里字段名不匹配循环跑完却一个图形都没生成。我一般会在循环里加一个计数器跑完打印“共生成N个图形”有效避免这类静默错误。批量生成的另一个注意点是坐标范围。几万个点分布在几毫米见方的区域时还好如果坐标来自经纬度或者没做归一化数值可能超出工艺范围几百倍。建议在循环里加最小最大值检查超范围就直接抛异常不要带病往下跑。5. 避坑清单gdsCAD实战中五个反复翻车的细节正常流程走通后这一章写实战里最常翻车的五个问题。每一个都是同事或自己在交付前踩过的按“现象—原因—解决”的方式记录方便对着排查。5.1 现象图形尺寸在查看器里出现微小跳动矩形宽度在工具里显示为49.999或50.001不是预期的整数50。原因是GDSII坐标是整数记录浮点运算的二进制误差经过导出取整后产生了1nm或几nm的偏差连续排列的图形之间就会出现不均匀的间距。解决方法是构建几何时统一按DBU整数运算。比如DBU是1nm所有坐标先乘以1000再取整计算完再除以1000传入gdsCAD。更稳妥的做法是脚本开头定义一个GRID 0.001单位µm所有尺寸都用round(value / GRID) * GRID规整。从那以后我画阵列前都会强制跑一遍网格规整。5.2 现象布尔运算后的Cell里一个图形都没有shapely的difference执行后不报错转到gdsCAD再导出查看器打开一片空白。原因是差集结果可能是MultiPolygon、带内部孔的多边形或者退化成一条线。取exterior时拿到的是空对象转换逻辑没处理这些情况。解决方法是转换前先判断几何类型geom.geom_type是Polygon就直接转换是MultiPolygon就遍历geom.geoms是LineString说明退化了应该放弃这条边。另外做布尔运算前对所有多边形执行buffer(0)修复自相交能大幅减少这类问题。5.3 现象流片回来某层整体不对金属层莫名镂空版图里金属连线出现奇怪的缺口或者某层完全消失。原因是层号映射错误。GDSII的layer只是一个数字掩模厂按照工艺映射表把它解释成具体的光刻层。你写layer2对方可能映射为氧化层刻蚀而不是你以为的金属层。解决方法是交付前把图层映射规则写成一张表放进脚本目录。我用一个固定模板LAYER_1_META 1, DATATYPE_MAIN 0在脚本里全部用命名常量不用裸数字。这样映射关系一目了然也不会不小心改到层号。5.4 现象SREF镜像后文字反了一个包含文字标识的Cell被mirrorTrue引用后文字在曝光时变成镜像流片回来字符是反的。原因是SREF的镜像作用于整个Cell内容文字也不例外。GDSII本身不会自动对你的文字做“正向补偿”。解决方法有两种一是文字单独放一个Cell不参与镜像二是必须镜像时给被引用的Cell整体旋转180度做补偿。检查时在版图查看器里把镜像后的Cell放大看清楚文字方向不要只看图形轮廓。5.5 现象整版图形尺寸差1000倍导出的GDS在查看器里要么大得离谱要么完全看不见。原因是unit和precision的换算关系搞混了。unit是用户单位定义precision是DBU大小两者配合决定坐标值含义。把precision填错几位整个版图就按错误比例缩放。解决方法是固定参数组合。如果工艺要求不高就统一用unit1e-6, precision1e-9必要时把坐标全部换算成目标DBU再导出。交付前再做一个“已知尺寸”的参照图形比如一个标准的10µm×10µm正方形在查看器里量一下数字对不上就说明参数有问题。6. 交付前自检读回GDS做几何审计把问题留在导出前掩模交付前最重要的一件事是把导出的GDS读回来看一遍而不是只看脚本调用结果。我这里用gdstk做读取检查它和gdsCAD一个管写一个管读配合起来很稳定。import gdstk lib gdstk.read_gds(design_final.gds) for cell in lib.cells: # 跳过名字以$开头的临时单元 if not cell.name.startswith($): bbox cell.bounding_box() area cell.area() print(cell.name, bbox:, bbox, 面积:, area)这段代码会遍历所有Cell打印包围盒和面积。拿它跟设计预期比对能快速发现“坐标偏移”“图形缺失”“单元尺寸不对”的问题。如果某个Cell的包围盒范围超出了预定区域说明有图形被错误放到了版图角落。还有一道关键检查是网格精度审计。掩模厂要求坐标落在工艺网格上否则要额外修整修整可能改变设计尺寸。下面的脚本检查所有多边形顶点是否落在目标网格上import gdstk GRID 0.005 # 工艺网格5nm lib gdstk.read_gds(mask.gds) for cell in lib.cells: for poly in cell.polygons: for point in poly.points: x float(point[0]) y float(point[1]) if abs(x / GRID - round(x / GRID)) 1e-9: print(off-grid:, cell.name, x, y)gdstk读取后返回的坐标是µm所以GRID也按µm填。检查到异常后回到gdsCAD生成端重新规整再导出而不是在结果文件里硬修。我现在的交付习惯是固定三条生成脚本存版本目录、导出后跑网格审计、最后用查看器人工过一遍关键单元。从某次交付因为一个小矩形坐标写错导致整版重做之后我每次导完GDS都强制走一遍这个流程不再直接发文件。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑