指南 · L-Edit 桥

让 agent 双向驱动 Tanner L-Edit

一个源码即装载的 UPI 宏 + JSON 文件交换传输:无套接字、无防火墙弹窗、无需编译;按老版本 API 子集编写(理论兼容旧版,实测环境 v16.x)。agent 由此获得对 L-Edit 的白盒读写——直到 T-Cell 的生成器代码本身。0.5.7 把这座桥从单纯的画图传输升级成完整的编辑器控制面:导航(窗口、视图、cell 显隐)、显式目标的破坏性命令、经校验且可回滚的层级推送、L-Edit 自带的 DRC、以及回 KLayout 的 GDS 导出(宏 0.5.6 → 0.5.8,升级后需要在 L-Edit 里重新装载宏)。

装载:一个文件,零编译

pip install klayout-klink 后 klink init 会在项目里铺出 example_template/ledit_bridge/:

  • ledit_bridge.cpp —— 唯一的宏文件。在 L-Edit 里 Tools → Macro → Load Macro… 直接选它(L-Edit 自己现场编译;宏只用老版本 API 子集编写,理论兼容旧版、实测环境 v16.x;klink 不携带任何 Siemens/Tanner 文件,SDK 头文件来自你自己的 L-Edit 安装)。
  • driver.py —— 免依赖冒烟驱动:python driver.py ping 看到宏版本、当前设计与能力表即为连通。
  • tcell_workflows.py —— T-Cell 工作流 CLI(下文)。

传输是 %LOCALAPPDATA%\klink\ledit_bridge\ 下的 inbox/outbox JSON 文件对,宏在 UI 线程 200ms 轮询、原子写回;心跳文件 hello.json 提供活性判定。

能力面

类别命令
设计文件new_design(没开 tdb 也能自举建库)· open_design · save_design
图层ensure_layer(盖 GDS 号;新层自动配色:实心无边框)· set_layer_style · get_layers(含填充色与特殊层标记)
绘制create_cell · draw(box/polygon/wire/circle 批量)· place_instance(含 nx×ny 阵列)· clear_cell
全量读取get_selection · get_cell(图形+对象级属性树+实例+端口+标签;wire 带端帽/拐角,torus/pie 带精确参数)· list_cells(现在总是带 hidden 布尔字段,与之不一致时再加 hidden_property)
导航show_cell · set_cell_hidden · list_windows · close_window · layout_view · save_image
破坏性delete_cell · rename_cell · delete_objects · close_design
验证run_drc · drc_summary · export_gds
工艺知识get_drc_rules —— 整套设计规则表机读导出
T-Cellget_tcell_params · instance_tcell(程序化传参实例化)· set_tcell_code(把生成器代码写回成原生 T-Cell)

与 KLayout 互导(MCP 一调用)

配好 klink MCP 后,agent 直接获得 bridge_ledit 域的十八个意图级工具:下面五个覆盖与 KLayout 的双向交换,其余十三个(导航与视图、标记过桥、破坏性命令、DRC 与 GDS 回程)按主题分节说明在后面。

  • ledit.status —— 发现与分诊:宏活性、设计是否打开、心跳年龄。任何异常先叫它,错误信息会直接写明下一步。0.5.3 新增:宏支持 list_designs/list_cells 时,还会一并报出当前打开的设计和活动设计的 cell 列表(含 T-Cell 标记)——“L-Edit 里现在有什么”一次调用问完;旧版本宏行为不变(能力探测式降级)。
  • ledit.import_selection —— 你在 L-Edit 里选中,agent 导入 KLayout 新 cell:圆保持参数化(CIRCLE PCell)、层连名带号迁移(重名追加不覆盖)、不可转换对象如实列出。
  • ledit.push_cell —— KLayout 平坦 cell 推回 L-Edit(层同样带名建立)。
  • ledit.import_cell_tree —— 把 L-Edit 里一个 cell 连同它整棵层级导入 KLayout:children-first 重建,实例照旧建成实例(不是拍平成形状),层身份同样连名带号迁移;每个目标 cell 都会被重新建出,所以重复导入是幂等的;没有可转换轮廓的形状列进 not_convertible,绝不悄悄丢弃。
  • ledit.push_cell_tree —— 反方向:把 KLayout 一个 cell 连同它下面整棵子树推回 L-Edit,同样 children-first、实例重建成实例;默认 clear=True,让重复推送保持幂等(L-Edit 的 draw 只会追加);整棵树作为一次批量请求发过去;L-Edit 摆放表达不了的实例(缩放、非正交旋转、错切阵列)会如实列进 unsupported_instances,绝不近似凑合。

