前置条件
- 安装 klink:
pip install klayout-klink(一条命令装好 klink 和它的 Rust 内核,无需第三方科学库)。 - KLayout 正在运行,并已加载 klink 插件(RPC 端口默认
8765)。 - 建一个项目并进入:
klink init nano-demo && cd nano-demo——完整脚本就是python example_template/nanodevice/ebl_wraparound.py --live(想留住生成的 cell 便于查看再加--keep)。仓库 checkout 里等价的是python -m examples_klink.public.demos.nanodevice.ebl_wraparound --live --keep。
build_wraparound_demo() 内部一次性完成的批量调用拆成 7 个可观察的阶段。构造顺序是固定的:4 个对准十字(每个 3 个 item)→ flake 多边形 → keepout 框 + 文字 → 8 组接触(每组 7 个 item,顺序固定为 contact box / M1 走线 / via / M2 绕行走线 / 锥形过渡 / pad / 标签)→ 拼接 patch → writefield keepout 框。教程按这个真实顺序对 shape_items 做切片,没有引入教程专属的绘制逻辑。1建 cell + 层规划
这个器件的所有层都是 example-owned(example_template/nanodevice/ebl_wraparound.py 里的 WRAP_LAYERS),klink 本身不带任何工艺层。真实脚本用一个通用的 _ensure_layers helper 扫一遍 shape_items 里出现过的每个 (layer, datatype),自动建齐——包括 klink 保留的写场 keepout 层 900/0(它是从 writefield 障碍框的 shape item 里自然出现的,不用单独声明):
| 层 | 用途 |
|---|---|
30/0 | flake(器件有源区) |
10/0 | M1(近场接触走线) |
11/0 | M2(绕行走线到远端 pad) |
40/0 | via(接触过孔) |
20/0 | 探针 pad |
6/0 | 标签 + 对准标记 + 局部 keepout 框 |
113/0 | 写场拼接 patch |
900/0 | klink 保留的写场 keepout 层(自动出现) |
999/99 | klink 保留的 Port 标记层 |
999/1 | klink 保留的 Anchor 标记层 |
from klink import KLinkClient
from klink.domains.nanodevice.devices.wraparound import (
ANCHOR_LAYER, CELL, PORT_LAYER, build_wraparound_demo,
)
def _ensure_layers(client, items):
seen = set()
for item in items:
key = (int(item["layer"]), int(item.get("datatype", 0)))
if key not in seen:
seen.add(key)
client.layer_ensure(key[0], key[1], name=f"NANODEVICE_{key[0]}_{key[1]}")
with KLinkClient(port=PORT).connect() as c:
bundle = build_wraparound_demo(WRAP_LAYERS)
c.cell_create(CELL)
_ensure_layers(c, bundle["shape_items"])
c.call("port.set_layer", {"layer": PORT_LAYER})
c.call("anchor.set_layer", {"layer": ANCHOR_LAYER})
2画 flake + 局部 keepout + 对准标记
中心是一块不规则多边形 flake(真正的器件有源区),外面套一个局部 keepout 框([-54,-44,54,44],标签 "flake + local keepout")防止后续走线太靠近;四个角上各放一个对准十字,供 EBL 系统做全局对准:
c.shape_insert_many(CELL, bundle["shape_items"][0:15]) # 12 个对准十字 item + flake 多边形 + keepout 框 + 文字
30/0)+ 局部 keepout 框(6/0)+ 四角对准十字(6/0)。3画近场接触 + via
flake 周围伸出 8 条窄 M1 走线(C0–C7),从 flake 边缘附近的接触点斜着连到各自的 via——这一段是"近场"布线,尺度和 flake 本身相当:
contact_block = bundle["shape_items"][15:71] # 8 组 x 7 个 item
stage3 = [it for i, it in enumerate(contact_block) if i % 7 in (0, 1, 2)] # contact box / M1 走线 / via
c.shape_insert_many(CELL, stage3)
10/0)+ 8 个 via(40/0),像蜘蛛一样从 keepout 框边缘辐射出去。4绕行走线到探针 pad
这才是 "wraparound" 名字的由来:每条 via 到 pad 之间不是走直线,而是先水平/垂直拉到芯片边界附近,再拐向远端探针 pad——这样可以让 8 个 pad 均匀分布在芯片四周,同时避开 flake 和相邻走线:
stage4 = [it for i, it in enumerate(contact_block) if i % 7 in (3, 4, 5, 6)] # M2 绕行走线 / 锥形过渡 / pad / 标签
c.shape_insert_many(CELL, stage4)
11/0,含锥形过渡)+ 8 个探针 pad(20/0,标注 C0–C7)。5标记 Port + Anchor 意图
和 Hall bar 教程不同:这里几何是先手工画好的,Port/Anchor 是事后补标的,不是路由前的意图声明。原因是写场拼接走廊是硬约束,路径形状必须先按走廊定死,klink 的路由算法不适合在这种强约束下从零求解;Port/Anchor 在这里的作用是记录意图,供后面的验证步骤核对:
for port in bundle["port_marks"]: # 16 个:8 接触 + 8 pad
payload = dict(port); payload["cell"] = CELL
c.call("port.mark", payload)
for anchor in bundle["anchor_marks"][0:8]: # 8 个 via waypoint anchor
payload = dict(anchor); payload["cell"] = CELL
c.call("anchor.mark", payload)
999/99)+ 8 个 via waypoint Anchor(999/1,标签 via (wrap_0) … via (wrap_7))。6writefield 避障规划
这是这篇教程和 Hall bar 教程真正不同的地方。klink.domains.nanodevice.ebl.writefield.plan_writefields() 把 460×340 µm 的芯片按 115 µm 写场尺寸切成 4×4=16 个写场,在每条写场边界上画出 keepout 障碍框(klink 保留层 900/0)——除了 11 个预先规划好的"允许穿越窗口"。只有在这些窗口对应的位置,走线才被允许跨越写场边界;窗口本身用 corridor 类型的 Anchor 标出来:
obstacle_items = bundle["shape_items"][71 + bundle["patch_report"]["patch_count"]:] # 17 个 900/0 keepout 框
c.shape_insert_many(CELL, obstacle_items)
for anchor in bundle["anchor_marks"][8:]: # 11 个 corridor 窗口 anchor
payload = dict(anchor); payload["cell"] = CELL
c.call("anchor.mark", payload)
900/0)+ 11 个 corridor 窗口 Anchor(标签 WF_XL_MID、WF_Y0_WIDE 等)——已经画好的走线全部从窗口缺口穿过,不碰墙。7拼接 patch + 收尾
光刻写场拼接处存在对准误差,generate_wf_patches()(移植自 Klayout-Router 的 Auto-patching 宏)会在每条走线穿越写场边界的地方补一个方形 patch 做误差补偿。最后写入 cell 名标签,看整体和一处细节:
patch_items = bundle["shape_items"][71:71 + bundle["patch_report"]["patch_count"]]
c.shape_insert_many(CELL, patch_items)
view.zoom_fit):flake + 8 接触 + 8 绕行走线 + 写场网格/keepout/走廊 + 8 pad + 12 个拼接 patch。
C0 网络的 M2 绕行走线穿过 x=-115 写场边界的位置,正好落在 WF_XL_MID 走廊窗口内;灰色方块是拼接 patch,可以看到窗口上方 keepout 墙重新出现。
验证,不是截图
和教程首页说的一样,截图只是给人看的,不是完成依据。这次实际运行的构建报告:
{
"shape_items": 100,
"ports": 16,
"anchors": 19,
"patches": 12,
"wf_obstacles": 17,
"wf_crossings": 20,
"wf_crossing_violations": 0,
"route_centerline_overlaps": 0
}
wf_crossings=20 且 wf_crossing_violations=0,说明这 20 次"走线穿越写场边界"全部落在预先规划的走廊窗口内,没有一次直接撞在 keepout 墙上;route_centerline_overlaps=0 说明 8 条不同网络的走线中心线互不重叠、没有短路风险。这两个数字合在一起,才是"这版图对写场约束合规"的依据,而不是"看起来穿过去了"。
下一步
如果还没看过第一篇教程,建议先读《分步教程:画一个 Hall bar 器件》——它是这篇教程的前置:同样的 Port/Anchor/geometry-first 流程,但走线是 klink 路由算法先标记后求解,而不是像这里手工画好再补标。把 WRAP_LAYERS 和 build_wraparound_demo 里的 crossing_windows 改成你自己的写场尺寸、走廊位置,重跑一遍确认 wf_crossing_violations=0;Port/Anchor 的完整字段说明见 MCP 参考,更多离线器件背景见核心思路教程 · 离线器件教程。