clip_layout
TL;DR:
clip_layout
根据给定的切割网格, 将原始 layout 给切割成目标 layout
为了缩减非重点代码, 以及简化写法, 文中涉及代码可能通过C++11以上的特性或直接进行简写, 与源代码不一致. 总之就是因为个人喜好, 有关 Klayout 的源码会有所变动.
简介
主要流程非常明确:
flowchart LR
A[collect clip var] --> B[make clip var]
B --> C[clip cell by var]
C --> D[add new cell]
接下来逐步解析各个流程的代码
collect_clip_variants
在主流程中, 将会逐个遍历给定的切割网格队列, 搜集每个网格下的 variants
EDA 工具中常常会见到 Cell-Instance 的概念. 在 Klayout 中引入了 variant 的概念, 可以视作一个即将变化的 instance (i.e. instance 变量)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void
collect_clip_variants (const db::Layout &layout,
db::Layout &target_layout,
db::cell_index_type cell_index,
const db::Box &clip_box,
std::map <std::pair <db::cell_index_type, db::Box>, db::cell_index_type> &variants,
bool stable)
{
const db::Cell &cell = layout.cell (cell_index);
db::box_convert <db::CellInst> bc (layout);
db::Box cell_box;
if (stable) {
cell_box = clip_box;
} else {
cell_box = cell.bbox () & clip_box;
// 提前终止条件, 避免无意义更深遍历
if (cell_box.empty ()) {
return;
}
}
// 虽然 variants 的初衷只是收集需要的结果, 并不专用于 cache 优化 DFS. 但既然已经有了那就一石二鸟.
if (variants.insert ({ {cell_index, cell_box}, 0 }).second) {
for (auto inst = cell.begin_touching (cell_box); ! inst.at_end (); ++inst) {
for (auto a = inst->cell_inst ().begin_touching (cell_box, bc); ! a.at_end (); ++a) {
// 将剩余的切割网格 transform 至 instance a 所在的坐标系下
db::Box inst_clip_box = db::Box (cell_box.transformed (inst->cell_inst ().complex_trans (*a).inverted ()));
collect_clip_variants (layout, target_layout, inst->cell_index (), inst_clip_box, variants, false);
}
}
}
}
通过代码可以看出, 实质就是一个 DFS 递归直至切割网格不与 Cell 相交提前终止, 有以下要点:
- 应当明白, 何时才会存在 variant 重复收集的问题?
- 存在 variant 的复用, 即原 Instance 被切割后产生的新 variant( 也就是 Instance ), 可能被插入到不同的父 Instance 下. 因此从原 Instance 坐标系的角度下, 对应自己的 cell_index 是一样的, 同时当前坐标系下被切后的 cell_box 也一样
- 为何要将 clip_box transform 至对应的 Instance 坐标系下?
Cell::bbox()
获得的图形在当前 Cell 的坐标系下, 实质最核心的原因还是尽可能复用 instance 不产生更多的新 instance, 因此可以通过在当前坐标系下被切割后的区域判断是否能复用
- 参数
stable
的含义?
make_clip_variants
将前文收集到的 variant 信息(实质为 cell 信息)插入至目标 layout, 等价于新建一个 cell 入口, 该部分代码简单, 逻辑清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void
make_clip_variants (const db::Layout &layout,
db::Layout &target_layout,
std::map <std::pair <db::cell_index_type, db::Box>, db::cell_index_type> &variants)
{
for (auto &v : variants) {
// 对应的 box 不相等意味着当前 Cell 与 clip_box 相交, 因此需要新增 Cell
if (v.first.second != layout.cell (v.first.first).bbox () || &layout != &target_layout) {
// need for a new cell
v.second = target_layout.add_cell (layout, v.first.first);
} else { // 否则, 认为当前 Cell 完整在 clip_box 内部, 可以直接给原 layout 的 cell_index 用于拷贝
v.second = v.first.first;
}
}
}
rename_cell
这一部分实际并不是 clip_layout
的专属流程, 但对于理解最终获得的版图上 cell 名称的命名规律有所帮助.
Klayout 共有两种针对 Cell 进行重命名的方式:
Layout::rename_cell(cell_index_type id, const char *name)
: 将指定 cell index 对应的 cell 进行重命名Layout::add_cell
: 拥有一系列的重载, 但通常可以给予一个初始化的名称(可以是一个指定名称, 也可以是一个存在的 cell index 作为 base 进行一定的规则变化)或者系统自动进行命名
对于前者而言, 是一个很简单的操作, 就是将对应的 cell name 进行修改. 我们需要详细讨论一下后者, 因为后者涉及到针对现有的 cell 切割后, 产生的新 cell 的重命名规则.
核心重命名代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cell_index_type
Layout::add_cell (std::string &name)
{
if (!name) {
name = uniquify_cell_name (nullptr);
} else {
cell_map_type::const_iterator cm = m_cell_map.find (name);
if (cm != m_cell_map.end ()) {
const db::Cell &c= cell (cm->second);
if (c.is_ghost_cell () && c.empty ())
// ghost cells are available as new cells - the idea is to
// treat them as non-existing.
return cm->second;
else
// create a unique name
name = uniquify_cell_name (name);
}
}
// 省略一些对新 Cell 创建以及注册名称等等操作
}
std::string
Layout::uniquify_cell_name (std::string &name) const
{
// 如果给定的 name 已经是 unique, 直接返回
if (name != nullptr && m_cell_map.find (name) == m_cell_map.end ()) {
return std::string (name);
} else {
std::string b;
// if the cell does not have a valid name yet, create a unique one.
unsigned int j = 0;
// 一个 magic number
for (unsigned int m = 0x40000000; m > 0; m >>= 1) {
j += m;
b = std::string (name ? name : "") + "$" + tl::to_string (j);
if (m_cell_map.find (b) == m_cell_map.end ()) {
j -= m;
}
}
b = std::string (name ? name : "") + "$" + tl::to_string (j + 1);
return b;
}
}
uniquify_cell_name
实现逻辑比较简单和清晰, 但是实现的方法很耐人寻味. 要我自己实现就直接循环从 1 开始就完事儿了, 但他搞了一个看上去很花哨的实现方案. 最终呈现的效果就是类似二进制状态压缩的策略. 将 n 转为二进制视为当前状态, 位上为 0 视为空闲, 1 视为占用, 这里举例:
- n = 5, 101: 表明 [4, 1] 被占用, 下一个重命名的空闲后缀为 6
- n = 7, 0111: 表明 [4, 2, 1] 被占用, 下一个重命名的空闲后缀为 8
即, 一定从最大占用点到上一个空闲点之间找新的空闲点. 我们可以将 $2^n$ 的点位视作每个链表的首指针, 链表长度为 $2^{n-1}$. 这样就等价于 vector 中进行内存扩张的策略了.
此外
0x40000000
比较有意思. 首先可以明确的这是一个经验值, 因此具体如何考量的我这种菜鸟就不去深究了. 但是比较有趣的地方是, 这里用到了二进制状态的特性, 从而确保了一定能通过非空闲的首指针的和 + 1 得到下一个空闲点
clip_cell
实际上有非常多的前置知识需要掌握, 但本文本来就不是教科书, 目的也只是为了记录个人理解, 因此势必有很多已经掌握较为熟练的知识不会被重复提及.
Instance
是一个极度重要的概念, 请一定确信是否理解了 Klayout 作者留下的注释
1
2
3
4
5
6
/**
* @brief A instance reference
*
* In analogy to the "shape" reference (i.e. db::Shape), this instance
* reference points to a certain instance and provides an abstract interface.
*/
他将 Shape
与 Instance
做类比, 因此需要建立以下的认知: Shape
<=> Polygon
, Instance
<=> Cell
. 即, 我们能够将 Shape
给实例化 (instantiate) 为 Polygon
, 我们也能将 Instance
给实例化为 Cell
. 前者我们通过 Shape::instantiate
来实现, 后者我们通过 layout.cell (inst->cell_index ())
来实现. 当我们建立了这个认知之后, 对于 Klayout 的许多与 Layout
相关代码理解就会很轻松.
TL;DR. 就是
Instance
具备Cell
的属性.
在下方核心代码省略的实际切割图形的部分, 混用了 Shape::box()
, Shape::bbox()
, 以及实例化为其他类型后的 bbox, 在这里做如下解释:
Shape::box()
: 当且仅当Shape
类型为 box 相关类型时使用, 直接返回对应的 box.Shape::bbox()
: 等价于实例化后的 bbox, 含义就是实例化后的图形的 bbox.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
static void
clip_cell (const db::Layout &layout,
db::cell_index_type cell_index,
db::Layout &target_layout,
db::cell_index_type target_cell_index,
const db::Box &clip_box,
std::map <std::pair <db::cell_index_type, db::Box>, db::cell_index_type> &variants)
{
const db::Cell &cell = layout.cell (cell_index);
db::Cell &target_cell = target_layout.cell (target_cell_index);
// 当 cell 完全在 clip_box 内部时
if (cell.bbox ().inside (clip_box)) {
// 要么 layout 不一致,要么同 layout 下 Cell 不一致
// 否则等于你 input/output 同一个管道
if (&target_layout != &layout || cell_index != target_cell_index) {
// simplification of shape copy op in case of no clipping ..
for (unsigned int l = 0; l < layout.layers (); ++l) {
if (layout.is_valid_layer (l)) {
target_cell.shapes (l) = cell.shapes (l);
}
}
// 开始进行 instance 层面的拷贝
// 当前 Cell 的 bbox 完全 inside clip_box, 证明所有其下的子 instance 也必定在 clip_box 内
for (db::Cell::const_iterator inst = cell.begin (); ! inst.at_end (); ++inst) {
// instance is completely inside: nevertheless we must look up the target cell for the different layout case.
db::CellInstArray new_inst = inst->cell_inst ();
// 这个 Instance 实体所在的位置(实例化 Instance 为 Cell)
const db::Cell &inst_cell = layout.cell (inst->cell_index ());
// 意思是没有检查 cell_index 是否合法只能用这个方式检查?
if (! inst_cell.bbox ().empty ()) {
auto vmp = variants.find (std::make_pair (inst->cell_index (), inst_cell.bbox ()));
tl_assert (vmp != variants.end ());
// 拿到 target_layout 中开好的 instance 的 cell index
new_inst.object () = db::CellInst (vmp->second);
// TODO: keep properties
target_cell.insert (new_inst);
}
}
}
} else { // cell 被切割时
tl_assert (&layout != &target_layout || target_cell_index != cell_index);
// 针对每个 layer 中每个与 clip_box 相交的 shape 进行处理
// 实际的处理过程就是 clip_shape 的过程, 由于 shape 类型较多因此需要分别处理
for (unsigned int l = 0; l < layout.layers (); ++l) {
if (layout.is_valid_layer (l)) {
for (db::ShapeIterator sh = cell.shapes (l).begin_touching (clip_box, db::ShapeIterator::All); ! sh.at_end (); ++sh) {
// 省略大量根据 shape 类型进行不同切割操作
}
}
}
db::box_convert <db::CellInst> bc (layout);
// 处理完 Cell 开始对子 Instance 进行处理
// 当前 Cell bbox 与 clip_box 相交, 因此拿到与 clip_box 相交的所有 子 instance 迭代器
for (db::Cell::touching_iterator inst = cell.begin_touching (clip_box); ! inst.at_end (); ++inst) {
if (inst->cell_inst ().bbox (bc).inside (clip_box)) { // 完全 inside 直接插入
// instance is completely inside
db::CellInstArray new_inst = inst->cell_inst ();
const db::Cell &inst_cell = layout.cell (inst->cell_index ());
std::map <std::pair <db::cell_index_type, db::Box>, db::cell_index_type>::const_iterator vmp = variants.find (std::make_pair (inst->cell_index (), inst_cell.bbox ()));
tl_assert (vmp != variants.end ());
new_inst.object () = db::CellInst (vmp->second);
// TODO: keep properties
target_cell.insert (new_inst);
} else {
// 部分相交, 拿相关 instance
for (db::CellInstArray::iterator a = inst->cell_inst ().begin_touching (clip_box, bc); ! a.at_end (); ++a) {
db::Box inst_clip_box = db::Box (clip_box.transformed (inst->cell_inst ().complex_trans (*a).inverted ()));
const db::Cell &inst_cell = layout.cell (inst->cell_index ());
inst_clip_box &= inst_cell.bbox ();
// 排除不相交部分
if (! inst_clip_box.empty ()) {
// 仍然需要找到 collect 过程中获得的信息
// 实质是根据被切割后的 cell box 寻找已经新生成的 rename cell, 在这一步对于新生成的 cell 会尽可能复用
std::map <std::pair <db::cell_index_type, db::Box>, db::cell_index_type>::const_iterator vmp = variants.find (std::make_pair (inst->cell_index (), inst_clip_box));
tl_assert (vmp != variants.end ());
db::CellInstArray new_inst;
// 非 magnification/rotation 直接创建, 否则需要额外做一个 transform
// 因为 CellInstArray 是一个 Array 中只存了 simple_trans
if (inst->is_complex ()) {
new_inst = db::CellInstArray (db::CellInst (vmp->second), inst->cell_inst ().complex_trans (*a));
} else {
new_inst = db::CellInstArray (db::CellInst (vmp->second), *a);
}
target_cell.insert (new_inst);
}
}
}
}
}
}
总结
我们需要明白, clip_layout
能够正确运转是建立在 instance proxy 的机制上. 即, 我们可以任意地插入 instance 构建 hierarchy 层级上的父子关系后, 最后更新这些 instance 指代的 Cell, 从而在版图上同步更新对应的 instance.
- 这也是为何最后在执行
clip_cell
时并未严格保证自底向上计算. 反正过程插入的都是抽象的 proxy 而非实体, 且最终每个实体都会被正确插入 shape 与 child_inst. - 这也是为何针对每个 Cell 执行的计算过程只需要关注直接相连的子 instance. BTW, Klayout 执行
flatten
, 意为 flatten 指定 cell 的代码中会出现 direct_children 这个变量名, 含义相同.
此外, 其整个算法流程设计的基本思路是, 每个 target_layout 的 Cell 必定唯一对应一个 src_layout 的 Cell. 即 Cell 被切割成多个, 则是多个 clipped cell 对应一个 src_cell, 未被切割则是一一对应.
- 这也是为何整个流程中, 都无需构建 hierarchy relation. instance 的父子关系完全依照 src_layout 中的情况.