From acd77ebe1dd85e8eb9b360dfea031a7012d0b5ca Mon Sep 17 00:00:00 2001 From: Sonia Date: Mon, 3 May 2021 13:16:27 +0200 Subject: [PATCH] re-organize data structure --- docs/views.rst | 7 +- include/mockturtle/views/aqfp_view.hpp | 407 ++++++++++++------------- test/views/aqfp_view.cpp | 33 +- 3 files changed, 221 insertions(+), 226 deletions(-) diff --git a/docs/views.rst b/docs/views.rst index 0e836df3a..0ba569ef3 100644 --- a/docs/views.rst +++ b/docs/views.rst @@ -101,10 +101,13 @@ algorithm. Several views are implemented in mockturtle. .. doxygenclass:: mockturtle::out_of_place_color_view :members: -`aqfp_view`: Counts number of buffers and splitters after AQFP technology mapping -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`aqfp_view`: Counts number of buffers and splitters for AQFP +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Header:** ``mockturtle/views/aqfp_view.hpp`` +.. doxygenstruct:: mockturtle::aqfp_view_params + :members: + .. doxygenclass:: mockturtle::aqfp_view :members: diff --git a/include/mockturtle/views/aqfp_view.hpp b/include/mockturtle/views/aqfp_view.hpp index 74facc27a..ecc155b71 100644 --- a/include/mockturtle/views/aqfp_view.hpp +++ b/include/mockturtle/views/aqfp_view.hpp @@ -40,21 +40,21 @@ #include "mockturtle/networks/mig.hpp" #include "mockturtle/views/depth_view.hpp" -#include -#include #include -#include -#include +#include +#include //std::pow, std::ceil +#include //std::numeric_limits namespace mockturtle { +/*! \brief Parameters for AQFP buffer counting. + * + * The data structure `aqfp_view_params` holds configurable parameters with + * default arguments for `aqfp_view`. + */ struct aqfp_view_params { - bool update_on_add{true}; - bool update_on_modified{true}; - bool update_on_delete{true}; - /*! \brief Whether PIs need to be branched with splitters */ bool branch_pis{false}; @@ -64,12 +64,14 @@ struct aqfp_view_params /*! \brief Whether POs need to be path-balanced */ bool balance_pos{true}; - uint32_t splitter_capacity{4u}; - uint32_t max_splitter_levels{2u}; + /*! \brief The maximum number of fanouts each splitter (buffer) can have */ + uint32_t splitter_capacity{3u}; + + /*! \brief The maximum additional depth of a node introduced by splitters (0 = unlimited) */ + uint32_t max_splitter_levels{0u}; }; -/*! \brief Implements `foreach_fanout`, `depth`, `level` - * `num_buffers`, `num_splitter_levels` methods for MIG network. +/*! \brief Computes levels considering AQFP splitters and counts AQFP buffers/splitters. * * This view calculates the number of buffers (for path balancing) and * splitters (for multi-fanout) after AQFP technology mapping from an MIG @@ -79,7 +81,7 @@ struct aqfp_view_params * * In AQFP technology, (1) MAJ gates can only have one fanout. If more than one * fanout is needed, a splitter has to be inserted in between, which also - * takes one clock cycle (counts toward the network depth). (2) All fanins of + * takes one clock cycle (counted towards the network depth). (2) All fanins of * a MAJ gate have to arrive at the same time (at the same level). If one * fanin path is shorter, buffers have to be inserted to balance it. * Buffers and splitters are essentially the same component in this technology. @@ -88,15 +90,12 @@ struct aqfp_view_params * on whether PIs should be branched and whether PIs and POs have to be balanced * can be set in the parameters. * - * The number of fanouts of each buffer is restricted to `splitter_capacity`. - * The additional depth of a node introduced by splitters is limited to at - * most `max_splitter_levels`. These two parameters are defined in - * `aqfp_view_params`. Following these restrictions, the maximum number of - * fanouts of a node in the original MIG network is limited to + * The additional depth of a node introduced by splitters can be limited by + * setting the parameter `max_splitter_levels`, making the maximum number of + * fanouts of a node in the original MIG network being limited to * `pow(splitter_capacity, max_splitter_levels)`. To ensure this, one should * apply `fanout_limit_view` before `aqfp_view` to duplicate the nodes with - * too many fanouts. The template parameter `CheckFanoutLimit` can be set to - * `true` to check for this restriction. + * too many fanouts. * * The network depth and the levels of each node are determined first by the * number of fanouts and adding sufficient levels for splitters assuming all @@ -106,16 +105,21 @@ struct aqfp_view_params * before the level where they are needed (i.e., sharing of buffers among * multiple fanouts is maximized). * - * Updating the network with this view is supported (`substitute_node` and - * `create_po` are overwritten) but not advised because of efficiency concern. - * * **Required network functions:** * - `foreach_node` + * - `foreach_gate` + * - `foreach_pi` + * - `foreach_po` * - `foreach_fanin` + * - `is_pi` + * - `is_constant` + * - `get_node` + * - `fanout_size` + * - `size` * */ -template -class aqfp_view : public Ntk +template +class aqfp_view : public immutable_view { public: using storage = typename Ntk::storage; @@ -133,90 +137,50 @@ class aqfp_view : public Ntk aqfp_view* aqfp; }; - aqfp_view( Ntk const& ntk, aqfp_view_params const& ps = {} ) - : Ntk( ntk ), _fanout( ntk ), _external_ref_count( ntk ), _ps( ps ), _max_fanout( std::pow( ps.splitter_capacity, ps.max_splitter_levels ) ), _node_depth( this ), _depth_view( ntk, _node_depth, {/*count_complements*/false, /*pi_cost*/ps.branch_pis} ) + explicit aqfp_view( Ntk const& ntk, aqfp_view_params const& ps = {} ) + : immutable_view( ntk ), _ps( ps ), _fanouts( ntk ), _external_ref_count( ntk ), + _node_depth( this ), _levels( *this ) { static_assert( !has_foreach_fanout_v && "Ntk already has fanout interfaces" ); static_assert( !has_depth_v && !has_level_v && !has_update_levels_v, "Ntk already has depth interfaces" ); static_assert( has_foreach_node_v, "Ntk does not implement the foreach_node method" ); + static_assert( has_foreach_gate_v, "Ntk does not implement the foreach_gate method" ); + static_assert( has_foreach_pi_v, "Ntk does not implement the foreach_pi method" ); + static_assert( has_foreach_po_v, "Ntk does not implement the foreach_po method" ); static_assert( has_foreach_fanin_v, "Ntk does not implement the foreach_fanin method" ); + static_assert( has_is_pi_v, "Ntk does not implement the is_pi method" ); + static_assert( has_is_constant_v, "Ntk does not implement the is_constant method" ); + static_assert( has_get_node_v, "Ntk does not implement the get_node method" ); + static_assert( has_fanout_size_v, "Ntk does not implement the fanout_size method" ); + static_assert( has_size_v, "Ntk does not implement the size method" ); if constexpr ( !std::is_same::value ) { std::cerr << "[w] base_type of Ntk is not mig_network.\n"; } - update_fanout(); - - if ( _ps.update_on_add ) + if ( _ps.max_splitter_levels ) { - Ntk::events().on_add.push_back( [this]( auto const& n ) { - _fanout.resize(); - _external_ref_count.resize(); - Ntk::foreach_fanin( n, [&, this]( auto const& f ) { - _fanout[f].push_back( n ); - } ); - } ); + _max_fanout = std::pow( _ps.splitter_capacity, _ps.max_splitter_levels ); } - if ( _ps.update_on_modified ) - { - Ntk::events().on_modified.push_back( [this]( auto const& n, auto const& previous ) { - (void)previous; - for ( auto const& f : previous ) { - _fanout[f].erase( std::remove( _fanout[f].begin(), _fanout[f].end(), n ), _fanout[f].end() ); - } - Ntk::foreach_fanin( n, [&, this]( auto const& f ) { - _fanout[f].push_back( n ); - on_update( Ntk::get_node( f ) ); - } ); - _depth_view.update_levels(); - } ); - } - - if ( _ps.update_on_delete ) - { - Ntk::events().on_delete.push_back( [this]( auto const& n ) { - _fanout[n].clear(); - Ntk::foreach_fanin( n, [&, this]( auto const& f ) { - _fanout[f].erase( std::remove( _fanout[f].begin(), _fanout[f].end(), n ), _fanout[f].end() ); - } ); - _depth_view.update_levels(); - } ); - } + update(); } - template - void foreach_fanout( node const& n, Fn&& fn ) const - { - assert( n < this->size() ); - detail::foreach_element( _fanout[n].begin(), _fanout[n].end(), fn ); - } - - void update_fanout() - { - compute_fanout(); - } - - /*! \brief Additional depth caused by the splitters of node `n`. */ - uint32_t num_splitter_levels ( node const& n ) const - { - return std::ceil( std::log( fanout_size( n ) ) / std::log( _ps.splitter_capacity ) ); - } - - /*! \brief Level of node `n` itself. Not the highest level of its splitters */ + /*! \brief Level of node `n` considering buffer/splitter insertion. */ uint32_t level ( node const& n ) const { - return _depth_view.level( n ) - num_splitter_levels( n ); + assert( n < this->size() ); + return _levels[n]; } - /*! \brief Circuit depth */ + /*! \brief Network depth considering AQFP buffers/splitters. */ uint32_t depth() const { - return _depth_view.depth(); + return _depth; } - /*! \brief Get the number of buffers/splitters in the whole circuit */ + /*! \brief The total number of buffers/splitters in the network. */ uint32_t num_buffers() const { uint32_t count = 0u; @@ -237,15 +201,17 @@ class aqfp_view : public Ntk return count; } - /*! \brief Get the number of buffers/splitters between `n` and all its fanouts */ + /*! \brief The number of buffers/splitters between `n` and all of its fanouts */ uint32_t num_buffers( node const& n ) const { + auto const& fo_infos = _fanouts[n]; + if ( num_splitter_levels( n ) == 0u ) { /* single fanout */ - if ( fanout_size( n ) > 0u ) + if ( this->fanout_size( n ) > 0u ) { - assert( fanout_size( n ) == 1u ); + assert( this->fanout_size( n ) == 1u ); if ( this->is_pi( n ) ) { assert( level( n ) == 0u ); @@ -255,7 +221,8 @@ class aqfp_view : public Ntk } else /* PI -- gate */ { - return _ps.balance_pis ? level( _fanout[n][0] ) - 1u : 0u; + assert( fo_infos.size() == 1u ); + return _ps.balance_pis ? fo_infos.front().relative_depth - 1u : 0u; } } else if ( _external_ref_count[n] > 0u ) /* gate -- PO */ @@ -264,200 +231,200 @@ class aqfp_view : public Ntk } else /* gate -- gate */ { - return level( _fanout[n][0] ) - level( n ) - 1u; + assert( fo_infos.size() == 1u ); + return fo_infos.front().relative_depth - 1u; } } /* dangling */ return 0u; } - /* fanout sizes at each level (pair) */ - std::vector> fanout_sizes; - uint32_t nlevel = level( n ) + 1u; - fanout_sizes.emplace_back( std::make_pair( nlevel, 0u ) ); - for ( auto fo : _fanout[n] ) + if ( fo_infos.size() == 0u ) { - assert( level( fo ) >= nlevel ); - if ( level( fo ) == nlevel ) - { - fanout_sizes.back().second++; - } - else - { - nlevel = level( fo ); - fanout_sizes.emplace_back( std::make_pair( nlevel, 1u ) ); - } - } - if ( _external_ref_count[n] > 0u ) - { - if ( _ps.balance_pos ) - { - fanout_sizes.emplace_back( std::make_pair( depth() + 1u, _external_ref_count[n] ) ); - } - else /* don't balance POs, just need enough signals */ - { - if ( _fanout[n].size() == 0u ) /* multiple PO refs but no gate fanout */ - { - return std::ceil( float( _external_ref_count[n] - 1 ) / float( _ps.splitter_capacity - 1 ) ); - } - /* else: check there are enough slots for POs later */ - } + /* special case: don't balance POs; multiple PO refs but no gate fanout */ + assert( !_ps.balance_pos && this->fanout_size( n ) == _external_ref_count[n] ); + return std::ceil( float( _external_ref_count[n] - 1 ) / float( _ps.splitter_capacity - 1 ) ); } - assert( fanout_sizes.size() > 1u ); - assert( fanout_sizes[0].second == 0u ); // the first element should be (level(n)+1, 0) - /* count buffers from the highest level */ - uint32_t count = 0u; - for ( auto i = fanout_sizes.size() - 1; i > 0; --i ) + auto it = fo_infos.begin(); + auto count = it->num_edges; + auto rd = it->relative_depth; + for ( ++it; it != fo_infos.end(); ++it ) { - auto l = fanout_sizes[i].first - 1; - /* number of splitters needed in level `l` */ - auto s = num_splitters( fanout_sizes[i].second ); - count += s; - - if ( fanout_sizes[i-1].first == l ) - { - fanout_sizes[i-1].second += s; - } - else /* there is no other fanouts in level `l` */ - { - if ( s == 1 ) - { - count += l - fanout_sizes[i-1].first; - fanout_sizes[i-1].second++; - } - else - { - fanout_sizes.insert( fanout_sizes.begin() + i, std::make_pair( l, s ) ); - ++i; - } - } + count += it->num_edges - it->fanouts.size() + it->relative_depth - rd - 1; + rd = it->relative_depth; } - assert( fanout_sizes[0].second == 1 ); // the first element should be (level(n)+1, 1) if ( !_ps.balance_pis && this->is_pi( n ) ) /* only branch PIs, but don't balance them */ { /* remove the lowest balancing buffers, if any */ - count -= fanout_sizes[1].first - fanout_sizes[0].first - 1; + it = fo_infos.begin(); + ++it; + count -= it->relative_depth - fo_infos.front().relative_depth - 1; } if ( !_ps.balance_pos && _external_ref_count[n] > 0u ) { auto slots = count * ( _ps.splitter_capacity - 1 ) + 1; - int32_t needed = fanout_size( n ) - slots; + int32_t needed = this->fanout_size( n ) - slots; if ( needed > 0 ) { count += std::ceil( float( needed ) / float( _ps.splitter_capacity - 1 ) ); } } - - return count; - } - - void substitute_node( node const& old_node, signal const& new_signal ) - { - std::stack> to_substitute; - to_substitute.push( {old_node, new_signal} ); - - while ( !to_substitute.empty() ) + else { - const auto [_old, _new] = to_substitute.top(); - to_substitute.pop(); - - const auto parents = _fanout[_old]; - for ( auto n : parents ) - { - if ( const auto repl = Ntk::replace_in_node( n, _old, _new ); repl ) - { - to_substitute.push( *repl ); - } - } - - /* check outputs */ - Ntk::replace_in_outputs( _old, _new ); - - /* reset fan-in of old node */ - Ntk::take_out_node( _old ); + count -= _external_ref_count[n]; } - compute_fanout(); - } - uint32_t create_po( signal const& f, std::string const& name = std::string() ) - { - auto const ret = Ntk::create_po( f, name ); - _external_ref_count[f]++; - return ret; + return count; } -private: - uint32_t fanout_size( node const& n ) const + /*! \brief (Upper bound on) the additional depth caused by a balanced splitter + tree at the output of node `n`. */ + uint32_t num_splitter_levels ( node const& n ) const { - return _fanout[n].size() + _external_ref_count[n]; + assert( n < this->size() ); + return std::ceil( std::log( this->fanout_size( n ) ) / std::log( _ps.splitter_capacity ) ); } +private: /* Return the number of splitters needed in one level lower */ uint32_t num_splitters( uint32_t const& num_fanouts ) const { return std::ceil( float( num_fanouts ) / float( _ps.splitter_capacity ) ); } - void compute_fanout() + void update() { - _fanout.reset(); - _external_ref_count.reset(); + compute_levels(); + compute_fanouts(); this->foreach_gate( [&]( auto const& n ){ - this->foreach_fanin( n, [&]( auto const& c ){ - auto& fanout = _fanout[c]; - if ( std::find( fanout.begin(), fanout.end(), n ) == fanout.end() ) - { - fanout.push_back( n ); - } - }); - }); + count_edges( n ); + }); - this->foreach_po( [&]( auto const& f ){ - _external_ref_count[f]++; + if ( _ps.branch_pis ) + { + this->foreach_pi( [&]( auto const& n ){ + count_edges( n ); }); + } + } + + void compute_levels() + { + /* Use depth_view to naively compute an initial level assignment */ + depth_view _depth_view( *this, _node_depth, {/*count_complements*/false, /*pi_cost*/_ps.branch_pis} ); + _levels.reset( 0 ); + this->foreach_node( [&]( auto const& n ){ + _levels[n] = _depth_view.level( n ) - num_splitter_levels( n ); + }); + _depth = _depth_view.depth(); + } - _depth_view.update_levels(); + void compute_fanouts() + { + _external_ref_count.reset( 0u ); + this->foreach_po( [&]( auto const& f ){ + _external_ref_count[f]++; + }); + _fanouts.reset(); this->foreach_gate( [&]( auto const& n ){ - on_update( n ); + this->foreach_fanin( n, [&]( auto const& fi ){ + auto const ni = this->get_node( fi ); + if ( !this->is_constant( ni ) ) + { + insert_fanout( ni, n ); + } }); + }); + } - if ( _ps.branch_pis ) + void insert_fanout( node const& n, node const& fanout ) + { + auto const rd = level( fanout ) - level( n ); + auto& fo_infos = _fanouts[n]; + for ( auto it = fo_infos.begin(); it != fo_infos.end(); ++it ) { - this->foreach_pi( [&]( auto const& n ){ - on_update( n ); - }); + if ( it->relative_depth == rd ) + { + it->fanouts.push_back( fanout ); + ++it->num_edges; + return; + } + else if ( it->relative_depth > rd ) + { + fo_infos.insert( it, {rd, {fanout}, 1u} ); + return; + } } + fo_infos.push_back( { rd, {fanout}, 1u } ); } - void on_update( node const& n ) + void count_edges( node const& n ) { - if constexpr ( CheckFanoutLimit ) + assert( this->fanout_size( n ) <= _max_fanout ); + auto& fo_infos = _fanouts[n]; + + if ( _external_ref_count[n] && _ps.balance_pos ) + { + fo_infos.push_back( {depth() + 1 - level( n ), {}, _external_ref_count[n]} ); + } + + if ( fo_infos.size() == 0u || ( fo_infos.size() == 1u && fo_infos.front().num_edges == 1u ) ) { - if ( fanout_size( n ) > _max_fanout ) + return; + } + assert( fo_infos.front().relative_depth > 1u ); + fo_infos.push_front( {1u, {}, 0u} ); + + auto it = fo_infos.end(); + --it; + for ( ; it != fo_infos.begin(); --it ) + { + auto splitters = num_splitters( it->num_edges ); + auto rd = it->relative_depth; + --it; + if ( it->relative_depth == rd - 1 ) + { + it->num_edges += splitters; + ++it; + } + else if ( splitters == 1 ) + { + ++(it->num_edges); + ++it; + } + else { - std::cerr << "[e] node " << n << " has too many (" << fanout_size( n ) << ") fanouts!\n"; + ++it; + fo_infos.insert( it, {rd - 1, {}, splitters} ); } } - /* sort the fanouts by their level */ - auto& fanout = _fanout[n]; - std::sort( fanout.begin(), fanout.end(), [&](node a, node b){ - return level( a ) < level( b ); - }); + assert( fo_infos.front().relative_depth == 1u ); + assert( fo_infos.front().num_edges <= 1u ); } private: - node_map, Ntk> _fanout; - node_map _external_ref_count; + struct fanout_info + { + uint32_t relative_depth{0u}; + std::list fanouts; + uint32_t num_edges{0u}; + }; + using fanouts_by_level = std::list; + aqfp_view_params _ps; - uint64_t _max_fanout; + uint64_t _max_fanout{std::numeric_limits::max()}; + + node_map _fanouts; + node_map _external_ref_count; - node_depth _node_depth; - depth_view _depth_view; + node_depth _node_depth; /* naive cost function for depth_view */ + node_map _levels; + uint32_t _depth{0u}; }; template diff --git a/test/views/aqfp_view.cpp b/test/views/aqfp_view.cpp index 9b3cd5496..cbc7c8c1c 100644 --- a/test/views/aqfp_view.cpp +++ b/test/views/aqfp_view.cpp @@ -21,7 +21,13 @@ TEST_CASE( "aqfp_view simple test", "[aqfp_view]" ) auto const f4 = mig.create_maj( f1, f2, f3 ); mig.create_po( f4 ); - aqfp_view view{mig}; + aqfp_view_params ps; + ps.branch_pis = false; + ps.balance_pis = false; + ps.balance_pos = true; + ps.splitter_capacity = 4u; + aqfp_view view( mig, ps ); + CHECK( view.level( view.get_node( f1 ) ) == 1u ); CHECK( view.level( view.get_node( f2 ) ) == 3u ); CHECK( view.level( view.get_node( f3 ) ) == 3u ); @@ -64,7 +70,13 @@ TEST_CASE( "two layers of splitters", "[aqfp_view]" ) auto const f12 = mig.create_maj( f9, f10, f11 ); mig.create_po( f12 ); - aqfp_view view{mig}; + aqfp_view_params ps; + ps.branch_pis = false; + ps.balance_pis = false; + ps.balance_pos = true; + ps.splitter_capacity = 4u; + aqfp_view view( mig, ps ); + CHECK( view.num_buffers( view.get_node( f2 ) ) == 4u ); CHECK( view.num_buffers( view.get_node( f6 ) ) == 2u ); CHECK( view.depth() == 7u ); @@ -87,7 +99,12 @@ TEST_CASE( "PO splitters and buffers", "[aqfp_view]" ) mig.create_po( f2 ); mig.create_po( !f2 ); - aqfp_view view{mig}; + aqfp_view_params ps; + ps.branch_pis = false; + ps.balance_pis = false; + ps.balance_pos = true; + ps.splitter_capacity = 4u; + aqfp_view view( mig, ps ); CHECK( view.depth() == 4u ); @@ -128,7 +145,13 @@ TEST_CASE( "chain of fanouts", "[aqfp_view]" ) mig.create_po( f8 ); mig.create_po( f9 ); - aqfp_view view{mig}; + aqfp_view_params ps; + ps.branch_pis = false; + ps.balance_pis = false; + ps.balance_pos = true; + ps.splitter_capacity = 4u; + aqfp_view view( mig, ps ); + CHECK( view.num_buffers( view.get_node( f1 ) ) == 9u ); CHECK( view.depth() == 8u ); CHECK( view.num_buffers() == 11u ); @@ -156,6 +179,8 @@ TEST_CASE( "branch but not balance PIs", "[aqfp_view]" ) aqfp_view_params ps; ps.branch_pis = true; ps.balance_pis = false; + ps.balance_pos = true; + ps.splitter_capacity = 4u; aqfp_view view( mig, ps ); CHECK( view.level( view.get_node( f1 ) ) == 2u );