导航与视图

ledit.show_cell 打开/前置某个 cell 的版图窗口并把它设为当前可见(“在 L-Edit 里打开 X”);ledit.set_cell_hidden 切换 T-Cell 变体常带的 “Hide In Lists” 标记;ledit.list_windows / ledit.close_window 枚举/关闭版图、文本、日志窗口(按 cell 名,或 ledit.list_windows 给出的 index);ledit.layout_view 是视图的单一动词——不带参数读当前视图,rect_um 设视图,home 复位到 cell 的 home view,调用后总是回读实际视图(加 has_window);ledit.save_image 把 cell/区域渲染成 PNG/BMP/JPG——只在用户明确要图片时调用,从不当验证证据,规矩和 KLayout 的 view.screenshot 一样:验证请用 get_cell / status。

把一个五路扇出的 klink tapered 路由 cell ROUTE_05_FANOUT 推入 L-Edit 之后,ledit.show_cell 的真实返回:

{"cell": "ROUTE_05_FANOUT", "window_opened": false, "via": "window", "file": "klink_site_demo"}
推入的 ROUTE_05_FANOUT cell 在 L-Edit v16.3 窗口中打开,图层调色板按名字列出 klink 创建的层(M1_DEVICE_OR_PAD、KLINK_ROUTES、KLINK_PORTS 等)及其 GDS 号,cell 窗口里可见推入的扇出路由
ledit.push_cell_tree 把 ROUTE_05_FANOUT 连同它下面的 cell 推入后,ledit.show_cell 打开的真实 L-Edit v16.3 窗口:图层调色板里是按名字建出的 klink 层(含 GDS 号),cell 窗口里是推入的扇出路由。
{"cell": "ROUTE_05_FANOUT", "mode": "get", "rect_um": [-51.139, -4.598, 114.139, 52.598], "has_window": true, "file": "klink_site_demo"}
ledit.save_image 渲染的整个 ROUTE_05_FANOUT cell(L-Edit 自己的渲染器,1600x1000):五条来自 klink tapered 路由器的路由、焊盘,以及零宽 Port 三角形显示为细线
ledit.save_image 对整个 cell 的渲染(L-Edit 自身渲染器,1600×1000):五条 klink tapered 路由、焊盘,以及零宽 Port 三角形——它们显示为细线,而不是填色三角形。

标记以零宽线过桥

klink 的 Port / Anchor / Region 标记从 0.5.6 起统一是零面积的纯标记(width_um=0 的路径轮廓)。0.5.7 之前,draw 会自己拒绝 width_um == 0 的 wire;现在这层拒绝交给了 L-Edit 自己(LWire_New),拒绝时错误信息会说明原因。在真实的 L-Edit v16.3 上验证过:零宽线是被接受的,并渲染成 1 像素的轮廓线——klink 的零宽标记由此第一次能完整过桥。ledit.push_cell_tree 的返回里用 zero_width_wires 报出零宽线的总数。

把 ROUTE_05_FANOUT 连同它下面整棵子树(含 Port、CorridorAnchor 等标记 cell)推入 L-Edit 的真实报告:

{"cell_count": 13, "ops": 57, "zero_width_wires": 10, "requests": 1, "unsupported_instances": []}

