Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F27032965
partition.hpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Authored By
Unknown
Size
24 KB
Referenced Files
None
Subscribers
None
partition.hpp
View Options
// Copyright 2025 Oliver Theimer
#pragma once
#include
<algorithm>
#include
<cassert>
#include
<cstdint>
#include
<optional>
#include
<vector>
#include
"mockturtle/networks/cover.hpp"
#include
"mockturtle/utils/window_utils.hpp"
#include
"mockturtle/views/color_view.hpp"
/**
* @class block_t
* @brief one subcircuit of a support-bounded partition
*
* Every gate is owned by exactly one block; a leaf is a primary input, a
* constant, or a root of another block.
*/
struct
block_t
{
/// input support of the block, at most `max_inputs` entries
std
::
vector
<
uint64_t
>
leaves
;
/// gates owned by the block, disjoint between blocks, includes the roots
std
::
vector
<
uint64_t
>
gates
;
/// gates that drive a primary output or a gate outside of this block
std
::
vector
<
uint64_t
>
roots
;
};
/**
* @brief maps network nodes to the decision diagram variables of a single block
*
* Renumbers a block's leaves to the contiguous ids 2..k+1, because bbdd's
* POS(in, cvo) is `in % cvo->size()`. BuDDy gets `id - 2`.
*/
struct
leaf_map_t
{
/// node index -> diagram variable id in 2..k+1, 0 when not a leaf
std
::
vector
<
uint32_t
>
local
;
/// diagram variable id - 2 -> node index
std
::
vector
<
uint64_t
>
nodes
;
uint32_t
operator
[](
uint64_t
n
)
const
{
return
local
[
n
];
}
size_t
size
()
const
{
return
nodes
.
size
();
}
};
/**
* @brief builds the leaf map of a block
*
* @param network_size number of nodes in the network the block came from
* @param leaves leaves of the block, at most one diagram variable each
* @return leaf map assigning the contiguous ids 2..k+1 to the leaves
*/
inline
leaf_map_t
make_leaf_map
(
size_t
network_size
,
std
::
vector
<
uint64_t
>
const
&
leaves
)
{
leaf_map_t
map
;
map
.
local
.
assign
(
network_size
,
0
);
map
.
nodes
.
reserve
(
leaves
.
size
());
for
(
uint64_t
const
&
l
:
leaves
)
{
map
.
local
[
l
]
=
static_cast
<
uint32_t
>
(
map
.
nodes
.
size
())
+
2
;
map
.
nodes
.
push_back
(
l
);
}
return
map
;
}
namespace
partition_detail
{
using
cover_color_view
=
mockturtle
::
out_of_place_color_view
<
mockturtle
::
cover_network
>
;
/// number of times the cut is allowed to exceed the budget before giving up,
/// mirrors mockturtle::expand_towards_tfi
static
constexpr
uint32_t
MAX_ITERATIONS
=
5u
;
/**
* @brief zero cost expansion of a cut towards the transitive fanin
*
* mockturtle::expand0_towards_tfi, except that gates owned by another block
* are terminal too, so a cone never duplicates foreign logic.
*
* @param ntk network wrapped in a color view
* @param leaves cut that should be expanded, modified in place
* @param is_terminal predicate that decides where the expansion has to stop
* @return true if and only if every leaf is a terminal
*/
template
<
typename
Ntk
,
typename
TermFn
>
bool
expand0_bounded
(
Ntk
const
&
ntk
,
std
::
vector
<
typename
Ntk
::
node
>
&
leaves
,
TermFn
&&
is_terminal
)
{
using
node
=
typename
Ntk
::
node
;
bool
trivial_cut
=
true
;
bool
changed
=
true
;
std
::
vector
<
node
>
new_leaves
;
while
(
changed
)
{
trivial_cut
=
true
;
changed
=
false
;
for
(
auto
it
=
std
::
begin
(
leaves
);
it
!=
std
::
end
(
leaves
);)
{
if
(
is_terminal
(
*
it
))
{
++
it
;
continue
;
}
trivial_cut
=
false
;
// count the fanins that are already part of the cut
uint32_t
count_fanin_inside
=
0
;
std
::
optional
<
node
>
outside
;
ntk
.
foreach_fanin
(
*
it
,
[
&
](
auto
const
&
fi
)
{
node
const
f
=
ntk
.
get_node
(
fi
);
if
(
ntk
.
eval_color
(
f
,
[
&
ntk
](
auto
c
)
{
return
c
==
ntk
.
current_color
();
}))
{
++
count_fanin_inside
;
}
else
{
outside
=
f
;
}
});
// expansion would grow the cut, leave this leaf alone
if
(
count_fanin_inside
+
1
<
ntk
.
fanin_size
(
*
it
))
{
++
it
;
continue
;
}
if
(
outside
)
{
if
(
ntk
.
eval_color
(
*
outside
,
[
&
ntk
](
auto
c
)
{
return
c
!=
ntk
.
current_color
();
}))
{
new_leaves
.
push_back
(
*
outside
);
ntk
.
paint
(
*
outside
);
}
}
it
=
leaves
.
erase
(
it
);
changed
=
true
;
}
std
::
copy
(
std
::
begin
(
new_leaves
),
std
::
end
(
new_leaves
),
std
::
back_inserter
(
leaves
));
new_leaves
.
clear
();
}
return
trivial_cut
;
}
/**
* @brief selects the next fanin that should be pulled into the cut
*
* select_next_fanin_to_expand_tfi with a custom terminal predicate: the most
* referenced fanin wins, ties go to the higher fanout.
*
* @param ntk network wrapped in a color view
* @param leaves current cut
* @param is_terminal predicate that decides which leaves cannot be expanded
* @return the fanin to expand, or nothing when the cut is terminal
*/
template
<
typename
Ntk
,
typename
TermFn
>
std
::
optional
<
typename
Ntk
::
node
>
select_next_fanin
(
Ntk
const
&
ntk
,
std
::
vector
<
typename
Ntk
::
node
>
const
&
leaves
,
TermFn
&&
is_terminal
)
{
using
node
=
typename
Ntk
::
node
;
std
::
vector
<
std
::
pair
<
node
,
uint32_t
>>
candidates
;
for
(
auto
const
&
l
:
leaves
)
{
if
(
is_terminal
(
l
))
{
continue
;
}
ntk
.
foreach_fanin
(
l
,
[
&
](
auto
const
&
fi
)
{
node
const
f
=
ntk
.
get_node
(
fi
);
if
(
ntk
.
is_constant
(
f
))
{
return
;
}
auto
it
=
std
::
find_if
(
std
::
begin
(
candidates
),
std
::
end
(
candidates
),
[
&
f
](
auto
const
&
p
)
{
return
p
.
first
==
f
;
});
if
(
it
==
std
::
end
(
candidates
))
{
candidates
.
emplace_back
(
f
,
1u
);
}
else
{
++
it
->
second
;
}
});
}
if
(
candidates
.
empty
())
{
return
std
::
nullopt
;
}
std
::
pair
<
node
,
uint32_t
>
best
=
candidates
.
front
();
for
(
auto
const
&
candidate
:
candidates
)
{
if
(
candidate
.
second
>
best
.
second
||
(
candidate
.
second
==
best
.
second
&&
ntk
.
fanout_size
(
candidate
.
first
)
>
ntk
.
fanout_size
(
best
.
first
)))
{
best
=
candidate
;
}
}
return
best
.
first
;
}
/// how many merge partners one block examines per visit; caps the merge pass
/// on designs that start out with tens of thousands of blocks
static
constexpr
size_t
MAX_MERGE_CANDIDATES
=
64u
;
/// how often the merge pass sweeps all blocks before giving up on a fixpoint
static
constexpr
uint32_t
MAX_MERGE_PASSES
=
4u
;
/**
* @brief leaf set that two blocks would have if they were merged
*
* Union of both leaf sets minus the nets either block drives, since those
* become internal.
*
* @param a leaves of the first block, sorted
* @param b leaves of the second block, sorted
* @param owner gate -> block id + 1, 0 for anything that is not a gate
* @param id_a id of the first block
* @param id_b id of the second block
* @return the merged leaf set, sorted
*/
inline
std
::
vector
<
uint64_t
>
merge_leaf_sets
(
std
::
vector
<
uint64_t
>
const
&
a
,
std
::
vector
<
uint64_t
>
const
&
b
,
std
::
vector
<
uint32_t
>
const
&
owner
,
uint32_t
id_a
,
uint32_t
id_b
)
{
std
::
vector
<
uint64_t
>
u
;
u
.
reserve
(
a
.
size
()
+
b
.
size
());
std
::
set_union
(
std
::
begin
(
a
),
std
::
end
(
a
),
std
::
begin
(
b
),
std
::
end
(
b
),
std
::
back_inserter
(
u
));
u
.
erase
(
std
::
remove_if
(
std
::
begin
(
u
),
std
::
end
(
u
),
[
&
](
uint64_t
l
)
{
uint32_t
const
o
=
owner
[
l
];
return
o
==
id_a
+
1
||
o
==
id_b
+
1
;
}),
std
::
end
(
u
));
return
u
;
}
/**
* @brief merges blocks for as long as the budget allows
*
* Growth stops every cone on gates an earlier cone claimed, so no block ever
* exceeds one output cone. This greedily merges pairs whose merged support
* still fits `max_inputs`. Candidates are the block driving a leaf and the
* blocks sharing a leaf, capped per visit.
*
* @param ntk network the blocks were derived from
* @param cntk the same network in the color view, for collect_outputs
* @param blocks partition to merge in place; roots are recomputed afterwards
* @param owner gate -> block id + 1, updated in place and stale once the
* surviving blocks are renumbered
* @param refs scratch reference counts for collect_outputs, restored to 0
* @param max_inputs maximum input support per block
*/
template
<
typename
ColorNtk
>
inline
void
merge_blocks
(
mockturtle
::
cover_network
const
&
ntk
,
ColorNtk
const
&
cntk
,
std
::
vector
<
block_t
>
&
blocks
,
std
::
vector
<
uint32_t
>
&
owner
,
std
::
vector
<
uint32_t
>
&
refs
,
uint32_t
max_inputs
)
{
using
node
=
mockturtle
::
cover_network
::
node
;
uint32_t
const
count
=
static_cast
<
uint32_t
>
(
blocks
.
size
());
if
(
count
<
2
)
{
return
;
}
std
::
vector
<
char
>
alive
(
count
,
1
);
// leaf -> blocks that have it as a leaf, so that two blocks over the same
// support find each other without scanning the whole partition
std
::
vector
<
std
::
vector
<
uint32_t
>>
leaf_blocks
(
ntk
.
size
());
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
leaf_blocks
[
l
].
push_back
(
b
);
}
}
// dedupes the candidate list without clearing a set on every visit
std
::
vector
<
uint32_t
>
stamp
(
count
,
0
);
uint32_t
stamp_id
=
0
;
std
::
vector
<
uint32_t
>
candidates
;
bool
changed
=
true
;
for
(
uint32_t
pass
=
0
;
changed
&&
pass
<
MAX_MERGE_PASSES
;
pass
++
)
{
changed
=
false
;
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
// saturate this block before moving on: every merge changes its leaf
// set, which can open up a partner that did not fit a moment ago
for
(
bool
grown
=
true
;
alive
[
b
]
&&
grown
;)
{
grown
=
false
;
++
stamp_id
;
stamp
[
b
]
=
stamp_id
;
candidates
.
clear
();
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
if
(
owner
[
l
]
!=
0
&&
stamp
[
owner
[
l
]
-
1
]
!=
stamp_id
)
{
stamp
[
owner
[
l
]
-
1
]
=
stamp_id
;
candidates
.
push_back
(
owner
[
l
]
-
1
);
}
for
(
uint32_t
const
&
o
:
leaf_blocks
[
l
])
{
if
(
stamp
[
o
]
==
stamp_id
)
{
continue
;
}
stamp
[
o
]
=
stamp_id
;
candidates
.
push_back
(
o
);
if
(
candidates
.
size
()
>=
MAX_MERGE_CANDIDATES
)
{
break
;
}
}
if
(
candidates
.
size
()
>=
MAX_MERGE_CANDIDATES
)
{
break
;
}
}
// smallest merged support first; first-fit gets stuck far earlier
uint32_t
best
=
UINT32_MAX
;
std
::
vector
<
node
>
best_leaves
;
for
(
uint32_t
const
&
c
:
candidates
)
{
if
(
c
==
b
||
!
alive
[
c
])
{
continue
;
}
std
::
vector
<
node
>
leaves
=
merge_leaf_sets
(
blocks
[
b
].
leaves
,
blocks
[
c
].
leaves
,
owner
,
b
,
c
);
if
(
leaves
.
size
()
>
max_inputs
)
{
continue
;
}
if
(
best
==
UINT32_MAX
||
leaves
.
size
()
<
best_leaves
.
size
())
{
best
=
c
;
best_leaves
=
std
::
move
(
leaves
);
}
}
if
(
best
!=
UINT32_MAX
)
{
uint32_t
const
c
=
best
;
std
::
vector
<
node
>
leaves
=
std
::
move
(
best_leaves
);
std
::
vector
<
node
>
gates
;
gates
.
reserve
(
blocks
[
b
].
gates
.
size
()
+
blocks
[
c
].
gates
.
size
());
std
::
merge
(
std
::
begin
(
blocks
[
b
].
gates
),
std
::
end
(
blocks
[
b
].
gates
),
std
::
begin
(
blocks
[
c
].
gates
),
std
::
end
(
blocks
[
c
].
gates
),
std
::
back_inserter
(
gates
));
for
(
node
const
&
g
:
blocks
[
c
].
gates
)
{
owner
[
g
]
=
b
+
1
;
}
blocks
[
b
].
leaves
=
std
::
move
(
leaves
);
blocks
[
b
].
gates
=
std
::
move
(
gates
);
blocks
[
c
]
=
block_t
{};
alive
[
c
]
=
0
;
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
if
(
leaf_blocks
[
l
].
empty
()
||
leaf_blocks
[
l
].
back
()
!=
b
)
{
leaf_blocks
[
l
].
push_back
(
b
);
}
}
grown
=
true
;
changed
=
true
;
}
}
}
}
// drop the absorbed blocks and recompute the roots of the survivors: a
// boundary net that a merge turned into an internal net is not a root any
// more unless something outside the merged block still reads it
std
::
vector
<
block_t
>
survivors
;
survivors
.
reserve
(
count
);
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
if
(
!
alive
[
b
])
{
continue
;
}
std
::
vector
<
node
>
roots
=
mockturtle
::
collect_outputs
(
cntk
,
blocks
[
b
].
leaves
,
blocks
[
b
].
gates
,
refs
);
std
::
stable_sort
(
std
::
begin
(
roots
),
std
::
end
(
roots
));
survivors
.
push_back
(
block_t
{
std
::
move
(
blocks
[
b
].
leaves
),
std
::
move
(
blocks
[
b
].
gates
),
std
::
move
(
roots
)});
}
blocks
=
std
::
move
(
survivors
);
}
}
// namespace partition_detail
/**
* @brief splits a network into subcircuits with a bounded input support
*
* Grows a cone back from every primary output while its support fits the
* budget; a fanin that would exceed it becomes a boundary net and the root of
* a new block. merge_blocks then joins blocks while the budget allows.
* `n >= |PI|` short-circuits to the unpartitioned single block.
*
* @param ntk network that should be partitioned
* @param max_inputs maximum input support per block, 0 disables partitioning
* @return the blocks, in the order they were discovered
*/
inline
std
::
vector
<
block_t
>
partition_cover
(
mockturtle
::
cover_network
&
ntk
,
uint32_t
max_inputs
)
{
using
node
=
mockturtle
::
cover_network
::
node
;
const
uint32_t
size
=
ntk
.
size
();
std
::
vector
<
block_t
>
blocks
;
// n == 0 or n >= |PI|: one block over all inputs. Handled explicitly because
// greedy pairwise merging can get stuck before reaching it.
if
(
max_inputs
==
0
||
max_inputs
>=
ntk
.
_storage
->
inputs
.
size
())
{
block_t
all
;
all
.
leaves
.
assign
(
std
::
begin
(
ntk
.
_storage
->
inputs
),
std
::
end
(
ntk
.
_storage
->
inputs
));
ntk
.
foreach_gate
([
&
](
auto
const
&
n
)
{
all
.
gates
.
push_back
(
n
);
});
ntk
.
foreach_co
([
&
](
auto
const
&
f
)
{
node
const
d
=
ntk
.
get_node
(
f
);
if
(
std
::
find
(
std
::
begin
(
all
.
roots
),
std
::
end
(
all
.
roots
),
d
)
==
std
::
end
(
all
.
roots
))
{
all
.
roots
.
push_back
(
d
);
}
});
blocks
.
emplace_back
(
std
::
move
(
all
));
return
blocks
;
}
assert
(
max_inputs
>=
mockturtle
::
cover_network
::
min_fanin_size
+
1
&&
"budget too small to ever cover a single gate"
);
// block id + 1 for owned gates, 0 while a gate is still unassigned
std
::
vector
<
uint32_t
>
assigned
(
size
,
0
);
// scratch reference counts for collect_outputs, always restored to 0
std
::
vector
<
uint32_t
>
refs
(
size
,
0
);
// guards against queueing the same root twice
std
::
vector
<
char
>
queued
(
size
,
0
);
partition_detail
::
cover_color_view
cntk
{
ntk
};
auto
is_terminal
=
[
&
](
node
n
)
{
return
ntk
.
is_constant
(
n
)
||
ntk
.
is_ci
(
n
)
||
assigned
[
n
]
!=
0
;
};
// seed with the primary output drivers, in a deterministic order so that
// repeated runs on the same design produce the same partition
std
::
vector
<
node
>
worklist
;
ntk
.
foreach_co
([
&
](
auto
const
&
f
)
{
node
const
d
=
ntk
.
get_node
(
f
);
if
(
!
queued
[
d
])
{
queued
[
d
]
=
1
;
worklist
.
push_back
(
d
);
}
});
// the worklist grows while it is walked: every boundary net that is cut
// becomes the root of a block of its own
for
(
size_t
w
=
0
;
w
<
worklist
.
size
();
++
w
)
{
node
const
root
=
worklist
[
w
];
if
(
is_terminal
(
root
))
{
// a primary output driven straight by an input or a constant, or a
// boundary net that a later cone happened to absorb
continue
;
}
// grow the cone from the root towards the inputs
std
::
vector
<
node
>
leaves
{
root
};
cntk
.
new_color
();
cntk
.
paint
(
root
);
bool
trivial
=
partition_detail
::
expand0_bounded
(
cntk
,
leaves
,
is_terminal
);
std
::
optional
<
std
::
vector
<
node
>>
best
;
if
(
leaves
.
size
()
<=
max_inputs
)
{
best
=
leaves
;
}
uint32_t
iterations
=
0
;
while
(
!
trivial
&&
(
leaves
.
size
()
<=
max_inputs
||
iterations
<
partition_detail
::
MAX_ITERATIONS
))
{
auto
next
=
partition_detail
::
select_next_fanin
(
cntk
,
leaves
,
is_terminal
);
if
(
!
next
)
{
break
;
}
leaves
.
push_back
(
*
next
);
cntk
.
paint
(
*
next
);
trivial
=
partition_detail
::
expand0_bounded
(
cntk
,
leaves
,
is_terminal
);
iterations
=
leaves
.
size
()
>
max_inputs
?
iterations
+
1
:
0
;
if
(
leaves
.
size
()
<=
max_inputs
&&
(
!
best
||
best
->
size
()
<=
leaves
.
size
()))
{
best
=
leaves
;
}
}
if
(
best
)
{
leaves
=
*
best
;
}
// the budget could not be met even by a single gate, fall back to a block
// holding just the root so that the partition still covers the network
if
(
leaves
.
size
()
>
max_inputs
)
{
leaves
.
clear
();
ntk
.
foreach_fanin
(
root
,
[
&
](
auto
const
&
fi
)
{
node
const
f
=
ntk
.
get_node
(
fi
);
if
(
std
::
find
(
std
::
begin
(
leaves
),
std
::
end
(
leaves
),
f
)
==
std
::
end
(
leaves
))
{
leaves
.
push_back
(
f
);
}
});
}
// everything strictly between the root and the leaves belongs to the block
std
::
vector
<
node
>
gates
=
mockturtle
::
cover
(
cntk
,
root
,
leaves
);
if
(
gates
.
empty
())
{
continue
;
}
const
uint32_t
id
=
static_cast
<
uint32_t
>
(
blocks
.
size
());
for
(
node
const
&
g
:
gates
)
{
assert
(
assigned
[
g
]
==
0
&&
"gate claimed by two blocks"
);
assigned
[
g
]
=
id
+
1
;
}
// a gate whose fanout count is not fully explained by uses inside the
// block drives either a primary output or a foreign block
std
::
vector
<
node
>
roots
=
mockturtle
::
collect_outputs
(
cntk
,
leaves
,
gates
,
refs
);
// every leaf that is a gate is a boundary net and needs a block of its own
for
(
node
const
&
l
:
leaves
)
{
if
(
!
ntk
.
is_constant
(
l
)
&&
!
ntk
.
is_ci
(
l
)
&&
!
assigned
[
l
]
&&
!
queued
[
l
])
{
queued
[
l
]
=
1
;
worklist
.
push_back
(
l
);
}
}
std
::
stable_sort
(
std
::
begin
(
leaves
),
std
::
end
(
leaves
));
std
::
stable_sort
(
std
::
begin
(
gates
),
std
::
end
(
gates
));
std
::
stable_sort
(
std
::
begin
(
roots
),
std
::
end
(
roots
));
blocks
.
push_back
(
block_t
{
std
::
move
(
leaves
),
std
::
move
(
gates
),
std
::
move
(
roots
)});
}
partition_detail
::
merge_blocks
(
ntk
,
cntk
,
blocks
,
assigned
,
refs
,
max_inputs
);
return
blocks
;
}
/**
* @brief merges blocks for as long as the merged diagram is actually smaller
*
* Like merge_blocks, but a merge is kept only if cost_fn(merged) is below the
* sum of its parts, so the total node count never rises. Support is a poor
* proxy for diagram size. `cap` bounds the support, and with it the cost of
* a trial build; the smallest support is tried first.
*
* @param ntk network the blocks were derived from
* @param blocks partition to merge in place; roots are recomputed as it goes
* @param cap maximum input support a merged block may have
* @param max_passes how often all blocks are swept before giving up
* @param max_candidates how many partners one block examines per visit
* @param cost_fn builds a block and returns its node count; must leave the
* network as it found it
*/
template
<
typename
CostFn
>
inline
void
merge_blocks_by_gain
(
mockturtle
::
cover_network
&
ntk
,
std
::
vector
<
block_t
>
&
blocks
,
uint32_t
cap
,
uint32_t
max_passes
,
uint32_t
max_candidates
,
CostFn
&&
cost_fn
)
{
using
node
=
mockturtle
::
cover_network
::
node
;
uint32_t
const
count
=
static_cast
<
uint32_t
>
(
blocks
.
size
());
if
(
count
<
2
||
cap
==
0
)
{
return
;
}
partition_detail
::
cover_color_view
cntk
{
ntk
};
std
::
vector
<
uint32_t
>
refs
(
ntk
.
size
(),
0
);
std
::
vector
<
uint32_t
>
owner
(
ntk
.
size
(),
0
);
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
for
(
node
const
&
g
:
blocks
[
b
].
gates
)
{
owner
[
g
]
=
b
+
1
;
}
}
std
::
vector
<
char
>
alive
(
count
,
1
);
std
::
vector
<
uint64_t
>
cost
(
count
,
0
);
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
cost
[
b
]
=
cost_fn
(
blocks
[
b
]);
}
std
::
vector
<
std
::
vector
<
uint32_t
>>
leaf_blocks
(
ntk
.
size
());
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
leaf_blocks
[
l
].
push_back
(
b
);
}
}
// dedupes the candidate list without clearing a set on every visit
std
::
vector
<
uint32_t
>
stamp
(
count
,
0
);
uint32_t
stamp_id
=
0
;
std
::
vector
<
std
::
pair
<
uint32_t
,
std
::
vector
<
uint64_t
>>>
candidates
;
bool
changed
=
true
;
for
(
uint32_t
pass
=
0
;
changed
&&
pass
<
max_passes
;
pass
++
)
{
changed
=
false
;
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
// saturate this block before moving on: a merge changes its leaf set,
// which can open up a partner that did not fit a moment ago
for
(
bool
grown
=
true
;
alive
[
b
]
&&
grown
;)
{
grown
=
false
;
++
stamp_id
;
stamp
[
b
]
=
stamp_id
;
candidates
.
clear
();
auto
consider
=
[
&
](
uint32_t
c
)
{
if
(
c
==
b
||
!
alive
[
c
]
||
stamp
[
c
]
==
stamp_id
)
{
return
;
}
stamp
[
c
]
=
stamp_id
;
std
::
vector
<
uint64_t
>
leaves
=
partition_detail
::
merge_leaf_sets
(
blocks
[
b
].
leaves
,
blocks
[
c
].
leaves
,
owner
,
b
,
c
);
if
(
leaves
.
size
()
>
cap
)
{
return
;
}
candidates
.
emplace_back
(
c
,
std
::
move
(
leaves
));
};
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
if
(
owner
[
l
]
!=
0
)
{
consider
(
owner
[
l
]
-
1
);
}
for
(
uint32_t
const
&
o
:
leaf_blocks
[
l
])
{
consider
(
o
);
}
if
(
candidates
.
size
()
>=
max_candidates
)
{
break
;
}
}
std
::
stable_sort
(
std
::
begin
(
candidates
),
std
::
end
(
candidates
),
[](
auto
const
&
x
,
auto
const
&
y
)
{
return
x
.
second
.
size
()
<
y
.
second
.
size
();
});
if
(
candidates
.
size
()
>
max_candidates
)
{
candidates
.
resize
(
max_candidates
);
}
for
(
auto
&
candidate
:
candidates
)
{
uint32_t
const
c
=
candidate
.
first
;
block_t
merged
;
merged
.
leaves
=
std
::
move
(
candidate
.
second
);
merged
.
gates
.
reserve
(
blocks
[
b
].
gates
.
size
()
+
blocks
[
c
].
gates
.
size
());
std
::
merge
(
std
::
begin
(
blocks
[
b
].
gates
),
std
::
end
(
blocks
[
b
].
gates
),
std
::
begin
(
blocks
[
c
].
gates
),
std
::
end
(
blocks
[
c
].
gates
),
std
::
back_inserter
(
merged
.
gates
));
merged
.
roots
=
mockturtle
::
collect_outputs
(
cntk
,
merged
.
leaves
,
merged
.
gates
,
refs
);
std
::
stable_sort
(
std
::
begin
(
merged
.
roots
),
std
::
end
(
merged
.
roots
));
uint64_t
const
merged_cost
=
cost_fn
(
merged
);
if
(
merged_cost
>=
cost
[
b
]
+
cost
[
c
])
{
continue
;
}
for
(
node
const
&
g
:
blocks
[
c
].
gates
)
{
owner
[
g
]
=
b
+
1
;
}
blocks
[
b
]
=
std
::
move
(
merged
);
blocks
[
c
]
=
block_t
{};
alive
[
c
]
=
0
;
cost
[
b
]
=
merged_cost
;
for
(
node
const
&
l
:
blocks
[
b
].
leaves
)
{
if
(
leaf_blocks
[
l
].
empty
()
||
leaf_blocks
[
l
].
back
()
!=
b
)
{
leaf_blocks
[
l
].
push_back
(
b
);
}
}
grown
=
true
;
changed
=
true
;
break
;
}
}
}
}
std
::
vector
<
block_t
>
survivors
;
survivors
.
reserve
(
count
);
for
(
uint32_t
b
=
0
;
b
<
count
;
b
++
)
{
if
(
alive
[
b
])
{
survivors
.
push_back
(
std
::
move
(
blocks
[
b
]));
}
}
blocks
=
std
::
move
(
survivors
);
}
/**
* @brief checks the invariants a partition has to satisfy
*
* Meant to be called behind an assert or from a test: every gate is owned by
* exactly one block, no block exceeds the budget, and every leaf is either a
* primary input, a constant, or a root of some block.
*
* @param ntk network the blocks were derived from
* @param blocks partition that should be checked
* @param max_inputs budget the partition was built with, 0 skips the check
* @return true if and only if the partition is well formed
*/
inline
bool
partition_is_valid
(
mockturtle
::
cover_network
const
&
ntk
,
std
::
vector
<
block_t
>
const
&
blocks
,
uint32_t
max_inputs
)
{
using
node
=
mockturtle
::
cover_network
::
node
;
std
::
vector
<
uint32_t
>
owner
(
ntk
.
size
(),
0
);
std
::
vector
<
char
>
is_root
(
ntk
.
size
(),
0
);
for
(
size_t
i
=
0
;
i
<
blocks
.
size
();
i
++
)
{
if
(
max_inputs
>
0
&&
blocks
[
i
].
leaves
.
size
()
>
max_inputs
)
{
std
::
cerr
<<
"[ERROR] block "
<<
i
<<
" has "
<<
blocks
[
i
].
leaves
.
size
()
<<
" leaves, budget is "
<<
max_inputs
<<
"
\n
"
;
return
false
;
}
for
(
node
const
&
g
:
blocks
[
i
].
gates
)
{
if
(
owner
[
g
]
!=
0
)
{
std
::
cerr
<<
"[ERROR] gate "
<<
g
<<
" owned by block "
<<
owner
[
g
]
-
1
<<
" and block "
<<
i
<<
"
\n
"
;
return
false
;
}
owner
[
g
]
=
static_cast
<
uint32_t
>
(
i
)
+
1
;
}
for
(
node
const
&
r
:
blocks
[
i
].
roots
)
{
is_root
[
r
]
=
1
;
}
}
for
(
auto
const
&
b
:
blocks
)
{
for
(
node
const
&
l
:
b
.
leaves
)
{
if
(
ntk
.
is_constant
(
l
)
||
ntk
.
is_ci
(
l
))
{
continue
;
}
if
(
!
is_root
[
l
])
{
std
::
cerr
<<
"[ERROR] leaf "
<<
l
<<
" is neither an input nor a root
\n
"
;
return
false
;
}
}
}
bool
complete
=
true
;
ntk
.
foreach_gate
([
&
](
auto
const
&
n
)
{
if
(
owner
[
n
]
==
0
)
{
std
::
cerr
<<
"[ERROR] gate "
<<
n
<<
" is not covered by any block
\n
"
;
complete
=
false
;
}
});
return
complete
;
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Tue, Oct 27, 3:32 PM (21 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
405748
Default Alt Text
partition.hpp (24 KB)
Attached To
Mode
R272 SoC_NEM_Synthesis
Attached
Detach File
Event Timeline