做硅光芯片设计这几年,我的体会一直都是:单点工具从来不缺,缺的是把物理仿真、电路仿真和版图设计串起来的那根线。Lumerical和OptoCompiler放在一起,恰好就是为解决这件事来的——前者负责把光子器件的电磁特性和半导体物理效应算到足够准,后者负责把器件放到电路里、落到版图上、跑通链路级和电光协同仿真。这篇内容我会从实际项目视角出发,聊一聊两者集成后的工作流怎么搭、PDK怎么建、MZM这种典型电光链路怎么跑协同仿真,最后把我在反复实操中踩过的坑整理成清单。不管你是刚接触光子集成电路设计的学生,还是已经在用OptoCompiler画版图的工程师,这篇东西应该都能帮你绕开几条弯路。
1. 单点工具不缺,链条断开才是光子集成开发的真痛点
1.1 没有集成时,我们是怎么折腾的
早先做PIC项目时,最常见的路径是:先用FDTD把无源器件算了,得到透过率、插损、相位;再用CHARGE或者Device这类工具把调制器的电学行为算了,得到载流子浓度变化;导出数据之后,再手动把这些结果填到链路仿真工具里,手工调一段网表,最后再去画版图。听起来每一步都有工具在做,但问题是步骤和步骤之间全靠人肉搬运。
只要版图改过一次几何尺寸,前面所有仿真结果里对应的那个元件模型就得跟着重来一遍。有一次我改了一个MMI的长宽,仿真数据已更新,但链路模型文件忘了同步,结果系统级仿真跑出来插损异常,排查了两天才发现是模型和版图对不上。这种问题在工具链路没有打通的项目组里几乎每隔几周就出现一次。真正制约光子集成电路设计效率的,不是某个求解器不够快,而是器件级结果和电路级模型之间的断链。
1.2 Lumerical和OptoCompiler分别解决哪一层
Lumerical家族擅长的是物理级仿真。FDTD Solutions做无源器件,MODE Solutions做波导模式分析,CHARGE做半导体电学载流子输运,INTERCONNECT做光子链路级频域和时域仿真。它解决的问题是:某个具体结构,在给定波长、偏振、电压、温度下,光学行为到底是什么。
OptoCompiler解决的是系统设计和工程化问题:原理图怎么组织、器件该怎么摆进版图、PDK该怎么管理、DRC/LVS怎么做、最终怎么和电学仿真器协同。你可以把它理解为设计师的工作台,所有器件以单元形式挂进来,用网表描述连接关系,版图由PCell自动生成,最后还能把光域结果和电域结果合并看眼图。
两者集成真正解决的核心问题,就是把“这个器件物理上性能如何”和“这颗芯片系统上表现如何”这两层内容用一套自动化的数据流串起来。没有了这层数据流,你得到的只会是一堆孤岛式仿真报告。
2. 集成链路的数据流:从物理场到电路网表,信息一层层被“抽象”
2.1 光学器件是怎么被抽象成模型的
任何仿真流程的本质都是做抽象。FDTD这种场级工具,里面看的是电场分布、磁场分布,网格小到纳米级,仿真结果是连续的三维数据体。但到了电路级仿真,我们不可能让SPICE或光子链路仿真器去解三维麦克斯韦方程,必须把器件行为压缩成端口网络模型。
Lumerical里通常的做法是,对一个器件跑完场级仿真之后,提取它的散射矩阵。常见输出是S参数文件,比如touchstone格式,再配合群延迟、相位响应、模式变换这些参数。对调制器这类有源器件,还要加上电学端口的RC寄生参数,以及电压引起的折射率变化曲线。这些东西打包在一起,就构成了电路仿真器能识别的一个紧凑模型。
从场级数据到紧凑模型的过程,就是这个抽象过程中最需要小心的一步。因为S参数是频域的,到了INTERCONNECT或者OptoCompiler的时域仿真里,还需要做频时变换。如果频带取的太窄,或者频点不够密,时域结果就会出现拖尾和非物理振荡。所以我一直强调,集成流程不只是把文件导出来再导进去,背后有一整套关于频带、插值、参考面的约定,这些细节直接决定协同仿真的准确性。
2.2 电学世界的接入:SPICE、Verilog-A与光网络的对话
电光协同仿真最难的地方在于,光路是连续波导,电路是带电阻电容和晶体管的电网络,时间尺度还经常差着数量级。
在OptoCompiler这套环境里,光路部分通过光端口连接,每个端口携带模式信息、波长和功率;电路部分则走标准电学网表,兼容SPICE一类工具。建模的时候,无源光器件一般表现为光域的多端口S参数,电光调制器则是一个四端口以上的混合器件:光输入、光输出、电正极、电负极。仿真器需要同时解“光沿着波导传了多少”和“电信号如何在电极上传播”这两件事。
我见过比较稳妥的做法是分层处理:光域用Lumerical的紧凑模型,电域用SPICE模型,两者在器件层面通过“由电信号改变光学参数”的关系耦合。这层耦合关系通常写成查找表或者Verilog-A等效电路,仿真时在每个时间步读取电压,再去查对应的光学响应。整个过程听起来复杂,但正是因为OptoCompiler把光网络和电网络统一到了一个仿真环境里,我们才能把这两套物理域放到同一把时域下游标下评估。
2.3 协同仿真的两种模式怎么选
实际项目里,电光协同仿真通常有两个方向。
一种是光路主导的链路仿真。光发射端、波导链路、无源器件、接收端全部用光学模型,电驱动器用理想源或者简化电阻负载。这种模式跑得快,适合早期架构评估,比如链路预算、功耗估算、接收端灵敏度分析。
另一种是电路主导的联合仿真。把电驱动器、TIA、跨阻放大器这些用真实PDK的SPICE模型搭起来,光路部分继续保留光学器件模型,两边同时仿真。这种模式精度高,但耗时成倍上涨,适合芯片已经进入实现阶段,需要验证驱动器和调制器之间阻抗匹配、带宽、反射这些实际问题。
两种模式不是替代关系,而是前后关系。我的一般流程是先跑光路主导版本把链路预算定下来,再逐步把电域模型换成真实电路模型做精细化验证,最后输出给后仿。
3. 把Lumerical结果变成OptoCompiler能用的PDK,这一步决定了后续仿真质量
3.1 一个光PDK应该包含哪些层级
刚开始用OptoCompiler时,我犯过一个很自然的错误:把PDK理解成光器件的仿真模型集合。结果做下来才发现,一套可用的光PDK至少要包含四个层面。
第一层是器件原理图符号,工程师画链路图时看到的东西。第二层是紧凑模型,包括光学S参数、电学寄生参数、调制响应数据,这是仿真时真正计算的依据。第三层是版图PCell,也就是从原理图参数化生成GDS布局的代码。第四层是验证规则,包括层映射、DRC间距规则、LVS连接识别规则。
四个层面缺一个,流程就会在某个环节卡住。比如有模型没PCell,那仿真能跑但版图画不了;有PCell没模型,那版图画得出来但没法验证性能。我建议在集成初期就把这四层结构定清楚,不要等所有器件都做完再补,否则后面图中口径不统一,返工量非常大。
3.2 层映射、命名规则和符号库里的沙尘暴
不是技术上的高深问题,但命名和层映射真的能消耗掉项目组大量时间。
光PDK里常见的坑是:Lumerical INTERCONNECT中的端口名、OptoCompiler里的原理图引脚名、GDS上的几何层名,三套体系经常对不上。有时候是大小写不一致,光端口叫mod_in,电引脚叫Mod_In,导进去直接断开;有时候是波导层号不同,仿真层用21,版图层用121,DRC不认。这些错误在原理图仿真阶段根本不报,直到LVS才突然爆发。
我的办法是在PDK开发第一天就定一张层映射表,把所有工具的命名对应关系列清楚,然后写一个脚本去自动检查一致性。比如下面这个示意脚本,每次更新PDK后跑一遍:
#!/bin/bash # 检查光端口命名一致性 grep "MOD_IN" optical_pdk.mapping | while read line; do port_name=$(echo $line | awk '{print $1}') sim_name=$(echo $line | awk '{print $2}') if [ "$port_name" != "$sim_name" ]; then echo "[ERROR] 端口命名不一致: $port_name vs $sim_name" fi done这不算漂亮,但它确实救过我很多次。命名规则这种东西,人眼检查十次都不如脚本检查一次靠谱。
3.3 从Lumerical导出S参数并生成模型文件的实际操作
器件级仿真完成后,导出模型这一步需要把关键信息全部带上,否则后面链路线仿会缺参。
我常用的流程是:在FDTD里跑完指定波长范围,然后在结果中提取端口S参数,导出为touchstone格式。接着到INTERCONNECT的模型中,把S参数文件导入进光网络元件,同时把电学端口的RC参数填进对应的电学属性里。整个过程用Lumerical的脚本语言可以自动化,大致逻辑是下面这样:
# Lumerical脚本示意:提取MZM的S参数并生成紧凑模型 select("MZM_OPTICAL"); set("port", 1); set("frequency range", 193.1e12, 193.7e12); set("number of points", 2001); runsweep(); export("mzm_s4p.touchstone", "S"); select("MZM_ELECTRICAL"); set("resistance", 45); set("capacitance", 0.12e-12); export("mzm_electrical.json", "model");导出后要做的一件事是检查频带宽度。我之前有次就是导出时图省事,只选了200GHz带宽,结果做25GBaud的PAM4仿真时,时域波形明显畸变。原因是S参数没有覆盖到足够的高频分量,时域变换之后高频信息恢复不出来。建议至少按信号波特率的5到8倍带宽来取频点。
3.4 最小可用闭环:先打通再扩充
我特别提倡先做一个最简单的“最小PDK”,而不是一上来就把几十个器件全部入库。比如先做一条波导、一个Y分支、一个调制器,把从原理图仿真到版图生成的全流程走通。
走通这个闭环能验证三件事:原理图和版图是否同步;模型的参数能不能正确落实到版图PCell;DRC和LVS能不能不报错地认下这组单元。只要这个最小闭环通了,后面的器件扩充只是在重复同一套流程,风险小很多。
4. 一条MZM链路的电光协同仿真,完整流程拆给你看
4.1 先把电域搭起来:驱动器、电极与阻抗匹配
MZM链路是电光协同仿真最经典的案例。我先从电域开始搭。
驱动器的SPICE模型按项目实际电路来,高速信号源接在驱动器输入端,驱动器输出经过一小段传输线接到MZM的射频电极。电极模型在集成流程里常被简化成一条有损传输线,再并联一个RC并联网络。这里最容易忽视的是阻抗匹配:硅光调制器的TWE电极一般设计在接近50欧姆附近,驱动器输出阻抗也是50欧姆,两边的共轭匹配如果没对齐,反射会体现在最终的光眼图上。
我会在仿真里特意加一个电探针,去看MZM电极上的实际电压波形,而不是只看驱动器输出端的激励信号。很多时候驱动器输出信号是好的,但过了传输线和电极之后已经变形了,光信号自然不会好。
4.2 光域建模:调制器、波导、探测器怎么挂
电域搭好之后,光域部分在OptoCompiler里挂器件。
光源用连续波激光器模型,通过波导连到MZM的光输入端口。MZM的光学部分就是前面从Lumerical导出的S参数模型,它里面包含了分光、相位、损耗和调制电压对透过率的响应关系。MZM输出连一段延迟波导,再接探测器。
探测器的模型这时候要特别留意,它既有光端口又有电端口。光端口接收功率,内部通过响应度把光功率转换为光生电流,再送到后级TIA。很多新手在这里会直接把光信号接成电信号,导致仿真器报错。正确做法是让探测器模型完成功率到电流的转换,之后才进入电学仿真域。
4.3 激励设置:PRBS、眼图与时序对齐
仿真激励我一般用PRBS7或者PRBS9,速率按项目目标来,比如56GBaud PAM4。此时要设置好信号源速率和仿真总时长,总时长至少要覆盖几个完整的码元周期,才能让眼图充分闭合。
时域仿真里的步长设置是关键。步长太大会丢失高频细节,步长太小则仿真时间爆炸。我的经验是先跑一个短时长的粗步长快速检查,确认链路能通,再加密步长跑正式结果。光学部分的数据点数也需要和高频信号匹配,否则光信号的上升沿会被模型插值磨平。
这里有一个很容易忽略的点:光路和电路之间经常存在时延差,特别是当链路里包含长波导时,光信号会比电信号晚到几十甚至上百皮秒。在眼图分析时,如果不先把接收端的电信号做时间对齐,测出来的眼图看起来就是一团糊的。
4.4 结果验证:光眼图和电眼图一起看
协同仿真的输出不要只看电眼图,一定要结合光眼图一起分析。光电协同链路里,光眼图反映的是调制器本身和光路部分的状态,电眼图则反映的是探测和接收电路的状态。
有一次我遇到链路整体消光比偏低的情况,单独看电眼图完全正常,但光眼图的“1”电平明显下塌。最后定位是MZM模型的偏置电压点设置不对,静态工作点接近了传输曲线非线性的区域。如果当时只看电域结果,这个问题根本不会暴露。所以我在流程里固定要求:每次协同仿真,必须同时抓取光口和电口的眼图、功率计读数、电压波形的频谱,这样出了问题才能快速判断故障是在调制侧还是接收侧。
5. 事后复盘:我在这套流程里反复踩坑的地方
5.1 S参数频带宽度不够,时域结果拖尾
这是我在协同仿真里遇到频率最高的问题。
现象是光眼图的上升沿和下降沿后面跟着一个长长的“尾巴”,看起来像振铃。排除掉电路反射之后,问题锁定在S参数文件上。因为导出时频带只覆盖到信号速率的3倍左右,时域变换后高频信息缺失,系统响应自然就多出来一段虚的拖尾。
解决方法是把S参数频率范围扩展到信号波特率的5到8倍,同时增加频点密度。需要注意的另一方面是频带扩展后,仿真步长也要跟着变细,不然扩展了也没意义,照样会因为时间采样不足而出现混叠。
5.2 端口参考面不一致,相位偏差很隐蔽
不同器件导出S参数时,参考面通常取在器件边界。但到了OptoCompiler链路里,两个器件连接时会把端口直接拼在一起,如果中间还有一段波导没有建模,相位延迟就会缺了一截。
这个坑在无源器件级联时尤其隐蔽,因为幅度响应看不出问题,只有相位叠加之后才能发现端口对不上。我现在习惯是每个器件在导出之前,都检查一下端口的参考面定义,并在链路里保留明确的波导连接段,宁可多花一点仿真时间,也不让参考面偷偷“吃”掉一段波导。
5.3 PDK版本和仿真器版本混乱,玄学报错
集成环境里的版本问题相当折磨人。OptoCompiler、Lumerical、PDK库、甚至操作系统账号环境下装的Python库,都可能互相影响。我遇到过一种情况:同一个PDK,在V21版本下能跑,在V23版本下就报“missing property”,但原理图和模型文件完全没动过。
后来排查才发现,不同版本对模型文件里某个默认值的解析逻辑变了,老版本自动补全,新版本直接报错。因为这个问题,我现在对所有PDK都固定版本,并且建立了一个环境的版本快照文件。升级工具之前,先在一个独立目录里完整跑一遍回归测试,确认所有模型和Rule文件都兼容再切换。
5.4 大批量参数扫描时的资源占用
电光协同仿真一旦涉及参数扫描,资源消耗会非常夸张。比如想扫MZM调制器在不同偏压下对眼图的影响,每个偏压点都是一次完整时域仿真,单纯用循环去跑,可能一星期都不够。
我的做法是把参数扫描拆成两段。先把不依赖电信号的参数(比如偏压点、无源损耗)用链路仿真短时间粗扫,找到可疑区间;再对可疑的少数几个点做完整电光协同仿真。另外还可以尽量利用工具本身的多核并行能力,把不同偏压点分配给不同核并行跑,而不是在同一个任务的循环里串行等待。
下表是我自己整理出来的常见问题和对应排查方向,碰到类似情况可以参考一下:
| 现象 | 根因方向 | 优先检查项 |
|---|---|---|
| 眼图高频拖尾 | S参数频率范围不足 | 频率上限、频点密度 |
| 两器件级联后相位异常 | 端口参考面不一致 | 器件边界到连接端口的波导长度 |
| 原理图仿真正常,LVS报错 | 层映射或命名不一致 | 层映射表和符号库命名 |
| 相同网表在不同版本结果不同 | 工具版本或PDK版本漂移 | 版本快照、回归脚本 |
| 长链路仿真时内存爆炸 | 数据点过多且无压缩 | 步长选择、结果采样策略 |
6. 目前我比较认可的工作模式
6.1 把脚本化检查嵌入流程
集成流程跑通之后,我最想强调的一点是:要把所有检查项做成脚本,而不是靠人工盯。
我个人习惯的做法是建立三个自动检查点。第一是模型导出时对S参数带宽和频点密度的自动校验,不合规直接中断;第二是PDK更新后的端口命名和层映射一致性检查;第三是每次长仿真前对网表做一次短路和悬空端口扫描。这样做之后,我最明显的感觉是很多低级错误的定位时间从小时级降到了分钟级。
6.2 器件级和电路级仿真的分工边界
还有一个很重要的认知:不是所有东西都值得放进同一个仿真里跑。
器件级仿真里,FDTD、CHARGE这种工具的职责是把单个器件的物理特性摸清楚。这一步不应该和电路仿真混在一起,因为时间和网格尺度差距太大。到了电路级和系统级仿真,大量用到的应该是已经验证过的紧凑模型。紧凑模型的可靠性,来自器件级仿真阶段的充分验证。把这两个阶段清楚切开,反而是提高整体研发效率的最好方式。
有些人一上来就希望搭一个完全万能的模型,让器件级和电路级在一个环境里同时仿真,但这样做往往导致精度高的跑不动,跑得动的又不够准。真正高效的流程是:物理级的归物理级,系统级的归系统级,中间靠模型传递。
6.3 扩展:工艺角、统计仿真与后仿的真正价值
当Lumerical和OptoCompiler的集成链路稳定之后,再把工艺角、统计偏差这些因子放进来,就能做更有工程价值的验证。
比如对MZM来说,工艺偏差会影响波导宽度,进而影响有效折射率和调制效率。在OptoCompiler里,只要在模型参数上加入工艺角变量,就能通过参数扫描考察整个链路在工艺波动下的良率趋势。这种分析在流片前意义很大,能提前帮助你判断设计余量是否充足。
后仿的作用同样重要。版图寄生提取之后,把寄生的RC参数反标回协同仿真模型,跑出来的眼图和流片后测试的吻合度会明显提升。做到这一步,整个从Lumerical到OptoCompiler的流程才算真正闭环,模型和版图不再是两套互不相干的数据。
按照我个人经验,这套集成流程前期搭建时确实比传统人肉搬运流程要花更多准备工作,但一旦跑通,后续每个项目迭代节省下来的时间都是成倍数的。建议刚接触这套环境的团队,不必追求一次把所有器件都做进PDK,拿一条MZM链路作为切入点,把整个数据流跑顺畅,再用相同的方法去扩充其他器件。有了这个底座,光子集成电路设计和电光协同仿真才谈得上真正的高效。