13 个 cell、57 条绘制操作装进 1 次批量请求,其中 10 条是零宽线——正是 Port 标记的三角轮廓。用 ledit.layout_view 设视图到某个 Port 附近,再 ledit.save_image 截这块区域:

ledit.layout_view 设置矩形后 ledit.save_image 渲染的局部区域:两个焊盘、一条路由,以及 Port 标记三角形显示为一条细线(零宽 wire,读回 width_um 为 0,4 个点)
用 layout_view(rect_um=...) 缩到一个 Port 标记附近再 save_image:两个焊盘、一条路由、以及 Port 三角形——它作为零宽 wire 过桥,读回 width_um: 0、4 个点,L-Edit 把它渲染成一条细线,而不是填色三角形。

破坏性命令:显式目标、会拒绝

ledit.delete_cell 按显式名字删除 cell;除非 force=true,否则遇到“是当前可见 cell”“被其他 cell 实例化”“是 T-Cell 生成器”都会拒绝,并点名引用者。真实的拒绝:

delete_cell failed: refusing to delete 'Port$17': it is instanced 1 time(s) by ROUTE_05_FANOUT

ledit.rename_cell 改名,新名已存在则拒绝。ledit.delete_objects 按图层和/或 rect_um 删形状——只删边界框完全落在矩形内的对象,一条只是穿过矩形的路由留着不动;实例不受影响(清整个 cell 用 clear_cell)。真实返回(先 rename 再 delete_objects):

{"old": "DRC_DEMO", "new": "DRC_DEMO_RENAMED"}
{"cell": "DRC_DEMO_RENAMED", "deleted": 1, "by_layer": {"Poly": 1}}

ledit.close_design 按名字关闭一个已打开的设计;有未保存改动时会拒绝,除非 discard=true——这是清理 klink 自建 scratch 设计(new_design)的唯一办法,因为关掉设计的最后一扇窗口不会关闭设计本身:

{"file": "klink_site_demo", "closed": true, "discarded_changes": true}

ledit.push_cell_tree 本身也变得更安全:发送前先逐条校验每个绘制项;一批请求里任何一步失败,会把这次调用里新建的 cell 删掉(rolled_back),并把清空过的既有 cell 报成 clobbered——不留半成品。

DRC 与 GDS 回程

ledit.run_drc 用设计里已加载的规则集跑 L-Edit 自己的 DRC(整个 cell,或 rect_um 限定一块区域),只报错误数量和 status——L-Edit v16.3 的 UPI 不暴露违例几何,要看几何请走 ledit.export_gds 导出后交给 klink 的 KLayout 侧 DRC 工具。ledit.drc_summary 不重新跑,只读上一次结果;首次跑之前 errors 是 null,status 是 "needed"。

一个 DRC 演示:两个相距 0.4 微米的 Poly 方块,设计加载了 82 条规则:

drc_summary(跑之前):  {"cell": "DRC_DEMO", "errors": null, "status": "needed"}
run_drc(整个 cell):    {"cell": "DRC_DEMO", "errors": 1, "status": "failed", "rules": 82}
run_drc(只框左边方块): {"cell": "DRC_DEMO", "errors": 0, "status": "failed", "rules": 82, "rect_um": [-1, -1, 4, 6]}
两个相距 0.4 微米的 Poly 方块;ledit.run_drc 用设计的 82 条规则报错误数 1、status failed,只框住左边方块的区域跑报错误数 0
两个刻意留了 0.4 微米间距的 Poly 方块,触发一条间距规则:run_drc 在整个 cell 上报 errors: 1 / status: failed(82 条规则);只框住左边方块的区域跑报 errors: 0——数量对得上,但没有违例的具体形状或坐标。

ledit.export_gds 把整个设计或一个 cell(连同层级)用 LFile_ExportGDSII 写成 GDS 文件——这是 L-Edit 到 KLayout 最便宜的回程:KLayout 那边 layout.file_info / layout.import_file 原样读取,这个方向不需要任何补齐字节。真实导出 + KLayout 侧读回:

