有些人可能觉得这类话题太“后端”,不如版图、仿真那么直观,但真正跑过 tapeout 的人都知道,PDK 里一份烂工艺文件能把人折腾到怀疑人生。尤其是 EMX 这种以电磁仿真见长的工具,喂给它的工艺文件如果层级、厚度、方块电阻这些基础参数有偏差,后仿结果跟你实际流片回来的测试数据能差出一大截。而这个文件恰恰又经常是从代工厂给的 ITF 或 IRCX 里转过来的,手动转?你真的会用记事本打开 ITF,然后一行一行对着映射表去写 EMX 的 tech file 吗?我试过,几百层金属和过孔堆叠在一起,光是理清层间关系就能耗掉一个下午,更别提里面那些“同一个 layer 在两个文件里命名完全不一样”的坑。
这篇文章我就用自己的实际经历,说一下怎么从 ITF/IRCX 自动生成 EMX 工艺文件。不是讲现成工具下载,因为我都是自己写脚本解决的,Candence 系的东西写个 Skill 或者 Perl 脚本都很常见,我这里主要用脚本思路来拆解。适合正在为 PDK 迁移、工艺库适配、后仿精度问题头疼的模拟/射频工程师,也适合刚接触 EMX 但不想在这个环节浪费时间的新人。
1. 内容整体设计与思路拆解
1.1 ITF/IRCX 和 EMX 工艺文件到底是啥关系
先说基础,不然后面说原理你会懵。
ITF(Interconnect Technology File)是代工厂提供的一种文本格式,描述金属层、过孔层、介质层的物理参数,比如层名、厚度、方块电阻、介电常数、最小间距等。IRCX 是另一种类似的东西,常见于某些 PDK 里,本质上是同一类信息的不同组织方式,字段风格和 ITF 略有差异,但核心信息一致,像是给互连工艺做“体检报告”。
EMX 是 Integrand 家的电磁仿真器,主要做无源器件建模,比如电感、变压器、传输线。它仿真时读入的工艺文件结构上非常有序:一层层的导体、介质、过孔按堆叠顺序排列好,每层有自己的厚度、电导率、介电常数、损耗角正切等。EMX 不吃 ITF 原始格式,它有自己的 tech file 格式(通常是 .emx 后缀,或者通过 EMX 提供的工具转换)。
问题就在这:代工厂给的是 ITF/IRCX,EMX 要的是自有的 tech file。这两个东西不是简单改个后缀就能互相认的,中间隔着一层“数据转述”的过程。
1.2 为什么选择脚本而不是手动转换
我知道有人会说:“就几层金属,手动填一下 EMX 的界面不行吗?”
顶层金属六层、厚铝两层、过孔十二种、钝化层若干……一旦工艺节点到了 28nm 以下,互连层数轻松奔着 10+ 去,每个金属层还分厚度、方块电阻、密度规则。你手动在 GUI 里一层一层敲,敲完第一遍心里就没底:第 7 层金属到底有没有敲错厚度?过孔 8 到底连的是金属 7 和金属 8 还是金属 6 和金属 7?
更关键的是,EMX 里有一个概念叫“return path”,也就是回流路径。这个回流路径跟顶层金属的厚度、电导率直接相关,如果你手动录入时把顶层金属填错一点,电感仿真出来的 Q 值能偏 5% 以上。这种影响很隐蔽,不是一眼能看出来的。
所以,自动化就三个好处:速度快、可重复、可审计。速度快,指的是几百层内容秒级生成;可重复,指的是你换了工艺库、改了某个参数,脚本重跑一遍就完事;可审计,指的是 ITF 里的原始参数和 EMX 里的最终值能逐条对上,评审、答疑、回归测试都有据可查。
1.3 整体方案的思路框架
我最终落地的方案是这样的:
- 输入:代工厂提供的 ITF 或 IRCX 文件。
- 解析:用脚本提取分层信息、物理参数、导电属性、过孔连接关系。
- 映射:把代工厂层名(比如 M1、M2、V1、V2……)映射成 EMX tech file 需要的层编号和类型。
- 输出:直接生成 .emx 格式的工艺文件。
- 校验:再写一段反向解析代码,把生成的 .emx 读回来,跟原始 ITF 的关键参数做一致性对比。
这套流程里,最难的不是“提取”也不是“输出”,而是“映射规则”。因为 ITF 里的层描述跟 EMX 的层描述不是一一对应的,中间存在一个“物理含义到仿真语义”的转换。比如 ITF 里 M6 的 thickness 可能写的是物理厚度,但 EMX 里某些层需要写的是“集肤深度等效厚度”,这是要根据电导率和频率自己换算的。
这个思路定型以后,后面的事情就顺畅多了。
2. 核心细节解析与实操要点
2.1 逐段拆解:ITF 文件的结构与解析要点
你要写脚本解析 ITF,第一件事不是写代码,而是打开 ITF 文件,把它的结构摸清楚。不同代工厂的 ITF 尽管大体相似,但细节字段差异很大。通常你能看到这样的段落:
metal { name = M1 thickness = 0.18u resistivity = 0.12 ohm/sq min_width = 0.1u min_space = 0.1u } via { name = V1 thickness = 0.4u resistance = 5 ohm array_x = 2 array_y = 2 } dielectric { name = D1 thickness = 0.4u epsilon = 4.1 tan_delta = 0.02 }这只是个高度简化的示例,真实 ITF 字段要更复杂,命名也是五花八门:有的用thick,有的用thickness,有的用t;有的用rho表示电阻率,有的用rs表示方块电阻。解析的关键点是建立一个“容错”机制,不能把字段名写死。
我的做法是,先用正则表达式按块分割,然后用一个哈希表去匹配不同代工厂可能用的同义字段。比如:
thick_patterns = [r'\bthickness\b', r'\bthick\b', r'\bT\b'] rho_patterns = [r'\bresistivity\b', r'\brho\b', r'\bRs\b']这个过程看起来不起眼,但实际上是最花时间的一步。我一开始偷懒,默认所有 ITF 都用thickness字段,结果换了个 PDK 就挂了。后来规规矩矩写了字段别名表,稳了。
2.2 EMX 工艺文件的格式与“坑”
EMX 的 tech file 我拿到的版本长这样(简化版):
substrate { eps = 11.9 thickness = 250u rho = 10 ohm-cm } metal { name = M1 type = conductor thickness = 0.45u conductivity = 5.8e7 metal_layers = M1_EMX } via { name = V1 type = via thickness = 0.3u conductivity = 5.8e7 connects = M1_EMX M2_EMX }看着和 ITF 差不多?但里面有三个隐蔽的坑:
第一个坑,ITF 通常给的是方块电阻ohm/sq,而 EMX 某些版本要的是体电导率S/m。这两者的换算关系是:电导率 = 1 / (方块电阻 × 厚度)。这里厚度要用米,不能用微米,很多人在这儿栽跟头。
第二个坑,EMX 里每一层导体不仅要有物理名(比如 M1),还要有一个 “EMX 内部图层名”(比如 M1_EMX),这个内部名用于 EMX 识别上下连接关系。很多生成的脚本把物理名和内部名混用,结果 EMX 直接报错“layer mismatch”。
第三个坑,过孔的阵列信息。ITF 里写的是array_x和array_y,表示过孔阵列的个数,但 EMX 需要的是过孔本身的尺寸和间距,或者需要知道每个过孔的电阻。你要么把阵列信息转换成单个过孔的电阻值,要么找 EMX 文档里对应的字段。如果你不处理,生成的工艺文件在仿真时虽然报错不严重,但仿真结果里的过孔电阻会差得很离谱。
2.3 脚本的核心处理流程
在我实际写的脚本里,核心流程分四步。
第一步,读取 ITF/IRCX 文件,按段落解析出所有层。这一步要注意“三个层级”:金属层、过孔层、介质层,以及可能的顶层钝化层。不同层有不同的关键字,要根据段落内的name字段和关键字共同判断。
第二步,建立“物理层到 EMX 层”的映射表。这一步不是写死在代码里,而是用一份配置文件(YAML、JSON 都可以)维护。原因很简单:代工厂层名和 EMX 层名天然不一样,比如 GF 的 ITF 里叫M4,EMX 里叫M4_EMX,但也有叫M4_C或者Metal4的。配置文件里维护一个physical_name -> emx_name的键值对,脚本只负责读取配置并执行。
第三步,参数换算。把 ITF/IRCX 里的厚度、方块电阻、介电常数等转换成 EMX 需要的电导率、等效厚度、介电常数、损耗角正切等。换算公式我在后面的实操部分会详细给出来。
第四步,生成 .emx 文件。生成的文件要组织成清晰的段落结构,并附上注释,标明每一行参数来自 ITF/IRCX 中的哪个原始字段。这一步对后续审计非常重要,不然过三个月你自己都忘了那些数字是怎么来的。
3. 实操过程与核心环节实现
3.1 准备工作与环境搭建
我用的环境是 Linux 工作站,脚本用 Python 3 写,没有用太重的外部依赖,只用了正则表达式和标准库。这样做的原因是 PDK 文件往往在无网的服务器上,你装不了pip install一大堆东西,标准库的方案最保险。
你需要准备的输入文件有三样:ITF 文件(或 IRCX 文件)、EMX tech file 模板(或者你从 EMX 安装目录里扒一个例子出来)、代工厂的连接关系文档(一般在 PDK 的 Readme 里,或者你从版图工具里导出的 layer map 文件里获取)。
如果你手头连一个 EMX 模板都没有,也别慌。EMX 集成在 Virtuoso 里的时候,会在某个目录下生成一份emxTechExample,找到它就能做骨架。
3.2 Python 解析 ITF/IRCX 的代码骨架
我直接给出一个能用的解析骨架,然后解释关键点。
import re import json def parse_itf(filename): layers = [] with open(filename, 'r') as f: content = f.read() # 按 metal / via / dielectric 分块 for m in re.finditer(r'(metal|via|dielectric)\s*\{(.*?)\}', content, re.S): block_type = m.group(1) block_body = m.group(2) data = parse_block(block_type, block_body) layers.append(data) return layers def parse_block(block_type, body): data = {'type': block_type} name_match = re.search(r'name\s*=\s*(\S+)', body) data['name'] = name_match.group(1) if name_match else None # 提取物理参数 thick_m = re.search(r'(?:thickness|thick|T)\s*=\s*([0-9.]+)\s*(u|n|m)?', body) if thick_m: val = float(thick_m.group(1)) unit = thick_m.group(2) or 'u' data['thickness'] = convert_to_meters(val, unit) rs_m = re.search(r'(?:resistivity|rho|Rs)\s*=\s*([0-9.]+)', body) if rs_m: data['sheet_res_ohm_sq'] = float(rs_m.group(1)) eps_m = re.search(r'epsilon\s*=\s*([0-9.]+)', body) if eps_m: data['epsilon'] = float(eps_m.group(1)) return data def convert_to_meters(val, unit): if unit == 'u': return val * 1e-6 elif unit == 'n': return val * 1e-9 elif unit == 'm': return val * 1e-3 return val这个骨架解决的问题是:把 ITF 里不同风格的字段名都归一到一致的数据结构里。运行完parse_itf之后,你会得到一个列表,里面每个元素是一个字典,包含type、name、thickness、sheet_res_ohm_sq、epsilon这些键。
注意一个细节:解析文件时用re.S标志,这样.能匹配换行符。我一开始漏了这个,导致块匹配失败,解析出来的层数少了一半,排查了半天。
3.3 参数换算与 EMX 生成逻辑
这一步是整个流程的核心,我把换算公式写清楚:
金属层电导率(siemens/m)= 1 / (方块电阻 ohm/sq × 厚度 m)
介质的介电常数直接用 ITF 里的epsilon,但要注意 EMX 里有些层需要epsilon_r和tan_delta分开给,如果 ITF 里面只给了一个loss_tangent,那就直接映射过去。
过孔电阻:ITF 里如果给了resistance,那很好,直接填入。如果给的是array_x = 2、array_y = 2,而单孔电阻是5 ohm,那最终每根过杆的等效电阻就是 5 / (2 × 2) = 1.25 ohm。这个不做,后续仿真结果偏得离谱。
有了这几个公式,生成代码就顺理成章了:
def generate_emx_tech(itf_data, layer_map, output_path): with open(output_path, 'w') as f: f.write('// Generated from ITF via script\n') f.write('// Timestamp: 2024-05-20\n\n') for item in itf_data: if item['type'] == 'metal': thickness = item['thickness'] rs = item['sheet_res_ohm_sq'] conductivity = 1.0 / (rs * thickness) if rs and thickness else 5.8e7 emx_name = layer_map.get(item['name'], item['name'] + '_EMX') f.write(f'metal {{\n') f.write(f' name = {emx_name}\n') f.write(f' thickness = {thickness * 1e6:.4f}u\n') f.write(f' conductivity = {conductivity:.4e}\n') f.write('}\n\n') elif item['type'] == 'via': # 处理过孔 pass elif item['type'] == 'dielectric': # 处理介质 pass实际跑的时候,你会遇到一些“非标准”的 ITF。比如某家代工厂的 IRCX 里,金属层的方块电阻给的不是ohm/sq,而是ohm/mil,我第二次做自动化转换时就踩了这个坑。这种单位换算如果不注意,生成的 EMX 文件里的电导率会差一万倍,因为在 EMX 看来金属层变成了绝缘体。所以我会建议在脚本里加入单位检查,如果发现resistivity的值大于 1,多半是ohm/mil,就要做单位换算。
3.4 加入层间连接关系的自动推导
ITF/IRCX 里通常会直接给出每层过孔的两端金属层名,比如 V1 连接 M1 和 M2。但是,有些工艺库的 ITF 里只写了过孔的厚度,没有写连接关系。这时候就要靠“堆叠顺序”来推导。
思路:把 ITF 里的层按厚度排列逻辑排序(介质层夹在金属层之间,过孔层联通上下金属),如果 Vx 的厚度等于 Dx(第 x 层介质)的厚度,那么 Vx 大概率连接 Mx 和 M(x+1)。这个规则不是 100% 可靠,但 90% 的情况下能猜对,剩下 10% 靠连接关系文档来校验。
我建议你在脚本里输出一份“层间连接关系表”到日志文件里,人眼过一遍,比直接信脚本要靠谱。我就在项目里干过“太自信、没仔细检查连接表”的事,结果把 V12 和 V13 搞混了,后仿电感值差了几 nH,排查了半天,最后发现是连接关系搞错了。
3.5 生成结果验证
生成的 .emx 文件怎么验证对不对?我一般做三道检查。
第一道,EMX 自带的 parser 跑一遍,看能否通过基本语法检查。这一步能抓出括号不匹配、字段缺失这类低级错误。
第二道,反向提取。写一个小的读取脚本,把生成的 .emx 文件重新解析成数据字典,然后跟原始 ITF 解析出的数据字典做 diff。厚度差超过 1%,电导率差超过 5%,就要报警。这相当于你的转换过程是“可逆”的。
第三道,跑一个简单的仿真测试。用一个已知的 inductor 版图,分别用官方 PDK 转换好的工艺文件和你的脚本生成的工艺文件仿真,对比感值、Q 值、自谐振频率。如果在 2% 以内,基本可以说转换成功了。如果差得多,别急着怀疑 EMX,大概率是你的电导率或者过孔电阻算错了。
4. 常见问题与排查技巧实录
4.1 解析后厚度单位错了?
我遇到过一次,ITF 里厚度写了0.45u,但我的脚本默认单位是米,转换的时候把 0.45 当成了 0.45 米乘以 1e-6,结果 EMX 里厚度变成了 450 微米,离谱到没法看。
排查方法很简单:在生成 .emx 之后,先打印出每一层的厚度,跟 ITF 原文件逐项对比。不要用眼睛扫,写一个自动对比的代码,任何一层的差值超过阈值就直接报错。
4.2 方块电阻和电导率换算错了?
这是最容易犯的错误。EMX 需要电导率,单位是 S/m,而 ITF 里给的是方块电阻,单位是 ohm/sq。你必须用厚度去换算。
但这里还有一层坑:厚度是有单位的,换算的时候要特别注意。如果你在 ITF 解析时把厚度转成了微米,1e-6 的因子就很容易丢。我最后的解决方案是,在所有换算之前统一转成“米”,然后在输出 .emx 的时候再转回“微米”。中间不做任何其他单位运算,减少出错可能。
4.3 过孔数太多导致 EMX 仿真特别慢?
有的工艺库里有十几层过孔,每一层过孔又分了很多种(比如 V1、V1a、V1b),直接全部转成 EMX 里的过孔层,会让仿真网格变得极其密集,速度慢到怀疑人生。
我的处理办法是:转换时增加一个“简化开关”。对于非关键路径上的过孔层,可以合并成一个等效过孔,或者把某些过孔阵列替换成一个大的导电块(前提是电磁特性等效)。这个开关在高频段可能不适用,但对于低频段的快速验证,效果极佳。
4.4 EMX 报 layer mismatch 错误?
这个报错我见过太多次了,几乎都是因为过孔连接关系写错了,或者某个金属层在 EMX 里的内部名没有被正确映射。我的排查套路是:先在 EMX 的原始例子里查一下它使用的内部层名(比如 M1_EMX),再看我的脚本生成的层名是否完全一致。只要把映射表里的名字对齐,问题马上消失。
4.5 IRCX 文件和 ITF 文件格式差异大吗?
差异是有的,主要体现在块结构上。IRCX 里往往多了interconnect段落,而且某些参数名用缩写,比如MTT表示金属厚度,RSH表示薄层电阻。我的应对策略是:在预处理阶段,把 IRCX 先标准化成 ITF 的结构,然后再走同一套解析流程。这样只写一个转换器,而不是两套解析器。
这里给你一个参考表格,快速对比两个文件格式里的常见字段映射:
| ITF 常见字段 | IRCX 常见字段 | 含义 |
|---|---|---|
| thickness | MTT | 厚度 |
| resistivity | RSH | 方块电阻 |
| epsilon | DK | 介电常数 |
| loss_tangent | DF | 损耗角正切 |
| array_x | NX | 过孔阵列横向个数 |
| array_y | NY | 过孔阵列纵向个数 |
| name | LayerName | 层名 |
表格里的映射不是绝对固定的,但你可以按这个思路去适配。做自动化转换时,宁可多做几层防御,也别指望代工厂能给你一份完全标准的文件。
5. 工具选型与脚本语言的选择思路
5.1 Python 还是 Perl:哪个更合适
很多老工程师写这类工具会用 Perl,因为历史原因,PDK 社区里 Perl 脚本确实多。但我自己选 Python,原因有三。
第一,Python 的字典、列表、正则表达式用起来非常直观,尤其是处理嵌套的块结构时,写代码和维护都省心。第二,Python 的调试工具更好用,pdb、ipdb 能让你一行行看解析过程,Perl 就相对粗糙。第三,后续如果你想把这个脚本扩展成带界面的小工具,Python 可以直接挂个 tkinter 或 PyQt,Perl 就麻烦得多。
不过 Perl 也不是没优点。Perl 的正则是图灵完备的,处理非常“脏”的古老 PDK 文件反而顺手。我见过某些老代工厂的 IRCX 里夹杂着大量历史注释,Python 正则写出开可能要几十行,Perl 可能几行就搞定了。
5.2 有没有更省事的现成方案
有,但我不推荐一上来就找现成工具。
EMX 官方其实提供了一些转换脚本(通常是 Python 或 Skill),但它们的匹配逻辑是基于特定代工厂的 PDK 写的,换个工艺库就得改。更坑的是,官方脚本有时是为了某个特定项目的流片任务定制的,未必能覆盖你手头的所有层。
我的观点是,第一版一定要自己写,哪怕只是几十行的“小破脚本”。这个过程能让你彻底搞懂 ITF/IRCX 里每一层参数的物理含义和 EMX 工具内部的层组织方式。以后遇到问题,你会比任何现成工具都更从容。
当然,如果你时间特别紧,也可以先用官方转换工具产一份结果,然后写脚本对比你的自动化输出,两边互相验证。我项目里跑过一个 28nm 工艺库,手动转换 + 官方工具花了两天,我的脚本+自动对比只花了一个下午(加上调试脚本的时间一共一天),这个 ROI 还是很值得的。
5.3 配置文件设计:让脚本适应多种工艺节点
我在第三节提到过映射表配置,这里展开讲一下怎么设计。
我用的 YAML 配置大概是这个风格:
layers: M1: emx_name: M1_EMX type: metal V1: emx_name: V1_EMX type: via connects: [M1_EMX, M2_EMX] D1: emx_name: D1_EMX type: dielectric connects: [M1_EMX, M2_EMX]脚本读取这份配置后,解析 ITF 得到的层数据会和配置里的emx_name做关联。这样你换工艺库时,只需要改配置文件,不需要改 Python 代码。
这个设计让我在多个项目里复用同一套脚本。只要 ITF 文档结构没有大改,配置文件一换,十分钟就能出新的 EMX 工艺文件。
6. 实操心得与扩展思考
最后再分享几个实际干活时才体会到的点。
第一,不要只想着“转换成功”,要想着“可逆审计”。我每次生成 .emx 文件后,都会保留一份 ITF 原始参数和 EMX 生成参数的对照表,输出成 CSV 或者 Markdown,跟着项目文档走。这样做的好处是:可以随时回溯一个异常仿真结果对应的工艺文件是哪一行参数有问题。没有这个审计文件,你面对一个神秘的后仿偏差时会无从下手。
第二,同一套脚本可以扩展到其他工具。我讲的是从 ITF/IRCX 转 EMX,但实际这套“配置文件+解析器+生成器”的三段式架构,完全可以套用到转 HFSS、转 ADS、转 Sonnet 等工具的工艺文件上。你只需要改改 EMX 生成器那块代码,换成对应的输出模板,剩下的解析和映射逻辑几乎不用动。
第三,PDK 文件里的版本信息一定要看。有一段时间我发现自己更新工艺库后,脚本突然多出了好几个解析失败,排查半天才发现代工厂的新版本 ITF 里把字段名从array_x改成了num_x。所以提醒一下,每次拿到新 PDK,第一步不是跑脚本,而是 diff 一下 ITF 文件跟上一版有什么区别。哪怕你已经写好了容错机制,提前知道变化总比事后排查强。
第四,也是最重要的,生成工艺文件后的“物理合理性”检查绝不能被省掉。脚本帮你把数字填进去了,但数字合不合理要靠工程师的判断。比如,介质层的介电常数一般是 3.9 到 4.5 之间,如果脚本输出的数值是 1.5,那显然不对,要么是解析错误,要么是原文件用了其他单位(可能是epsilon_r而不是epsilon)。这种异常值如果是人工录入,大概率会发现;但脚本一旦跑起来,人往往就松懈了。我最后在脚本里加了“范围检查”逻辑,超出合理范围的参数直接高亮警告,这救过我好几回。
写这份自动化脚本的时间成本不算低,但它带来的收益是持续的。每次拿到新工艺库、新 PDK 版本,我都能在一天之内跑出一份可靠的 EMX 工艺文件。再过几个月,如果机台、工艺线宽或温度系数发生变化,我也能快速重新生成并对比差异。这个过程让我彻底摆脱了手动逐行填写工艺文件的噩梦,也让我对每个参数的来源、转换、影响都有了更清晰的认识。建议你也动手写一个,不用多漂亮,能跑通就行。