EDIF_Part 模块集成指南¶
本页集中说明 Partition、Routing 与 TDM 三个模块的集成方式。三个模块按执行顺序组织为同级章节:
Partition:构造超图与平台,生成 FPGA/Die 划分结果;
Routing:补齐割线、端口、时序和容量数据,生成物理布线树;
TDM:在布线树上执行连续优化、离散化和物理通道合法化。
各模块算法细节见独立技术手册;issues 页面仅收录 Partition 模块的问题记录。
Partition 模块集成指南¶
本指南面向需要在项目中集成超图划分模块的开发者。阅读完本文后,你可以:
知道调哪个函数(
partition())知道填哪些参数、每个参数怎么构造
知道执行完后从哪里取结果
所有 API 的详细参数说明见 src/partition/partition.h 中的注释。
一、30 秒速览:最小调用流程¶
1. 构造 graph → 填入你的网表(节点 + 超边)
2. 构造 fpga → 填入目标硬件的资源和拓扑
3. 构造 PartitionParams → 设置算法参数
4. 调用 partition() → 得到划分结果
5. 从 parts[i] 中读取节点 i 被分配到了哪个硬件块
二、你需要构造的三个核心数据结构¶
2.1 graph — 你的网表¶
graph 是超图数据结构(定义在 defs.h),表示待划分的网络。你需要填写以下字段:
字段 |
类型 |
含义 |
怎么填 |
|---|---|---|---|
|
|
所有节点 |
每个 node 设置 |
|
|
所有超边(线网) |
每个 net 设置 |
|
|
超边的端点展平数组 |
按顺序拼接每条 net 连接的 node ID,通过 |
|
|
节点的关联超边展平数组 |
按顺序拼接每个 node 连接的 net ID,通过 |
|
|
固定分配(可选) |
初始化为全 -1;若节点 i 必须分配到特定硬件块,设 |
|
|
区域约束(可选) |
若节点 i 只能放在某些块中,设 |
|
|
初始划分提示(可选) |
如果有初始分配建议,设 |
构造示例(3 个节点、2 条超边的简单图):
graph g;
int num_nodes = 3, num_nets = 2;
// 节点:每个节点有资源向量
g.nodes.resize(num_nodes);
g.nodes[0].resources = VectorXi::Ones(4); // LUT=1, FF=1, DSP=1, BRAM=1
g.nodes[1].resources = VectorXi::Ones(4);
g.nodes[2].resources = VectorXi::Ones(4);
// 超边 0 连接节点 0,1;超边 1 连接节点 1,2
// incident_nodes = [0, 1, 1, 2]
g.incident_nodes = {0, 1, 1, 2};
g.nets.resize(num_nets);
g.nets[0] = {1.0, 0, 2}; // weight=1, begin=0, size=2 → 引用 incident_nodes[0..1]
g.nets[1] = {1.0, 2, 2}; // weight=1, begin=2, size=2 → 引用 incident_nodes[2..3]
// 反向索引:incident_nets
// 节点0连接net0, 节点1连接net0和net1, 节点2连接net1
g.incident_nets = {0, 0, 1, 1};
g.nodes[0] = {1, 0, 1, g.nodes[0].resources}; // begin=0, size=1
g.nodes[1] = {1, 1, 2, g.nodes[1].resources}; // begin=1, size=2
g.nodes[2] = {1, 3, 1, g.nodes[2].resources}; // begin=3, size=1
// 默认无固定分配
g.fixed_assign.resize(num_nodes, -1);
说明:
incident_nodes是 CSR 格式的展平数组。nets[i].begin指向该超边第一个端点在incident_nodes中的偏移,nets[i].size是端点个数。incident_nets同理,从节点角度索引超边。
2.2 fpga — 目标硬件平台¶
fpga 描述划分目标平台的资源与拓扑(定义在 defs.h):
字段 |
类型 |
含义 |
怎么填 |
|---|---|---|---|
|
|
每个硬件块的资源容量 |
|
|
|
连通性矩阵 |
|
|
|
最短跳数矩阵 |
|
|
|
每个块允许的最大跳数 |
从 |
|
|
资源上限(可选) |
启用 |
|
|
资源下限(可选) |
启用 |
构造示例(4 个硬件块、环形拓扑):
fpga fpgas;
int K = 4;
int res_dim = 4; // 资源维度:LUT, FF, DSP, BRAM
// 每个块的资源容量(例如每个 FPGA 有 10000 LUT、20000 FF、100 DSP、50 BRAM)
fpgas.resources.resize(K);
for (int i = 0; i < K; i++)
fpgas.resources[i] = (VectorXi(4) << 10000, 20000, 100, 50).finished();
// 环形拓扑
fpgas.topology = {{0,1,0,1},{1,0,1,0},{0,1,0,1},{1,0,1,0}};
// 计算最短跳数(用 Floyd-Warshall 或直接手填)
fpgas.dist = {{0,1,2,1},{1,0,1,2},{2,1,0,1},{1,2,1,0}};
fpgas.maxDist = {3, 3, 3, 3};
2.3 PartitionParams — 算法参数¶
PartitionParams(定义在 defs.h)控制算法行为。以下是最常用的参数:
参数 |
类型 |
默认值 |
含义 |
建议设置 |
|---|---|---|---|---|
|
int |
200 |
大规模超边过滤阈值:超过此引脚数的 net 在粗化和细化中被跳过以提升性能 |
通常不用改;设大则包含更多大 net,设小则过滤更多 |
|
int |
30 |
最大粗化层数 |
通常不用改 |
|
int |
80 |
生成多少个初始方案 |
越多越可能找到好解,但更慢;建议 32~128 |
|
int |
10 |
保留多少个精英解进入细化 |
建议 4~16 |
|
bool |
false |
是否有固定分配约束 |
有固定节点时设为 true |
|
bool |
false |
是否有区域约束 |
有区域约束时设为 true |
|
bool |
false |
是否有 IO/拓扑约束 |
需要考虑引脚带宽时设为 true |
|
bool |
false |
是否强制拓扑约束 |
需要严格满足跳数限制时设为 true |
|
bool |
false |
是否启用时序驱动 |
需要时序优化时设为 true |
|
int |
10 |
并行线程数 |
根据机器配置设置 |
|
int |
-1 |
最大允许跳数 |
-1 表示自动估计 |
|
int |
1 |
粗化方法 |
1=重边匹配(推荐);0=贪心 |
注意: 划分的块数(FPGA 数量)是由
fpgas.resources.size()决定——你给fpga对象填几个块的资源容量,就会划分成几块。
三、调用 partition() — 主入口函数¶
3.1 函数签名¶
int partition(
graph &finest, // [输入] 你的网表
fpga &fpgas, // [输入] 硬件平台
vector<int> &parts, // [输出] 划分结果 ★核心输出★
PartitionParams ¶, // [输入/输出] 算法参数
flat_hash_map<string, fixInfo> &fixed_assignment, // [输入] 固定分配约束
unordered_map<string, vector<string>> &group_assignment, // [输入] 组约束
flat_hash_map<string, int> &name_map, // [输入] 名称到节点ID的映射
shared_ptr<timingEvaluator> &evaluator, // [输入] 时序评估器(可为 nullptr)
HSFullTiming::HSPartitionFlow &pFlow // [输入] 时序数据流(不用时序时传空对象)
);
3.2 返回值¶
返回 1:划分成功
返回 -1:划分失败(通常是因为资源总量超出硬件容量,无法合法分配)
3.3 核心输出:parts¶
函数执行完毕后,parts[i] 就是节点 i 被分配到的硬件块编号(范围 [0, fpgas.resources.size()-1])。
例如:parts = [2, 0, 0, 1, 2] 表示:
节点 0 → 块 2
节点 1 → 块 0
节点 2 → 块 0
节点 3 → 块 1
节点 4 → 块 2
四、完整调用示例¶
示例 1:最简场景(纯图分割,无时序,无约束)¶
#include "partition/partition.h"
#include "partition/defs.h"
using namespace std;
// ---- 1. 构造超图 ----
graph finest;
// ... 填充 nodes, nets, incident_nodes, incident_nets(见第 2.1 节)
// ---- 2. 构造硬件平台 ----
fpga fpgas;
// ... 填充 resources, topology, dist(见第 2.2 节)
// ---- 3. 配置参数 ----
PartitionParams para;
// 注意:划分块数由 fpgas.resources.size() 决定(上面已填了 4 个块)
para.num_initial_solutions = 64; // 生成 64 个初始方案
para.thread = 8; // 用 8 个线程
// ---- 4. 准备输出和约束(均为空) ----
vector<int> parts; // 输出:划分结果
flat_hash_map<string, fixInfo> fixed_assignment; // 空:无固定约束
unordered_map<string, vector<string>> group_assignment; // 空:无组约束
flat_hash_map<string, int> name_map; // 空:不需要名字映射
shared_ptr<timingEvaluator> evaluator = nullptr; // 空:不使用时序
HSFullTiming::HSPartitionFlow pFlow; // 空:不使用时序数据流
// ---- 5. 调用划分 ----
int ret = partition(finest, fpgas, parts, para,
fixed_assignment, group_assignment, name_map,
evaluator, pFlow);
if (ret == 1) {
// 划分成功!
// parts[i] 即为节点 i 的归属块号
for (int i = 0; i < parts.size(); i++) {
printf("节点 %d → 块 %d\n", i, parts[i]);
}
} else {
printf("划分失败\n");
}
示例 2:有固定分配约束¶
某些节点必须放在特定硬件块上,不能被移动:
// 假设节点名叫 "cpu_core_0",必须放在块 2
flat_hash_map<string, fixInfo> fixed_assignment;
name_map["cpu_core_0"] = 42; // 名字到 ID 的映射
fixed_assignment["cpu_core_0"] = {2, {}}; // fpgaNo=2 表示固定到块 2
para.has_fix = true; // 必须开启这个开关
示例 3:有区域约束¶
某些节点只能在指定的几个块中选择:
// 节点 "mem_ctrl" 只能放在块 0 或块 1
name_map["mem_ctrl"] = 7;
fixInfo region_info;
region_info.fpgaNo = -2; // -2 表示这是区域约束(不是固定分配)
region_info.id = {{0, 0, 0}, {0, 0, 1}}; // 每个内层向量的第 3 个元素是候选块号
fixed_assignment["mem_ctrl"] = region_info;
para.has_region = true; // 必须开启这个开关
示例 4:有组约束¶
某些节点必须被分到同一个块:
// "macro_a"、"macro_b"、"macro_c" 三个节点必须在一起
group_assignment["macro_group"] = {"macro_a", "macro_b", "macro_c"};
name_map["macro_a"] = 10;
name_map["macro_b"] = 11;
name_map["macro_c"] = 12;
示例 5:使用时序数据¶
// 声明pFlow变量,circuitFolder为时序网络文件所在目录路径(一般为--result_netedge所指定目录)
HSFullTiming::HSPartitionFlow pFlow;
sta::parse(pFlow, circuitFolder);
sta::buildNextObjInfo(pFlow);
para.partitionParams.has_timing = true;
五、执行流程说明¶
调用 partition() 后,内部自动执行以下步骤:
partition()
│
├─ 1. 处理约束(固定分配、区域约束映射到图节点)
│
├─ 2. 多线程并行调用 multilevel() × N 次
│ │
│ ├─ 2a. 粗化(Coarsening):图逐步缩小
│ ├─ 2b. 初始划分(Initial Partitioning):在最小图上生成多个候选解
│ └─ 2c. 细化(Refinement):解映射回原图的过程中用 FM/PM 等算法优化
│
├─ 3. [可选] COCP:对候选解做更强的粗化-细化
│
├─ 4. [可选] V-Cycle:对最优解进行多轮深度优化
│
└─ 5. 从所有候选解中选出综合最优解 → 写入 parts
你只需要调用 partition(),这些步骤全自动完成。
六、常见问题¶
Q: 资源向量(resources)的维度必须一致吗?¶
是的。node.resources、fpga.resources[i]、fpga.upper_resources 的维度必须相同,代表同一组资源类型(如 LUT, FF, DSP, BRAM)。
Q: 不需要时序优化时,evaluator 和 pFlow 怎么传?¶
传 nullptr 给 evaluator,pFlow 传一个默认构造的空对象即可。同时确保 para.has_timing = false。
Q: name_map 是必须的吗?¶
如果你不需要固定分配约束(has_fix=false)和区域约束(has_region=false),name_map 可以为空。如果需要这些约束,name_map 必须包含约束中引用的所有节点名到 ID 的映射。
Q: topology 和 dist 需要手算吗?¶
topology 是邻接矩阵,需要你提供。dist 可以用 Floyd-Warshall 从 topology 推导。如果你不需要拓扑约束,可以不填 topology 和 dist,并将 force_topo 和 has_io 设为 false。
Q: 划分结果质量不满意怎么办?¶
主要调整以下参数:
para.num_initial_solutions:增大(如 128、256)以探索更多候选para.V_Cycle_on = 1:开启 V-Cycle 深度优化para.COCP_on = 1:开启 COCP 粗化优化para.refine_iters:增加细化迭代次数para.max_move:增大单次细化允许的移动步数
Q: 如何验证划分结果的正确性?¶
// 计算割边权重(越小越好)
vector<vector<int>> cut_weights;
Metrics m = fpgas.computeCutWeights(finest, parts, cut_weights);
printf("Cut = %d, Violation = %d\n", m.cut, m.violation);
七、模块依赖¶
依赖 |
用途 |
必须 |
|---|---|---|
Eigen3 |
矩阵/向量运算(资源计算) |
是 |
TBB (Intel Threading Building Blocks) |
并行计算 |
是 |
spdlog |
日志输出 |
是 |
OR-Tools |
ILP 初始划分求解(COCP_ILP) |
否(不用 ILP 可跳过) |
nlohmann/json |
JSON 序列化 |
是 |
parallel-hashmap |
高性能哈希表 |
是 |
Routing 模块集成指南¶
本指南面向已经取得 Partition 结果、需要继续完成跨 FPGA/Die 布线的开发者。阅读完本文后,你可以:
知道调哪个函数(
routing_flow::routingFlow())知道 Partition 后、Routing 前还要补充哪些数据
知道哪些参数必须确认,以及从哪里读取布线结果
所有 API 的详细参数说明见 src/routing/routing.h 中的注释;算法细节见 Routing 模块技术手册。
一、30 秒速览:最小调用流程¶
1. 取得 Partition 输出的 parts
2. 补充 die_parts、cutweights 和可选的时序/端口数据
3. 构造共享的 DelayLibrary 和 Routing
4. 调用 routing_flow::routingFlow()
5. 从 router.route_trees 等字段读取结果
二、你需要准备的数据¶
2.1 Partition 之前已经存在的数据¶
数据 |
必填性 |
怎么准备 |
|---|---|---|
|
必须 |
直接复用 Partition 的网表。ARE/NET 输入可调用 |
|
必须 |
直接复用 Partition 使用的平台对象,不要为 Routing 重新编号 |
|
必须 |
直接复用顶层配置解析得到的 |
|
时序模式必填 |
通常由 |
|
条件必填 |
|
2.2 Partition 后、Routing 前补充的数据¶
数据 |
必填性 |
怎么准备 |
|---|---|---|
|
必须 |
直接使用 |
|
Die 模式必填 |
调用 |
|
必须 |
完整流程可直接取 |
|
可选 |
|
割线网级 |
时序模式条件必填 |
调用 |
|
多层级模式必填 |
调用 |
|
必须提供对象 |
直接声明 |
cut_nets、route_trees和cut_mat是 Routing 的结果,不需要在调用前手工构造。
2.3 Routing 产生的核心结果¶
字段 |
含义 |
|---|---|
|
跨分区线网及其物理端点 |
|
转换后的割线网时序路径 |
|
原始 net ID 对应的物理布线树 |
|
带端口和方向信息的布线边 |
|
布线完成后的方向负载 |
|
延迟库是否成功加载 |
三、Routing 参数¶
参数 |
必填性 |
默认值 |
建议值或填写方式 |
|---|---|---|---|
|
必须确认 |
|
需要执行 Routing/TDM 时保持 |
|
条件必填 |
|
平台包含 Die 时设为 |
|
可选 |
|
时序驱动布线时设为 |
|
可选 |
|
一般保持 |
|
可选 |
|
建议从 |
|
可选 |
|
一般保持默认值 |
|
可选 |
|
建议从 |
|
必须确认 |
|
填平台真实值,并保证 Routing 与 TDM 一致 |
|
时序/TDM 模式条件必填 |
空 |
使用与目标平台匹配且已验证的 JSON |
四、完整调用示例¶
下面的代码以一个已经完成 Partition 的小型网表为基础。示例中的容量、ratio 和 delay 只用于说明字段含义,移植时应替换为目标平台的实际数据。
4.1 构造可选的 SimpleTiming¶
只有开启时序驱动布线时才需要 simpleTimings。一条包含 3 个节点、2 条 net 的路径可以这样填写:
vector<SimpleTiming> simpleTimings;
simpleTimings.emplace_back(
vector<int>{0, 1, 2}, // path_node:从 source 到 sink 的 graph node ID
vector<int>{0, 1}, // path_net:相邻节点之间经过的原始 net ID
"-0.25", // slack:原始裕量,单位 ns
"10.0", // period:时钟周期,单位 ns
"clk_main", // clock_domain:时钟域名称
0); // pathId:该路径的唯一编号
填写时应满足:
path_node[k]与path_node[k+1]由path_net[k]连接;通常有
path_node.size() == path_net.size() + 1;node ID 和 net ID 必须来自传给 Routing 的同一份
finest;slack、period虽然是字符串,内容仍应是可解析的纳秒数值;非时序模式直接传空的
vector<SimpleTiming>。
已有 pFlow 时,推荐调用 HSFullTiming::HSCutNetFlow::buildFrom(pFlow, parts),再用 convertCutNetPathsToSimpleTimings() 自动生成割线网级路径,不必手填。
4.2 构造可选的 CutConnection¶
CutConnection 主要为 routing_cut_info.json 提供分区端口和网表连接信息,不负责替代 parts 或 cutweights。不需要稳定端口名时直接传空容器。
下面表示从物理分区 0 的 data_out 连接到物理分区 1 的 data_in:
vector<CutConnection> cutConnections;
CutConnection connection{};
connection.from_part = 0;
connection.to_part = 1;
CutConnItem item{};
item.from_port = "data_out";
item.to_port = "data_in";
item.bypass = false;
// nc 是可选的原网表追踪信息。
item.nc.nc_drv.inst = "u_source";
item.nc.nc_drv.port = "Q";
NC load{};
load.inst = "u_sink";
load.port = "D";
item.nc.nc_loads.push_back(load);
connection.connections.push_back(item);
cutConnections.push_back(connection);
from_part 和 to_part 必须与最终 route_trees_edges 使用的物理编号一致:
无 Die 模式填写 FPGA ID;
Die 模式填写展平 Die ID,即
fpga_id * dies_per_fpga + die_id;多层级模式填写
path_to_fpga生成的全局叶级 FPGA ID。
若已经调用 result::computeSubEDIF(),可直接使用 convertSubConnetsToCutConnections(resultContent.subConnets) 生成,不建议再次手写。
4.3 构造 DelayLibrary¶
Routing 和 TDM 必须共用同一个 DelayLibrary。ratio 表示一条物理通道上时分复用的逻辑信号数量,delay 表示该 ratio 下测得或标定的端到端延迟,单位为 ns。GIO 与 MGT 的硬件特性不同,必须分别填写。
方法一:直接在 C++ 中赋值¶
下面给出一个不调用 buildFromJson() 的完整示例:
DelayLibrary makeManualDelayLibrary()
{
DelayLibrary lib;
// 与 data/delay.json 一致:每个 ratio 都有一个对应的 delay(ns)。
const vector<double> gio_ratio = {
2, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80,
88, 96, 104, 112, 120, 128, 136, 144, 152,
160, 168, 176, 184, 192, 200, 208, 216,
224, 232, 240, 248, 256
};
const vector<double> gio_delay = {
4.57, 58, 62, 67, 72, 77, 81, 86, 90, 95, 100,
105, 110, 115, 120, 125, 130, 135, 140, 145,
150, 155, 160, 165, 170, 175, 180, 185,
190, 195, 200, 205, 210
};
const vector<double> mgt_ratio = {62, 124, 248, 496, 992};
const vector<double> mgt_delay = {96, 104, 124, 154, 216};
lib.gio.pwl = PWLModel::fromPoints(gio_ratio, gio_delay);
lib.mgt.pwl = PWLModel::fromPoints(mgt_ratio, mgt_delay);
if (!lib.gio.pwl.valid || !lib.mgt.pwl.valid) {
throw runtime_error("invalid ratio-delay points");
}
// 连续优化允许的 ratio 范围。
lib.gio_meta.rmin = gio_ratio.front();
lib.gio_meta.rmax = gio_ratio.back();
lib.gio_meta.has_bounds = true;
lib.mgt_meta.rmin = mgt_ratio.front();
lib.mgt_meta.rmax = mgt_ratio.back();
lib.mgt_meta.has_bounds = true;
// delay.json 没有单独写 choices,因此这里将全部采样 ratio 作为离散档位。
for (double ratio : gio_ratio)
lib.gio_meta.choices.push_back(static_cast<int>(ratio));
for (double ratio : mgt_ratio)
lib.mgt_meta.choices.push_back(static_cast<int>(ratio));
// GIO 的直通/低复用特殊点;delay 仍然是 ns。
lib.gio.has_bypass = true;
lib.gio.bypass_ratio = 2;
lib.gio.bypass_delay = 4.57;
return lib;
}
PWLModel::fromPoints() 会按 ratio 排序、合并重复 ratio,并建立分段线性斜率;区间内插值,区间外沿首段或末段外推。每类模型至少需要两个不同的 ratio 点,且 ratio 与 delay 数组长度必须一致。
手工预加载后,delay_lib_path 应留空,并在构造 Routing 后显式设置:
DelayLibrary delayLib = makeManualDelayLibrary();
para.tdmParams.delay_lib_path.clear();
// 按 4.7 节构造 Routing 后执行:
// router.delayLibReady = true;
只有在 GIO、MGT 模型都有效时才能把 delayLibReady 设为 true。
移植时应保证:
ratio 是正数,并按硬件支持的复用档位取值;
delay 是该类型物理链路在对应 ratio 下的延迟,单位统一为 ns;
choices最好是采样 ratio 的子集,使离散档位能直接命中实测点;rmin、rmax覆盖全部choices,但不要超出硬件支持范围。
方法二:使用 buildFromJson()¶
data/delay.json 采用最简格式:根对象只包含 gio 和 mgt,每个 points 元素都是 [ratio, delay_ns]:
{
"gio": {
"points": [
[2, 4.57], [8, 58], [16, 62], [24, 67], [32, 72],
[40, 77], [48, 81], [56, 86], [64, 90], [72, 95],
[80, 100], [88, 105], [96, 110], [104, 115],
[112, 120], [120, 125], [128, 130], [136, 135],
[144, 140], [152, 145], [160, 150], [168, 155],
[176, 160], [184, 165], [192, 170], [200, 175],
[208, 180], [216, 185], [224, 190], [232, 195],
[240, 200], [248, 205], [256, 210]
]
},
"mgt": {
"points": [
[62, 96], [124, 104], [248, 124], [496, 154], [992, 216]
]
}
}
buildFromJson() 接收已经解析好的 nlohmann::json,它本身不负责打开文件。读取当前文件的完整代码如下:
ifstream input("data/delay.json");
if (!input.is_open()) {
throw runtime_error("cannot open data/delay.json");
}
nlohmann::json delayJson;
input >> delayJson;
DelayLibrary delayLib;
if (!delayLib.buildFromJson(delayJson)) {
throw runtime_error("delay library is incomplete");
}
该方法会完成以下工作:
检查根对象中是否同时存在可用的
gio和mgt;将每个二元数组的第一个数解释为 ratio,第二个数解释为 delay(ns);
对 ratio 排序、合并重复点,并构造 PWL 模型与连续斜率;
以首尾采样 ratio 推导
min_ratio和max_ratio;将全部采样 ratio 推导为离散
choices;将 GIO 的最小采样点
[2, 4.57]推导为 bypass;建立可用的线性回退模型,并解析可选的多层级
scopes。
delay_detail.json 中的对象形式采样点也受支持,例如 {"ratio": 8, "delay_ns": 58};根级 bypass 也会被读取。当前 buildFromJson() 不读取 fit、fallback_linear 和 low_range_linear 字段,而是根据 points 自行建立运行时模型。
如果希望流程自动读文件,只需设置 para.tdmParams.delay_lib_path,不要提前调用 buildFromJson();routingFlow() 会读取文件并在成功后设置 router.delayLibReady=true。
4.4 示例一:无 Die 模式¶
假设 finest 有 3 个节点,Partition 输出 parts = {0, 1, 1},目标平台有 2 个 FPGA。下面的矩阵元素表示物理连接数量,不要预先乘以 ratio:
params para;
para.route = true;
para.has_die = false;
para.routing_mode = true;
para.partitionParams.constraints.has_timing = true;
para.tdmParams.gio_channel_grouping_capacity = 23;
para.tdmParams.delay_lib_path.clear(); // 本例手工预加载
vector<int> parts = {0, 1, 1};
// 无 Die 模式可传空;这里显式填写,便于统一节点映射。
vector<pair<int, int>> die_parts = {
{0, 0}, {1, 0}, {1, 0}
};
// fpgas.resources 已由 Partition 填好,这里只展示布线相关字段。
fpgas.topology = {
{0, 1},
{1, 0}
};
// 0->1 有 2 根 GIO 和 1 根 MGT;反方向相同。
fpgas.hio_channel_assignment = {
{0, 2},
{2, 0}
};
fpgas.mgt_channel_assignment = {
{0, 1},
{1, 0}
};
fpgas.cutweights_assignment = {
{0, 3},
{3, 0}
};
constraint::computeFPGATopo(fpgas);
vector<vector<int>> cutweights;
fpgas.computeCutWeightsOld(finest, parts, cutweights);
vector<SimpleTiming> simpleTimings;
simpleTimings.emplace_back(
vector<int>{0, 1, 2}, vector<int>{0, 1},
"-0.25", "10.0", "clk_main", 0);
vector<CutConnection> cutConnections;
// 若需要稳定端口名,可按 4.2 节加入 0 -> 1 的连接。
DelayLibrary delayLib = makeManualDelayLibrary();
这里的 hio_channel_assignment[0][1] = 2 表示两根物理 GIO,gio_channel_grouping_capacity = 23 表示每根 GIO 可承载 23 个逻辑通道;二者含义不同。
4.5 示例二:有 Die 模式¶
下面使用 2 个 FPGA、每个 FPGA 4 个 Die。parts 仍然保存 FPGA ID,die_parts 同时保存 FPGA ID 和 Die ID:
params para;
para.route = true;
para.has_die = true;
para.routing_mode = true;
para.partitionParams.constraints.has_timing = true;
para.tdmParams.gio_channel_grouping_capacity = 23;
para.tdmParams.delay_lib_path.clear();
vector<int> parts = {0, 0, 1};
vector<pair<int, int>> die_parts = {
{0, 0}, // node 0 -> FPGA 0, Die 0
{0, 1}, // node 1 -> FPGA 0, Die 1
{1, 0} // node 2 -> FPGA 1, Die 0
};
fpgas.dies_per_fpga = 4;
fpgas.topology = {
{0, 1},
{1, 0}
};
fpgas.cutweights_assignment = {
{0, 2},
{2, 0}
};
constraint::computeFPGATopo(fpgas);
DieConnection gio{};
gio.left_fpga = 0;
gio.left_die = 1;
gio.right_fpga = 1;
gio.right_die = 0;
gio.left_socket = "J10";
gio.right_socket = "J20";
gio.isMGT = false;
gio.left2right.total_channels = 1;
gio.right2left.total_channels = 1;
DieConnection mgt{};
mgt.left_fpga = 0;
mgt.left_die = 1;
mgt.right_fpga = 1;
mgt.right_die = 0;
mgt.left_socket = "MGT0";
mgt.right_socket = "MGT1";
mgt.isMGT = true;
mgt.left2right.total_channels = 1;
mgt.right2left.total_channels = 1;
fpgas.die_connections = {gio, mgt};
vector<vector<int>> cutweights;
fpgas.computeCutWeightsOld(finest, parts, cutweights);
vector<SimpleTiming> simpleTimings;
simpleTimings.emplace_back(
vector<int>{0, 1, 2}, vector<int>{0, 1},
"-0.25", "10.0", "clk_main", 0);
vector<CutConnection> cutConnections;
DelayLibrary delayLib = makeManualDelayLibrary();
实际工程中通常调用 partDie() 生成 die_parts,并由 FIT/socket 解析流程生成 die_connections。failed_channels 中填写不可用的物理通道编号;可用通道数按 total_channels - failed_channels.size() 计算。
4.6 示例三:多层级模式¶
假设 hierarchy_root 已由平台解析流程构造,node_paths[i] 是节点 i 的叶级层次路径:
params para;
para.route = true;
para.has_hierarchy = true;
para.has_die = false;
para.routing_mode = true;
para.partitionParams.constraints.has_timing = true;
para.tdmParams.gio_channel_grouping_capacity = 23;
para.tdmParams.delay_lib_path.clear();
fpga fpgas;
fpga fpgas0;
map<vector<int>, int> path_to_fpga;
const int fallback_capacity = 23;
if (!flattenHierarchyToFpgasForRouting(
hierarchy_root, fpgas, fpgas0, path_to_fpga,
fallback_capacity, /*delay_lib_path=*/"")) {
throw runtime_error("failed to flatten hierarchy");
}
constraint::computeFPGATopo(fpgas);
// node_paths 来自层次化 Partition,例如 {rack, cluster, board, fpga}。
vector<int> parts(node_paths.size(), -1);
for (size_t i = 0; i < node_paths.size(); ++i) {
auto it = path_to_fpga.find(node_paths[i]);
if (it == path_to_fpga.end()) {
throw runtime_error("node path has no leaf FPGA");
}
parts[i] = it->second;
}
vector<pair<int, int>> die_parts(parts.size());
for (size_t i = 0; i < parts.size(); ++i) {
die_parts[i] = {parts[i], 0};
}
vector<vector<int>> cutweights;
fpgas.computeCutWeightsOld(finest, parts, cutweights);
vector<SimpleTiming> simpleTimings;
// 按 4.1 节填写;其中仍使用 finest 的 node/net ID。
vector<CutConnection> cutConnections;
// 若需要端口信息,from_part/to_part 使用 path_to_fpga 的全局 ID。
DelayLibrary delayLib = makeManualDelayLibrary();
如果需要层级延迟,可以在手工基础库上为不同范围增加固定偏移:
void setScopeOffset(DelayLibrary &lib, DelayScope scope, double offset_ns)
{
auto &dst = lib.scoped_models[static_cast<int>(scope)];
dst.has_gio = true;
dst.has_mgt = true;
dst.gio = lib.gio;
dst.mgt = lib.mgt;
dst.gio_meta = lib.gio_meta;
dst.mgt_meta = lib.mgt_meta;
DelayLibrary::addDelayOffset(dst.gio, offset_ns);
DelayLibrary::addDelayOffset(dst.mgt, offset_ns);
lib.has_scoped_models = true;
}
delayLib.initDefaultScopeModels();
setScopeOffset(delayLib, DelayScope::Board, 0.0);
setScopeOffset(delayLib, DelayScope::InterBoard, 38.79);
setScopeOffset(delayLib, DelayScope::InterCluster, 128.007);
setScopeOffset(delayLib, DelayScope::InterRack, 160.0);
偏移量同样是 ns,应来自对应板内、跨板、跨 cluster 和跨 rack 链路的测量结果。也可以在 JSON 的 scopes 中为某个范围提供独立 points,而不仅是固定偏移。
4.7 三种模式共用的 Routing 调用¶
完成 4.4、4.5 或 4.6 中任意一组输入后,调用方式相同:
Routing router(
finest,
die_parts,
fpgas,
fpgas.die_connections,
para.partitionParams.timing.extra_delay_cut,
para.partitionParams.timing.extra_delay_tdm,
para.partitionParams.timing.extra_delay_cut_die,
para.routing_mode,
para.partitionParams.timing.mul_clock_attr,
para.has_die,
para.routingParams.routing_cost_factor,
simpleTimings,
&delayLib);
router.timing_routing_cost_mode =
para.routingParams.timing_routing_cost_mode;
router.timing_hop_penalty_multiplier =
para.routingParams.timing_hop_penalty_multiplier;
router.channel_grouping_capacity =
para.tdmParams.gio_channel_grouping_capacity > 0
? para.tdmParams.gio_channel_grouping_capacity
: 23;
// 本文示例已经手工构造 delayLib,因此显式标记为可用。
// 如果使用 delay_lib_path 自动加载,不要提前设置该标志。
router.delayLibReady = true;
routing_flow::routingFlow(
finest,
parts,
die_parts,
fpgas,
cutweights,
para,
router,
pFlow,
&delayLib,
cutConnections);
if (router.route_trees.empty() && !router.cut_nets.empty()) {
throw runtime_error("Routing produced no route tree");
}
Routing 引用 finest、die_parts、fpgas 和 DelayLibrary,这些对象必须至少存活到 TDM 执行结束。
五、执行流程说明¶
Partition 输出
|
v
补齐割线、Die、时序和平台容量
|
v
提取 cut_nets 并压缩时序路径
|
v
排序并生成初始布线树
|
v
按需执行拆线重布
|
v
写入 route_trees、cut_mat 和连接结果
这些步骤由 routingFlow() 自动完成,集成层不需要逐个调用内部算法。
5.1 多层级平台¶
多层级流程先把所有叶级 FPGA 展平为 routing_fpgas,再用全局 FPGA ID 执行与普通平台相同的布线。fpga_hierarchy_paths[id] 保存该 FPGA 从外层到内层的层级路径,Routing 据此判断连接跨越的层级;同一份展平平台和路径随后交给 TDM。
注意:编号不应混用。
parts、die_parts、cutweights、routing_fpgas和层级路径必须使用同一套展平编号。
六、结果读取¶
建议按以下顺序检查:
cut_nets是否覆盖所有跨 FPGA/Die 线网;route_trees中每个 sink 是否能回溯到 source;cut_mat是否与布线树的实际负载一致;route_trees_edges的端口和方向是否完整;时序模式下,
cut_timing_paths是否引用有效的局部 cut-net 编号。
常用输出包括:
输出 |
内容 |
|---|---|
|
端口连接和可用的布线父节点关系 |
|
程序内的物理布线树 |
|
可用于导出的命名布线边 |
七、常见问题¶
Q:Partition 成功,为什么没有生成 cut_nets?¶
先检查 parts 是否确实包含多个 FPGA ID,以及 cutweights 是否非空。如果所有 net 的端点都在同一 FPGA,cut_nets 为空是正常结果。
Q:无 Die 模式必须填写 die_parts 吗?¶
不是。has_die=false 时可以传空容器;如果希望统一节点映射,也可以填写 {parts[i], 0}。
Q:为什么布线结果不可达?¶
检查平台拓扑、方向容量、FPGA/Die 编号和 cutweights 维度。多层级模式还要检查展平编号与 fpga_hierarchy_paths 是否对应。
八、模块依赖¶
依赖 |
用途 |
|---|---|
Partition 输出 |
提供 |
Eigen3、TBB |
数据计算与并行执行 |
spdlog |
日志输出 |
nlohmann/json |
延迟库和结果 JSON |
TDM 模块集成指南¶
本指南面向已经完成 Routing、需要继续分配时分复用比例和物理通道的开发者。阅读完本文后,你可以:
知道调哪个函数(
tdm::executeTDM())知道需要提供哪些 Routing 结果和参数
知道从哪些文件读取最终 TDM 结果
所有 API 的详细参数说明见 src/tdm/tdm.h 和 src/tdm/delay_lib.h 中的注释;算法细节见 TDM 模块技术手册。
一、30 秒速览:最小调用流程¶
1. 完成 routing_flow::routingFlow()
2. 确认 Routing 的布线树、容量和延迟库有效
3. 设置 TDMParams
4. 调用 tdm::executeTDM()
5. 从 tdmDelayReport.json 和 routing.out 读取结果
二、你需要准备的四个输入¶
输入 |
必填性 |
怎么准备 |
|---|---|---|
|
必须 |
先按上一节构造 |
|
必须 |
直接复用 Routing 使用的平台对象;多层级流程使用 |
|
必须 |
直接复用 Routing 的顶层配置对象,并在调用前确认 |
|
必须 |
在 Routing 前声明一次并传给两个模块; |
timing_edges、tdm_groups 和离散 ratio 都由 executeTDM() 内部生成,不需要手工填写。
三、TDM 参数¶
参数 |
必填性 |
默认值 |
建议值或填写方式 |
|---|---|---|---|
|
时序模式条件必填 |
空 |
使用与平台匹配且已验证的 JSON |
|
必须确认 |
|
填平台真实值,并与 Routing 保持一致 |
|
可选 |
|
首次集成保持默认值 |
|
可选 |
|
首次集成保持默认值 |
|
可选 |
|
容量联调时可临时设为 |
|
可选 |
|
正式结果保持 |
|
可选 |
|
一般保持 |
|
可选 |
|
稳定基线用 |
|
可选 |
|
日常保持 |
3.1 tdm_opt_mode 的作用¶
取值 |
作用 |
|---|---|
|
使用稳定的默认后处理流程 |
|
启用 GIO root 方向拆分 |
|
启用最终局部打磨 |
|
同时启用 root split 和最终打磨 |
|
跳过最终后处理,主要用于调试 |
四、完整调用示例¶
executeTDM() 的四个参数在三种平台模式下相同,区别在于 router 和 fpgas 中保存的编号、物理连接与层级信息。不要手工构造内部 Tdm、timing_edges 或 tdm_groups。
4.1 函数签名¶
void executeTDM(
Routing &router,
const fpga &fpgas,
const params ¶,
DelayLibrary &delayLib);
4.2 示例一:无 Die 模式¶
接续 Routing 4.4 和 4.7 的 router、fpgas、delayLib:
para.has_die = false;
para.tdmParams.max_iters = 100;
para.tdmParams.convergence_threshold = 1e-3;
para.tdmParams.avg_only = false;
para.tdmParams.tdm_fast_mode = false;
para.tdmParams.tdm_opt_mode = "legacy";
para.tdmParams.allow_mgt_on_non_timing_edges = false;
para.tdmParams.gio_channel_grouping_capacity = 23;
if (!router.delayLibReady) {
throw runtime_error("DelayLibrary is not ready");
}
tdm::executeTDM(router, fpgas, para, delayLib);
TDM 会从 router.route_trees、router.cut_nets 和 router.cut_mat 自动建立容量组。parts 或 die_parts 不需要再次传入。
4.3 示例二:有 Die 模式¶
接续 Routing 4.5 和 4.7。只要 para.has_die、router.die_parts、fpgas.dies_per_fpga 和 fpgas.die_connections 已经一致,调用本身没有变化:
para.has_die = true;
para.tdmParams.max_iters = 100;
para.tdmParams.avg_only = false;
para.tdmParams.tdm_opt_mode = "legacy";
para.tdmParams.gio_channel_grouping_capacity = 23;
if (router.die_parts.size() != router.finest.nodes.size()) {
throw runtime_error("die_parts size mismatch");
}
tdm::executeTDM(router, fpgas, para, delayLib);
TDM 内部使用 fpga_id * dies_per_fpga + die_id 生成展平物理节点,并从 die_connections 区分片内、GIO 和 MGT 连接。
4.4 示例三:多层级模式¶
接续 Routing 4.6 和 4.7。多层级 TDM 必须继续传入展平后的 fpgas,不能换回 hierarchy_root.fpgas:
para.has_hierarchy = true;
para.has_die = false;
para.tdmParams.max_iters = 100;
para.tdmParams.avg_only = false;
para.tdmParams.tdm_opt_mode = "legacy";
para.tdmParams.gio_channel_grouping_capacity = 23;
if (fpgas.fpga_hierarchy_paths.size() != fpgas.resources.size()) {
throw runtime_error("hierarchy path count mismatch");
}
if (!delayLib.has_scoped_models) {
throw runtime_error("hierarchy delay scopes are not configured");
}
tdm::executeTDM(router, fpgas, para, delayLib);
当 delayLib.has_scoped_models=true 且 fpga_hierarchy_paths 非空时,Routing 和 TDM 会自动按 Board、InterBoard、InterCluster、InterRack 选择延迟模型。
4.5 使用文件自动加载延迟库¶
如果不希望在 C++ 中填写 ratio–delay 点,可以让流程读取 JSON:
DelayLibrary delayLib;
para.tdmParams.delay_lib_path = "data/delay.json";
Routing router(
finest, die_parts, fpgas, fpgas.die_connections,
para.partitionParams.timing.extra_delay_cut,
para.partitionParams.timing.extra_delay_tdm,
para.partitionParams.timing.extra_delay_cut_die,
para.routing_mode,
para.partitionParams.timing.mul_clock_attr,
para.has_die,
para.routingParams.routing_cost_factor,
simpleTimings,
&delayLib);
// 不要在这里手工设置 router.delayLibReady。
routing_flow::routingFlow(
finest, parts, die_parts, fpgas, cutweights, para,
router, pFlow, &delayLib, cutConnections);
if (!router.delayLibReady) {
throw runtime_error("failed to load delay library");
}
tdm::executeTDM(router, fpgas, para, delayLib);
这里 routingFlow() 负责打开文件、解析为 nlohmann::json、调用 buildFromJson() 并设置 delayLibReady。如果直接从 TDM 入口开始,executeTDM() 也会在该标志为 false 时尝试相同的加载流程。
函数没有返回值。标准流程会写出 TDM 报告,配置、容量或合法化失败信息通过日志输出。
五、执行流程说明¶
Routing 结果
|
v
构造时序边和容量组
|
v
连续分配 TDM ratio
|
v
映射到离散档位并选择 GIO/MGT
|
v
检查物理通道合法性
|
v
重新计算时序并导出结果
连续阶段使用拉格朗日松弛在共享容量约束下分配 ratio;离散档位和物理通道检查在后续阶段完成。更详细的推导和选择原因见 TDM 模块技术手册。
六、输出与结果读取¶
输出 |
主要内容 |
|---|---|
|
物理连接、链路类型、ratio、延迟、channel 和 slot |
|
每个线网的 source、sinks、物理路径、ratio 和延迟 |
|
条件生成的连接对容量和类型统计 |
|
条件生成的 TDM 前后时序信息 |
建议至少检查:
ratio 是否属于延迟库提供的离散 choices;
每个方向是否满足容量约束;
GIO channel、socket/root 和 MGT lane/clock 是否可合法装箱;
最终离散延迟下的 WNS 是否符合要求。
七、多层级平台¶
TDM 直接复用 Routing 使用的展平平台和 fpga_hierarchy_paths。它根据源、目的 FPGA 的层级路径选择对应的延迟范围,因此不需要集成层再次转换编号。
如果延迟库没有层级 scopes,或平台没有提供层级路径,流程会使用基础延迟模型。多层级模式下仍需遵守同一原则:
注意:编号不应混用。 TDM 输入中的物理节点、容量矩阵和层级路径必须与 Routing 的展平编号一致。
八、常见问题¶
Q:为什么连续容量可行,离散结果仍然失败?¶
连续解只满足共享容量约束;最终结果还必须落在延迟库的离散档位中,并满足 GIO/MGT、socket/root、lane/clock 等物理限制。
Q:为什么采用拉格朗日松弛?¶
它可以把共享容量约束转化为容量价格,使各容量组能够并行更新,同时让关键时序边获得更合适的 ratio。连续解还可以为后续离散化提供较好的起点。
Q:为什么 Routing 与 TDM 的延迟不一致?¶
确认两个模块使用同一个 DelayLibrary、相同的 gio_channel_grouping_capacity,并检查 router.delayLibReady。多层级平台还要确认层级路径已传入。
九、模块依赖¶
依赖 |
用途 |
|---|---|
Routing 输出 |
提供布线树、方向负载和割线时序路径 |
Partition 平台数据 |
提供 FPGA/Die 编号、拓扑和容量 |
TBB、spdlog |
并行执行和日志输出 |
nlohmann/json |
延迟库与结果文件 |