export_gds:         {"bytes": 4096, "scope": "specified_cell", "cell": "ROUTE_05_FANOUT"}
klayout_file_info:  {"top_cells": ["ROUTE_05_FANOUT"], "dbu": 0.001,
                      "layers": [{"layer":1,"datatype":0}, {"layer":11,"datatype":0},
                                 {"layer":996,"datatype":99}, {"layer":999,"datatype":1},
                                 {"layer":999,"datatype":99}]}
ledit.export_gds 写出的 GDS 文件在 KLayout 里打开(layout.show_file),顶层 cell 为 ROUTE_05_FANOUT,5 个图层,带 KLayout 的比例尺
export_gds 写出的 GDS 用 layout.show_file 在 KLayout 里打开:顶层 cell ROUTE_05_FANOUT,5 个图层,和上面 klayout_file_info 的读数一致。

T-Cell:双向参数化

T-Cell 的生成器代码存在 cell 属性里,桥可以整段读出——参数名从代码的 DO-NOT-EDIT 段稳定解析。由此打通两个方向,验收标准只有一个:逐字节一致(整数纳米坐标逐箱相等,以 L-Edit 实际生成的几何为真值)。

python tcell_workflows.py read NFET_Generator      # 参数与默认值
python tcell_workflows.py variants NFET_Generator --paramsets "[...]" --out ex.json
python tcell_workflows.py writeback MyGen --code gen.cpp --params "[...]"
python tcell_workflows.py verify MyGen --reference ref.py:boxes --paramsets "[...]"
python tcell_workflows.py fit MyGen --paramsets "[...]" --check "[...]" --out fit.json --register MY_DEVICE
python tcell_workflows.py to_pcell MyGen --reference ref.py:boxes --params-spec spec.json --paramsets ps.json --check chk.json --register MY_DEVICE
  • T-Cell → KLayout(纯几何路线):fit(v0.2.2)采样样本并拟合 v3 重复组模型——数量、间距、位置全部是精确整数律,在留出检查点上与 L-Edit 新鲜变体逐字节对决,然后注册为 KLayout PCell(放置同样逐字节验证)。模型表达不了或钉不死的结构一律 REFUSE 并点名箱族——交替/奇偶结构请走下面的 to_pcell 移植路线。
  • T-Cell → KLayout(代码移植路线,to_pcell):to_pcell 是这条移植路线的脚手架化版本——三个阶段全部过关才注册,任何一步不过直接以非零退出码退出并给出指令性文字,绝不留半成品。第 1 步:把你移植好的参考生成器(Python,entry(params) -> {层名: [[x1,y1,x2,y2] 整数纳米盒子]},harvest 原生格式,和 verify 用的字典结构完全一致)在若干 --paramsets 上与 L-Edit 新鲜变体逐字节对决;第 2 步:同一个函数原样注册成原生 KLayout PCell(库名 klink_custom;机制是新的 klink.domains.structdevice.pcell_native,注册走显式的 exec.python 逃生舱——刻意不开一条能直接接收代码的 plugin RPC;层名按你设计自己的层表映射到 GDS L/D 号,没在层表里的 GDS 号一律拒绝,klink 从不替你发明);第 3 步:验收循环——在每个 --check 点把注册好的 PCell 真的放进 live KLayout、把画出的盒子收割回来,再和一份新鲜的 L-Edit 变体逐字节对决。这条路线覆盖 v3 拟合器 REFUSE 的那类结构——奇偶交替的手指、M×L 双线性延展、按计数分档的结构——完整的 L/W/M 参数保真度。
  • KLayout → T-Cell:agent 生成 UPI 代码 → writeback 写入(同时定义参数表)→ L-Edit 现场编译 → 得到和手写无异的原生参数化 T-Cell。

