Length Matching 算法设计文档
1. 算法概述
1.1 目的
Length Matching 算法用于在PCB布线中添加trombone(长号)风格的蛇形走线结构,以实现等长匹配。这对于高速信号(如DDR、USB、PCIe等)的时序对齐至关重要。
1.2 核心思想
通过在走线路径中插入垂直的锯齿状蛇形结构,在不显著增加走线面积的情况下延长走线长度。算法自动寻找最长的直线段,并在该位置插入蛇形结构,同时进行严格的碰撞检测以确保不违反设计规则。
2. 算法输入与输出
2.1 输入参数
| 参数 | 类型 | 说明 |
|---|---|---|
net_results |
Dict[str, dict] |
网络名称到路由结果的映射字典 |
net_names |
List[str] |
需要进行等长匹配的网络名称列表 |
config |
GridRouteConfig |
布线配置对象 |
pcb_data |
PCBData |
PCB数据,包含所有线段、过孔、焊盘 |
prev_group_segments |
List[Segment] |
前一组已处理网络的线段(用于组间碰撞检测) |
prev_group_vias |
List[Via] |
前一组已处理网络的过孔(用于组间碰撞检测) |
2.2 输出结果
| 返回值 | 类型 | 说明 |
|---|---|---|
net_results |
Dict[str, dict] |
修改后的网络路由结果,包含添加蛇形后的新线段 |
| 每个网络结果包含: | ||
- new_segments |
List[Segment] |
蛇形化后的走线段列表 |
- new_vias |
List[Via] |
过孔列表 |
- length_matched |
bool |
是否成功完成等长匹配 |
3. 算法流程
3.1 主流程 apply_length_matching_to_group()
┌─────────────────────────────────────────────────────────────────┐
│ apply_length_matching_to_group │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: 筛选成功路由的网络并计算当前长度 │
│ - 提取每个网络的 new_segments 和 new_vias │
│ - 计算每个网络的当前走线长度 │
│ - 记录网络类型(单端/差分对) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: 确定目标长度 │
│ - 找出组内最长网络的长度作为 target_length │
│ - 设置允许误差 tolerance (默认 0.1mm) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 构建空间索引 (ClearanceIndex) │
│ - 将所有已布线对象的线段、过孔、焊盘建立空间索引 │
│ - 网格大小: SPATIAL_CELL_SIZE (2.0mm) │
│ - 用于快速碰撞检测查询 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: 按长度排序处理网络 │
│ - 从最长的网络开始处理(最长的可能不需要添加蛇形) │
│ - 最短的网络最后处理(需要添加最多的蛇形) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 5: 对每个网络应用蛇形化 │
│ 调用 _apply_meanders_to_net_with_iteration() │
│ (详见 3.2 节) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 6: 更新已处理对象列表 │
│ - 将当前网络的新线段和过孔添加到已处理列表 │
│ - 供后续网络的碰撞检测使用 │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回修改后的 net_results
3.2 单网络蛇形化流程 _apply_meanders_to_net_with_iteration()
┌─────────────────────────────────────────────────────────────────┐
│ _apply_meanders_to_net_with_iteration │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 初始化: 计算需要添加的额外长度 │
│ delta = target_metric - current_metric │
│ extra_length = extra_length_func(delta) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ 是差分对? │
└─────────────────┘
│ │
是 否
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ apply_meanders_ │ │ apply_meanders_to_ │
│ _to_diff_pair │ │ _route (单端网络) │
└──────────────────┘ └──────────────────────┘
│ │
└─────┬─────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: 迭代添加蛇形直到满足长度要求 │
│ while (current < target - tolerance) and (iterations < 20): │
│ - 增加 bump_count │
│ - 重新计算需要的额外长度 │
│ - 调用蛇形生成函数(使用 min_bumps 参数) │
│ - 如果无法添加更多蛇形,则中断 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 振幅缩放精确匹配目标长度 │
│ for scale_iter in range(5): │
│ - 如果已满足精度要求,则跳出 │
│ - 计算缩放比例: scale = delta / actual_added │
│ - 生成试验性蛇形(使用缩放后的振幅) │
│ - 如果试验结果更好,则更新;否则中断 │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回 (修改后的结果, 线段列表, bump数量, 最终长度)
3.3 单端网络蛇形化 apply_meanders_to_route()
┌─────────────────────────────────────────────────────────────────┐
│ apply_meanders_to_route │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 1: 寻找所有直线段运行 │
│ - 调用 find_all_straight_runs() │
│ - 最小长度要求: amplitude * 2 │
│ - 按长度降序排序 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 2: 构建或使用空间索引 │
│ if clearance_index is None: │
│ clearance_index = ClearanceIndex() │
│ clearance_index.build(pcb_data, config, ...) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 3: 遍历候选直线段,尝试添加蛇形 │
│ for (start_idx, end_idx, run_length) in runs: │
│ - 检查是否与排除的中心线范围重叠 │
│ - 合并直线段为一个整体线段 │
│ - 调用 generate_trombone_meander() │
│ - 如果成功生成bump,使用此直线段并跳出循环 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Step 4: 替换原始线段 │
│ new_segments = segments[:start_idx] + meander_segs + │
│ segments[end_idx + 1:] │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回 (new_segments, bump_count)
3.4 蛇形生成 generate_trombone_meander()
┌─────────────────────────────────────────────────────────────────┐
│ generate_trombone_meander │
│ (Trombone蛇形结构生成) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 原始直线: ────────────────────────────────────> │
│ │
│ 蛇形结构: ──╮╭╮╭╮╭──> │
│ │││││││ │
│ ╰╯╰╯╰╯ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 初始化参数: │
│ - chamfer = 0.1mm (45度倒角大小) │
│ - bump_width = 4 * chamfer (每个bump的水平宽度) │
│ - max_bumps = seg_len * 0.9 / bump_width │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 添加直线导入段 (lead-in) │
│ - 在起点留出 margin 空间 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ while (需要添加更多bump): │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. 检查是否有空间放置下一个bump │ │
│ │ dist_to_end < bump_width + margin ? break │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 2. 碰撞检测: 获取安全振幅 │ │
│ │ - 调用 get_safe_amplitude_at_point() │ │
│ │ - 如果当前方向受阻,尝试相反方向 │ │
│ │ - 如果两个方向都受阻,跳过此位置 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 3. 处理同方向bump的间隔(如果需要) │ │
│ │ - 如果当前bump与前一个同方向,添加退出/进入倒角 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 4. 生成bump的各个线段: │ │
│ │ a) 进入倒角 (仅第一个bump) │ │
│ │ b) 上升立柱 (riser) │ │
│ │ c) 顶部倒角 1 (继续远离 + 前进) │ │
│ │ d) 顶部倒角 2 (开始返回 + 前进) │ │
│ │ e) 下降立柱 (riser) │ │
│ │ 注意: 不添加退出倒角(下一个bump直接连接) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 5. 更新进度: │ │
│ │ - total_extra_added += extra_this_bump │ │
│ │ - bump_count += 1 │ │
│ │ - direction *= -1 (交替方向) │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 添加退出倒角 (exit chamfer) │
│ - 将路径从 ±chamfer 偏移返回到中心线 │
│ - 使用第一个bump的方向确定退出方向 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 添加直线导出段 (lead-out) │
│ - 连接到原始线段的终点 │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回 (new_segments, bump_count)
3.5 碰撞检测与安全振幅计算 get_safe_amplitude_at_point()
┌─────────────────────────────────────────────────────────────────┐
│ get_safe_amplitude_at_point (二分搜索法) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 计算 clearance 需求: │
│ - meander_clearance_margin = grid_step / 2 │
│ - corner_margin = track_width / 2 * CORNER_BLOAT_FACTOR │
│ - required_clearance = track_width + clearance + margins │
│ - via_clearance = via_size/2 + track_width/2 + clearance │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 准备测试振幅列表 (二分搜索): │
│ test_amplitudes = [max_amplitude, 0.7*max, 0.49*max, ..., │
│ min_amplitude] │
│ (每次乘以0.7,直到达到min_amplitude) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ for test_amp in test_amplitudes: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. 生成当前振幅的bump线段 │ │
│ │ bump_segs = get_bump_segments(...) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 2. 计算bump的边界框 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 3. 使用空间索引查询附近对象: │ │
│ │ - nearby_segments = clearance_index.query_segments() │ │
│ │ - nearby_vias = clearance_index.query_vias() │ │
│ │ - nearby_pads = clearance_index.query_pads() │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 4. 逐个检查碰撞: │ │
│ │ for bump_seg in bump_segs: │ │
│ │ for other in nearby_objects: │ │
│ │ if same_layer and not_same_net: │ │
│ │ dist = segment_to_segment_distance(...) │ │
│ │ if dist < required_clearance: │ │
│ │ conflict_found = True; break │ │
│ └──────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 5. 如果无冲突,返回当前振幅 │ │
│ │ if not conflict_found: │ │
│ │ return test_amp │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
返回 0 (所有振幅都有冲突)
4. 关键数据结构
4.1 ClearanceIndex 空间索引
python
class ClearanceIndex:
"""
空间索引,用于高效的碰撞检测。
将PCB板划分为网格单元,每个单元格存储与之重叠的
线段/过孔/焊盘引用。这允许高效查询特定区域内的项目,
而无需检查所有项目。
"""
cell_size: float = 2.0 # 网格单元大小 (mm)
# 单元格 -> (线段, 层) 列表
segment_cells: Dict[Tuple[int, int], List]
# 单元格 -> 过孔列表
via_cells: Dict[Tuple[int, int], List]
# 单元格 -> (焊盘, 扩展层) 列表
pad_cells: Dict[Tuple[int, int], List]
工作原理:
- 构建阶段: 将每个对象映射到其覆盖的所有单元格
- 查询阶段: 只检查查询区域所覆盖的单元格中的对象
- 优势: 将碰撞检测复杂度从 O(N) 降低到 O(K),其中 K 是区域内对象数量
4.2 蛇形Bump几何结构
单个bump由以下线段组成:
P点 (centerline + t*chamfer方向)
│
│ riser_height
▼
╔═════╝ ← 顶部倒角2 (返回)
║
╚═════╗ ← 顶部倒角1 (远离)
│
│ riser_height
▼
C点 (centerline)
│
╔═════╝ ← 进入倒角 (仅第一个bump)
│
─────S点─────────── ← 中心线
参数说明:
chamfer = 0.1mm: 45度倒角边长riser_height = amplitude - 2*chamfer: 垂直立柱高度bump_width = 4*chamfer: 单个bump的水平占位
5. 算法原理
5.1 Trombone蛇形原理
Trombone蛇形通过在走线路径中添加垂直于走线方向的往返运动来延长走线。
长度增加计算:
原始直线长度: L = bump_width
单个bump长度:
- 有进入倒角: 3*chamfer*√2 + 2*riser_height
- 无进入倒角: 2*chamfer*√2 + 2*riser_height
额外长度 = bump长度 - bump_width
≈ 2*amplitude (当 amplitude >> chamfer)
5.2 碰撞检测原理
算法使用线段到线段距离进行精确的碰撞检测:
distance(S1, S2) =
if S1, S2 平行:
端点到直线的最小距离
else:
|(P2 - P1) × (P2 - P3)| / |P2 - P1|
** Clearance 考虑因素:**
- 基础 clearance :
track_width + clearance - 拐角补偿 :
track_width/2 * (√2 - 1)--- 45度拐角处铜皮会延伸 - 网格对齐容差 :
grid_step / 2--- 网格合并时可能的坐标偏移 - 差分对特殊处理: 对内匹配使用较小的 clearance
5.3 振幅缩放原理
当初始蛇形生成后,如果实际增加的长度与目标有偏差,算法通过缩放振幅进行精确调整:
scale = target_delta / actual_added
new_amplitude = base_amplitude * scale
迭代上限:
- 最多5次缩放迭代
- 最小振幅限制: 0.2mm
- 如果缩放后无改善则中断
6. 配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
meander_amplitude |
2.0mm | 蛇形最大振幅 |
track_width |
0.2mm | 走线宽度 |
clearance |
0.15mm | 最小间距 |
via_size |
0.6mm | 过孔外径 |
grid_step |
0.05mm | 网格步长 |
diff_chamfer_extra |
2.0 | 差分对倒角额外倍数 |
7. 边界情况处理
7.1 无足够空间放置蛇形
- 返回原始线段
- 记录警告信息
- bump_count = 0
7.2 部分空间受限
- 逐点检测安全振幅
- 动态调整每个bump的振幅
- 必要时跳过某些位置
7.3 差分对内匹配
- 使用中心线蛇形化
- P/N走线由中心线偏移生成
- 中心线与P/N使用最小 clearance
7.4 同方向bump处理
- 添加退出/进入倒角返回中心线
- 如果退出方向受阻,使用平面段替代
8. 性能考虑
8.1 时间复杂度
- 空间索引构建: O(N),N为对象数量
- 单个bump碰撞检测: O(K),K为附近对象数量
- 完整蛇形生成: O(B*K),B为bump数量
- 总复杂度 : O(N + GBK),G为网络组数量
8.2 优化策略
- 空间索引: 大幅减少碰撞检测的检查对象数量
- 层级检查: 先检查层,再检查网络,最后计算距离
- 提前退出: 发现冲突立即中断当前振幅测试
- 二分搜索: 快速找到最大安全振幅
9. 示例
9.1 单端网络等长匹配
python
# 输入: 3个网络,目标长度 20mm
net_names = ['NET_A', 'NET_B', 'NET_C']
current_lengths = {'NET_A': 18.5mm, 'NET_B': 17.2mm, 'NET_C': 16.0mm}
# 处理后:
# NET_A: 添加 1.5mm (2-3个bump)
# NET_B: 添加 2.8mm (4-5个bump)
# NET_C: 添加 4.0mm (6-7个bump)
9.2 差分对等长匹配
python
# 输入: DDR4 DQ 差分对组
diff_pairs = ['DQ0_P', 'DQ0_N', 'DQ1_P', 'DQ1_N', ...]
# 算法自动:
# 1. 计算每对的中心线长度
# 2. 在中心线上添加蛇形
# 3. 从蛇形中心线重新生成P/N走线
# 4. 确保所有差分对等长
10. 总结
Length Matching 算法通过以下关键技术实现高效的等长匹配:
- 智能位置选择: 自动寻找最长的直线段进行蛇形化
- 精确碰撞检测: 空间索引 + 二分搜索确保不违反设计规则
- 自适应振幅: 动态调整每个bump的振幅以适应局部约束
- 精确长度匹配: 迭代添加 + 振幅缩放确保满足精度要求
- 差分对支持: 中心线蛇形化自动处理差分对约束
该算法已成功应用于DDR4、USB、PCIe等多种高速接口的等长匹配。