diff --git a/bindings/pyfiction/include/pyfiction/algorithms/physical_design/post_layout_optimization.hpp b/bindings/pyfiction/include/pyfiction/algorithms/physical_design/post_layout_optimization.hpp index 0397c60d7..16571324f 100644 --- a/bindings/pyfiction/include/pyfiction/algorithms/physical_design/post_layout_optimization.hpp +++ b/bindings/pyfiction/include/pyfiction/algorithms/physical_design/post_layout_optimization.hpp @@ -32,8 +32,8 @@ inline void post_layout_optimization(pybind11::module& m) DOC(fiction_post_layout_optimization_params_optimize_pos_only)) .def_readwrite("planar_optimization", &fiction::post_layout_optimization_params::planar_optimization, DOC(fiction_post_layout_optimization_params_planar_optimization)) - - ; + .def_readwrite("timeout", &fiction::post_layout_optimization_params::timeout, + DOC(fiction_post_layout_optimization_params_timeout)); py::class_(m, "post_layout_optimization_stats", DOC(fiction_post_layout_optimization_stats)) diff --git a/bindings/pyfiction/include/pyfiction/algorithms/physical_design/wiring_reduction.hpp b/bindings/pyfiction/include/pyfiction/algorithms/physical_design/wiring_reduction.hpp index a9e17d22b..b676e5607 100644 --- a/bindings/pyfiction/include/pyfiction/algorithms/physical_design/wiring_reduction.hpp +++ b/bindings/pyfiction/include/pyfiction/algorithms/physical_design/wiring_reduction.hpp @@ -23,6 +23,11 @@ inline void wiring_reduction(pybind11::module& m) namespace py = pybind11; using namespace pybind11::literals; + py::class_(m, "wiring_reduction_params", DOC(fiction_wiring_reduction_params)) + .def(py::init<>()) + .def_readwrite("timeout", &fiction::wiring_reduction_params::timeout, + DOC(fiction_wiring_reduction_params_timeout)); + py::class_(m, "wiring_reduction_stats", DOC(fiction_wiring_reduction_stats)) .def(py::init<>()) .def("__repr__", @@ -55,7 +60,7 @@ inline void wiring_reduction(pybind11::module& m) ; m.def("wiring_reduction", &fiction::wiring_reduction, "layout"_a, - "statistics"_a = nullptr, DOC(fiction_wiring_reduction)); + "parameters"_a = fiction::wiring_reduction_params{}, "statistics"_a = nullptr, DOC(fiction_wiring_reduction)); } } // namespace pyfiction diff --git a/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp b/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp index ecd286676..31c5e57d6 100644 --- a/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp +++ b/bindings/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp @@ -3959,44 +3959,6 @@ Parameter ``c``: Parameter ``g_val``: New g-value for c.)doc"; -static const char *__doc_fiction_detail_add_fanin_to_route = -R"doc(This helper function is used to add a fanin coordinate to the -appropriate route based on whether it belongs to the the route from -the first or second fanin to the gate. - -Template parameter ``Lyt``: - Cartesian gate-level layout type. - -Parameter ``fanin``: - The fanin coordinate to be added to the route. - -Parameter ``is_first_fanin``: - A boolean indicating whether this is part of the route from the - first fanin to the gate. - -Parameter ``ffd``: - Reference to the fanin_fanout_data structure containing the - routes.)doc"; - -static const char *__doc_fiction_detail_add_fanout_to_route = -R"doc(This helper function is used to add a fanout coordinate to the -appropriate route based on whether it belongs to the the route from -the gate to the first or second fanout. - -Template parameter ``Lyt``: - Cartesian gate-level layout type. - -Parameter ``fanout``: - The fanout coordinate to be added to the route. - -Parameter ``is_first_fanout``: - A boolean indicating whether it belongs to the route from the gate - to the first fanout. - -Parameter ``ffd``: - Reference to the fanin_fanout_data structure containing the - routes.)doc"; - static const char *__doc_fiction_detail_add_obstructions = R"doc(Add obstructions to the layout. @@ -5835,19 +5797,6 @@ Parameter ``key``: An iterator to the found parameter point in the map, or `map.cend()` if not found.)doc"; -static const char *__doc_fiction_detail_fix_wires = -R"doc(Utility function to move wires that cross over empty tiles down one -layer. This can happen if the wiring of a gate is deleted. - -Template parameter ``Lyt``: - Cartesian gate-level layout type. - -Parameter ``lyt``: - Gate-level layout. - -Parameter ``deleted_coords``: - Tiles that got deleted.)doc"; - static const char *__doc_fiction_detail_gate_level_drvs_impl = R"doc()doc"; static const char *__doc_fiction_detail_gate_level_drvs_impl_border_io_check = @@ -6082,25 +6031,6 @@ static const char *__doc_fiction_detail_generate_edge_intersection_graph_impl_ps static const char *__doc_fiction_detail_generate_edge_intersection_graph_impl_run = R"doc()doc"; -static const char *__doc_fiction_detail_get_fanin_and_fanouts = -R"doc(Utility function to trace back fanins and fanouts of a gate. Based on -the gate to be moved, this function returns the location of the fanins -and fanouts, as well as the wiring in between them. Additionally, all -wire tiles between fanins and the gate, as well as between the gate -and fanouts are collected for deletion. - -Template parameter ``Lyt``: - Cartesian gate-level layout type. - -Parameter ``lyt``: - Cartesian Gate-level layout. - -Parameter ``op``: - coordinate of the gate to be moved. - -Returns: - fanin and fanout gates, wires to be deleted and old routing paths.)doc"; - static const char *__doc_fiction_detail_get_offset = R"doc(Utility function to calculate the offset that has to be subtracted from any x-coordinate on the hexagonal layout. @@ -6150,30 +6080,6 @@ Parameter ``end``: Returns: The computed path as a sequence of coordinates in the layout.)doc"; -static const char *__doc_fiction_detail_get_path_and_obstruct = -R"doc(This helper function computes a path between two coordinates using the -A* algorithm. It then obstructs the tiles along the path in the given -layout. - -Template parameter ``Lyt``: - Cartesian gate-level layout type. - -Parameter ``lyt``: - Reference to the layout. - -Parameter ``start``: - The starting coordinate of the path. - -Parameter ``end``: - The ending coordinate of the path. - -Parameter ``planar_optimization``: - Only allow relocation if a crossing-free wiring can be found. - Defaults to false. - -Returns: - The computed path as a sequence of coordinates in the layout.)doc"; - static const char *__doc_fiction_detail_get_po_levels = R"doc()doc"; static const char *__doc_fiction_detail_graph_coloring_impl = R"doc()doc"; @@ -6600,34 +6506,6 @@ Parameter ``place_info``: Parameter ``ssg``: The search space graph.)doc"; -static const char *__doc_fiction_detail_improve_gate_location = -R"doc(Utility function that moves gates to new coordinates and checks if -routing is possible. This includes: - -- removing the old wiring between fanins, the gate and fanouts - -updating the incoming signals - determining coordinates that would -improve the layout - testing all those coordinates by moving the gate -to each one and checking if a new wiring can be found - if a new -coordinate is found and wiring is possible, it is applied and incoming -signals are updated - if no better coordinate is found, the old wiring -is restored - -Template parameter ``Lyt``: - Cartesian obstruction gate-level layout type. - -Parameter ``lyt``: - 2DDWave-clocked cartesian obstruction gate-level layout. - -Parameter ``old_pos``: - Old position of the gate to be moved. - -Parameter ``planar_optimization``: - Only allow relocation if a crossing-free wiring can be found. - Defaults to false. - -Returns: - `true` if the gate was moved successfully, `false` otherwise.)doc"; - static const char *__doc_fiction_detail_is_balanced_impl = R"doc()doc"; static const char *__doc_fiction_detail_is_balanced_impl_balanced = R"doc()doc"; @@ -7475,6 +7353,145 @@ static const char *__doc_fiction_detail_placement_info_node2pos = R"doc(Mapping static const char *__doc_fiction_detail_post_layout_optimization_impl = R"doc()doc"; +static const char *__doc_fiction_detail_post_layout_optimization_impl_add_fanin_to_route = +R"doc(This helper function is used to add a fanin coordinate to the +appropriate route based on whether it belongs to the the route from +the first or second fanin to the gate. + +Parameter ``fanin``: + The fanin coordinate to be added to the route. + +Parameter ``is_first_fanin``: + A boolean indicating whether this is part of the route from the + first fanin to the gate. + +Parameter ``ffd``: + Reference to the fanin_fanout_data structure containing the + routes.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_add_fanout_to_route = +R"doc(This helper function is used to add a fanout coordinate to the +appropriate route based on whether it belongs to the the route from +the gate to the first or second fanout. + +Parameter ``fanout``: + The fanout coordinate to be added to the route. + +Parameter ``is_first_fanout``: + A boolean indicating whether it belongs to the route from the gate + to the first fanout. + +Parameter ``ffd``: + Reference to the fanin_fanout_data structure containing the + routes.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_check_new_position = +R"doc(Attempts to relocate a gate to a new position within the layout and +updates routing connections accordingly. + +Parameter ``lyt``: + Obstructed gate-level layout being optimized. + +Parameter ``new_pos``: + The target tile position to which the gate is to be relocated. + +Parameter ``num_gate_relocations``: + Reference to a counter tracking the number of gate relocations + performed. + +Parameter ``current_pos``: + Reference to the current position of the gate being relocated. + This will be updated upon successful relocation. + +Parameter ``fanins``: + Vector containing the tile positions of all fan-in connections to + the gate. + +Parameter ``fanouts``: + Vector containing the tile positions of all fan-out connections + from the gate. + +Parameter ``moved_gate``: + Reference to a boolean flag that will be set to `true` if the gate + is successfully moved. + +Parameter ``old_pos``: + The original tile position of the gate before the relocation + attempt. + +Returns: + `true` if the gate was successfully relocated to `new_pos` and all + routing paths were established. `false` if the relocation resulted + in no movement (i.e., `new_pos` is the same as `old_pos`).)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_fix_wires = +R"doc(Utility function to move wires that cross over empty tiles down one +layer. This can happen if the wiring of a gate is deleted. + +Parameter ``lyt``: + Obstructed gate-level layout. + +Parameter ``deleted_coords``: + Tiles that got deleted.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_get_fanin_and_fanouts = +R"doc(Utility function to trace back fanins and fanouts of a gate. Based on +the gate to be moved, this function returns the location of the fanins +and fanouts, as well as the wiring in between them. Additionally, all +wire tiles between fanins and the gate, as well as between the gate +and fanouts are collected for deletion. + +Parameter ``lyt``: + Obstructed gate-level layout. + +Parameter ``op``: + coordinate of the gate to be moved. + +Returns: + fanin and fanout gates, wires to be deleted and old routing paths.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_get_path_and_obstruct = +R"doc(This helper function computes a path between two coordinates using the +A* algorithm. It then obstructs the tiles along the path in the given +layout. + +Parameter ``lyt``: + Obstructed gate-level layout. + +Parameter ``start_tile``: + The starting coordinate of the path. + +Parameter ``end_tile``: + The ending coordinate of the path. + +Returns: + The computed path as a sequence of coordinates in the layout.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_improve_gate_location = +R"doc(Utility function that moves gates to new coordinates and checks if +routing is possible. This includes: + +- removing the old wiring between fanins, the gate and fanouts - +updating the incoming signals - determining coordinates that would +improve the layout - testing all those coordinates by moving the gate +to each one and checking if a new wiring can be found - if a new +coordinate is found and wiring is possible, it is applied and incoming +signals are updated - if no better coordinate is found, the old wiring +is restored + +Parameter ``lyt``: + Obstructed gate-level layout. + +Parameter ``old_pos``: + Old position of the gate to be moved. + +Returns: + `true` if the gate was moved successfully, `false` otherwise.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_max_gate_relocations = R"doc(Maximum number of relocations to try for each gate.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_max_non_po = R"doc(Maximum coordinate of all gates that are not POs.)doc"; + static const char *__doc_fiction_detail_post_layout_optimization_impl_plyt = R"doc(2DDWave-clocked Cartesian gate-level layout to optimize.)doc"; static const char *__doc_fiction_detail_post_layout_optimization_impl_post_layout_optimization_impl = R"doc()doc"; @@ -7483,8 +7500,61 @@ static const char *__doc_fiction_detail_post_layout_optimization_impl_ps = R"doc static const char *__doc_fiction_detail_post_layout_optimization_impl_pst = R"doc(Statistics about the post-layout optimization process.)doc"; +static const char *__doc_fiction_detail_post_layout_optimization_impl_restore_original_wiring = +R"doc(Restores the original wiring if relocation of a gate fails. + +This function moves the gate back to its original position and +reinstates the previous wiring paths between the gate and its fan- +in/fan-out connections. It also updates the obstructions in the layout +accordingly. + +Parameter ``lyt``: + Obstructed gate-level layout. + +Parameter ``old_path_from_fanin_1_to_gate``: + The original routing path from the first fan-in to the gate (if + exists). + +Parameter ``old_path_from_fanin_2_to_gate``: + The original routing path from the second fan-in to the gate (if + exists). + +Parameter ``old_path_from_gate_to_fanout_1``: + The original routing path from the gate to the first fan-out (if + exists). + +Parameter ``old_path_from_gate_to_fanout_2``: + The original routing path from the gate to the second fan-out (if + exists). + +Parameter ``current_pos``: + Current position of the gate after relocation attempt. + +Parameter ``old_pos``: + Original position of the gate before relocation attempt. + +Parameter ``fanouts``: + Vector of fanout tiles connected to the gate.)doc"; + static const char *__doc_fiction_detail_post_layout_optimization_impl_run = R"doc()doc"; +static const char *__doc_fiction_detail_post_layout_optimization_impl_start = R"doc(Start time.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_timeout_limit_reached = R"doc(Timeout limit reached.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_update_timeout = +R"doc(Calculates the elapsed milliseconds since the `start` time, sets the +`timeout_limit_reached` flag if the timeout is exceeded, and returns +the remaining time. + +Returns: + Remaining time in milliseconds before timeout, or `0` if timeout + has been reached.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_wiring_reduction_params = R"doc(Wiring reduction parameters.)doc"; + +static const char *__doc_fiction_detail_post_layout_optimization_impl_wiring_reduction_stats = R"doc(Wiring reduction stats.)doc"; + static const char *__doc_fiction_detail_priority_queue = R"doc(A priority queue class for managing elements with associated priorities. The elements are stored in a priority queue, with the @@ -8153,10 +8223,16 @@ static const char *__doc_fiction_detail_wiring_reduction_impl = R"doc()doc"; static const char *__doc_fiction_detail_wiring_reduction_impl_plyt = R"doc(The 2DDWave-clocked layout whose wiring is to be reduced.)doc"; +static const char *__doc_fiction_detail_wiring_reduction_impl_ps = R"doc(Wiring reduction parameters.)doc"; + static const char *__doc_fiction_detail_wiring_reduction_impl_pst = R"doc(Statistics about the wiring_reduction process.)doc"; static const char *__doc_fiction_detail_wiring_reduction_impl_run = R"doc()doc"; +static const char *__doc_fiction_detail_wiring_reduction_impl_start = R"doc(Start time.)doc"; + +static const char *__doc_fiction_detail_wiring_reduction_impl_timeout_limit_reached = R"doc(Timeout limit reached.)doc"; + static const char *__doc_fiction_detail_wiring_reduction_impl_wiring_reduction_impl = R"doc()doc"; static const char *__doc_fiction_detail_wiring_reduction_layout = @@ -14860,6 +14936,14 @@ R"doc(Disable the creation of crossings during optimization. If set to true, gates will only be relocated if a crossing-free wiring is found. Defaults to false.)doc"; +static const char *__doc_fiction_post_layout_optimization_params_timeout = +R"doc(Timeout limit (in ms). Specifies the maximum allowed time in +milliseconds for the optimization process. For large layouts, the +actual execution time may slightly exceed this limit because it's +impractical to check the timeout at every algorithm step and the +functional correctness has to be ensured by completing essential +algorithm steps.)doc"; + static const char *__doc_fiction_post_layout_optimization_stats = R"doc(This struct stores statistics about the post-layout optimization process.)doc"; @@ -17871,9 +17955,21 @@ Template parameter ``Lyt``: Parameter ``lyt``: The 2DDWave-clocked layout whose wiring is to be reduced. +Parameter ``ps``: + Parameters. + Parameter ``pst``: - Pointer to a `wiring_reduction_stats` object to record runtime - statistics.)doc"; + Statistics.)doc"; + +static const char *__doc_fiction_wiring_reduction_params = R"doc(Parameters for the wiring reduction algorithm.)doc"; + +static const char *__doc_fiction_wiring_reduction_params_timeout = +R"doc(Timeout limit (in ms). Specifies the maximum allowed time in +milliseconds for the optimization process. For large layouts, the +actual execution time may slightly exceed this limit because it's +impractical to check the timeout at every algorithm step and the +functional correctness has to be ensured by completing essential +algorithm steps.)doc"; static const char *__doc_fiction_wiring_reduction_stats = R"doc(This struct stores statistics about the wiring reduction process.)doc"; diff --git a/bindings/pyfiction/test/algorithms/physical_design/test_post_layout_optimization.py b/bindings/pyfiction/test/algorithms/physical_design/test_post_layout_optimization.py index 055cac44b..797259111 100644 --- a/bindings/pyfiction/test/algorithms/physical_design/test_post_layout_optimization.py +++ b/bindings/pyfiction/test/algorithms/physical_design/test_post_layout_optimization.py @@ -57,6 +57,7 @@ def test_post_layout_optimization_with_stats_and_parameters(self): params = post_layout_optimization_params() params.max_gate_relocations = 1 + params.timeout = 1000000 stats = post_layout_optimization_stats() post_layout_optimization(layout, params, statistics=stats) diff --git a/bindings/pyfiction/test/algorithms/physical_design/test_wiring_reduction.py b/bindings/pyfiction/test/algorithms/physical_design/test_wiring_reduction.py index 6c50d00ba..6438de825 100644 --- a/bindings/pyfiction/test/algorithms/physical_design/test_wiring_reduction.py +++ b/bindings/pyfiction/test/algorithms/physical_design/test_wiring_reduction.py @@ -18,6 +18,18 @@ def test_wiring_reduction_default(self): self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) + def test_wiring_reduction_with_parameters(self): + network = read_technology_network(dir_path + "/../../resources/mux21.v") + + layout = orthogonal(network) + + self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) + + params = wiring_reduction_params() + wiring_reduction(layout, params) + + self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) + def test_wiring_reduction_with_stats(self): network = read_technology_network(dir_path + "/../../resources/mux21.v") @@ -26,7 +38,31 @@ def test_wiring_reduction_with_stats(self): self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) stats = wiring_reduction_stats() - wiring_reduction(layout, stats) + wiring_reduction(layout, statistics=stats) + + self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) + self.assertGreater(stats.time_total.total_seconds(), 0) + self.assertEqual(stats.x_size_before, 6) + self.assertEqual(stats.y_size_before, 8) + self.assertEqual(stats.x_size_after, 6) + self.assertEqual(stats.y_size_after, 5) + self.assertEqual(stats.num_wires_before, 21) + self.assertEqual(stats.num_wires_after, 15) + self.assertEqual(stats.wiring_improvement, 28.57) + self.assertEqual(stats.area_improvement, 37.50) + + def test_wiring_reduction_with_stats_and_parameters(self): + network = read_technology_network(dir_path + "/../../resources/mux21.v") + + layout = orthogonal(network) + + self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) + + params = wiring_reduction_params() + params.timeout = 1000000 + + stats = wiring_reduction_stats() + wiring_reduction(layout, params, stats) self.assertEqual(equivalence_checking(network, layout), eq_type.STRONG) self.assertGreater(stats.time_total.total_seconds(), 0) diff --git a/cli/cmd/physical_design/optimize.hpp b/cli/cmd/physical_design/optimize.hpp index 1c464f1f9..7fff91fbd 100644 --- a/cli/cmd/physical_design/optimize.hpp +++ b/cli/cmd/physical_design/optimize.hpp @@ -49,6 +49,7 @@ class optimize_command : public command "layouts, the resulting layout will also be planar. If the layout already contains crossings, the " "optimized layout will have the same number of crossings or less."); add_flag("--verbose,-v", "Be verbose"); + add_option("--timeout,-t", ps.timeout, "Timeout in seconds"); } protected: @@ -60,6 +61,7 @@ class optimize_command : public command * Parameters. */ fiction::post_layout_optimization_params ps{}; + fiction::wiring_reduction_params psw{}; /** * Statistics. */ @@ -94,6 +96,13 @@ class optimize_command : public command return; } + if (is_set("timeout")) + { + // convert timeout entered in seconds to milliseconds + ps.timeout *= 1000; + psw.timeout = ps.timeout; + } + const auto apply_optimization = [&](auto&& lyt_ptr) { using Lyt = typename std::decay_t::element_type; @@ -104,7 +113,7 @@ class optimize_command : public command { if (is_set("wiring_reduction_only")) { - fiction::wiring_reduction(lyt_copy, &stw); + fiction::wiring_reduction(lyt_copy, psw, &stw); if (is_set("verbose")) { stw.report(env->out()); diff --git a/docs/algorithms/wiring_reduction.rst b/docs/algorithms/wiring_reduction.rst index 52d7779f4..834d8060f 100644 --- a/docs/algorithms/wiring_reduction.rst +++ b/docs/algorithms/wiring_reduction.rst @@ -19,11 +19,15 @@ spaces upward and subsequently reconnecting them. This iterative process continu .. tab:: C++ **Header:** ``fiction/algorithms/physical_design/wiring_reduction.hpp`` + .. doxygenstruct:: fiction::wiring_reduction_params + :members: .. doxygenstruct:: fiction::wiring_reduction_stats :members: .. doxygenfunction:: fiction::wiring_reduction .. tab:: Python + .. autoclass:: mnt.pyfiction.wiring_reduction_params + :members: .. autoclass:: mnt.pyfiction.wiring_reduction_stats :members: .. autofunction:: mnt.pyfiction.wiring_reduction diff --git a/docs/cli.rst b/docs/cli.rst index 36b4b732d..54122c73a 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -223,6 +223,7 @@ For more information, see `this paper (gate_level_layout, &wiring_reduction_stats); + fiction::wiring_reduction(gate_level_layout, {}, &wiring_reduction_stats); // compute critical path and throughput const auto cp_tp_stats_after = fiction::critical_path_length_and_throughput(gate_level_layout); diff --git a/include/fiction/algorithms/physical_design/one_pass_synthesis.hpp b/include/fiction/algorithms/physical_design/one_pass_synthesis.hpp index 7d527a736..9a8ab7109 100644 --- a/include/fiction/algorithms/physical_design/one_pass_synthesis.hpp +++ b/include/fiction/algorithms/physical_design/one_pass_synthesis.hpp @@ -9,13 +9,14 @@ #include "fiction/algorithms/iter/aspect_ratio_iterator.hpp" #include "fiction/layouts/clocking_scheme.hpp" -#include "fiction/layouts/coordinates.hpp" +#include "fiction/traits.hpp" #include "fiction/utils/name_utils.hpp" #include "utils/mugen_info.hpp" #include #include #include +#include #include #if (PROGRESS_BARS) @@ -23,16 +24,19 @@ #endif #include +#include #include +#include +#include #include -#include #include #include #include #include -#include #include +#include #include +#include #include // pybind11 has quite some warnings in its code; let's silence them a little @@ -45,7 +49,10 @@ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wrange-loop-analysis" #pragma warning(push, 0) // MSVC +#include #include +#include +#include #pragma GCC diagnostic pop // GCC #pragma warning(pop) // MSVC @@ -158,7 +165,7 @@ class mugen_handler * Standard constructor. * * @param spec The Boolean functions to synthesize. - * @param lyt Reference to an empty layout that serves as a floor plan for S&P&R by Mugen. + * @param sketch Reference to an empty layout that serves as a floor plan for S&P&R by Mugen. * @param p The configurations to respect in the SAT instance generation process. */ mugen_handler(const std::vector& spec, Lyt& sketch, one_pass_synthesis_params p) : @@ -363,8 +370,7 @@ class mugen_handler // set up the iterator to skip the PIs auto pi_it_end = nodes.begin(); // use std::advance because there is no 'operator+' overload - std::advance(pi_it_end, - static_cast::difference_type>(num_pis + 1)); + std::advance(pi_it_end, static_cast::difference_type>(num_pis)); return pi_it_end; } @@ -779,10 +785,23 @@ class one_pass_synthesis_impl update_timeout(handler, pst.time_total); } } - // timeout reached + catch (const pybind11::error_already_set& e) + { + // timeout reached + if (e.matches(PyExc_TimeoutError)) + { + return std::nullopt; + } + + // unexpected error + std::cout << "[e] something unexpected happened in Python; this needs investigation" << std::endl; + throw; + } + // unexpected exception catch (...) { - return std::nullopt; + std::cout << "[e] something unexpected happened; this needs investigation" << std::endl; + throw; } } diff --git a/include/fiction/algorithms/physical_design/post_layout_optimization.hpp b/include/fiction/algorithms/physical_design/post_layout_optimization.hpp index e201fc8ee..3579e53f9 100644 --- a/include/fiction/algorithms/physical_design/post_layout_optimization.hpp +++ b/include/fiction/algorithms/physical_design/post_layout_optimization.hpp @@ -20,10 +20,14 @@ #include #include +#include #include +#include #include #include +#include +#include #include #include #include @@ -52,6 +56,12 @@ struct post_layout_optimization_params * crossing-free wiring is found. Defaults to false. */ bool planar_optimization = false; + /** + * Timeout limit (in ms). Specifies the maximum allowed time in milliseconds for the optimization process. For large + * layouts, the actual execution time may slightly exceed this limit because it's impractical to check the timeout + * at every algorithm step and the functional correctness has to be ensured by completing essential algorithm steps. + */ + uint64_t timeout = std::numeric_limits::max(); }; /** @@ -121,64 +131,6 @@ struct post_layout_optimization_stats namespace detail { -/** - * Utility function to move wires that cross over empty tiles down one layer. This can happen if the wiring of a gate is - * deleted. - * - * @tparam Lyt Cartesian gate-level layout type. - * @param lyt Gate-level layout. - * @param deleted_coords Tiles that got deleted. - */ -template -void fix_wires(Lyt& lyt, const std::vector>& deleted_coords) noexcept -{ - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - - std::unordered_set> moved_tiles{}; - moved_tiles.reserve(deleted_coords.size()); - for (const auto& tile : deleted_coords) - { - const auto ground = lyt.below(tile); - const auto above = lyt.above(tile); - - if (lyt.is_empty_tile(ground) && lyt.is_wire_tile(above)) - { - const auto incoming_tile = lyt.incoming_data_flow(above).front(); - const auto outgoing_tile = lyt.outgoing_data_flow(above).front(); - - // move wire from z=1 to z=0 - lyt.move_node(lyt.get_node(above), ground, {lyt.make_signal(lyt.get_node(incoming_tile))}); - - // if outgoing tile has other incoming signals (e.g. AND), update children - if (const auto in_flow = lyt.incoming_data_flow(outgoing_tile); !in_flow.empty()) - { - const auto front = in_flow.front(); - - if (std::find(deleted_coords.cbegin(), deleted_coords.cend(), front) == deleted_coords.cend() || - std::find(moved_tiles.cbegin(), moved_tiles.cend(), front) != moved_tiles.cend()) - { - lyt.move_node(lyt.get_node(outgoing_tile), outgoing_tile, - {lyt.make_signal(lyt.get_node(ground)), lyt.make_signal(lyt.get_node(front))}); - } - } - // otherwise, the wire is the only incoming signal - else - { - lyt.move_node(lyt.get_node(outgoing_tile), outgoing_tile, {lyt.make_signal(lyt.get_node(ground))}); - } - - if constexpr (has_is_obstructed_coordinate_v) - { - // update obstructions - lyt.obstruct_coordinate(ground); - lyt.clear_obstructed_coordinate(above); - } - - moved_tiles.insert(tile); - } - } -} /** * This struct stores information about the fan-in and fan-out connections of a gate in a layout. * These fan-in and fan-outs are the preceding and succeeding gates in the logic network. @@ -232,455 +184,762 @@ struct fanin_fanout_data layout_coordinate_path route_gate_to_fanout_2; }; /** - * This helper function is used to add a fanin coordinate to the appropriate route - * based on whether it belongs to the the route from the first or second fanin to the gate. + * Utility function that moves outputs from the last row to the previous row, and from the last column to the previous + * column, if possible. * * @tparam Lyt Cartesian gate-level layout type. - * @param fanin The fanin coordinate to be added to the route. - * @param is_first_fanin A boolean indicating whether this is part of the route from the first fanin to the gate. - * @param ffd Reference to the fanin_fanout_data structure containing the routes. + * @param lyt Gate-level layout. */ template -void add_fanin_to_route(const tile& fanin, bool is_first_fanin, fanin_fanout_data& ffd) noexcept +void optimize_output_positions(Lyt& lyt) noexcept { - if (is_first_fanin) + static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); + static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); + + bool optimizable = true; + + for (uint64_t x = 0; x <= lyt.x(); ++x) { - ffd.route_fanin_1_to_gate.insert(ffd.route_fanin_1_to_gate.cbegin(), fanin); + if (!(lyt.is_empty_tile({x, lyt.y()}) || + (lyt.is_po_tile({x, lyt.y(), 0}) && lyt.is_empty_tile({x + 1, lyt.y() - 1, 0}) && (x < lyt.x())))) + { + optimizable = false; + } } - else + + if (optimizable) { - ffd.route_fanin_2_to_gate.insert(ffd.route_fanin_2_to_gate.cbegin(), fanin); + for (uint64_t x = 0; x < lyt.x(); ++x) + { + if (lyt.is_po_tile({x, lyt.y(), 0})) + { + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({x, lyt.y()}))); + lyt.foreach_fanin(lyt.get_node({x, lyt.y()}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); + lyt.move_node(lyt.get_node({x, lyt.y()}), {x + 1, lyt.y() - 1, 0}, signals); + } + } } -} -/** - * This helper function is used to add a fanout coordinate to the appropriate route - * based on whether it belongs to the the route from the gate to the first or second fanout. - * - * @tparam Lyt Cartesian gate-level layout type. - * @param fanout The fanout coordinate to be added to the route. - * @param is_first_fanout A boolean indicating whether it belongs to the route from the gate to the first fanout. - * @param ffd Reference to the fanin_fanout_data structure containing the routes. - */ -template -void add_fanout_to_route(const tile& fanout, bool is_first_fanout, fanin_fanout_data& ffd) noexcept -{ - if (is_first_fanout) + + optimizable = true; + + for (uint64_t y = 0; y <= lyt.y(); ++y) { - ffd.route_gate_to_fanout_1.push_back(fanout); + if (!(lyt.is_empty_tile({lyt.x(), y}) || + (lyt.is_po_tile({lyt.x(), y, 0}) && lyt.is_empty_tile({lyt.x() - 1, y + 1, 0}) && (y < lyt.y())))) + { + optimizable = false; + } } - else + + if (optimizable) { - ffd.route_gate_to_fanout_2.push_back(fanout); + for (uint64_t y = 0; y < lyt.y(); ++y) + { + if (lyt.is_po_tile({lyt.x(), y, 0})) + { + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), y}))); + lyt.foreach_fanin(lyt.get_node({lyt.x(), y}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); + lyt.move_node(lyt.get_node({lyt.x(), y}), {lyt.x() - 1, y + 1, 0}, signals); + } + } } -} -/** - * Utility function to trace back fanins and fanouts of a gate. Based on the gate to be moved, this function returns the - * location of the fanins and fanouts, as well as the wiring in between them. Additionally, all wire tiles between - * fanins and the gate, as well as between the gate and fanouts are collected for deletion. - * - * @tparam Lyt Cartesian gate-level layout type. - * @param lyt Cartesian Gate-level layout. - * @param op coordinate of the gate to be moved. - * @return fanin and fanout gates, wires to be deleted and old routing paths. - */ -template -[[nodiscard]] fanin_fanout_data get_fanin_and_fanouts(const Lyt& lyt, const tile& op) noexcept -{ - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - - fanin_fanout_data ffd{}; - - auto fanin1 = tile{}; - auto fanin2 = tile{}; - auto fanout1 = tile{}; - auto fanout2 = tile{}; + // calculate bounding box + auto bounding_box = bounding_box_2d(lyt); + lyt.resize({bounding_box.get_max().x, bounding_box.get_max().y, lyt.z()}); - std::unordered_set> fanins_set{}; - fanins_set.reserve(lyt.num_wires() + lyt.num_gates() - 2); - std::unordered_set> fanouts_set{}; - fanouts_set.reserve(lyt.num_wires() + lyt.num_gates() - 2); + // check for misplaced POs in second last row and move them one row down + for (uint64_t x = 0; x < lyt.x(); ++x) + { + if (lyt.is_po_tile({x, lyt.y() - 1, 0})) + { + // get fanin signal of the PO + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({x, lyt.y()}))); + lyt.foreach_fanin(lyt.get_node({x, lyt.y() - 1}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); - lyt.foreach_fanin(lyt.get_node(op), - [&lyt, &fanins_set, &op, &fanin1, &fanin2, &ffd](const auto& fin) - { - auto fanin = static_cast>(fin); - if (fanins_set.find(fanin) == fanins_set.cend()) - { + // move PO one row down + lyt.move_node(lyt.get_node({x, lyt.y() - 1}), {x, lyt.y(), 0}, {}); - // add fanin to the respective route - add_fanin_to_route(op, fanins_set.empty(), ffd); - add_fanin_to_route(fanin, fanins_set.empty(), ffd); + // create a wire segment at the previous location of the PO and connect it with its fanin + lyt.create_buf(signals[0], {x, lyt.y() - 1}); - // continue until gate or primary input (PI) is found - while (lyt.is_wire_tile(fanin) && lyt.fanout_size(lyt.get_node(fanin)) == 1 && - !lyt.is_pi_tile(fanin)) - { - ffd.to_clear.push_back(fanin); - fanin = lyt.incoming_data_flow(fanin).front(); + // connect the PO with the new wire segment + lyt.move_node(lyt.get_node({x, lyt.y()}), {x, lyt.y(), 0}, + {lyt.make_signal(lyt.get_node({x, lyt.y() - 1}))}); + } + } - // add fanin to the respective route - add_fanin_to_route(fanin, fanins_set.empty(), ffd); - } + // check for misplaced POs in second last column and move them one column to the right + for (uint64_t y = 0; y < lyt.y(); ++y) + { + if (lyt.is_po_tile({lyt.x() - 1, y, 0})) + { + // get fanin signal of the PO + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), y}))); + lyt.foreach_fanin(lyt.get_node({lyt.x() - 1, y}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); - // set the respective fanin based on the route - if (fanins_set.empty()) - { - fanin1 = fanin; - } - else - { - fanin2 = fanin; - } + // move PO one column to the right + lyt.move_node(lyt.get_node({lyt.x() - 1, y}), {lyt.x(), y, 0}, {}); - fanins_set.insert(fanin); - } - }); - // same for fanouts - lyt.foreach_fanout(lyt.get_node(op), - [&lyt, &fanouts_set, &op, &fanout1, &fanout2, &ffd](const auto& fout) - { - tile fanout = lyt.get_tile(fout); + // create a wire segment at the previous location of the PO and connect it with its fanin + lyt.create_buf(signals[0], {lyt.x() - 1, y}); - if (fanouts_set.find(fanout) == fanouts_set.cend()) - { + // connect the PO with the new wire segment + lyt.move_node(lyt.get_node({lyt.x(), y}), {lyt.x(), y, 0}, + {lyt.make_signal(lyt.get_node({lyt.x() - 1, y}))}); + } + } - // add fanout to the respective route - add_fanout_to_route(op, fanouts_set.empty(), ffd); - add_fanout_to_route(fanout, fanouts_set.empty(), ffd); + // update bounding box + bounding_box.update_bounding_box(); + lyt.resize({bounding_box.get_max().x, bounding_box.get_max().y, lyt.z()}); - // continue until gate or primary output (PO) is found - while (lyt.is_wire_tile(fanout) && lyt.fanout_size(lyt.get_node(fanout)) != 0 && - lyt.fanout_size(lyt.get_node(fanout)) != 2) - { - ffd.to_clear.push_back(fanout); - fanout = lyt.outgoing_data_flow(fanout).front(); + // check if PO is located in bottom right corner and relocation would save more tiles (only possible for layouts + // with a single PO) + if (lyt.is_po_tile({lyt.x(), lyt.y(), 0}) && (lyt.num_pos() == 1)) + { + // check if relocation would save tiles + if (lyt.has_western_incoming_signal({lyt.x(), lyt.y(), 0}) && + ((lyt.x() * (lyt.y() + 2)) < ((lyt.x() + 1) * (lyt.y() + 1)))) + { + // get fanin signal of the PO + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), lyt.y()}))); + lyt.foreach_fanin(lyt.get_node({lyt.x(), lyt.y()}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); - // add fanout to the respective route - add_fanout_to_route(fanout, fanouts_set.empty(), ffd); - } + // resize layout + lyt.resize({lyt.x(), lyt.y() + 1, lyt.z()}); - // set the respective fanout based on the route - if (fanouts_set.empty()) - { - fanout1 = fanout; - } - else - { - fanout2 = fanout; - } + // move PO one tile down and to the left + lyt.move_node(lyt.get_node({lyt.x(), lyt.y() - 1}), {lyt.x() - 1, lyt.y(), 0}, signals); + } + // check if relocation would save tiles + else if (lyt.has_northern_incoming_signal({lyt.x(), lyt.y(), 0}) && + (((lyt.x() + 2) * lyt.y()) < ((lyt.x() + 1) * (lyt.y() + 1)))) + { + // get fanin signal of the PO + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), lyt.y()}))); + lyt.foreach_fanin(lyt.get_node({lyt.x(), lyt.y()}), + [&signals](const auto& fanin) { signals.push_back(fanin); }); - fanouts_set.insert(fanout); - } - }); + // resize layout + lyt.resize({lyt.x() + 1, lyt.y(), lyt.z()}); - // add fanins and fanouts if existing - if (!fanin1.is_dead()) - { - ffd.fanins.push_back(fanin1); - } - if (!fanin2.is_dead()) - { - ffd.fanins.push_back(fanin2); - } - if (!fanout1.is_dead()) - { - ffd.fanouts.push_back(fanout1); - } - if (!fanout2.is_dead()) - { - ffd.fanouts.push_back(fanout2); + // move PO one tile up and to the right + lyt.move_node(lyt.get_node({lyt.x() - 1, lyt.y()}), {lyt.x(), lyt.y() - 1, 0}, signals); + } } - - return ffd; } /** - * This helper function computes a path between two coordinates using the A* algorithm. - * It then obstructs the tiles along the path in the given layout. + * Custom comparison function for sorting tiles based on the sum of their coordinates that breaks ties based on the + * x-coordinate. * * @tparam Lyt Cartesian gate-level layout type. - * @param lyt Reference to the layout. - * @param start The starting coordinate of the path. - * @param end The ending coordinate of the path. - * @param planar_optimization Only allow relocation if a crossing-free wiring can be found. Defaults to false. - * @return The computed path as a sequence of coordinates in the layout. + * @param a First tile to compare. + * @param b Second tile to compare. + * @return `true` iff `a < b` based on the aforementioned rule. */ template -layout_coordinate_path get_path_and_obstruct(Lyt& lyt, const tile& start, const tile& end, - const bool planar_optimization = false) +bool compare_gate_tiles(const tile& a, const tile& b) { static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - using dist = twoddwave_distance_functor; - using cost = unit_cost_functor; - static a_star_params params{}; - params.crossings = !planar_optimization; - - layout_coordinate_path path = a_star>(lyt, {start, end}, dist(), cost(), params); - - // obstruct the tiles along the computed path. - for (const auto& tile : path) - { - lyt.obstruct_coordinate(tile); - } - - return path; + return static_cast(std::make_pair(a.x + a.y, a.x) < std::make_pair(b.x + b.y, b.x)); } -/** - * Utility function that moves gates to new coordinates and checks if routing is possible. - * This includes: - * - * - removing the old wiring between fanins, the gate and fanouts - * - updating the incoming signals - * - determining coordinates that would improve the layout - * - testing all those coordinates by moving the gate to each one and checking if a new wiring can be found - * - if a new coordinate is found and wiring is possible, it is applied and incoming signals are updated - * - if no better coordinate is found, the old wiring is restored - * - * @tparam Lyt Cartesian obstruction gate-level layout type. - * @param lyt 2DDWave-clocked cartesian obstruction gate-level layout. - * @param old_pos Old position of the gate to be moved. - * @param planar_optimization Only allow relocation if a crossing-free wiring can be found. Defaults to false. - * @return `true` if the gate was moved successfully, `false` otherwise. - */ + template -bool improve_gate_location(Lyt& lyt, const tile& old_pos, const tile& max_non_po, - const uint64_t max_gate_relocations, const bool planar_optimization = false) noexcept +class post_layout_optimization_impl { - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); + public: + post_layout_optimization_impl(const Lyt& lyt, const post_layout_optimization_params& p, + post_layout_optimization_stats& st) : + plyt{lyt}, + ps{p}, + pst{st}, + start{std::chrono::high_resolution_clock::now()} + {} - const auto& [fanins, fanouts, to_clear, old_path_from_fanin_1_to_gate, old_path_from_fanin_2_to_gate, - old_path_from_gate_to_fanout_1, old_path_from_gate_to_fanout_2] = get_fanin_and_fanouts(lyt, old_pos); + void run() + { + static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); + static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - uint64_t min_x = 0; - uint64_t min_y = 0; + // start the stopwatch to measure total optimization time + const mockturtle::stopwatch stop{pst.time_total}; - // determine minimum coordinates for new placements - if (!fanins.empty()) - { - min_x = - std::max_element(fanins.cbegin(), fanins.cend(), [](const auto& a, const auto& b) { return a.x < b.x; })->x; - min_y = - std::max_element(fanins.cbegin(), fanins.cend(), [](const auto& a, const auto& b) { return a.y < b.y; })->y; - } + // record initial layout statistics + pst.x_size_before = plyt.x() + 1; + pst.y_size_before = plyt.y() + 1; + pst.num_wires_before = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); + pst.num_crossings_before = plyt.num_crossings(); - const auto max_x = old_pos.x; - const auto max_y = old_pos.y; - const auto max_diagonal = max_x + max_y; + // determine the maximum number of gate relocations + max_gate_relocations = ps.max_gate_relocations.value_or((plyt.x() + 1) * (plyt.y() + 1)); - auto new_pos = tile{}; + // create an obstruction layout based on the original layout + auto layout = obstruction_layout(plyt); - // if gate is directly connected to one of its fanins, no improvement is possible - for (const auto& fanin : fanins) - { - for (const auto& i : lyt.incoming_data_flow(old_pos)) + // initialize flags to control the optimization loop + bool moved_at_least_one_gate = true; + bool reduced_wiring = true; + timeout_limit_reached = false; + + if (max_gate_relocations == 0) { - if (i == fanin) + // update the remaining timeout + wiring_reduction_params.timeout = update_timeout(); + + if (!timeout_limit_reached) { - return false; + fiction::wiring_reduction(layout, wiring_reduction_params, &wiring_reduction_stats); } } - } - - // if gate is a PI and directly connected to its fanout, no improvement is possible - if (lyt.is_pi_tile(old_pos)) - { - for (const auto& fanout : fanouts) + else { - for (const auto& outgoing_tile : lyt.outgoing_data_flow(old_pos)) + // iteratively optimize the layout until no more improvements can be made or timeout is reached + while ((moved_at_least_one_gate || reduced_wiring) && !timeout_limit_reached) { - if (outgoing_tile == fanout) + reduced_wiring = false; + + // update the remaining timeout + wiring_reduction_params.timeout = update_timeout(); + + if (moved_at_least_one_gate && !ps.optimize_pos_only && !timeout_limit_reached) { - return false; - } - } + fiction::wiring_reduction(layout, wiring_reduction_params, &wiring_reduction_stats); + + // check if wiring reduction made any improvements + if (wiring_reduction_stats.area_improvement != 0ull || + wiring_reduction_stats.wiring_improvement != 0ull) + { + reduced_wiring = true; + } + } + + // gather all relevant gate tiles for relocation + std::vector> gate_tiles{}; + gate_tiles.reserve(layout.num_gates() + layout.num_pis() - layout.num_pos()); + layout.foreach_node( + [&layout, &gate_tiles](const auto& node) noexcept + { + if (const tile gate_tile = layout.get_tile(node); + (layout.is_gate(node) && !layout.is_wire(node)) || layout.is_fanout(node) || + layout.is_pi_tile(gate_tile) || layout.is_po_tile(gate_tile)) + { + layout.obstruct_coordinate({gate_tile.x, gate_tile.y, 1}); + gate_tiles.emplace_back(gate_tile); + } + }); + + // sort the gate tiles using the custom comparator + std::sort(gate_tiles.begin(), gate_tiles.end(), compare_gate_tiles); + + max_non_po = tile{0, 0}; + // determine minimal border for POs + for (const auto& gate_tile : gate_tiles) + { + if (!layout.is_po_tile(gate_tile)) + { + max_non_po.x = std::max(max_non_po.x, gate_tile.x); + max_non_po.y = std::max(max_non_po.y, gate_tile.y); + } + } + + // reset the gate movement flag + moved_at_least_one_gate = false; + + // attempt to relocate each gate tile + for (const auto& gate_tile : gate_tiles) + { + if (!timeout_limit_reached) + { + if (!ps.optimize_pos_only || (ps.optimize_pos_only && layout.is_po_tile(gate_tile))) + { + if (improve_gate_location(layout, gate_tile)) + { + moved_at_least_one_gate = true; + } + } + + // update the remaining timeout after each relocation attempt + update_timeout(); + } + } + + // resize the layout to fit within the new bounding box after relocations + const auto bounding_box = bounding_box_2d(layout); + layout.resize({bounding_box.get_max().x, bounding_box.get_max().y, layout.z()}); + } + } + + // if the optimization did not time out, optimize the output positions + if (!timeout_limit_reached) + { + optimize_output_positions(layout); } + + // final bounding box calculation and layout resizing + const auto final_bounding_box = bounding_box_2d(layout); + layout.resize({final_bounding_box.get_max().x, final_bounding_box.get_max().y, layout.z()}); + + // update final layout statistics + pst.x_size_after = layout.x() + 1; + pst.y_size_after = layout.y() + 1; + + const uint64_t area_before = pst.x_size_before * pst.y_size_before; + const uint64_t area_after = pst.x_size_after * pst.y_size_after; + + double area_percentage_difference = + static_cast(area_before - area_after) / static_cast(area_before) * 100.0; + pst.area_improvement = std::round(area_percentage_difference * 100) / 100.0; + + pst.num_wires_after = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); + pst.num_crossings_after = plyt.num_crossings(); } - // remove wiring - for (const auto& tile : to_clear) + private: + /** + * Alias for an obstruction layout based on the given layout type. + */ + using ObstrLyt = obstruction_layout; + /** + * 2DDWave-clocked Cartesian gate-level layout to optimize. + */ + const Lyt& plyt; + /** + * Post-layout optimization parameters. + */ + post_layout_optimization_params ps; + /** + * Statistics about the post-layout optimization process. + */ + post_layout_optimization_stats& pst; + /** + * Start time. + */ + std::chrono::time_point start; + /** + * Maximum number of relocations to try for each gate. + */ + std::optional max_gate_relocations = std::nullopt; + /** + * Maximum coordinate of all gates that are not POs. + */ + tile max_non_po{0, 0}; + /** + * Timeout limit reached. + */ + bool timeout_limit_reached = false; + /** + * Wiring reduction parameters. + */ + fiction::wiring_reduction_params wiring_reduction_params{}; + /** + * Wiring reduction stats. + */ + fiction::wiring_reduction_stats wiring_reduction_stats{}; + /** + * Utility function to move wires that cross over empty tiles down one layer. This can happen if the wiring of a + * gate is deleted. + * + * @param lyt Obstructed gate-level layout. + * @param deleted_coords Tiles that got deleted. + */ + void fix_wires(ObstrLyt& lyt, const std::vector>& deleted_coords) noexcept { - lyt.clear_tile(tile); - lyt.clear_obstructed_coordinate(tile); + phmap::parallel_flat_hash_set> moved_tiles{}; + moved_tiles.reserve(deleted_coords.size()); + for (const auto& tile : deleted_coords) + { + const auto ground = lyt.below(tile); + const auto above = lyt.above(tile); + + if (lyt.is_empty_tile(ground) && lyt.is_wire_tile(above)) + { + const auto incoming_tile = lyt.incoming_data_flow(above).front(); + const auto outgoing_tile = lyt.outgoing_data_flow(above).front(); + + // move wire from z=1 to z=0 + lyt.move_node(lyt.get_node(above), ground, {lyt.make_signal(lyt.get_node(incoming_tile))}); + + // if outgoing tile has other incoming signals (e.g. AND), update children + if (const auto in_flow = lyt.incoming_data_flow(outgoing_tile); !in_flow.empty()) + { + const auto front = in_flow.front(); + + if (std::find(deleted_coords.cbegin(), deleted_coords.cend(), front) == deleted_coords.cend() || + std::find(moved_tiles.cbegin(), moved_tiles.cend(), front) != moved_tiles.cend()) + { + lyt.move_node(lyt.get_node(outgoing_tile), outgoing_tile, + {lyt.make_signal(lyt.get_node(ground)), lyt.make_signal(lyt.get_node(front))}); + } + } + // otherwise, the wire is the only incoming signal + else + { + lyt.move_node(lyt.get_node(outgoing_tile), outgoing_tile, {lyt.make_signal(lyt.get_node(ground))}); + } + + if constexpr (has_is_obstructed_coordinate_v) + { + // update obstructions + lyt.obstruct_coordinate(ground); + lyt.clear_obstructed_coordinate(above); + } + + moved_tiles.insert(tile); + } + } } + /** + * This helper function is used to add a fanin coordinate to the appropriate route + * based on whether it belongs to the the route from the first or second fanin to the gate. + * + * @param fanin The fanin coordinate to be added to the route. + * @param is_first_fanin A boolean indicating whether this is part of the route from the first fanin to the gate. + * @param ffd Reference to the fanin_fanout_data structure containing the routes. + */ + void add_fanin_to_route(const tile& fanin, bool is_first_fanin, fanin_fanout_data& ffd) noexcept + { + auto& target_route = is_first_fanin ? ffd.route_fanin_1_to_gate : ffd.route_fanin_2_to_gate; - // remove children of gate to be moved - lyt.resize({lyt.x() + 2, lyt.y(), lyt.z()}); - lyt.move_node(lyt.get_node(old_pos), {lyt.x(), 0}, {}); + target_route.insert(target_route.cbegin(), fanin); + } + /** + * This helper function is used to add a fanout coordinate to the appropriate route + * based on whether it belongs to the the route from the gate to the first or second fanout. + * + * @param fanout The fanout coordinate to be added to the route. + * @param is_first_fanout A boolean indicating whether it belongs to the route from the gate to the first fanout. + * @param ffd Reference to the fanin_fanout_data structure containing the routes. + */ + void add_fanout_to_route(const tile& fanout, bool is_first_fanout, + fanin_fanout_data& ffd) noexcept + { + auto& target_route = is_first_fanout ? ffd.route_gate_to_fanout_1 : ffd.route_gate_to_fanout_2; - // update children of fanouts - for (const auto& fanout : fanouts) + target_route.push_back(fanout); + } + /** + * Utility function to trace back fanins and fanouts of a gate. Based on the gate to be moved, this function returns + * the location of the fanins and fanouts, as well as the wiring in between them. Additionally, all wire tiles + * between fanins and the gate, as well as between the gate and fanouts are collected for deletion. + * + * @param lyt Obstructed gate-level layout. + * @param op coordinate of the gate to be moved. + * @return fanin and fanout gates, wires to be deleted and old routing paths. + */ + [[nodiscard]] fanin_fanout_data get_fanin_and_fanouts(const ObstrLyt& lyt, + const tile& op) noexcept { - std::vector> fins{}; - fins.reserve(2); - lyt.foreach_fanin(lyt.get_node(fanout), - [&lyt, &fins, &old_pos](const auto& i) + fanin_fanout_data ffd{}; + + auto fanin1 = tile{}; + auto fanin2 = tile{}; + auto fanout1 = tile{}; + auto fanout2 = tile{}; + + phmap::parallel_flat_hash_set> fanins_set{}; + fanins_set.reserve(lyt.num_wires() + lyt.num_gates() - 2); + phmap::parallel_flat_hash_set> fanouts_set{}; + fanouts_set.reserve(lyt.num_wires() + lyt.num_gates() - 2); + + lyt.foreach_fanin(lyt.get_node(op), + [&lyt, &fanins_set, &op, &fanin1, &fanin2, &ffd, this](const auto& fin) { - auto fout = static_cast>(i); - if (fout != old_pos) + auto fanin = static_cast>(fin); + if (fanins_set.find(fanin) == fanins_set.cend()) { - fins.push_back(lyt.make_signal(lyt.get_node(fout))); + + // add fanin to the respective route + add_fanin_to_route(op, fanins_set.empty(), ffd); + add_fanin_to_route(fanin, fanins_set.empty(), ffd); + + // continue until gate or primary input (PI) is found + while (lyt.is_wire_tile(fanin) && lyt.fanout_size(lyt.get_node(fanin)) == 1 && + !lyt.is_pi_tile(fanin)) + { + ffd.to_clear.push_back(fanin); + fanin = lyt.incoming_data_flow(fanin).front(); + + // add fanin to the respective route + add_fanin_to_route(fanin, fanins_set.empty(), ffd); + } + + // set the respective fanin based on the route + if (fanins_set.empty()) + { + fanin1 = fanin; + } + else + { + fanin2 = fanin; + } + + fanins_set.insert(fanin); } }); + // same for fanouts + lyt.foreach_fanout(lyt.get_node(op), + [&lyt, &fanouts_set, &op, &fanout1, &fanout2, &ffd, this](const auto& fout) + { + tile fanout = lyt.get_tile(fout); - lyt.move_node(lyt.get_node(fanout), fanout, fins); - } - - // remove children of gate to be moved - lyt.move_node(lyt.get_node({lyt.x(), 0}), old_pos, {}); - lyt.resize({lyt.x() - 2, lyt.y(), lyt.z()}); + if (fanouts_set.find(fanout) == fanouts_set.cend()) + { - // fix wires that cross over empty tiles - fix_wires(lyt, to_clear); + // add fanout to the respective route + add_fanout_to_route(op, fanouts_set.empty(), ffd); + add_fanout_to_route(fanout, fanouts_set.empty(), ffd); - auto moved_gate = false; - auto current_pos = old_pos; + // continue until gate or primary output (PO) is found + while (lyt.is_wire_tile(fanout) && lyt.fanout_size(lyt.get_node(fanout)) != 0 && + lyt.fanout_size(lyt.get_node(fanout)) != 2) + { + ffd.to_clear.push_back(fanout); + fanout = lyt.outgoing_data_flow(fanout).front(); + + // add fanout to the respective route + add_fanout_to_route(fanout, fanouts_set.empty(), ffd); + } + + // set the respective fanout based on the route + if (fanouts_set.empty()) + { + fanout1 = fanout; + } + else + { + fanout2 = fanout; + } + + fanouts_set.insert(fanout); + } + }); - uint64_t num_gate_relocations = 0; + // add fanins and fanouts if existing + if (!fanin1.is_dead()) + { + ffd.fanins.push_back(fanin1); + } + if (!fanin2.is_dead()) + { + ffd.fanins.push_back(fanin2); + } + if (!fanout1.is_dead()) + { + ffd.fanouts.push_back(fanout1); + } + if (!fanout2.is_dead()) + { + ffd.fanouts.push_back(fanout2); + } - // iterate over layout diagonally - for (uint64_t k = 0; k < lyt.x() + lyt.y() + 1; ++k) + return ffd; + } + /** + * This helper function computes a path between two coordinates using the A* algorithm. + * It then obstructs the tiles along the path in the given layout. + * + * @param lyt Obstructed gate-level layout. + * @param start_tile The starting coordinate of the path. + * @param end_tile The ending coordinate of the path. + * @return The computed path as a sequence of coordinates in the layout. + */ + layout_coordinate_path get_path_and_obstruct(ObstrLyt& lyt, const tile& start_tile, + const tile& end_tile) { - for (uint64_t x = 0; x < k + 1; ++x) + using dist = twoddwave_distance_functor; + using cost = unit_cost_functor; + static a_star_params astar_params{}; + astar_params.crossings = !ps.planar_optimization; + + const auto path = + a_star>(lyt, {start_tile, end_tile}, dist(), cost(), astar_params); + + // obstruct the tiles along the computed path. + for (const auto& tile : path) { - const uint64_t y = k - x; + lyt.obstruct_coordinate(tile); + } - if (moved_gate || ((num_gate_relocations >= max_gate_relocations) && !lyt.is_po_tile(current_pos))) + return path; + } + /** + * Calculates the elapsed milliseconds since the `start` time, sets the `timeout_limit_reached` flag + * if the timeout is exceeded, and returns the remaining time. + * + * @return Remaining time in milliseconds before timeout, or `0` if timeout has been reached. + */ + uint64_t update_timeout() noexcept + { + const auto current_time = std::chrono::high_resolution_clock::now(); + const auto elapsed_ms = + static_cast(std::chrono::duration_cast(current_time - start).count()); + timeout_limit_reached = (elapsed_ms >= ps.timeout); + return timeout_limit_reached ? 0 : ps.timeout - elapsed_ms; + } + /** + * Attempts to relocate a gate to a new position within the layout and updates routing connections accordingly. + * + * @param lyt Obstructed gate-level layout being optimized. + * @param new_pos The target tile position to which the gate is to be relocated. + * @param num_gate_relocations Reference to a counter tracking the number of gate relocations performed. + * @param current_pos Reference to the current position of the gate being relocated. This will be updated + * upon successful relocation. + * @param fanins Vector containing the tile positions of all fan-in connections to the gate. + * @param fanouts Vector containing the tile positions of all fan-out connections from the gate. + * @param moved_gate Reference to a boolean flag that will be set to `true` if the gate is successfully + * moved. + * @param old_pos The original tile position of the gate before the relocation attempt. + * + * @return `true` if the gate was successfully relocated to `new_pos` and all routing paths were established. + * `false` if the relocation resulted in no movement (i.e., `new_pos` is the same as `old_pos`). + */ + bool check_new_position(ObstrLyt& lyt, const tile& new_pos, uint64_t& num_gate_relocations, + tile& current_pos, const std::vector>& fanins, + const std::vector>& fanouts, bool& moved_gate, + const tile& old_pos) noexcept + { + if (lyt.is_empty_tile(new_pos) && lyt.is_empty_tile({new_pos.x, new_pos.y, 1})) + { + num_gate_relocations++; + // move gate to new positions and update obstructions + lyt.move_node(lyt.get_node(current_pos), new_pos, {}); + lyt.obstruct_coordinate(new_pos); + lyt.obstruct_coordinate({new_pos.x, new_pos.y, 1}); + lyt.clear_obstructed_coordinate(current_pos); + lyt.clear_obstructed_coordinate({current_pos.x, current_pos.y, 1}); + + // get paths for fanins and fanouts + layout_coordinate_path new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, + new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2; + // get paths for fanins and fanouts + if (!fanins.empty()) { - break; + new_path_from_fanin_1_to_gate = get_path_and_obstruct(lyt, fanins[0], new_pos); } - // only check better positions - if (lyt.y() >= y && y >= min_y && lyt.x() >= x && x >= min_x && ((x + y) <= max_diagonal) && - (((x + y) < max_diagonal) || (y <= max_y)) && - ((!lyt.is_pi_tile(current_pos)) || (lyt.is_pi_tile(current_pos) && (x == 0 || y == 0))) && - !(lyt.is_po_tile(current_pos) && (((x < max_non_po.x) && (y < max_non_po.y)) || - ((x + y) == static_cast(old_pos.x + old_pos.y))))) + if (fanins.size() == 2) { - new_pos = tile{x, y}; - if (lyt.is_empty_tile(new_pos) && lyt.is_empty_tile({new_pos.x, new_pos.y, 1})) - { - num_gate_relocations++; - // move gate to new positions and update obstructions - lyt.move_node(lyt.get_node(current_pos), new_pos, {}); - lyt.obstruct_coordinate(new_pos); - lyt.obstruct_coordinate({new_pos.x, new_pos.y, 1}); - lyt.clear_obstructed_coordinate(current_pos); - lyt.clear_obstructed_coordinate({current_pos.x, current_pos.y, 1}); - - // get paths for fanins and fanouts - layout_coordinate_path new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, - new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2; - // get paths for fanins and fanouts - if (!fanins.empty()) - { - new_path_from_fanin_1_to_gate = - get_path_and_obstruct(lyt, fanins[0], new_pos, planar_optimization); - } - - if (fanins.size() == 2) - { - new_path_from_fanin_2_to_gate = - get_path_and_obstruct(lyt, fanins[1], new_pos, planar_optimization); - } + new_path_from_fanin_2_to_gate = get_path_and_obstruct(lyt, fanins[1], new_pos); + } - if (!fanouts.empty()) - { - new_path_from_gate_to_fanout_1 = - get_path_and_obstruct(lyt, new_pos, fanouts[0], planar_optimization); - } + if (!fanouts.empty()) + { + new_path_from_gate_to_fanout_1 = get_path_and_obstruct(lyt, new_pos, fanouts[0]); + } - if (fanouts.size() == 2) - { - new_path_from_gate_to_fanout_2 = - get_path_and_obstruct(lyt, new_pos, fanouts[1], planar_optimization); - } + if (fanouts.size() == 2) + { + new_path_from_gate_to_fanout_2 = get_path_and_obstruct(lyt, new_pos, fanouts[1]); + } - // if possible routing was found, it will be applied - if (!(fanins.size() > 0 && new_path_from_fanin_1_to_gate.empty()) && - !(fanins.size() == 2 && new_path_from_fanin_2_to_gate.empty()) && - !(fanouts.size() > 0 && new_path_from_gate_to_fanout_1.empty()) && - !(fanouts.size() == 2 && new_path_from_gate_to_fanout_2.empty())) + if (!(!fanins.empty() && new_path_from_fanin_1_to_gate.empty()) && + !(fanins.size() == 2 && new_path_from_fanin_2_to_gate.empty()) && + !(!fanouts.empty() && new_path_from_gate_to_fanout_1.empty()) && + !(fanouts.size() == 2 && new_path_from_gate_to_fanout_2.empty())) + { + for (const auto& path : {new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, + new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2}) + { + if (!path.empty()) { - for (const auto& path : {new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, - new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2}) + route_path(lyt, path); + for (const auto& tile : path) { - if (!path.empty()) - { - route_path(lyt, path); - for (const auto& tile : path) - { - lyt.obstruct_coordinate(tile); - } - } + lyt.obstruct_coordinate(tile); } + } + } - moved_gate = true; + moved_gate = true; - // update children based on number of fanins - if (fanins.size() == 2) - { - lyt.move_node(lyt.get_node(new_pos), new_pos, - { - lyt.make_signal(lyt.get_node(new_path_from_fanin_1_to_gate.end()[-2])), - lyt.make_signal(lyt.get_node(new_path_from_fanin_2_to_gate.end()[-2])), - }); - } - else if (fanins.size() == 1) - { - lyt.move_node(lyt.get_node(new_pos), new_pos, - {lyt.make_signal(lyt.get_node(new_path_from_fanin_1_to_gate.end()[-2]))}); - } + // update children based on number of fanins + if (fanins.size() == 2) + { + lyt.move_node(lyt.get_node(new_pos), new_pos, + { + lyt.make_signal(lyt.get_node(new_path_from_fanin_1_to_gate.end()[-2])), + lyt.make_signal(lyt.get_node(new_path_from_fanin_2_to_gate.end()[-2])), + }); + } + else if (fanins.size() == 1) + { + lyt.move_node(lyt.get_node(new_pos), new_pos, + {lyt.make_signal(lyt.get_node(new_path_from_fanin_1_to_gate.end()[-2]))}); + } - // update children of fanouts - for (const auto& fanout : fanouts) - { - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node(fanout))); + // update children of fanouts + for (const auto& fanout : fanouts) + { + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node(fanout))); - lyt.foreach_fanin(lyt.get_node(fanout), - [&lyt, &signals](const auto& i) - { - auto fout = static_cast>(i); - signals.push_back(lyt.make_signal(lyt.get_node(fout))); - }); + lyt.foreach_fanin(lyt.get_node(fanout), + [&lyt, &signals](const auto& i) + { + auto fout = static_cast>(i); + signals.push_back(lyt.make_signal(lyt.get_node(fout))); + }); - lyt.move_node(lyt.get_node(fanout), fanout, signals); - } + lyt.move_node(lyt.get_node(fanout), fanout, signals); + } - if (new_pos == old_pos) - { - return false; - } - } - // if no routing was found, remove added obstructions - else + if (new_pos == old_pos) + { + return false; + } + } + // if no routing was found, remove added obstructions + else + { + for (const auto& path : {new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, + new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2}) + { + for (const auto& tile : path) { - for (const auto& path : {new_path_from_fanin_1_to_gate, new_path_from_fanin_2_to_gate, - new_path_from_gate_to_fanout_1, new_path_from_gate_to_fanout_2}) - { - for (const auto& tile : path) - { - lyt.clear_obstructed_coordinate(tile); - } - } + lyt.clear_obstructed_coordinate(tile); } - - current_pos = new_pos; } } - } - if (moved_gate || ((num_gate_relocations >= max_gate_relocations) && !lyt.is_po_tile(current_pos))) - { - break; + current_pos = new_pos; } + return true; } - - // if no better coordinate was found, restore old wiring - if (!moved_gate) + /** + * Restores the original wiring if relocation of a gate fails. + * + * This function moves the gate back to its original position and reinstates the previous wiring paths + * between the gate and its fan-in/fan-out connections. It also updates the obstructions in the layout + * accordingly. + * + * @param lyt Obstructed gate-level layout. + * @param old_path_from_fanin_1_to_gate The original routing path from the first fan-in to the gate (if exists). + * @param old_path_from_fanin_2_to_gate The original routing path from the second fan-in to the gate (if exists). + * @param old_path_from_gate_to_fanout_1 The original routing path from the gate to the first fan-out (if exists). + * @param old_path_from_gate_to_fanout_2 The original routing path from the gate to the second fan-out (if exists). + * @param current_pos Current position of the gate after relocation attempt. + * @param old_pos Original position of the gate before relocation attempt. + * @param fanouts Vector of fanout tiles connected to the gate. + */ + void restore_original_wiring(ObstrLyt& lyt, const layout_coordinate_path old_path_from_fanin_1_to_gate, + const layout_coordinate_path old_path_from_fanin_2_to_gate, + const layout_coordinate_path old_path_from_gate_to_fanout_1, + const layout_coordinate_path old_path_from_gate_to_fanout_2, + const tile& current_pos, const tile& old_pos, + const std::vector> fanouts) noexcept { lyt.move_node(lyt.get_node(current_pos), old_pos, {}); @@ -689,7 +948,7 @@ bool improve_gate_location(Lyt& lyt, const tile& old_pos, const tile& { if (!r.empty()) { - route_path>(lyt, r); + route_path>(lyt, r); } for (const auto& t : r) { @@ -704,13 +963,13 @@ bool improve_gate_location(Lyt& lyt, const tile& old_pos, const tile& lyt.obstruct_coordinate({old_pos.x, old_pos.y, 1}); // update children on old position - std::vector> signals{}; + std::vector> signals{}; signals.reserve(lyt.fanin_size(lyt.get_node(old_pos))); lyt.foreach_fanin(lyt.get_node(old_pos), [&lyt, &signals](const auto& i) { - auto fanin = static_cast>(i); + auto fanin = static_cast>(i); signals.push_back(lyt.make_signal(lyt.get_node(fanin))); }); @@ -719,323 +978,177 @@ bool improve_gate_location(Lyt& lyt, const tile& old_pos, const tile& // update children of fanouts for (const auto& fanout : fanouts) { - std::vector> fout_signals{}; + std::vector> fout_signals{}; fout_signals.reserve(lyt.fanin_size(lyt.get_node(fanout))); lyt.foreach_fanin(lyt.get_node(fanout), [&lyt, &fout_signals](const auto& i) { - auto fout = static_cast>(i); + auto fout = static_cast>(i); fout_signals.push_back(lyt.make_signal(lyt.get_node(fout))); }); lyt.move_node(lyt.get_node(fanout), fanout, fout_signals); } - return false; } - - return true; -} -/** - * Utility function that moves outputs from the last row to the previous row, and from the last column to the previous - * column, if possible. - * - * @tparam Lyt Cartesian gate-level layout type. - * @param lyt Gate-level layout. - */ -template -void optimize_output_positions(Lyt& lyt) noexcept -{ - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - - bool optimizable = true; - - for (uint64_t x = 0; x <= lyt.x(); ++x) + /** + * Utility function that moves gates to new coordinates and checks if routing is possible. + * This includes: + * + * - removing the old wiring between fanins, the gate and fanouts + * - updating the incoming signals + * - determining coordinates that would improve the layout + * - testing all those coordinates by moving the gate to each one and checking if a new wiring can be found + * - if a new coordinate is found and wiring is possible, it is applied and incoming signals are updated + * - if no better coordinate is found, the old wiring is restored + * + * @param lyt Obstructed gate-level layout. + * @param old_pos Old position of the gate to be moved. + * @return `true` if the gate was moved successfully, `false` otherwise. + */ + bool improve_gate_location(ObstrLyt& lyt, const tile& old_pos) noexcept { - if (!(lyt.is_empty_tile({x, lyt.y()}) || - (lyt.is_po_tile({x, lyt.y(), 0}) && lyt.is_empty_tile({x + 1, lyt.y() - 1, 0}) && (x < lyt.x())))) - { - optimizable = false; - } - } + const auto& [fanins, fanouts, to_clear, old_path_from_fanin_1_to_gate, old_path_from_fanin_2_to_gate, + old_path_from_gate_to_fanout_1, old_path_from_gate_to_fanout_2] = + get_fanin_and_fanouts(lyt, old_pos); - if (optimizable) - { - for (uint64_t x = 0; x < lyt.x(); ++x) + uint64_t min_x = 0; + uint64_t min_y = 0; + + // determine minimum coordinates for new placements + if (!fanins.empty()) { - if (lyt.is_po_tile({x, lyt.y(), 0})) - { - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({x, lyt.y()}))); - lyt.foreach_fanin(lyt.get_node({x, lyt.y()}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - lyt.move_node(lyt.get_node({x, lyt.y()}), {x + 1, lyt.y() - 1, 0}, signals); - } + min_x = + std::max_element(fanins.cbegin(), fanins.cend(), [](const auto& a, const auto& b) { return a.x < b.x; }) + ->x; + min_y = + std::max_element(fanins.cbegin(), fanins.cend(), [](const auto& a, const auto& b) { return a.y < b.y; }) + ->y; } - } - optimizable = true; + const auto max_x = old_pos.x; + const auto max_y = old_pos.y; + const auto max_diagonal = max_x + max_y; - for (uint64_t y = 0; y <= lyt.y(); ++y) - { - if (!(lyt.is_empty_tile({lyt.x(), y}) || - (lyt.is_po_tile({lyt.x(), y, 0}) && lyt.is_empty_tile({lyt.x() - 1, y + 1, 0}) && (y < lyt.y())))) - { - optimizable = false; - } - } + auto new_pos = tile{}; - if (optimizable) - { - for (uint64_t y = 0; y < lyt.y(); ++y) + // if gate is directly connected to one of its fanins, no improvement is possible + for (const auto& fanin : fanins) { - if (lyt.is_po_tile({lyt.x(), y, 0})) + for (const auto& i : lyt.incoming_data_flow(old_pos)) { - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), y}))); - lyt.foreach_fanin(lyt.get_node({lyt.x(), y}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - lyt.move_node(lyt.get_node({lyt.x(), y}), {lyt.x() - 1, y + 1, 0}, signals); + if (i == fanin) + { + return false; + } } } - } - // calculate bounding box - auto bounding_box = bounding_box_2d(lyt); - lyt.resize({bounding_box.get_max().x, bounding_box.get_max().y, lyt.z()}); - // check for misplaced POs in second last row and move them one row down - for (uint64_t x = 0; x < lyt.x(); ++x) - { - if (lyt.is_po_tile({x, lyt.y() - 1, 0})) + // if gate is a PI and directly connected to its fanout, no improvement is possible + if (lyt.is_pi_tile(old_pos)) { - // get fanin signal of the PO - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({x, lyt.y()}))); - lyt.foreach_fanin(lyt.get_node({x, lyt.y() - 1}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - - // move PO one row down - lyt.move_node(lyt.get_node({x, lyt.y() - 1}), {x, lyt.y(), 0}, {}); - - // create a wire segment at the previous location of the PO and connect it with its fanin - lyt.create_buf(signals[0], {x, lyt.y() - 1}); - - // connect the PO with the new wire segment - lyt.move_node(lyt.get_node({x, lyt.y()}), {x, lyt.y(), 0}, - {lyt.make_signal(lyt.get_node({x, lyt.y() - 1}))}); + for (const auto& fanout : fanouts) + { + for (const auto& outgoing_tile : lyt.outgoing_data_flow(old_pos)) + { + if (outgoing_tile == fanout) + { + return false; + } + } + } } - } - // check for misplaced POs in second last column and move them one column to the right - for (uint64_t y = 0; y < lyt.y(); ++y) - { - if (lyt.is_po_tile({lyt.x() - 1, y, 0})) + // remove wiring + for (const auto& tile : to_clear) { - // get fanin signal of the PO - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), y}))); - lyt.foreach_fanin(lyt.get_node({lyt.x() - 1, y}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - - // move PO one column to the right - lyt.move_node(lyt.get_node({lyt.x() - 1, y}), {lyt.x(), y, 0}, {}); - - // create a wire segment at the previous location of the PO and connect it with its fanin - lyt.create_buf(signals[0], {lyt.x() - 1, y}); - - // connect the PO with the new wire segment - lyt.move_node(lyt.get_node({lyt.x(), y}), {lyt.x(), y, 0}, - {lyt.make_signal(lyt.get_node({lyt.x() - 1, y}))}); + lyt.clear_tile(tile); + lyt.clear_obstructed_coordinate(tile); } - } - // update bounding box - bounding_box.update_bounding_box(); - lyt.resize({bounding_box.get_max().x, bounding_box.get_max().y, lyt.z()}); + // remove children of gate to be moved + lyt.resize({lyt.x() + 2, lyt.y(), lyt.z()}); + lyt.move_node(lyt.get_node(old_pos), {lyt.x(), 0}, {}); - // check if PO is located in bottom right corner and relocation would save more tiles (only possible for layouts - // with a single PO) - if (lyt.is_po_tile({lyt.x(), lyt.y(), 0}) && (lyt.num_pos() == 1)) - { - // check if relocation would save tiles - if (lyt.has_western_incoming_signal({lyt.x(), lyt.y(), 0}) && - ((lyt.x() * (lyt.y() + 2)) < ((lyt.x() + 1) * (lyt.y() + 1)))) - { - // get fanin signal of the PO - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), lyt.y()}))); - lyt.foreach_fanin(lyt.get_node({lyt.x(), lyt.y()}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - - // resize layout - lyt.resize({lyt.x(), lyt.y() + 1, lyt.z()}); - - // move PO one tile down and to the left - lyt.move_node(lyt.get_node({lyt.x(), lyt.y() - 1}), {lyt.x() - 1, lyt.y(), 0}, signals); - } - // check if relocation would save tiles - else if (lyt.has_northern_incoming_signal({lyt.x(), lyt.y(), 0}) && - (((lyt.x() + 2) * lyt.y()) < ((lyt.x() + 1) * (lyt.y() + 1)))) + // update children of fanouts + for (const auto& fanout : fanouts) { - // get fanin signal of the PO - std::vector> signals{}; - signals.reserve(lyt.fanin_size(lyt.get_node({lyt.x(), lyt.y()}))); - lyt.foreach_fanin(lyt.get_node({lyt.x(), lyt.y()}), - [&signals](const auto& fanin) { signals.push_back(fanin); }); - - // resize layout - lyt.resize({lyt.x() + 1, lyt.y(), lyt.z()}); + std::vector> fins{}; + fins.reserve(2); + lyt.foreach_fanin(lyt.get_node(fanout), + [&lyt, &fins, &old_pos](const auto& i) + { + auto fout = static_cast>(i); + if (fout != old_pos) + { + fins.push_back(lyt.make_signal(lyt.get_node(fout))); + } + }); - // move PO one tile up and to the right - lyt.move_node(lyt.get_node({lyt.x() - 1, lyt.y()}), {lyt.x(), lyt.y() - 1, 0}, signals); + lyt.move_node(lyt.get_node(fanout), fanout, fins); } - } -} -/** - * Custom comparison function for sorting tiles based on the sum of their coordinates that breaks ties based on the - * x-coordinate. - * - * @tparam Lyt Cartesian gate-level layout type. - * @param a First tile to compare. - * @param b Second tile to compare. - * @return `true` iff `a < b` based on the aforementioned rule. - */ -template -bool compare_gate_tiles(const tile& a, const tile& b) -{ - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - return static_cast(std::make_pair(a.x + a.y, a.x) < std::make_pair(b.x + b.y, b.x)); -} + // remove children of gate to be moved + lyt.move_node(lyt.get_node({lyt.x(), 0}), old_pos, {}); + lyt.resize({lyt.x() - 2, lyt.y(), lyt.z()}); -template -class post_layout_optimization_impl -{ - public: - post_layout_optimization_impl(const Lyt& lyt, const post_layout_optimization_params& p, - post_layout_optimization_stats& st) : - plyt{lyt}, - ps{p}, - pst{st} - {} + // fix wires that cross over empty tiles + fix_wires(lyt, to_clear); - void run() - { - static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); - static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); - - const mockturtle::stopwatch stop{pst.time_total}; - pst.x_size_before = plyt.x() + 1; - pst.y_size_before = plyt.y() + 1; - pst.num_wires_before = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); - pst.num_crossings_before = plyt.num_crossings(); + auto moved_gate = false; + auto current_pos = old_pos; - uint64_t max_gate_relocations = ps.max_gate_relocations.value_or((plyt.x() + 1) * (plyt.y() + 1)); + uint64_t num_gate_relocations = 0; - // optimization - auto layout = obstruction_layout(plyt); - bool moved_at_least_one_gate = true; - bool reduced_wiring = true; - - if (max_gate_relocations == 0) - { - fiction::wiring_reduction_stats wiring_reduction_stats{}; - fiction::wiring_reduction(layout, &wiring_reduction_stats); - } - else + // iterate over layout diagonally + for (uint64_t k = 0; k < lyt.x() + lyt.y() + 1; ++k) { - while (moved_at_least_one_gate || reduced_wiring) + for (uint64_t x = 0; x < k + 1; ++x) { - reduced_wiring = false; - fiction::wiring_reduction_stats wiring_reduction_stats{}; - if (moved_at_least_one_gate && !ps.optimize_pos_only) - { - fiction::wiring_reduction(layout, &wiring_reduction_stats); - if ((wiring_reduction_stats.area_improvement != 0ull) || - (wiring_reduction_stats.wiring_improvement != 0ull)) - { - reduced_wiring = true; - } - } + const uint64_t y = k - x; - std::vector> gate_tiles{}; - gate_tiles.reserve(layout.num_gates() + layout.num_pis() - layout.num_pos()); - layout.foreach_node( - [&layout, &gate_tiles](const auto& node) - { - if (const tile tile = layout.get_tile(node); - (layout.is_gate(node) && !layout.is_wire(node)) || layout.is_fanout(node) || - layout.is_pi_tile(tile) || layout.is_po_tile(tile)) - { - layout.obstruct_coordinate({tile.x, tile.y, 1}); - gate_tiles.emplace_back(tile); - } - }); - - std::sort(gate_tiles.begin(), gate_tiles.end(), detail::compare_gate_tiles); - - tile max_non_po{0, 0}; - // determine minimal border for POs - for (const auto& gate_tile : gate_tiles) + if (moved_gate || ((num_gate_relocations >= max_gate_relocations) && !lyt.is_po_tile(current_pos)) || + timeout_limit_reached) { - if (!layout.is_po_tile(gate_tile)) - { - max_non_po.x = std::max(max_non_po.x, gate_tile.x); - max_non_po.y = std::max(max_non_po.y, gate_tile.y); - } + break; } - moved_at_least_one_gate = false; - for (auto& gate_tile : gate_tiles) + + update_timeout(); + // only check better positions + if (lyt.y() >= y && y >= min_y && lyt.x() >= x && x >= min_x && ((x + y) <= max_diagonal) && + (((x + y) < max_diagonal) || (y <= max_y)) && + ((!lyt.is_pi_tile(current_pos)) || (lyt.is_pi_tile(current_pos) && (x == 0 || y == 0))) && + !(lyt.is_po_tile(current_pos) && (((x < max_non_po.x) && (y < max_non_po.y)) || + ((x + y) == static_cast(old_pos.x + old_pos.y))))) { - if (!ps.optimize_pos_only || (ps.optimize_pos_only && layout.is_po_tile(gate_tile))) + new_pos = tile{x, y}; + if (!check_new_position(lyt, new_pos, num_gate_relocations, current_pos, fanins, fanouts, + moved_gate, old_pos)) { - if (detail::improve_gate_location(layout, gate_tile, max_non_po, max_gate_relocations, - ps.planar_optimization)) - { - moved_at_least_one_gate = true; - } + return false; } } - // calculate bounding box - const auto bounding_box = bounding_box_2d(layout); - layout.resize({bounding_box.get_max().x, bounding_box.get_max().y, layout.z()}); } - } - detail::optimize_output_positions(layout); - // calculate bounding box - const auto bounding_box = bounding_box_2d(layout); - layout.resize({bounding_box.get_max().x, bounding_box.get_max().y, layout.z()}); + if (moved_gate || ((num_gate_relocations >= max_gate_relocations) && !lyt.is_po_tile(current_pos))) + { + break; + } + } - pst.x_size_after = layout.x() + 1; - pst.y_size_after = layout.y() + 1; - const uint64_t area_before = pst.x_size_before * pst.y_size_before; - const uint64_t area_after = pst.x_size_after * pst.y_size_after; - double_t area_percentage_difference = - static_cast(area_before - area_after) / static_cast(area_before) * 100.0; - area_percentage_difference = std::round(area_percentage_difference * 100) / 100; - pst.area_improvement = area_percentage_difference; + // if no better coordinate was found, restore old wiring + if (!moved_gate) + { + restore_original_wiring(lyt, old_path_from_fanin_1_to_gate, old_path_from_fanin_2_to_gate, + old_path_from_gate_to_fanout_1, old_path_from_gate_to_fanout_2, current_pos, + old_pos, fanouts); + return false; + } - pst.num_wires_after = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); - pst.num_crossings_after = plyt.num_crossings(); + return true; } - - private: - /** - * 2DDWave-clocked Cartesian gate-level layout to optimize. - */ - const Lyt& plyt; - /** - * Post-layout optimization parameters. - */ - post_layout_optimization_params ps; - /** - * Statistics about the post-layout optimization process. - */ - post_layout_optimization_stats& pst; }; + } // namespace detail /** diff --git a/include/fiction/algorithms/physical_design/wiring_reduction.hpp b/include/fiction/algorithms/physical_design/wiring_reduction.hpp index 1aecb0a73..c25640bb3 100644 --- a/include/fiction/algorithms/physical_design/wiring_reduction.hpp +++ b/include/fiction/algorithms/physical_design/wiring_reduction.hpp @@ -19,10 +19,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -30,6 +32,19 @@ namespace fiction { +/** + * Parameters for the wiring reduction algorithm. + */ +struct wiring_reduction_params +{ + /** + * Timeout limit (in ms). Specifies the maximum allowed time in milliseconds for the optimization process. For large + * layouts, the actual execution time may slightly exceed this limit because it's impractical to check the timeout + * at every algorithm step and the functional correctness has to be ensured by completing essential algorithm steps. + */ + uint64_t timeout = std::numeric_limits::max(); +}; + /** * This struct stores statistics about the wiring reduction process. */ @@ -1034,7 +1049,12 @@ template class wiring_reduction_impl { public: - wiring_reduction_impl(const Lyt& lyt, wiring_reduction_stats& st) : plyt{lyt}, pst{st} {} + wiring_reduction_impl(const Lyt& lyt, const wiring_reduction_params& p, wiring_reduction_stats& st) : + plyt{lyt}, + ps{p}, + pst{st}, + start{std::chrono::high_resolution_clock::now()} + {} void run() { @@ -1044,54 +1064,97 @@ class wiring_reduction_impl // measure run time mockturtle::stopwatch stop{pst.time_total}; + // record initial layout statistics pst.num_wires_before = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); pst.x_size_before = plyt.x() + 1; pst.y_size_before = plyt.y() + 1; + // create an obstruction layout based on the original layout auto layout = obstruction_layout(plyt); + // initialize the list of wires to delete layout_coordinate_path>> to_delete = {}; bool found_wires = true; + // lambda to update the timeout status and calculate remaining time + const auto update_timeout = [start = this->start, &ps = this->ps, + &timeout_limit_reached = this->timeout_limit_reached]() noexcept -> void + { + const auto current_time = std::chrono::high_resolution_clock::now(); + const auto elapsed_ms = static_cast( + std::chrono::duration_cast(current_time - start).count()); + timeout_limit_reached = (elapsed_ms >= ps.timeout); + }; + // perform wiring reduction iteratively until no further wires can be deleted - while (found_wires) + while (found_wires && !timeout_limit_reached) { - // continue until no further wires can be deleted found_wires = false; for (const auto direction : {search_direction::HORIZONTAL, search_direction::VERTICAL}) { + // update the remaining timeout + update_timeout(); + + if (timeout_limit_reached) + { + break; + } + + // create wiring reduction layout for the current direction auto wiring_reduction_lyt = create_wiring_reduction_layout(layout, 1, 1, direction); add_obstructions(wiring_reduction_lyt); - to_delete = {}; - // get the initial possible path + // update the remaining timeout + update_timeout(); + + if (timeout_limit_reached) + { + break; + } + + // reset the list of wires to delete + to_delete.clear(); + + // get the initial possible path for wire deletion auto possible_path = get_path(wiring_reduction_lyt, {0, 0}, {wiring_reduction_lyt.x(), wiring_reduction_lyt.y()}); - // iterate while there is a possible path - while (!possible_path.empty()) + // iterate while there is a possible path and timeout not reached + while (!possible_path.empty() && !timeout_limit_reached) { + // update the list of wires to delete based on the current path update_to_delete_list>>(wiring_reduction_lyt, possible_path, to_delete); - // update possible_path for the next iteration - possible_path = - get_path(wiring_reduction_lyt, {0, 0}, {wiring_reduction_lyt.x(), wiring_reduction_lyt.y()}); + // update the remaining timeout after processing the path + update_timeout(); + + if (!timeout_limit_reached) + { + // get the next possible path for wire deletion + possible_path = get_path(wiring_reduction_lyt, {0, 0}, + {wiring_reduction_lyt.x(), wiring_reduction_lyt.y()}); + } } if (!to_delete.empty()) { - // calculate offset matrix and delete wires based on to-delete list + // delete the identified wires from the layout delete_wires(layout, wiring_reduction_lyt, to_delete); found_wires = true; } } } - pst.x_size_after = plyt.x() + 1; - pst.y_size_after = plyt.y() + 1; + // calculate the final bounding box and resize the layout accordingly + const auto bounding_box = bounding_box_2d(layout); + layout.resize({bounding_box.get_max().x, bounding_box.get_max().y, layout.z()}); + + // update final layout statistics + pst.x_size_after = layout.x() + 1; + pst.y_size_after = layout.y() + 1; const uint64_t area_before = pst.x_size_before * pst.y_size_before; const uint64_t area_after = pst.x_size_after * pst.y_size_after; @@ -1099,15 +1162,17 @@ class wiring_reduction_impl double area_percentage_difference = static_cast(area_before - area_after) / static_cast(area_before) * 100.0; - area_percentage_difference = std::round(area_percentage_difference * 100) / 100; + // round the area improvement to two decimal places + area_percentage_difference = std::round(area_percentage_difference * 100.0) / 100.0; pst.area_improvement = area_percentage_difference; - pst.num_wires_after = plyt.num_wires() - plyt.num_pis() - plyt.num_pos(); + pst.num_wires_after = layout.num_wires() - layout.num_pis() - layout.num_pos(); double wiring_percentage_difference = static_cast(pst.num_wires_before - pst.num_wires_after) / static_cast(pst.num_wires_before) * 100.0; - wiring_percentage_difference = std::round(wiring_percentage_difference * 100) / 100; + // round the wiring improvement to two decimal places + wiring_percentage_difference = std::round(wiring_percentage_difference * 100.0) / 100.0; pst.wiring_improvement = wiring_percentage_difference; } @@ -1116,10 +1181,22 @@ class wiring_reduction_impl * The 2DDWave-clocked layout whose wiring is to be reduced. */ const Lyt& plyt; + /** + * Wiring reduction parameters. + */ + wiring_reduction_params ps; /** * Statistics about the wiring_reduction process. */ wiring_reduction_stats& pst; + /** + * Timeout limit reached. + */ + bool timeout_limit_reached = false; + /** + * Start time. + */ + std::chrono::time_point start; }; } // namespace detail @@ -1136,10 +1213,11 @@ class wiring_reduction_impl * * @tparam Lyt Cartesian gate-level layout type. * @param lyt The 2DDWave-clocked layout whose wiring is to be reduced. - * @param pst Pointer to a `wiring_reduction_stats` object to record runtime statistics. + * @param ps Parameters. + * @param pst Statistics. */ template -void wiring_reduction(const Lyt& lyt, wiring_reduction_stats* pst = nullptr) noexcept +void wiring_reduction(const Lyt& lyt, wiring_reduction_params ps = {}, wiring_reduction_stats* pst = nullptr) noexcept { static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); @@ -1153,7 +1231,7 @@ void wiring_reduction(const Lyt& lyt, wiring_reduction_stats* pst = nullptr) noe // initialize stats for runtime measurement wiring_reduction_stats st{}; - detail::wiring_reduction_impl p{lyt, st}; + detail::wiring_reduction_impl p{lyt, ps, st}; p.run(); diff --git a/libs/Catch2 b/libs/Catch2 index fa43b7742..0b2af5627 160000 --- a/libs/Catch2 +++ b/libs/Catch2 @@ -1 +1 @@ -Subproject commit fa43b77429ba76c462b1898d6cd2f2d7a9416b14 +Subproject commit 0b2af562710d1bac7fd32ca3f05bf1d713bb97e4 diff --git a/libs/mugen/requirements.txt b/libs/mugen/requirements.txt index e692a27c0..7f452d0fd 100644 --- a/libs/mugen/requirements.txt +++ b/libs/mugen/requirements.txt @@ -1,3 +1,3 @@ python-sat==0.1.6.dev6 -wrapt_timeout_decorator -graphviz +wrapt_timeout_decorator==1.5.1 +graphviz==0.20.3 diff --git a/libs/pybind11 b/libs/pybind11 index af67e8739..f7e14e985 160000 --- a/libs/pybind11 +++ b/libs/pybind11 @@ -1 +1 @@ -Subproject commit af67e87393b0f867ccffc2702885eea12de063fc +Subproject commit f7e14e985be167ca158fd3ee2fe5d8a4f175fa87 diff --git a/test/algorithms/physical_design/post_layout_optimization.cpp b/test/algorithms/physical_design/post_layout_optimization.cpp index d2836b0fb..0f5114d6e 100644 --- a/test/algorithms/physical_design/post_layout_optimization.cpp +++ b/test/algorithms/physical_design/post_layout_optimization.cpp @@ -17,13 +17,11 @@ #include #include #include -#include #include #include #include -#include using namespace fiction; @@ -158,189 +156,66 @@ TEST_CASE("Layout equivalence", "[post_layout_optimization]") check_eq(blueprints::mux21_network(), layout); } - SECTION("Planar optimization with planar layout") + SECTION("Timeout") { using gate_layout = gate_level_layout>>>; - const auto layout = blueprints::planar_unoptimized_layout(); + const auto layout = orthogonal(blueprints::mux21_network(), {}); post_layout_optimization_stats stats{}; post_layout_optimization_params params{}; - params.planar_optimization = true; + params.timeout = 1000000; post_layout_optimization(layout, params, &stats); - check_eq(blueprints::planar_unoptimized_layout(), layout); - CHECK(layout.z() == 0); + check_eq(blueprints::mux21_network(), layout); } - SECTION("Planar optimization with crossing layout") + SECTION("Timeout exceeded") { using gate_layout = gate_level_layout>>>; - const auto layout = blueprints::planar_optimization_layout(); + const auto layout = orthogonal(blueprints::mux21_network(), {}); post_layout_optimization_stats stats{}; post_layout_optimization_params params{}; - - params.planar_optimization = true; + params.timeout = 0; post_layout_optimization(layout, params, &stats); - CHECK(!layout.is_inv(layout.get_node({1, 0}))); - params.planar_optimization = false; - post_layout_optimization(layout, params, &stats); - CHECK(layout.is_inv(layout.get_node({1, 0}))); - } -} - -TEST_CASE("Optimization steps", "[post_layout_optimization]") -{ - using gate_layout = gate_level_layout>>>; - using coord_path = layout_coordinate_path>; - - const auto layout = blueprints::optimization_layout(); - auto obstr_lyt = obstruction_layout(layout); - // I ▢ I - // ↓ ↓ - // = ▢ = - // ↓ ↓ - // F→=→& - // ↓ ↓ - // O ▢ O - SECTION("Get fanin and fanouts 1") - { - const coordinate old_pos = {0, 2}; - - const auto& [fanins, fanouts, to_clear, r1, r2, r3, r4] = detail::get_fanin_and_fanouts(obstr_lyt, old_pos); - - CHECK(fanins == std::vector>{{0, 0}}); - CHECK(fanouts == std::vector>{{2, 2}, {0, 3}}); - CHECK(to_clear == std::vector>{{0, 1}, {1, 2}}); - - CHECK(r1 == coord_path{{0, 0}, {0, 1}, {0, 2}}); - CHECK(r2.empty()); - CHECK(r3 == coord_path{{0, 2}, {1, 2}, {2, 2}}); - CHECK(r4 == coord_path{{0, 2}, {0, 3}}); + check_eq(blueprints::mux21_network(), layout); + CHECK(stats.area_improvement == 0); } - SECTION("Get fanin and fanouts 2") + SECTION("Planar optimization with planar layout") { - const coordinate old_pos = {2, 2}; - - const auto& [fanins, fanouts, to_clear, r1, r2, r3, r4] = detail::get_fanin_and_fanouts(obstr_lyt, old_pos); + using gate_layout = gate_level_layout>>>; - CHECK(fanins == std::vector>{{0, 2}, {2, 0}}); - CHECK(fanouts == std::vector>{{2, 3}}); - CHECK(to_clear == std::vector>{{1, 2}, {2, 1}}); + const auto layout = blueprints::planar_unoptimized_layout(); - CHECK(r1 == coord_path{{0, 2}, {1, 2}, {2, 2}}); - CHECK(r2 == coord_path{{2, 0}, {2, 1}, {2, 2}}); - CHECK(r3 == coord_path{{2, 2}, {2, 3}}); - CHECK(r4.empty()); - } + post_layout_optimization_stats stats{}; + post_layout_optimization_params params{}; + params.planar_optimization = true; + post_layout_optimization(layout, params, &stats); - const coordinate old_pos_1 = {2, 0}; - const coordinate new_pos_1 = {1, 0}; - - const auto moved_gate_1 = - detail::improve_gate_location(obstr_lyt, old_pos_1, {2, 2}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - // I I→= - // ↓ ↓ - // = ▢ = - // ↓ ↓ - // F→=→& - // ↓ ↓ - // O ▢ O - SECTION("Move gates 1") - { - CHECK(moved_gate_1); - CHECK(obstr_lyt.is_pi_tile(new_pos_1)); - CHECK_FALSE(obstr_lyt.is_pi_tile(old_pos_1)); + check_eq(blueprints::planar_unoptimized_layout(), layout); + CHECK(layout.z() == 0); } - const coordinate old_pos_2 = {0, 2}; - const coordinate new_pos_2 = {0, 1}; - - const auto moved_gate_2 = - detail::improve_gate_location(obstr_lyt, old_pos_2, {2, 2}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - // I I→= - // ↓ ↓ - // F→= = - // ↓ ↓ ↓ - // = =→& - // ↓ ↓ - // O ▢ O - SECTION("Move gates 2") + SECTION("Planar optimization with crossing layout") { - CHECK(moved_gate_2); - CHECK(obstr_lyt.fanout_size(obstr_lyt.get_node(old_pos_2)) == 1); - CHECK(obstr_lyt.fanout_size(obstr_lyt.get_node(new_pos_2)) == 2); - } + using gate_layout = gate_level_layout>>>; - const coordinate old_pos_3 = {2, 2}; - const coordinate new_pos_3 = {1, 1}; - - const auto moved_gate_3 = - detail::improve_gate_location(obstr_lyt, old_pos_3, {2, 2}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - // I I ▢ - // ↓ ↓ - // F→&→= - // ↓ ↓ - // = ▢ = - // ↓ ↓ - // O ▢ O - SECTION("Move gates 3") - { - CHECK(moved_gate_3); - CHECK(obstr_lyt.is_and(obstr_lyt.get_node(new_pos_3))); - CHECK_FALSE(obstr_lyt.is_and(obstr_lyt.get_node(old_pos_3))); - } + const auto layout = blueprints::planar_optimization_layout(); - const coordinate old_pos_4 = {0, 3}; - const coordinate new_pos_4 = {0, 2}; - - const auto moved_gate_4 = - detail::improve_gate_location(obstr_lyt, old_pos_4, {1, 1}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - // I I ▢ - // ↓ ↓ - // F→&→= - // ↓ ↓ - // O ▢ = - // ↓ - // ▢ ▢ O - SECTION("Move gates 4") - { - CHECK(moved_gate_4); - CHECK(obstr_lyt.is_po(obstr_lyt.get_node(new_pos_4))); - CHECK_FALSE(obstr_lyt.is_po(obstr_lyt.get_node(old_pos_4))); - } + post_layout_optimization_stats stats{}; + post_layout_optimization_params params{}; - const coordinate old_pos_5 = {2, 3}; - const coordinate new_pos_5 = {1, 2}; - - const auto moved_gate_5 = - detail::improve_gate_location(obstr_lyt, old_pos_5, {1, 1}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - // I I ▢ - // ↓ ↓ - // F→& ▢ - // ↓ ↓ - // O O ▢ - // - // ▢ ▢ ▢ - SECTION("Move gates 5") - { - CHECK(moved_gate_5); - CHECK(obstr_lyt.is_po(obstr_lyt.get_node(new_pos_5))); - CHECK_FALSE(obstr_lyt.is_po(obstr_lyt.get_node(old_pos_5))); - } + params.planar_optimization = true; + post_layout_optimization(layout, params, &stats); + CHECK(!layout.is_inv(layout.get_node({1, 0}))); - SECTION("Optimized layout") - { - CHECK(obstr_lyt.is_pi_tile(coordinate{0, 0})); - CHECK(obstr_lyt.is_pi_tile(coordinate{1, 0})); - CHECK(obstr_lyt.is_buf(obstr_lyt.get_node(coordinate{0, 1}))); - CHECK(obstr_lyt.is_and(obstr_lyt.get_node(coordinate{1, 1}))); - CHECK(obstr_lyt.is_po_tile(coordinate{0, 2})); - CHECK(obstr_lyt.is_po_tile(coordinate{1, 2})); + params.planar_optimization = false; + post_layout_optimization(layout, params, &stats); + CHECK(layout.is_inv(layout.get_node({1, 0}))); } } @@ -353,13 +228,6 @@ TEST_CASE("Wrong clocking scheme", "[post_layout_optimization]") SECTION("Call functions") { - const coordinate old_pos_1 = {2, 0}; - - const auto moved_gate_1 = - detail::improve_gate_location(obstr_lyt, old_pos_1, {0, 0}, (obstr_lyt.x() + 1) * (obstr_lyt.y() + 1)); - - CHECK_FALSE(moved_gate_1); - post_layout_optimization_stats stats_wrong_clocking_scheme{}; CHECK_NOTHROW(post_layout_optimization(obstr_lyt, {}, &stats_wrong_clocking_scheme)); diff --git a/test/algorithms/physical_design/wiring_reduction.cpp b/test/algorithms/physical_design/wiring_reduction.cpp index 906c05752..51165ee74 100644 --- a/test/algorithms/physical_design/wiring_reduction.cpp +++ b/test/algorithms/physical_design/wiring_reduction.cpp @@ -27,7 +27,7 @@ void check_layout_equiv(const Ntk& ntk) const auto layout = orthogonal(ntk, {}); wiring_reduction_stats stats{}; - wiring_reduction(layout, &stats); + wiring_reduction(layout, {}, &stats); check_eq(ntk, layout); @@ -106,14 +106,43 @@ TEST_CASE("Layout equivalence", "[wiring_reduction]") const auto layout_corner_case_1 = blueprints::optimization_layout_corner_case_outputs_1(); wiring_reduction_stats stats_corner_case_1{}; - wiring_reduction(layout_corner_case_1, &stats_corner_case_1); + wiring_reduction(layout_corner_case_1, {}, &stats_corner_case_1); check_eq(blueprints::optimization_layout_corner_case_outputs_1(), layout_corner_case_1); const auto layout_corner_case_2 = blueprints::optimization_layout_corner_case_outputs_2(); wiring_reduction_stats stats_corner_case_2{}; - wiring_reduction(layout_corner_case_2, &stats_corner_case_2); + wiring_reduction(layout_corner_case_2, {}, &stats_corner_case_2); check_eq(blueprints::optimization_layout_corner_case_outputs_2(), layout_corner_case_2); } + + SECTION("Timeout") + { + using gate_layout = gate_level_layout>>>; + + const auto layout = orthogonal(blueprints::mux21_network(), {}); + + wiring_reduction_stats stats{}; + wiring_reduction_params params{}; + params.timeout = 1000000; + wiring_reduction(layout, params, &stats); + + check_eq(blueprints::mux21_network(), layout); + } + + SECTION("Timeout exceeded") + { + using gate_layout = gate_level_layout>>>; + + const auto layout = orthogonal(blueprints::mux21_network(), {}); + + wiring_reduction_stats stats{}; + wiring_reduction_params params{}; + params.timeout = 0; + wiring_reduction(layout, params, &stats); + + check_eq(blueprints::mux21_network(), layout); + CHECK(stats.area_improvement == 0); + } } TEST_CASE("Wrong clocking scheme", "[wiring_reduction]") @@ -129,7 +158,7 @@ TEST_CASE("Wrong clocking scheme", "[wiring_reduction]") wiring_reduction_stats stats_wrong_clocking_scheme{}; - CHECK_NOTHROW(wiring_reduction(obstr_lyt, &stats_wrong_clocking_scheme)); + CHECK_NOTHROW(wiring_reduction(obstr_lyt, {}, &stats_wrong_clocking_scheme)); } } diff --git a/test/benchmark/post_layout_optimization.cpp b/test/benchmark/post_layout_optimization.cpp new file mode 100644 index 000000000..348d7bd44 --- /dev/null +++ b/test/benchmark/post_layout_optimization.cpp @@ -0,0 +1,76 @@ +// +// Created by simon on 14.10.24. +// + +#include +#include + +#include "../utils/blueprints/network_blueprints.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace fiction; + +TEST_CASE("Benchmark Post-Layout Optimization", "[benchmark]") +{ + using gate_layout = gate_level_layout>>>; + + const auto ntk = blueprints::parity_network(); + const auto layout = orthogonal(ntk); + + post_layout_optimization_params full_optimization_params{}; + + full_optimization_params.max_gate_relocations = (layout.x() + 1) * (layout.y() + 1); + full_optimization_params.optimize_pos_only = false; + full_optimization_params.planar_optimization = false; + full_optimization_params.timeout = 100000; + + BENCHMARK("post_layout_optimization: full optimization") + { + post_layout_optimization(layout.clone(), full_optimization_params); + }; + + post_layout_optimization_params wiring_reduction_only_params{}; + + wiring_reduction_only_params.max_gate_relocations = 0; + wiring_reduction_only_params.optimize_pos_only = false; + wiring_reduction_only_params.planar_optimization = false; + wiring_reduction_only_params.timeout = 100000; + + BENCHMARK("post_layout_optimization: wiring reduction only") + { + post_layout_optimization(layout.clone(), wiring_reduction_only_params); + }; +} + +// Mac M1, Sonoma 14.6.1, Apple clang version 15.0.0 (14.10.24) +// ------------------------------------------------------------------------------- +// Benchmark Post-Layout Optimization +// ------------------------------------------------------------------------------- +// /Users/simonhofmann/Documents/fiction_fork/fiction/test/benchmark/post_layout_optimization.cpp:24 +// ............................................................................... +// +// benchmark name samples iterations est run time +// mean low mean high mean +// std dev low std dev high std dev +// ------------------------------------------------------------------------------- +// post_layout_optimization: full +// optimization 100 1 3.08351 m +// 1.80757 s 1.80401 s 1.81184 s +// 19.6917 ms 16.8471 ms 23.0171 ms +// +// post_layout_optimization: wiring +// reduction only 100 1 3.31998 s +// 33.2883 ms 33.1682 ms 33.4216 ms +// 647.063 us 559.353 us 778.412 us +// +// +// ===============================================================================