下面是一次真实运行,针对的是 L-Edit 自带的 NFET_Generator 范例——正是拟合器会因为奇偶/M×L 结构而 REFUSE 的那个器件:

$ python tcell_workflows.py to_pcell NFET_Generator --reference nfet_ref.py:nfet_boxes \
    --params-spec spec.json --paramsets ps.json --check chk.json --register NFET_KLINK
step 1/3: byte-exact verify -- nfet_boxes vs L-Edit NFET_Generator at 5 paramset(s) ...
{'L': 2, 'W': 5, 'M': 1}: 10 boxes -> BYTE-EXACT
{'L': 2, 'W': 8, 'M': 2}: 16 boxes -> BYTE-EXACT
{'L': 3, 'W': 12, 'M': 3}: 24 boxes -> BYTE-EXACT
{'L': 2.5, 'W': 9, 'M': 4}: 24 boxes -> BYTE-EXACT
{'L': 2, 'W': 9.5, 'M': 2}: 16 boxes -> BYTE-EXACT
VERDICT: ALL BYTE-EXACT
step 2/3: registering 'NFET_KLINK' in library 'klink_custom' ...
registered KLayout PCell NFET_KLINK (library klink_custom, params ['L', 'W', 'M'])
step 3/3: acceptance loop -- 3 --check point(s), live KLayout placement vs fresh L-Edit variant ...
  [1/3] {'L': 4, 'W': 15, 'M': 5} ... BYTE-EXACT (34 boxes)
  [2/3] {'L': 2, 'W': 6, 'M': 2} ... BYTE-EXACT (13 boxes)
  [3/3] {'L': 3.5, 'W': 10.5, 'M': 3} ... BYTE-EXACT (20 boxes)
SUCCESS
三个由 to_pcell 注册出的原生 PCell 在 KLayout 里的实例,分别标注 M=1、M=2、M=5,手指数随 M 增加,接触行数随 W 呈 floor 律,金属列在上下轨之间交替勾连
一个合成演示器件(自定的任意尺寸,不对应任何真实工艺)在 M=1 / M=2 / M=5 三组参数下由 to_pcell 注册出的原生 PCell 现场画出:手指数量跟着 M 走,接触行数按 W 的 floor 律走,金属列在上下轨之间交替勾连——这正是拟合器会 REFUSE 的那种奇偶结构,to_pcell 把它原样搬进 KLayout,逐字节保真。

已知边界与排障

症状 / 边界含义 / 修法
心跳停了(hello.json 变旧)L-Edit 弹了模态对话框(常见于 T-Cell 编译错误)——关掉即恢复
改了 T-Cell 代码但几何没变L-Edit 缓存变体:换一组参数值,或 Tools → Regenerate T-Cells
画进已有 cell 内容翻倍draw 是追加式——重新生成请用 clear_cell 或新 cell
两个 L-Edit 同时开桥当前单命名空间,只保留一个装宏的实例
层的 GDS 号是 -1导出 GDS 会错——桥建层会盖号;写回代码请沿用模板的 need_layer 范式
工具报 macro 太旧 / ERR_MACRO_TOO_OLD重新装载宏:Tools → klink: Bridge Stop,然后 Tools → Macro → Load Macro…(错误信息本身会打出宏文件的路径)
run_drc 拿不到违例几何设计如此——L-Edit v16.3 的 UPI 只给数量和 status;要几何就 export_gds 后走 KLayout 侧 DRC 工具
关掉最后一扇窗口,设计还在符合设计——关窗口不等于关设计,用 ledit.close_design 才真正关闭
save_image 只是给人看的验证请用 get_cell / status,同 KLayout 截图的规矩——不要拿图片当验证证据

范围说明:参数化的智能(拟合、移植、验证)在 klink/KLayout 侧完成,L-Edit 收到的是原生结果(静态 cell 或真 T-Cell)。桥不是 L-Edit 插件,是随 klink 发行的外部适配层。