这是什么
five_routers.py 建五个小场景(各自的设备/pad 几何、Port、Anchor、一个 keepout),逐个路由,再逐个验证。诚实之处在于:五个场景的路由调用完全一样——都是先用 route_with_port_launch_stubs 画一条参考中心线 guide(只给人看,不参与验证),再用 route_tapered 规划出真正的锥形多边形路由、commit_tapered_routes 一次批量写入。场景之间的差异只在 Phase 1 建场景时喂给路由器的输入:要不要 Anchor、Anchor 是什么 kind、端口能不能滑动、障碍物 bbox 是什么。没有为任何一个场景切换到"另一个路由器"。
前置条件
- 安装 klink:
pip install klayout-klink。 - KLayout 正在运行,并已加载 klink 插件(RPC 端口默认
8765)。 - 需要一个 live 的 KLayout 会话——五个场景都是直接写入 live 版图的 RPC 调用,不是纯离线合成:
python example_template/routing/five_routers.py(--port <端口>指定 session,默认 8765)。
FIVE_ROUTERS_SCRATCH 只是占位脚手架 cell),五个真实场景 cell 在里面各自重建;跑完脚本会自己关掉这个 tab、把你原来的 tab 切回来——不会碰你手工的工作 tab。涉及的层
| 层 | 用途 |
|---|---|
999/99 | klink 保留的 Port 标记层 |
999/1 | klink 保留的 Anchor 标记层 |
1/0 | M1_DEVICE_OR_PAD:每个场景自己的设备/pad 几何 |
900/0 | KLINK_ROUTE_KEEPOUT:场景 ④ 里要绕开的 keepout 障碍物 |
996/99 | KLINK_EXPECTED_ROUTE_GUIDE:route_with_port_launch_stubs 画的参考中心线,只给人核对,不参与验证 |
11/0 | KLINK_TAPERED_RESULT:route_tapered → commit_tapered_routes 真正写入的锥形路由多边形 |
997/99 | KLINK_EXAMPLE_LABELS:每个场景顶部的说明文字 |
每个场景的路由调用都是同一个两步模式:
g = route_with_port_launch_stubs(p0, p1, inner_points or None)
guide(c, cell, g["points_um"], width=g["width_um"]) # 996/99 参考线,仅供人眼核对
t = route_tapered(p0, p1, inner_points or None, strategy="uniform", corner_style="miter")
write = commit_tapered_routes(c, cell, [t], route_layer="11/0", clear=False) # 真正的锥形路由
1直连基线
最简单的场景:两个同网络的端口,中间没有任何 Anchor。端口 A 在 [20, 5]、朝向 0°、线宽 5 µm;端口 B 在 [100, 5]、朝向 180°、线宽 2 µm——路由从 5 µm 平滑收窄到 2 µm,中间没有一次拐弯。
p0 = port(c, cell, "A", [20, 5], 0, net="net_straight", width=5.0)
p1 = port(c, cell, "B", [100, 5], 180, net="net_straight", width=2.0)
# inner_points 为空 -> route_tapered(p0, p1, None, ...)
2强制途经点
端口位置不变(分别在 18 µm 和 100 µm 处),但这次中间放了一个 waypoint_region 类型的 Anchor WP1,中心在 [60, 40]、12×10 µm——路由必须穿过这个框,而不是走两个端口之间最短的直线。klink 把它变成一个途经点,塞进 route_tapered 的 inner_points。
anchor(c, cell, "WP1", [60, 40], "waypoint_region", net="net_waypoint",
label="must_pass", width=12, height=10)
# requests: inner_points=[[60, 40]]
WP1 那个 waypoint_region Anchor(图中矩形标记),穿过它之后再拐向对面的端口——不是两端口之间最短的直线。3边缘滑动端口
A_EDGE 端口的 access_mode="edge"、slide_allowed=True,还带一个 slide_edge 参数——一段用数据库单位(当前默认 1 nm)表示的边缘坐标 "20000,40000,140000,40000",告诉路由器这个端口不是钉死在一个点上,而是可以沿设备顶边(20~140 µm 那一段)滑动,由路由器自己挑一个对布线最有利的出发位置。另一端 B 是普通端口;中间还有一个 EXIT waypoint_region Anchor,强制走线先冲出设备正上方再拐向 B。
# 存成 DBU 是因为当前默认数据库单位是 1 nm;这个示例是刻意把
# 面向路由器的 slide edge 写清楚,方便照抄。
slide_edge = "20000,40000,140000,40000"
p0 = port(c, cell, "A_EDGE", [80, 40], 90, net="net_slide", width=6.0,
access_mode="edge", slide_allowed=True, slide_edge=slide_edge)
p1 = port(c, cell, "B", [150, 72], 180, net="net_slide", width=3.0)
A_EDGE 没有钉死在一个固定坐标,路由器沿设备顶边选了一个出发点,先穿过 EXIT waypoint_region 再拐向 B。4绕开障碍物
900/0 层上画着一个 keepout box [52, -18, 86, 28](标着 KEEP_OUT 文字)。路由必须完全绕开它,不能只是擦边而过。Anchor BEND_ABOVE 是 bend_region 类型、priority=10,中心在障碍物正上方 [69, 42]——但只传这一个途经点还不够:单点只保证走线经过那个点,不保证整条走线全程都在障碍物外面。所以脚本给 route_tapered 传了两个 inner_points([48, 42] 和 [90, 42]),让走线整体贴着障碍物上方绕过去,而不是只在锚点处避开、别处又切回障碍物内部。
keepout_bbox = [52.0, -18.0, 86.0, 28.0]
box(c, cell, keepout_bbox, layer=KEEPOUT)
anchor(c, cell, "BEND_ABOVE", [69, 42], "bend_region", net="net_obstacle",
label="above", radius=6, priority=10)
# 两个途经点,让走线整体绕过去,只经过锚点中心是不够的
requests = [{"source": p0, "target": p1, "inner_points": [[48, 42], [90, 42]]}]
900/0 层上的 KEEP_OUT 障碍物、以及障碍物正上方的 BEND_ABOVE bend_region Anchor——这是路由器要处理的全部约束,此时还没有任何走线。
KEEP_OUT 保持间隙——这正是 Phase 3 里 obstacle_hit_count 要验证为 0 的地方。5需求端口扇出
四个需求端口 IN0..IN3(3 µm 宽,net=sig0..sig3)要连到六个候选 pad PAD0..PAD5 中的四个(8 µm 宽,port_type="candidate_sink",net="")——每条走线都从 3 µm 一路放宽到 8 µm。两组信号各自走一条命名 corridor Anchor(LOWER_CORRIDOR 带 net="sig0,sig1"、UPPER_CORRIDOR 带 net="sig2,sig3"),保证同组两条线沿同一条走廊前进,而不是各走各的。同一条走廊里的两条线还需要一个显式的车道偏移(lane_offset)才能互不接触:脚本注释里记录了一个真实教训——最初 ±3 µm 的车道间距不够,因为 route_tapered 中段的宽度已经超过 6 µm,±3 µm 的车道会重叠;改成 ±5 µm 才是这个几何下第一个能让 sibling_overlap_count 归零的间距。
anchor(c, cell, "LOWER_CORRIDOR", [45, 17], "corridor", net="sig0,sig1",
label="follow_lower", width=8.0, path_points="-15,-1;10,0;15,1")
anchor(c, cell, "UPPER_CORRIDOR", [75, 49.5], "corridor", net="sig2,sig3",
label="follow_upper", width=8.0, path_points="-9,-0.5;6,0.5;9,-1")
# +-3um 车道不够(route_tapered 中段宽度已超 6um,会重叠);
# +-5um 是这个几何下第一个能让 sibling_overlap_count 归零的车道间距
assignments = [
(10, 0, lower_corridor, -5.0),
(24, 14, lower_corridor, 5.0),
(38, 42, upper_corridor, -5.0),
(52, 56, upper_corridor, 5.0),
]
验证,不是截图
和核心思路说的一样,截图只是给人看的,不是完成依据。five_routers.py 的 Phase 3 对每个场景做同样三件事:统计 commit_tapered_routes 真正写入的多边形数量是否等于这个场景的预期请求数(route_count)、拿每条走线去和障碍物 bbox 做碰撞检测(obstacle_hit_count)、拿每条走线去和同一场景里其它走线做重叠检测(sibling_overlap_count)——三项全部达标才把 ok 记为 True。脚本给每个场景打印一行,格式固定:
%-22s ok=%-5s route_count=%d/%d obstacle_hit_count=%d sibling_overlap_count=%d
五个场景全部干净时是这样的:
ROUTE_01_STRAIGHT ok=True route_count=1/1 obstacle_hit_count=0 sibling_overlap_count=0
ROUTE_02_WAYPOINT ok=True route_count=1/1 obstacle_hit_count=0 sibling_overlap_count=0
ROUTE_03_EDGE_SLIDE ok=True route_count=1/1 obstacle_hit_count=0 sibling_overlap_count=0
ROUTE_04_OBSTACLE ok=True route_count=1/1 obstacle_hit_count=0 sibling_overlap_count=0
ROUTE_05_FANOUT ok=True route_count=4/4 obstacle_hit_count=0 sibling_overlap_count=0
all five scenarios routed cleanly
只要有一个场景 obstacle_hit_count 或 sibling_overlap_count 不是 0,或者写入的多边形数量对不上预期请求数,那个场景的 ok 就是 False——脚本会把不达标的场景连同数字列出来,退出码非 0,绝不会把它报告成"路由完成"。
下一步
把 example_template/routing/five_routers.py 里的场景数据换成你自己的端口坐标、线宽和障碍物声明,重跑确认五行全部 ok=True。想直接对着 MCP 工具写,完整的 9 个路由工具参数在 MCP 参考 · 路由后端,Port/Anchor 的完整字段说明在 MCP 参考 · Port、Anchor 与 Region。这篇教程里"先标 Port/Anchor 意图、再让路由算法补全布线"的思路和 Hall bar 器件教程的第 ⑤~⑥ 步是同一套 geometry-first 流程,只是这里换成了五种不同的输入组合;扇出场景里走廊 + 车道的批量路由思路,在更大规模上还能看 神经电极阵列 harness 教程。