Partition 模块技术手册
左侧边栏提供全文目录导航,点击即可跳转。以下是各章节概览:
入门:总览 → 快速上手 → 算法框架 → 数据结构 |
算法:粗化 → 初始划分 → 细化 |
约束:约束体系 → 时序驱动详解 |
集成:COCP/V-Cycle → API → 参数 → FAQ
1. 模块总览与定位
Partition 模块是 EDIF_Part 项目中负责多片 FPGA 自动化逻辑分割的核心算法引擎。它接收一个以超图(Hypergraph)形式建模的电路网表,在满足资源容量、拓扑连通、固定分配等多种约束的条件下,将电路节点分配到不同的 FPGA 块上,使得跨片连线数(Cut Size)最小,同时兼顾时序质量。
Partition 模块采用 V-型多级框架(Multilevel Paradigm),即「粗化缩小 → 初始求解 → 逐层细化还原」。这种将大规模 NP-hard 问题分而治之的策略,能在可接受的时间内产出高质量解。通过多候选解并行探索、COCP 和 V-Cycle 后处理等机制进一步提升解的质量。
模块源码结构
partition.h / .cpp # 主入口与流程控制 defs.h # 核心数据结构定义(graph, fpga, PartitionParams 等) coarsen.h / .cpp # 多层粗化引擎(MultilevelCoarsener) initial.h / .cpp # 初始划分(Initial) tools.h / .cpp # 工具函数(并查集、约束校验等) refine/ RefinementFacade.h/.cpp # 细化阶段门面类(Refinement) core/ RefineTypes.h # 细化基础类型(Gain, NetPartition, PartitionState) RefinementRunTypes.h # 候选解管理(CandidateBuffers, LevelWorkset) RefinementContext.h # 配置与会话上下文 engine/ GainEvaluator.h/.cpp # 增益计算引擎 MoveApplier.h/.cpp # 移动执行与回滚 bucket/ MoveBucket.h # 增益桶(PerPart / Global 两种) PerPartBucket.cpp # 分区级增益桶实现 GlobalBucket.cpp # 全局增益桶实现 pipeline/ CandidatePoolService # 并行候选解批处理 CandidateSelector # 最优解选择 CandidateProjection # 层间投影与拓扑重映射 StrategyRunner # 策略执行驱动 StrategyPlanner # 策略选择规划 TimingMetricService # 时序度量服务 FPGARemapper # FPGA 编号拓扑优化 strategy/ IRefineStrategy.h # 策略抽象接口 StrategyFactory # 策略工厂 FM / PM / DSFM / Greedy # 各细化策略实现
2. 快速上手(30 秒最小调用)
Step 1 — 构造 graph
填写你的网表:节点(nodes)、超边(nets)、展平索引(incident_nodes, incident_nets)
Step 2 — 构造 fpga
填写目标硬件:资源容量(resources)、拓扑矩阵(topology)、距离矩阵(dist)
Step 3 — 构造 PartitionParams
使用默认参数或按需调整(候选解数、线程数、策略开关等)
Step 4 — 调用 partition()
得到 parts[i],即节点 i 被分配到的 FPGA 块编号
C++#include "partition/partition.h"
#include "partition/defs.h"
using namespace std;
// 1. 构造超图(详见第 4 节数据结构)
graph finest;
// ... 填充 nodes, nets, incident_nodes, incident_nets ...
// 2. 构造硬件平台(划分块数由 resources.size() 决定)
fpga fpgas;
// ... 填充 resources, topology, dist ...
// 3. 使用默认参数
PartitionParams para;
para.constraints.has_fix = false;
para.constraints.has_timing = false;
// 4. 调用
vector<int> parts; // 输出
HSFullTiming::HSPartitionFlow pFlow; // 不用时序时传空对象
int ret = partition(finest, fpgas, parts, para, 8, pFlow);
if (ret == 1) {
// 划分成功: parts[i] = 节点 i 归属的 FPGA 编号
for (int i = 0; i < (int)parts.size(); i++)
printf("Node %d -> FPGA %d\n", i, parts[i]);
} else {
// 划分失败(通常因资源总量超出容量)
printf("Partition failed\n");
}
3. 算法框架:多级超图划分
Partition 模块的核心算法是多级超图划分(Multilevel Hypergraph Partitioning),这是一种经典的「分而治之」策略,将大规模组合优化问题分解为三个阶段:
三阶段职责
- 将高度连通的节点合并为超节点(Supernode)
- 采用重边匹配(HEM)策略优先合并共享超边多的节点对
- 逐层缩小图规模,直到节点数降至阈值
- 保留层间映射表用于后续还原
- 在最粗层图上生成多个多样化的候选解
- 策略包括:随机分配、贪心生长、SPFA 扩展、全局生长、轮询生长等
- 保证资源约束满足
- 选出精英解进入细化阶段
- 将候选解从最粗层逐层映射回最细层
- 每层使用 FM/PM/DSFM/Greedy 等策略局部优化
- 通过增益桶(Gain Bucket)驱动节点移动
- 支持移动回滚,确保解只向更优方向前进
完整执行流程
4. 核心数据结构设计
4.1 超图模型:graph
graph 类是整个模块的核心负载,封装了超图的全部信息。它使用 CSR(Compressed Sparse Row)展平索引格式存储节点与超边的关联关系,以获得紧凑的内存布局和高效的遍历性能。
| 字段 | 类型 | 说明 |
|---|---|---|
nodes | vector<node> | 所有节点。每个 node 包含 weight、begin(incident_nets 起始偏移)、size(关联超边数)、resources(资源向量,如 [LUT, FF, DSP, BRAM]) |
nets | vector<net> | 所有超边。每个 net 包含 weight(割边权重)、begin(incident_nodes 起始偏移)、size(端点数) |
incident_nodes | vector<int> | 超边端点展平数组。通过 nets[i].begin 和 nets[i].size 索引 |
incident_nets | vector<int> | 节点关联超边展平数组。通过 nodes[i].begin 和 nodes[i].size 索引 |
fixed_assign | vector<int> | 固定分配约束。初始化全 -1;若节点 i 必须固定在某块,设为块号 |
region_fixed | vector<bool> | 区域约束标记。若节点受区域约束则为 true |
candidates | vector<set<int>> | 每个节点的候选 FPGA 集合(区域约束时有效) |
community | vector<int> | 初始划分提示或粗化中间结果 |
timingInfo 字段graph.timingInfo 类型为 timing,保留了时序路径(TimingPath)、引脚 slack(pinSlack)等数据结构的定义,但在当前流程中已不参与细化阶段的时序评估。实际的时序驱动划分完全依赖外部传入的 HSFullTiming::HSPartitionFlow &pFlow 对象(详见第 9 节)。
4.2 硬件平台:fpga
| 字段 | 类型 | 说明 |
|---|---|---|
resources | vector<VectorXi> | 每个 FPGA 块的资源容量向量(维度与 node.resources 一致) |
topology | vector<vector<int>> | 连通性邻接矩阵。非零表示直连 |
dist | vector<vector<int>> | 最短跳数矩阵(可用 Floyd-Warshall 从 topology 推导) |
maxDist | vector<int> | 每个块允许的最大跳数 |
cutweights_assignment | vector<vector<int>> | FPGA 对间的通道带宽配置 |
bound_constraint | bool | 是否启用资源严格上下限 |
upper_resources | VectorXi | 资源上限向量(需 bound_constraint=true) |
lower_resources | VectorXi | 资源下限向量 |
tdmEstimate | HSIOTdmDelay | TDM 延迟评估引擎(来自 sta 模块) |
computeCutWeight(g, parts) — 计算割边总权重 | computeCutWeights(g, parts, cut_weights) — 计算完整的带 TDM 评估的割边矩阵 | computeCutWeightsOld(g, parts, cut_weights) — 基础版本,不含 TDM 深度评估
4.3 参数配置:PartitionParams
PartitionParams 采用分层结构体设计,将参数按阶段分组。所有子结构体均支持 JSON 序列化(通过 nlohmann/json 的 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT 宏)。
4.4 评价指标:Metrics
| 字段 | 类型 | 说明 |
|---|---|---|
cut | int | 跨 FPGA 超边总权重(核心指标) |
tdm | int | TDM 拥塞指标 |
topo | int | 拓扑违规罚分 |
violation | int | 约束违反次数/权重 |
cost | double | 综合代价分数 |
worstSlack | double | 最差时序冗余 (WNS) |
maxHop | int | 最大路由跳数 |
tdmDelay | float | 最大 TDM 通信延迟 |
5. 粗化阶段详解
粗化阶段由 MultilevelCoarsener 类实现,负责将原始大图逐层收缩为更小的抽象图。核心思想是:将紧密连通的节点对合并为超节点,从而在不损失重要结构信息的前提下指数级缩小问题规模。
5.1 粗化策略
| coarsen_method | 名称 | 原理 | 适用场景 |
|---|---|---|---|
| 1 推荐 | 重边匹配 (HEM) | 遍历超边计算节点对合并得分,将多条超边中重复出现的邻居对权重累加后选最高分匹配。对输入顺序不敏感。 | 通用场景,默认选择 |
| 0 | 基础贪心匹配 | 同样基于邻居权重匹配,但不做去重累加:每条超边独立产生一组邻居配对。对输入顺序敏感,cut 差距可达数倍。 | 快速模式 (mode=2) 或测试对比 |
| 2 | 首次选择匹配 (First Choice) | 在匹配得分计算上与 HEM 类似,但核心区别在于:已匹配到粗图节点(cluster)的邻居得分记入 matched_score_map(而非 score_map)。匹配时优先选择已形成的 cluster(matched=true),允许将多个节点持续吸收到同一 cluster 中,不限制为两两配对。 |
COCP 粗化等需要更大粒度聚合的场景 |
method=0:邻居得分不累加,同一条超边内同 cluster 不重复计分 → 节点对两两配对。
method=1:邻居得分全累加到 score_map,已匹配 cluster 也参与打分 → 节点对两两配对,但合并后可追加到已有 cluster。
method=2:已匹配 cluster 的得分单独记入 matched_score_map,且匹配时优先选择 cluster(即使分数相同也优先 matched)→ 允许多节点聚合成一个 cluster,形成更大粒度的超节点。
5.2 粗化流程
构建邻居关系
遍历超边,为每对邻居节点计算合并得分。大超边(> large_net_threshold)被跳过。
节点排序
按配置的 CoarsenOrder 策略排序:RANDOM(随机扰动)、DEGREE(按度数)、SIZE(按体量)、TIMING(按时序关键度)
贪心匹配
按排序顺序,每个节点尝试与得分最高的未匹配邻居合并。检查资源容量约束,确保合并后的超节点不超过单个 FPGA 的资源上限。固定节点不与非固定节点合并。
图收缩 (Contraction)
根据匹配结果构建新的粗图:合并节点、更新超边端点、删除被吞并的空超边。保存层间映射表。
停止条件
当节点数 < fpga_num × thr_coarsen_vertice 或达到 level 上限时停止。若单层缩减比低于 coarsening_ratio 也会提前停止。
5.3 关键数据结构
| 成员 | 说明 |
|---|---|
graphs | 层次图栈:[0] = 最细层(原始图),[level] = 最粗层 |
maps | 层间映射表:粗层节点 → 细层节点 |
map2origins | 反查表:粗层节点 → 原始图节点 |
isolated_nodes | 孤立节点(无超边连接),在初始划分后单独分配 |
固定节点约束:固定节点只与具有相同固定归属的节点合并。区域约束:候选区域取交集,若交集为空则不合并。组约束:在粗化之前,同一组的节点已预先合并为单个超节点。
6. 初始划分阶段详解
初始划分由 Initial 类实现,在最粗层图上生成多个满足资源约束的候选解。这些解的多样性直接决定了最终优化的质量。
6.1 多策略生成
Initial::initialPartition() 会综合运用以下多种策略来生成初始解:
| 策略 | 原理 | 特点 |
|---|---|---|
| 随机分配 | 将节点随机分配到资源可容纳的 FPGA 块 | 探索解空间,增加多样性 |
| 贪心生长 (Grow) | 从种子节点出发,按增益逐步扩展到邻域 | 局部质量较好 |
| SPFA 扩展 | 基于改进最短路径算法,考虑跳距代价 | 适用于拓扑约束场景 |
| 全局生长 | 所有分区同时扩展,均衡增长 | 平衡性好 |
| 顺序填充 | 按硬件块顺序依次填满 | 简单直接 |
| 轮询生长 (Round-Robin) | 各分区轮流扩展一个节点 | 均衡同步增长 |
| 拓扑约束划分 | 考虑跳数限制的专用策略 | force_topo 场景 |
6.2 精英选择
生成 num_initial_solutions 个候选解后,按 cut 指标排序,仅保留前 num_best_initial_solutions 个精英解进入细化阶段。这既控制了细化阶段的计算量,又保证了进入细化的解具有较高的起点质量。
流程// Initial::initialPartition() 内部逻辑
for seed in [0, num_initial_solutions):
switch (seed % num_strategies):
case 0: randomPart() // 随机
case 1: greedyPart() // 贪心
case 2: progressiveMinCutPart() // 最小割生长
case 3: globalGrowPart() // 全局生长
case 4: sequentialGrowPart() // 顺序填充
case 5: roundRobinGrowPart() // 轮询
...
// 按指标排序,保留精英
sort candidates by cut
retain top num_best_initial_solutions candidates
7. 细化阶段详解
细化阶段是整个划分流程中最核心的优化环节,负责将初始解逐层映射回原始图的过程中持续优化。细化采用 门面模式(Refinement Facade)设计,通过统一的 Refinement::refinement() 接口屏蔽内部复杂的策略调度细节。
7.1 整体架构
7.2 细化策略对比
PM (Pairwise Matching FM) 默认开启
算法:对 FPGA 块对(pair)排序,每对独立执行 FM 细化。
特点:成对处理降低了搜索空间,收敛快。支持回滚到最佳状态。
桶类型:PerPartBucketSet(分区级增益桶)
FM (Fiduccia-Mattheyses) 默认关闭
算法:经典 K-way FM 增益桶驱动细化。收集所有边界节点,每次选取最大增益移动。
特点:全局视角,适合复杂拓扑。支持回滚。
桶类型:PerPartBucketSet
DSFM_S (Directly Summed FM - Single Bucket) 默认关闭
算法:使用全局统一增益桶,引入约束延迟机制(不可行移入暂存队列)。
特点:避免「corking effect」导致的质量损失。单一视角简化调度。
桶类型:GlobalMoveBucket
DSFM_M (Directly Summed FM - Multi Bucket) 默认关闭
算法:保留分区级桶,但实现「corking」— 不可行时持续弹出直到找到可行移动。
特点:兼顾分区感知与约束延迟。
桶类型:PerPartBucketSet
Greedy (网级贪心) 默认开启
算法:以超边(而非节点)为单位,尝试将边界超边的所有端点移至最优分区。
特点:即时提交,无回滚。速度快,适合快速收敛。
桶类型:无(直接评估)
HER (High-Effort Refinement) 默认关闭
算法:高努力细化策略,消耗更多计算资源以获得更高质量的解。
特点:适合对质量要求极高的场景。
桶类型:视配置而定
7.3 策略组合机制
细化阶段支持 primary + suffix 策略组合。每次细化会先运行 primary 策略(如 PM),再运行 suffix 策略(如 Greedy),形成「先精细优化,再快速清扫」的效果。
| Primary | Suffix | 组合名 | 适用场景 |
|---|---|---|---|
| PM | Greedy | PM+Greedy | 默认组合,通用场景 |
| FM | Greedy | FM+Greedy | 需要更全局的优化 |
| DSFM_M | Greedy | DSFM_M+Greedy | 严格约束场景 |
| PM | HER | PM+HER | 高质量要求 |
| DSFM_S | HER | DSFM_S+HER | 约束严格+高质量 |
7.4 增益计算与移动机制
增益计算 (GainEvaluator)
增益(Gain)= 移动节点前后的割边权重差。GainEvaluatorCore 计算时综合考虑:
- 割边增益:移动节点减少的跨块超边权重
- 拓扑增益:减少的跳数违规罚分
- IO 增益:TDM 拥塞改善量
- 时序增益:关键路径 slack 改善(时序模式下)
移动执行 (MoveApplier)
MoveStateApplierCore 提供三种操作:
applyMoveToState()— 试探性移动(更新状态)acceptMove()— 确认移动(更新 NetPartition 快照)cancelMove()— 回滚移动(恢复状态)
回滚机制确保细化过程「只进不退」— 每轮 Pass 结束后回退到 Pass 内的最佳状态。
7.5 增益桶 (MoveBucket)
增益桶是 FM 系列算法的核心数据结构,提供 O(log n) 的最大增益节点选取。模块提供两种实现:
每个目标分区维护一个独立的优先队列。选取时比较所有桶的顶部元素。
优点:天然隔离不同目标分区的候选,避免分区间的干扰。
使用者:FM, PM, DSFM_M
所有目标分区共享一个统一的优先队列,每个元素携带目标分区信息。
优点:简化调度逻辑,便于实现约束延迟。
使用者:DSFM_S
7.6 细化流程:逐层展开
最粗层局部搜索
在初始划分结果上直接运行策略(不解层),快速改善粗粒度解的质量。
候选解裁剪
按指标评估,裁剪较弱的候选解(trimInitialCandidates),保留精英解。
逐层投影 + 细化
从 level-1 到 0,每层:1) 将候选解通过映射表投影到细层;2) 运行策略优化;3) 更新指标并选择最优解。
时序度量更新
时序模式下,每层细化后调用 TimingMetricService 更新 slack、延迟等时序指标。
8. 约束体系
Partition 模块支持多种物理约束,贯穿粗化、初始划分、细化全流程。所有约束标志集中在 PartitionConstraintConfig 中。
固定分配约束 (Fixed Assignment)
某些节点必须放在指定的 FPGA 块上,不能被移动。通过 finest.fixed_assign[node_id] = fpga_id 设置。
// 通过名字兼容入口设置
fixed_assignment["cpu_core"] = {2, {}};
para.constraints.has_fix = true;
区域约束 (Region Constraint)
某些节点只能在指定的几个 FPGA 块中选择。通过 finest.candidates[node_id] 设置候选集合。
// 设置区域约束(通过名字前缀匹配)
fixInfo info;
info.fpgaNo = -2; // -2 表示区域约束
info.id = {{0,0,0}, {0,0,1}}; // 候选块 0 和 1
fixed_assignment["mem_ctrl"] = info;
para.constraints.has_region = true;
组约束 (Group Constraint)
某些节点必须被分到同一个 FPGA 块。在粗化之前,同组节点已预先合并为单个超节点。
// 设置组约束
group_assignment["group_a"] = {"node_1", "node_2", "node_3"};
IO 约束 (IO / Bandwidth Constraint)
确保跨 FPGA 连线不超过通道带宽限制。影响增益计算:割边代价考虑带宽倒数权重,而非简单计数。
拓扑约束 (Topology Constraint)
强制满足 FPGA 间的跳数限制,不允许超出 max_hop 的分配。
时序约束 (Timing Constraint)
启用时序感知划分,优化关键路径的 slack。依赖外部传入的 pFlow(HSFullTiming::HSPartitionFlow)对象,由 sta 模块预先生成。详见下方第 9 节。
para.constraints.has_timing = true;
// pFlow 由 sta 模块解析生成:
// sta::parse(pFlow, circuitFolder);
// sta::buildNextObjInfo(pFlow);
9. 时序驱动划分详解
时序驱动划分是 Partition 模块中最复杂的运行模式。与纯 cut 优化不同,时序模式需要在减少割边数的同时兼顾关键路径的时序质量(Worst Negative Slack, WNS)。当前实现的核心机制是通过 HSFullTiming::HSPartitionFlow(简称 pFlow)对象与 sta 模块交互,而非模块内嵌的时序评估器。
9.1 时序数据的来源与传递
pFlow 是由外部 sta 模块生成的时序分析上下文,内部为每个候选解维护一个独立的 LocalData 对象,负责:
- 割边延迟矩阵(
setCutDelays):记录每对 FPGA 间的割边延迟,由 TDM 延迟模型或固定常量计算 - 时序图构建(
buildTimingEdge):根据当前划分建立时序约束图 - 时序更新(
updateTimingEdge):在节点移动后刷新时序状态 - 指标查询:
getMinSlack()(WNS)、getCriticalCut()(关键路径割边数)、getWorstIndex()(最大跳数)、getTopkMinSlackRatio()(Top-K slack 比值)
9.2 时序模式下的流程调整
当时序模式开启(has_timing = true)时,partition() 内部会做以下关键调整:
| 调整项 | 具体行为 | 原因 |
|---|---|---|
| 候选解数 | 使用 timing_hold_solution_num(默认 1)代替 hold_solution_num | 时序评估代价高,减少候选数控制耗时 |
| 并行策略 | 时序模式下不拆分线程(thread = 1),每个 pFlow 需独立状态 | pFlow 的 LocalData 不支持并发写入 |
| COCP 两阶段 | 先以 has_timing=false 跑一轮 COCP(COCP_on=2),获得好的 cut 基础后再开启时序跑 V-Cycle | 时序优化需要较好的初始 cut |
| IO + 拓扑预处理 | 若同时有 IO/区域/拓扑约束,先关闭这些约束跑一轮纯 cut,再恢复时序跑 V-Cycle | 多约束同时生效可能导致无解 |
| 细化策略 | V-Cycle 内关闭 PM(enable_PM = false),关闭混合细化 | PM 的成对处理不利于时序优化 |
9.3 TimingMetricService 的工作机制
初始化(最粗层)
initializeCoarsestLocalData():为每个候选解创建 LocalData,将粗图解映射回原始图后设置割边权重,构建时序边并执行首次时序更新。写入 cost(关键割边数)、worstSlack、maxHop 到 Metrics。
逐层细化后更新
每层细化完成后调用 fillMetricsFromPartitionFlow():从各候选解的 LocalData 提取最新 getMinSlack()、getCriticalCut()、getWorstIndex()、getTopkMinSlackRatio() 写入 Metrics。
TDM 延迟刷新
refreshCutDelaysFromTdm():当 IO 带宽配置非空时,从 fpgas.tdmEstimate 获取最新的 TDM 割边延迟矩阵,更新到各候选解的 LocalData 中。
最细层清理
到达 level 0(原始图)后调用 pFlow.cleanOurPartition() 清理中间状态。
9.4 割边延迟矩阵
每对 FPGA 之间的割边延迟由 buildCutDelayMatrix() 计算:
- 有 IO 带宽配置(
cutweights_assignment非空):使用fpgas.tdmEstimate.getTdmCutDelay(i, j)获取基于 TDM 复用率的延迟值 - 无 IO 配置:使用固定默认值
extra_delay_cut(默认 100.0 ns)
9.5 时序解的评估与选择
时序模式下,CandidateSelector::chooseBestCandidate() 的选择逻辑变为:
- 优先选
worstSlack最好(最大)的解 — 确保关键路径时序质量 - 在 slack 相近的解中,选
cost(关键路径割边数)更小的 fewer_cuts标志影响选择权重:当整体割边偏少时倾向选更小 cut
9.6 时序配置参数速查
| 参数 | 默认值 | 说明 |
|---|---|---|
timing.extra_delay_cut | 100.0 | FPGA 间割边固定延迟(ns),无 TDM 时使用 |
timing.extra_delay_cut_die | 2.0 | Die 间割边附加延迟 |
timing.timing_hold_solution_num | 1 | 时序模式保留候选解数(通常 ≤ 4) |
timing.guardband_flag | 1 | 启用悲观时序保护带 |
timing.enable_partition_HSFulltiming | 1 | 启用 HSFullTiming 时序算法 |
timing.timing_exp_factor | 2.0 | slack 惩罚指数因子 |
timing.path_timing_factor | 1.0 | 关键路径惩罚权重 |
10. 完整流程控制(COCP / V-Cycle)
10.1 主流程模式
| 模式 | flow.mode | 说明 |
|---|---|---|
| 标准模式 | 0 | 完整流程:多候选解 → COCP → V-Cycle |
| 快速模式 | 2 | 关闭 COCP、V-Cycle、PM 策略,使用贪心粗化。优先获得可行解。 |
10.2 COCP(Coarsening Optimization Candidate Processing)
COCP 是一种后处理优化机制:以当前最优候选解的分区信息作为社区提示(community hint),重新执行一轮粗化-细化,从而在已有解的基础上进行更深层次的结构优化。
COCP 粗化
使用当前最优解作为 community 提示,执行受引导的粗化(倾向于保留现有分区的社区结构)。
重新初始划分
在最粗层生成新的候选解,同时将 community 方案也作为候选。
细化
对所有候选解(包括新初始解和 community 方案)执行完整细化。
当时序模式 (has_timing) 开启时,COCP 会先在非时序模式下执行一轮以获得较好的 cut 基础,然后再切换到时序模式执行 V-Cycle。这是通过 COCP_on = 2 标志控制的。
10.3 V-Cycle 迭代优化
V-Cycle 是多级划分的经典增强技术:以当前最优解为起点,重复执行完整的「粗化 → 初始划分 → 细化」流程,每次都可能发现更优的解。
V-Cycle 伪代码for iter in [0, V_Cycle_run):
community = candidate_parts[best] // 以当前最优解为社区提示
mc = MultilevelCoarsener(finest, ...) // 新建粗化器
mc.multilevelCoarsening(finest) // 受引导粗化
initial.initialPartition(mc, ...) // 重新初始划分
refinement.refinement(mc, ...) // 细化
candidate_parts[0] = best result // 更新最优解
10.4 候选解选择 (CandidateSelector)
在多个候选解中,CandidateSelector::chooseBestCandidate() 综合以下指标选择最优解:
- 非 IO / 非时序模式:优先选
cut最小的解 - IO 模式:考虑
cost(TopK slack 之和)和topkratio - 时序模式:在 cut 可接受的前提下,优先选
worstSlack最好的解 - fewer_cuts 标志:当整体割边数偏少时,调整选择策略偏向更小 cut
11. 层次化(多层级)划分
前面章节描述的 partition() 是扁平划分:它把所有 FPGA 视作平级节点,目标是把超图切成 K 块。而真实的多片 FPGA 系统具有物理层级——机柜(Rack)→ 簇(Cluster)→ 板(Board)→ FPGA,命名形如 R*.C*.B*.F*,其下还可细分 Die。同板 FPGA 之间的互连带宽远大于跨板,资源也需按层级管控。层次化(多层级)划分就是为这种场景设计的递归外壳。
本文有两个相近术语,含义完全不同:
- 多级划分(Multilevel)=第 3 章的扁平内算法:粗化 → 初始划分 → 细化。它解决「在一个平级的 K 块上怎么分得好」。
- 多层级划分(Hierarchical)=本章:按硬件物理层级 Rack → Cluster → Board → FPGA 自顶向下分治递归。它解决「分给哪个物理位置」。
二者关系是外层递归 / 内层多级:层次化的每一层内部,调用的仍是完整的扁平 partition()。
11.1 动机与定位
扁平 partition() 有两个局限:① 它无法表达「同板近、跨板远」的层级拓扑,只能用一个统一的最短跳数矩阵近似;② 它无法按物理层级分层控制资源上限。层次化划分通过把硬件建模成一棵递归树,逐层分割、逐层约束,最终让每个电路节点落到一个叶子 FPGA,并记录其层次路径。
11.2 层次数据结构
层次结构定义在 defs.h,核心是一个递归类 hierarchy:每个节点既描述本层资源与拓扑,又通过 contents 持有子节点,天然形成一棵与物理硬件同构的树。
| 类型 / 字段 | 所在 | 含义 |
|---|---|---|
enum class HierarchyType | defs.h | 节点类型:RACK / CLUSTER / BOARD / FPGA / DIE |
hierarchy::type | defs.h | 本节点的层级类型 |
hierarchy::fpgas0 | defs.h | 聚合前的原始资源快照(硬件物理容量上限) |
hierarchy::fpgas | defs.h | 当前资源(含 reset 后的算法目标 + 本层拓扑约束) |
hierarchy::contents | defs.h | 子节点列表(递归构成下一层) |
hierarchy::topology_links | defs.h | 本层级的物理拓扑链路(HierarchyLink) |
hierarchy::alias / node_id / parent_node_id / children_node_ids / alias_to_node_id | defs.h | 别名(如 R0.C1)与节点 ID 索引 |
HierarchyLink | defs.h | 两 FPGA 端点间的物理连接:left/right 端点、is_mgt、channel_capacity、hio_channels / mgt_channels、cable |
HierarchyLinkEndpoint | defs.h | 链路端点:node_id、die_id、socket、fpga_alias |
fpgas0 与 fpgas这是层次化最关键的特殊设计。fpgas0 保存硬件真实物理容量(不可突破的天花板),fpgas 保存算法实际使用的资源目标(经 reset 校准后的值)。二者解耦,使得算法可以在不违反物理上限的前提下,按层级灵活分配资源余量。详见 11.4。
11.3 算法思路:自顶向下分治
层次化划分的入口是 partitionHierarchyGraph()(partition.h)。它按 hierarchy_root 定义的树自顶向下递归:在每一层调用扁平 partition() 把当前子图切成若干份,再用 separateGraph() 按结果拆成子图,递归交给下一层,直到叶子 FPGA。
递归伪代码// partition.cpp :partitionHierarchyNode(单节点递归)
int partitionHierarchyNode(graph &cur, const hierarchy &node, prefix, ...) {
int part_num = node.contents.size(); // 本层要切成几份
// 基线:只剩 1 份或已是叶子 → 把当前路径回填到 node_paths
if (part_num == 1 || node.contents.empty()) {
assignLeafPaths(cur, prefix, parts, node_paths); // node_paths[i] = [rack,cluster,board,fpga]
return 1;
}
// 递归:先扁平划分当前层,再切图,再对每个子节点递归
partition(cur, node.fpgas, parts, para, thread, pFlow); // 内层仍是完整多级划分
separateGraph(cur, node.fpgas, parts, child_graphs, id2node);
for (int c = 0; c < part_num; ++c)
partitionHierarchyNode(child_graphs[c], node.contents[c], prefix + [c], ...);
}
separateGraph(finest, fpgas, parts, sub_graphs, id2node) 负责把划分结果落地:遍历所有节点与超边,按 parts 归入对应子图,并重建每个子图的 incident_nets 索引(id2node 记录子图节点到原图节点的映射,保证递归回溯时身份不丢)。最终每个 graph node 都获得一条层次路径,写入 node_paths[i]。
层次化(外层)只决定「每个子图分给哪个物理子节点」,扁平 partition()(内层)只决定「在当前这组 FPGA 上怎么切得割边最小」。因此前面所有章节(粗化、初始划分、细化、COCP/V-Cycle、时序)的能力在每一层都完整可用。
11.4 资源上限分层设置
层次化场景下,资源约束必须按层级分级设置:顶层不能直接占满所有叶子的物理容量,否则下层无平衡空间。本项目用「两层容量 + 自底向上 reset + 分布式 slack 衰减」三件套解决。
第一步:自底向上 reset(resetHierarchyResourcesFromLeaves,partEDIF.h)。先用 collectLeafResources() 把所有叶子 FPGA 的 fpgas0(物理容量)平铺成一维列表,交给 checkResources() 计算平衡后的目标值,再写回叶子并逐级聚合到上层 fpgas。checkResources() 的核心是找瓶颈资源维度并按比例设定目标:
checkResources 核心逻辑(partEDIF.cpp)// 1. 找需求/容量比最高的资源维度作为瓶颈
int index = 0;
double ratio = required[0] / (double)total[0];
for (int r = 1; r < R; ++r)
if (required[r] / (double)total[r] > ratio) { index = r; ratio = required[r]/total[r]; }
// 2. 按瓶颈比率和用户设定推导目标 FPGA 数与每块资源上限
int target_fpga_num = max(1, (int)ceil(ratio * f.resources.size() * fpgaResourceRatioSet));
// fpgaResourceRatioSet == 0 表示自动估计
第二步:分布式 slack 衰减(aggregateHierarchyResourcesWithDistributedSlack,tools.h)。并非所有资源维度都需要分布式限制——只有总容量超过实际需求的维度才需要。流程为:getDistributedResourceDims() 识别这些维度 → computeHierarchySlacks() 计算每维 slack(slack = 总容量 / 需求上界,上限 kMaxHierarchyResourceSlack = 2.0)→ 自底向上聚合时对分布式维度施加渐进式 slack 衰减。
若上层节点直接占满所有叶子的物理容量,下层就没有任何腾挪空间,极易导致「上层合法、下层无解」。引入 reset 把目标收紧到瓶颈维度、并用分布式 slack 让余量自底向上递减释放,保证每一层都有平衡空间,从根源上缓解多层级资源不可行问题。
11.5 超边去重
网表中常存在大量平行超边——端点集合完全相同的多条 net(例如同一组宏之间的多比特信号被拆成多条线)。它们会让粗化阶段的匹配打分重复计算,也干扰后续时序/路由评估。粗化器提供 MultilevelCoarsener::deduplicateHyperedges(finest)(coarsen.h)在粗化前压缩这类冗余。
算法(coarsen.cpp):① 为每条超边计算特征哈希(ContractedHyperedgeInformation.hash,含引脚数 size);② 按 (hash, size) 分桶排序;③ 桶内用 check_if_hyperedges_are_parallel() 做精确结构比对(逐端点比较 incident_nodes,仅当哈希相同且端点序列完全一致才认定平行);④ 合并平行超边——权重累加到保留边,重复边标记为 invalid 跳过。该步配合 parallelContraction()(TBB 并发收缩)使用。
超边去重小例// 两条 net 都连接节点 {0, 1, 2},是平行超边
net A: incident = [0,1,2], weight = 1.0
net B: incident = [0,1,2], weight = 1.0
↓ deduplicateHyperedges
net A: incident = [0,1,2], weight = 2.0 // 合并,权重累加
net B: 标记为 invalid,跳过 // 图规模下降,打分不再重复
dedup_root_graph:编号不变的去重图partitionHierarchyGraph() 的可选出参 graph *dedup_root_graph 返回根层去重后的超图,且节点编号保持不变。因为编号不变,时序评价与路由需求评估可以直接复用这张更小的图,既加速又保证与原节点身份对齐。
11.6 时序评价(跨 FPGA 拓扑感知)
层次化把硬件拍平后,跨 FPGA 通信的代价取决于 FPGA 对的物理距离。本项目的时序评价由 TimingMetricService(构建延迟矩阵)、HSTimingLocalData(分区级时序状态)和 HSTimingDelta(增量评估器)协同完成,最近的提交又加入了拓扑距离与去重图两项增强。
| API | 所在 | 作用 |
|---|---|---|
TimingMetricService::buildCutDelayMatrix(fpgas, fpga_num, default_cut_delay) | TimingMetricService.h | 基于拍平后的 fpgas 拓扑构建 FPGA 对割边延迟矩阵(vector<vector<float>>) |
refreshCutDelaysFromTdm(...) | TimingMetricService.h | 从 TDM 分配结果刷新分区间割边延迟 |
HSTimingLocalData::m_fpgaDistances | HSTimingLocalData.h | FPGA 对最短拓扑距离矩阵 [fromPart][toPart] |
setFPGADistances(...) / getFPGADistances() | HSTimingLocalData.h | 下发 / 读取拓扑距离矩阵 |
getEdgeFPGADistance(edgeId) | HSTimingLocalData.h | 查询某条边跨越的 FPGA 最短拓扑距离 |
HSTimingDelta::getFPGADistance / getOldFPGADistance | HSTimingDelta.h | delta 视图下当前 / 基准拓扑距离(试探移动时增量评估) |
evaluatePartitionTimingComparison(finest, fpgas, parts, pFlow, topk) | tools.h | 划分完成后对 top-K 候选解做时序对比评估 |
拓扑距离 vs 简单 hop。 简单 hop 只数跨越的 FPGA 跳数;拓扑距离则结合物理拓扑矩阵给出更贴合真实延迟的距离。时序引擎用拓扑距离为每条 timing edge 估延迟,使关键路径评估更准确。
去重图(net + FPGA pair 去重)。 同一条 net 在同一 FPGA pair 上可能被重复计入关键割线 slack。HSTimingLocalData / HSTimingDelta 维护按 net + FPGA pair 去重的最差 slack bucket 统计,避免重复计数导致评估偏置,让候选解排序更可靠。
增量时序评估。 HSTimingDelta 为一次候选分区变更维护独立的差分账本而不改写基准状态:用 traceInfo(hop / slack / preEdgeId / edgeId)记录单步传播轨迹,updateTimingEdge() 增量更新,并支持 rollbackUndo()。最终把 cut delays + fpga distances 下发到每个候选的 local data 后即可快速评价。
11.7 端到端编排
层次化的端到端入口是 partHierarchyOnly()(partEDIF.h),它串联起建模、图构建、资源校准、递归分割与可选输出:
建模 + 图构建
解析 EDIF 与约束,构造最细层超图 finest 与 hierarchy_root(含 fpgas0 物理容量)。
资源校准
resetHierarchyResourcesFromLeaves() 自底向上 reset 各级 fpgas 目标(见 11.4)。
递归分割
partitionHierarchyGraph() 自顶向下分治,每层内部跑完整扁平 partition(),输出 node_paths。
时序评价(可选)
flattenHierarchyToFpgasForRouting() 拍平 → buildCutDelayMatrix + 拓扑距离 → 增量时序评估(见 11.6)。
输出产物
writeHierarchyPartitionArtifacts() 写入分配 JSON(entries)与辅助 JSON(fpga_stats + 各层 cut 矩阵)。
flattenHierarchyToFpgasForRouting(hierarchy_root, flat_fpgas, flat_fpgas0, path_to_fpga, fallback_capacity, delay_lib_path) 是层次化与扁平世界的桥梁:它把所有叶子 FPGA 拍平成一组平级的 flat_fpgas(含拓扑、容量、TDM 互联),并建立层次路径到全局 FPGA ID 的映射 path_to_fpga,供时序与路由评估复用前面章节的扁平算法。
11.8 层次化 API / 产物速查
| API | 所在 | 说明 |
|---|---|---|
partitionHierarchyGraph(finest, hierarchy_root, para, thread, node_paths, pFlow, cut_records?, dedup_root_graph?) | partition.h | 递归分割主入口;cut_records 记录每层 cut 指标与 cut 权重矩阵,dedup_root_graph 返回编号不变的去重根图 |
separateGraph(finest, fpgas, parts, sub_graphs, id2node) | partition.h | 按分区结果把超图拆成子图,重建 incident_nets |
HierarchyPartitionCutRecord | defs.h | 单层分割的 cut 指标与 cut_weights 矩阵记录 |
resetHierarchyResourcesFromLeaves(...) | partEDIF.h | 自底向上资源校准(11.4) |
writeHierarchyPartitionArtifacts(...) | tools.h | 输出 parts(entries)+ aux(fpga_stats + 各层 cuts)两个 JSON |
partHierarchyOnly(...) | partEDIF.h | 端到端编排(建模→校准→递归分割→输出) |
flattenHierarchyToFpgasForRouting(...) | partEDIF.h | 叶子拍平 + TDM 互联初始化(层次↔扁平桥梁) |
本章覆盖 tsb-19 自 6/1 起的层次化工作:9f1cbf2(层次结构与递归)、4a1c1d1(reset 容量与资源报告)、ae13f5f(超边去重与分区后时序评估)、da819b4(路由需求评估)、ddc2987(去重图与拓扑距离)。
12. API 接口与调用方法
12.1 主入口(纯抽象版)
定义在 partition.h,直接操作图级别的约束:
C++int partition(
graph &finest, // [输入] 超图网表
fpga &fpgas, // [输入] 硬件平台
vector<int> &parts, // [输出] 节点->FPGA 映射
const PartitionParams ¶, // [输入] 算法参数
int thread, // [输入] 线程数
HSFullTiming::HSPartitionFlow &pFlow // [输入] 时序上下文
);
// 返回值: 1 = 成功, -1 = 失败
12.2 兼容入口(名字级约束适配器)
提供了从名字级约束到图内节点 ID 的自动转换:
C++int partition(
graph &finest,
fpga &fpgas,
vector<int> &parts,
const PartitionParams ¶,
int thread,
flat_hash_map<string, fixInfo> &fixed_assignment, // 固定/区域约束
unordered_map<string, vector<string>> &group_assignment, // 组约束
flat_hash_map<string, int> &name_map, // 名字->ID 映射
HSFullTiming::HSPartitionFlow &pFlow
);
兼容入口会先将 fixed_assignment 转换为 finest.fixed_assign,将区域约束转换为 finest.candidates,将组约束通过 compute_group_constrain() 预聚合,然后调用纯抽象版入口。
12.3 完整调用示例
示例 1:纯图分割(无约束)
C++#include "partition/partition.h"
using namespace std;
graph g;
// ... 填充 nodes, nets, incident_nodes, incident_nets ...
fpga fpgas;
// ... 填充 resources, topology, dist ...
PartitionParams para;
para.refine.num_best_initial_solutions = 10;
vector<int> parts;
HSFullTiming::HSPartitionFlow pFlow;
int ret = partition(g, fpgas, parts, para, 8, pFlow);
示例 2:有固定分配约束
C++flat_hash_map<string, fixInfo> fixed_assignment;
flat_hash_map<string, int> name_map;
unordered_map<string, vector<string>> group_assignment;
name_map["cpu_core_0"] = 42;
fixed_assignment["cpu_core_0"] = {2, {}}; // 固定到块 2
para.constraints.has_fix = true;
int ret = partition(g, fpgas, parts, para, 8,
fixed_assignment, group_assignment, name_map, pFlow);
示例 3:使用时序数据
C++HSFullTiming::HSPartitionFlow pFlow;
// pFlow 由 sta 模块解析生成(需外部调用 sta::parse / sta::buildNextObjInfo)
para.constraints.has_timing = true;
para.timing.timing_hold_solution_num = 4;
int ret = partition(g, fpgas, parts, para, 8, pFlow);
示例 4:验证划分结果
C++// 计算割边权重
int cut = fpgas.computeCutWeight(g, parts);
printf("Cut = %d\n", cut);
// 计算完整指标(含 TDM)
vector<vector<int>> cut_weights(fpgas.resources.size(),
vector<int>(fpgas.resources.size(), 0));
Metrics m = fpgas.computeCutWeights(g, parts, cut_weights);
printf("Cut=%d TDM=%d Topo=%d Violation=%d MaxHop=%d\n",
m.cut, m.tdm, m.topo, m.violation, m.maxHop);
13. 参数配置速查
13.1 常用参数
| 参数路径 | 默认值 | 说明 | 调优建议 |
|---|---|---|---|
flow.COCP_on | 1 | 是否启用 COCP 后处理 | 开启可提升质量,关闭可加速 |
flow.V_Cycle_on | 1 | 是否启用 V-Cycle | 开启可显著提升质量 |
flow.V_Cycle_run | 2 | V-Cycle 迭代次数 | 1~3 次性价比较好 |
flow.hold_solution_num | 4 | 非时序模式保留候选解数 | 2~8 |
coarsen.level | 30 | 最大粗化层数 | 通常不需修改 |
coarsen.coarsen_method | 1 | 粗化方法(1=HEM) | 推荐 1(重边匹配) |
coarsen.large_net_threshold | 200 | 大超边过滤阈值 | 设小可过滤更多大超边 |
initial.num_initial_solutions | 64 | 初始候选解数 | 32~128,越多越好但更慢 |
refine.num_best_initial_solutions | 8 | 进入细化的精英解数 | 4~16 |
refine.max_move | 1000 | 单 Pass 最大移动次数 | 增大可更充分优化 |
refine.refine_iters | 1 | 每层细化迭代轮数 | 1~3 |
refine.enable_PM | true | 启用 PM 策略 | 推荐开启 |
refine.enable_Greedy | true | 启用 Greedy 策略 | 推荐开启 |
refine.enable_FM | false | 启用 FM 策略 | 按需开启 |
constraints.max_hop | -1 | 最大跳数(-1 自动估计) | 自动估计即可 |
13.2 时序相关参数
| 参数路径 | 默认值 | 说明 |
|---|---|---|
timing.extra_delay_cut | 100.0 | FPGA 间割边附加延迟(ns) |
timing.extra_delay_cut_die | 2.0 | Die 间割边附加延迟 |
timing.timing_exp_factor | 2.0 | slack 惩罚指数因子 |
timing.path_timing_factor | 1.0 | 关键路径惩罚系数 |
timing.timing_hold_solution_num | 1 | 时序模式保留候选解数 |
timing.has_timing_propogate | 1 | slack 传播模式(0/1/2) |
timing.guardband_flag | 1 | 是否启用时序保护带 |
14. 常见问题与调优
Q1: 资源向量维度必须一致吗?
是的。node.resources、fpga.resources[i]、fpga.upper_resources、fpga.lower_resources 的维度必须相同,代表同一组资源类型(如 LUT, FF, DSP, BRAM)。
Q2: 不需要时序优化时,pFlow 怎么传?
传一个默认构造的空 HSFullTiming::HSPartitionFlow 对象即可。确保 para.constraints.has_timing = false。
Q3: name_map 是必须的吗?
如果不需要固定分配约束(has_fix=false)和区域约束(has_region=false),name_map 可以为空。否则必须包含约束中引用的所有节点名到 ID 的映射。
Q4: topology 和 dist 需要手算吗?
topology 是邻接矩阵,需要你提供。dist 可以用 Floyd-Warshall 从 topology 推导。如果不需要拓扑约束,可以不填 topology 和 dist,并将 force_topo 和 has_io 设为 false。
Q5: 划分结果质量不满意怎么办?
按以下顺序调整参数,每步观察效果:
- 增加候选解数:
initial.num_initial_solutions = 128或更高 - 开启 V-Cycle:
flow.V_Cycle_on = 1,flow.V_Cycle_run = 3 - 开启 COCP:
flow.COCP_on = 1 - 增加细化迭代:
refine.refine_iters = 2 - 增大移动步数:
refine.max_move = 2000 - 尝试其他策略:开启 FM(
refine.enable_FM = true)或 DSFM - 增加线程:更多线程 = 更多并行候选解探索
Q6: 如何选择合适的粗化方法?
推荐使用默认的 HEM(coarsen_method=1)。它通过权重合并去重,显著降低了对输入顺序的敏感性。基础贪心(coarsen_method=0)仅在快速模式或特定测试中使用。
Q7: 时序模式和纯 cut 模式有什么区别?
| 方面 | 纯 Cut 模式 | 时序模式 |
|---|---|---|
| 优化目标 | 最小化割边权重 | 在 cut 可接受前提下优化 slack |
| 候选解数 | hold_solution_num | timing_hold_solution_num(通常更少) |
| 增益计算 | 仅割边增益 | 割边 + slack 惩罚 |
| 粗化策略 | SIZE/RANDOM | 可选用 TIMING 排序 |
| 需要额外输入 | 否 | 是(pFlow 时序数据) |