diff --git a/cli/cmd/physical_design/optimize.hpp b/cli/cmd/physical_design/optimize.hpp new file mode 100644 index 000000000..9c4a17415 --- /dev/null +++ b/cli/cmd/physical_design/optimize.hpp @@ -0,0 +1,104 @@ +// +// Created by Simon Hofmann on 02.08.23. +// + +#ifndef FICTION_CMD_OPTIMIZE_HPP +#define FICTION_CMD_OPTIMIZE_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace alice +{ +/** + * Optimizes a 2DDWave-clocked Cartesian layout. + */ +class optimize_command : public command +{ + public: + /** + * Standard constructor. Adds descriptive information, options, and flags. + * + * @param e alice::environment that specifies stores etc. + */ + explicit optimize_command(const environment::ptr& e) : + command(e, "Optimizes a 2DDWave-clocked Cartesian layout with respect to area. It achieves this objective " + "by strategically repositioning gates within the layout, removing excess wiring, and " + "effectively relocating outputs to more favorable positions.") + {} + + protected: + /** + * Statistics. + */ + fiction::post_layout_optimization_stats st{}; + + /** + * Optimizes a 2DDWave-clocked Cartesian layout. + */ + void execute() override + { + auto& gls = store(); + + // error case: empty gate-level layout store + if (gls.empty()) + { + env->out() << "[w] no gate layout in store" << std::endl; + return; + } + + const auto& lyt = gls.current(); + + const auto check_clocking_scheme = [](auto&& lyt_ptr) + { return lyt_ptr->is_clocking_scheme(fiction::clock_name::TWODDWAVE); }; + + // error case: layout is not 2DDWave-clocked + if (const auto is_twoddwave_clocked = std::visit(check_clocking_scheme, lyt); !is_twoddwave_clocked) + { + env->out() << "[e] layout has to be 2DDWave-clocked" << std::endl; + return; + } + + const auto apply_optimization = [&](auto&& lyt_ptr) + { + using Lyt = typename std::decay_t::element_type; + auto lyt_copy = lyt_ptr->clone(); + const auto lyt_copy_ptr = std::make_shared(lyt_copy); + + if constexpr (fiction::is_cartesian_layout_v) + { + fiction::post_layout_optimization(*lyt_copy_ptr, &st); + fiction::restore_names(*lyt_ptr, *lyt_copy_ptr); + gls.extend() = lyt_copy_ptr; + } + else + { + std::cout << "[e] layout has to be Cartesian" << std::endl; + } + }; + + try + { + std::visit(apply_optimization, lyt); + } + catch (...) + { + env->out() << "[e] an error occurred while optimizing" << std::endl; + } + } +}; + +ALICE_ADD_COMMAND(optimize, "Physical Design") + +} // namespace alice + +#endif // FICTION_CMD_OPTIMIZE_HPP diff --git a/cli/commands.hpp b/cli/commands.hpp index f4a52eea4..7587c22e3 100644 --- a/cli/commands.hpp +++ b/cli/commands.hpp @@ -26,6 +26,7 @@ #include "cmd/physical_design/exact.hpp" #include "cmd/physical_design/hex.hpp" #include "cmd/physical_design/onepass.hpp" +#include "cmd/physical_design/optimize.hpp" #include "cmd/physical_design/ortho.hpp" #include "cmd/technology/area.hpp" #include "cmd/technology/cell.hpp" diff --git a/docs/algorithms/algorithms.rst b/docs/algorithms/algorithms.rst index 8099bc5c8..d06562cb7 100644 --- a/docs/algorithms/algorithms.rst +++ b/docs/algorithms/algorithms.rst @@ -53,6 +53,7 @@ Physical Design color_routing.rst apply_gate_library.rst hexagonalization.rst + post_layout_optimization.rst Verification diff --git a/docs/algorithms/post_layout_optimization.rst b/docs/algorithms/post_layout_optimization.rst new file mode 100644 index 000000000..f30392bfc --- /dev/null +++ b/docs/algorithms/post_layout_optimization.rst @@ -0,0 +1,15 @@ +.. _post_layout_optimization: + +Optimizing 2DDWave-clocked Cartesian Layouts +-------------------------------------------- + +**Header:** ``fiction/algorithms/physical_design/post_layout_optimization.hpp`` + +This algorithm aims to decrease the overall layout area of a given 2DDWave-clocked Cartesian layout that has been +generated using either heuristic methods or machine learning techniques. It achieves this objective by strategically +repositioning gates within the layout, removing excess wiring, and effectively relocating outputs to more favorable +positions. + +.. doxygenstruct:: fiction::post_layout_optimization_stats + :members: +.. doxygenfunction:: fiction::post_layout_optimization diff --git a/experiments/post_layout_optimization/post_layout_optimization.cpp b/experiments/post_layout_optimization/post_layout_optimization.cpp new file mode 100644 index 000000000..3305abc97 --- /dev/null +++ b/experiments/post_layout_optimization/post_layout_optimization.cpp @@ -0,0 +1,108 @@ +#include "fiction_experiments.hpp" + +#include // scalable heuristic for physical design of FCN layouts +#include // scalable heuristic for physical design of FCN layouts +#include // critical path and throughput calculations +#include // SAT-based equivalence checking + +#include // output formatting +#include // Verilog/BLIF/AIGER/... file parsing +#include // call-backs to read Verilog files into networks + +#include +#include +#include +#include + +int main() // NOLINT +{ + using gate_lyt = + fiction::gate_level_layout>>>; + + experiments::experiment + optimization_exp{"optimization", + "benchmark", + "inputs", + "outputs", + "initial nodes", + "ortho layout width (in tiles)", + "ortho layout height (in tiles)", + "ortho layout area (in tiles)", + "optimized layout width (in tiles)", + "optimized layout height (in tiles)", + "optimized layout area (in tiles)", + "gates", + "wires", + "critical path", + "throughput", + "runtime ortho (in sec)", + "runtime optimization (in sec)", + "improvement (%)", + "equivalent"}; + + // stats for SMT-based physical design + fiction::orthogonal_physical_design_stats orthogonal_stats{}; + fiction::post_layout_optimization_stats post_layout_optimization_stats{}; + + static constexpr const uint64_t bench_select = + fiction_experiments::all & ~fiction_experiments::epfl & ~fiction_experiments::iscas85; + + for (const auto& benchmark : fiction_experiments::all_benchmarks(bench_select)) + { + fmt::print("[i] processing {}\n", benchmark); + + fiction::technology_network network{}; + + const auto read_verilog_result = + lorina::read_verilog(fiction_experiments::benchmark_path(benchmark), mockturtle::verilog_reader(network)); + assert(read_verilog_result == lorina::return_code::success); + + // perform layout generation with an OGD-based heuristic algorithm + auto gate_level_layout = fiction::orthogonal(network, {}, &orthogonal_stats); + + // compute critical path and throughput + fiction::critical_path_length_and_throughput_stats cp_tp_stats{}; + fiction::critical_path_length_and_throughput(gate_level_layout, &cp_tp_stats); + + // calculate bounding box + const auto bounding_box_before_optimization = fiction::bounding_box_2d(gate_level_layout); + + const auto width_before_optimization = bounding_box_before_optimization.get_x_size() + 1; + const auto height_before_optimization = bounding_box_before_optimization.get_y_size() + 1; + const auto area_before_optimization = width_before_optimization * height_before_optimization; + + // perform post-layout optimization + fiction::post_layout_optimization(gate_level_layout, &post_layout_optimization_stats); + + // check equivalence + fiction::equivalence_checking_stats eq_stats{}; + fiction::equivalence_checking(network, gate_level_layout, &eq_stats); + + const std::string eq_result = eq_stats.eq == fiction::eq_type::STRONG ? "STRONG" : + eq_stats.eq == fiction::eq_type::WEAK ? "WEAK" : + "NO"; + + // calculate bounding box + const auto bounding_box_after_optimization = fiction::bounding_box_2d(gate_level_layout); + + const auto width_after_optimization = bounding_box_after_optimization.get_x_size() + 1; + const auto height_after_optimization = bounding_box_after_optimization.get_y_size() + 1; + const auto area_after_optimization = width_after_optimization * height_after_optimization; + + const float improv = 100 * static_cast((area_before_optimization - area_after_optimization)) / + static_cast(area_before_optimization); + // log results + optimization_exp(benchmark, network.num_pis(), network.num_pos(), network.num_gates(), + width_before_optimization, height_before_optimization, area_before_optimization, + width_after_optimization, height_after_optimization, area_after_optimization, + gate_level_layout.num_gates(), gate_level_layout.num_wires(), cp_tp_stats.critical_path_length, + cp_tp_stats.throughput, mockturtle::to_seconds(orthogonal_stats.time_total), + mockturtle::to_seconds(post_layout_optimization_stats.time_total), improv, eq_result); + + optimization_exp.save(); + optimization_exp.table(); + } + + return EXIT_SUCCESS; +} diff --git a/include/fiction/algorithms/physical_design/post_layout_optimization.hpp b/include/fiction/algorithms/physical_design/post_layout_optimization.hpp new file mode 100644 index 000000000..545251354 --- /dev/null +++ b/include/fiction/algorithms/physical_design/post_layout_optimization.hpp @@ -0,0 +1,1163 @@ +// +// Created by simon on 14.06.23. +// + +#ifndef FICTION_POST_LAYOUT_OPTIMIZATION_HPP +#define FICTION_POST_LAYOUT_OPTIMIZATION_HPP + +#include "fiction/algorithms/path_finding/a_star.hpp" +#include "fiction/algorithms/path_finding/cost.hpp" +#include "fiction/algorithms/path_finding/distance.hpp" +#include "fiction/layouts/bounding_box.hpp" +#include "fiction/layouts/obstruction_layout.hpp" +#include "fiction/traits.hpp" +#include "fiction/utils/name_utils.hpp" +#include "fiction/utils/placement_utils.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace fiction +{ + +/** + * This struct stores statistics about the post-layout optimization process. + */ +struct post_layout_optimization_stats +{ + /** + * Runtime of the post-layout optimization process. + */ + mockturtle::stopwatch<>::duration time_total{0}; + /** + * Reports the statistics to the given output stream. + * + * @param out Output stream. + */ + void report(std::ostream& out = std::cout) const + { + out << fmt::format("[i] total time = {:.2f} secs\n", mockturtle::to_seconds(time_total)); + } +}; + +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 Gate-level layout type. + * + * @param lyt Gate-level layout. + * @param deleted_coords Tiles that got deleted. + * + * @note This function requires the gate-level layout to be Cartesian. + */ +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. + * It contains vectors for fan-ins, fan-outs, and temporary coordinates to clear before routing. + * Additionally, it includes layout coordinate paths for routing signals between the gate and its fan-in/fan-out + * connections. + * + * @tparam Lyt Gate-level layout type. + */ +template +struct fanin_fanout_data +{ + /** + * This vector holds the layout coordinates of all fan-in connections to the gate. + */ + std::vector> fanins; + + /** + * This vector holds the layout coordinates of all fan-out connections from the gate. + */ + std::vector> fanouts; + + /** + * During the gate relocation process, this vector holds temporary layout coordinates that need to be cleared or + * reset. + */ + std::vector> to_clear; + + /** + * This layout_coordinate_path object represents the path for routing signals from the first fan-in + * to the gate within the layout. + */ + layout_coordinate_path route_fanin_1_to_gate; + + /** + * This layout_coordinate_path object represents the path for routing signals from the second fan-in + * to the gate within the layout. + */ + layout_coordinate_path route_fanin_2_to_gate; + + /** + * This layout_coordinate_path object represents the path for routing signals from the gate to + * the first fan-out within the layout. + */ + layout_coordinate_path route_gate_to_fanout_1; + + /** + * This layout_coordinate_path object represents the path for routing signals from the gate to + * the second fan-out within the layout. + */ + 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. + * + * @tparam Lyt 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. + */ +template +void add_fanin_to_route(const tile& fanin, bool is_first_fanin, fanin_fanout_data& ffd) noexcept +{ + if (is_first_fanin) + { + ffd.route_fanin_1_to_gate.insert(ffd.route_fanin_1_to_gate.cbegin(), fanin); + } + else + { + ffd.route_fanin_2_to_gate.insert(ffd.route_fanin_2_to_gate.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. + * + * @tparam Lyt 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) + { + ffd.route_gate_to_fanout_1.push_back(fanout); + } + else + { + ffd.route_gate_to_fanout_2.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 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. + * + * @note This function requires the gate-level layout to be Cartesian. + */ +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{}; + + 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); + + 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()) + { + + // 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](const auto& fout) + { + tile fanout = lyt.get_tile(fout); + + if (fanouts_set.find(fanout) == fanouts_set.cend()) + { + + // Add fanout to the respective route + add_fanout_to_route(op, fanouts_set.empty(), ffd); + add_fanout_to_route(fanout, fanouts_set.empty(), ffd); + + // 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); + } + }); + + // 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); + } + + 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. + * + * @tparam Lyt Gate-level layout. + * + * @param lyt Reference to the layout. + * @param start The starting coordinate of the path. + * @param end The ending coordinate of the path. + * + * @return The computed path as a sequence of coordinates in the layout. + */ +template +layout_coordinate_path get_path_and_obstruct(Lyt& lyt, const tile& start, const tile& end) +{ + using dist = twoddwave_distance_functor; + using cost = unit_cost_functor; + static const a_star_params params{true}; + + 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; +} + +/** + * 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 Gate-level layout. + * @param old_pos Old position of the gate to be moved. + * @return Flag that indicates if gate was moved successfully and the new coordinate of the moved gate. + * + * @note This function requires the layout to be a gate-level layout, a Cartesian layout, 2DDWave-clocked and implement + * the obstruction interface. + */ +template +[[nodiscard]] std::pair> improve_gate_location(Lyt& lyt, const tile& old_pos) 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"); + if (!lyt.is_clocking_scheme(clock_name::TWODDWAVE)) + { + std::cout << "Clocking scheme is not 2DDWave" << std::endl; + std::make_pair(false, old_pos); + } + static_assert(has_is_obstructed_coordinate_v, "Lyt is not an obstruction layout"); + + 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); + + uint64_t min_x = 0; + uint64_t min_y = 0; + + // 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; + } + + const auto max_x = old_pos.x; + const auto max_y = old_pos.y; + const auto max_diagonal = max_x + max_y; + + auto new_pos = tile{}; + + // 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)) + { + if (i == fanin) + { + return std::make_pair(false, new_pos); + } + } + } + + // remove wiring + for (const auto& tile : to_clear) + { + lyt.clear_tile(tile); + lyt.clear_obstructed_coordinate(tile); + } + + // update children of fanouts + for (const auto& fanout : fanouts) + { + 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))); + } + }); + + lyt.move_node(lyt.get_node(fanout), fanout, fins); + } + + // remove children of gate to be moved + lyt.move_node(lyt.get_node(old_pos), old_pos, {}); + + // fix wires that cross over empty tiles + fix_wires(lyt, to_clear); + + bool moved_gate = false; + auto current_pos = old_pos; + bool improved_gate_location = false; + // iterate over layout diagonally + for (uint64_t k = 0; k < lyt.x() + lyt.y() + 1; ++k) + { + for (uint64_t x = 0; x < k + 1; ++x) + { + const uint64_t y = k - x; + + if (moved_gate) + { + break; + } + + // only check better positions + if (lyt.y() >= y && y >= min_y && lyt.x() >= x && x >= min_x && ((x + y) <= max_diagonal) && + ((!lyt.is_pi_tile(current_pos)) || (lyt.is_pi_tile(current_pos) && (x == 0 || y == 0)))) + { + new_pos = {x, y}; + if (lyt.is_empty_tile(new_pos) && lyt.is_empty_tile({new_pos.x, new_pos.y, 1})) + { + // 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); + } + + if (fanins.size() == 2) + { + 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]); + } + + 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())) + { + 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()) + { + route_path(lyt, path); + for (const auto& tile : path) + { + lyt.obstruct_coordinate(tile); + } + } + } + + moved_gate = true; + + if (new_pos != old_pos) + { + improved_gate_location = 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 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.move_node(lyt.get_node(fanout), fanout, signals); + } + } + // 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) + { + lyt.clear_obstructed_coordinate(tile); + } + } + } + + current_pos = new_pos; + } + } + } + + if (moved_gate) + { + break; + } + } + // if no better coordinate was found, restore old wiring + if (!moved_gate) + { + lyt.move_node(lyt.get_node(current_pos), old_pos, {}); + + for (const auto& r : {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}) + { + if (!r.empty()) + { + route_path>(lyt, r); + } + for (const auto& t : r) + { + lyt.obstruct_coordinate(t); + } + } + + // update obstructions + lyt.clear_obstructed_coordinate(current_pos); + lyt.clear_obstructed_coordinate({current_pos.x, current_pos.y, 1}); + lyt.obstruct_coordinate(old_pos); + lyt.obstruct_coordinate({old_pos.x, old_pos.y, 1}); + + // update children on old position + 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); + signals.push_back(lyt.make_signal(lyt.get_node(fanin))); + }); + + lyt.move_node(lyt.get_node(old_pos), old_pos, signals); + + // update children oóf fanouts + for (const auto& fanout : fanouts) + { + 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); + fout_signals.push_back(lyt.make_signal(lyt.get_node(fout))); + }); + + lyt.move_node(lyt.get_node(fanout), fanout, fout_signals); + } + } + + return std::make_pair(improved_gate_location, new_pos); +} + +/** + * Utility function that deletes all specified rows and columns. + * + * @param lyt Gate-level layout. + * @param rows_to_delete Rows to be deleted. + * @param columns_to_delete Columns to be deleted. + * + * @note This function requires the gate-level layout to be 2DDWave-clocked and Cartesian. + */ +template +void delete_rows_and_columns(Lyt& lyt, std::vector& rows_to_delete, + std::vector& columns_to_delete) 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"); + if (!lyt.is_clocking_scheme(clock_name::TWODDWAVE)) + { + std::cout << "Clocking scheme is not 2DDWave" << std::endl; + return; + } + + auto layout_copy = lyt.clone(); + + // delete all excess wiring in rows with only vertical wires + for (uint64_t row : rows_to_delete) + { + for (uint64_t x = 0; x <= lyt.x(); ++x) + { + if (const tile old_pos = {x, row, 0}; !lyt.is_empty_tile(old_pos)) + { + lyt.clear_tile(old_pos); + } + } + } + + // delete all excess wiring in columns with only horizontal wires + for (uint64_t column : columns_to_delete) + { + for (uint64_t y = 0; y <= lyt.y(); ++y) + { + if (const tile old_pos = {column, y, 0}; !lyt.is_empty_tile(old_pos)) + { + lyt.clear_tile(old_pos); + } + } + } + + for (uint64_t x = 0; x <= lyt.x(); ++x) + { + // calculate column offset based on number of columns deleted to the left of current column + uint64_t column_offset = 0; + if (!columns_to_delete.empty()) + { + column_offset = static_cast( + std::upper_bound(columns_to_delete.cbegin(), columns_to_delete.cend(), x) - columns_to_delete.cbegin()); + } + for (uint64_t y = 0; y <= lyt.y(); ++y) + { + // calculate row offset based on number of rows deleted above the current row + uint64_t row_offset = 0; + if (!rows_to_delete.empty()) + { + row_offset = static_cast(std::upper_bound(rows_to_delete.cbegin(), rows_to_delete.cend(), y) - + rows_to_delete.cbegin()); + } + + for (uint64_t z = 0; z <= lyt.z(); ++z) + { + + if (const tile old_pos = {x, y, z}; !lyt.is_empty_tile(old_pos)) + { + const tile new_pos = {x - column_offset, y - row_offset, z}; + + std::vector> fins{}; + + for (auto& fanin : layout_copy.incoming_data_flow(old_pos)) + { + // skip removed columns and decrease column offset + uint64_t excess_column_offset = 0; + if (!columns_to_delete.empty()) + { + while (std::find(std::cbegin(columns_to_delete), std::cend(columns_to_delete), fanin.x) != + std::cend(columns_to_delete)) + { + fanin = layout_copy.incoming_data_flow(fanin)[0]; + excess_column_offset++; + } + } + + // skip removed rows and decrease row offset + uint64_t excess_row_offset = 0; + if (!rows_to_delete.empty()) + { + while (std::find(std::begin(rows_to_delete), std::end(rows_to_delete), fanin.y) != + std::end(rows_to_delete)) + { + fanin = layout_copy.incoming_data_flow(fanin)[0]; + excess_row_offset++; + } + } + + fins.push_back( + lyt.make_signal(lyt.get_node({fanin.x - column_offset + excess_column_offset, + fanin.y - row_offset + excess_row_offset, fanin.z}))); + } + lyt.move_node(lyt.get_node(old_pos), new_pos, fins); + } + } + } + } +} + +/** + * Utility function that deletes rows that only contain vertically connected wires. + * + * @param lyt Gate-level layout. + * + * @note This function requires the gate-level layout to be 2DDWave-clocked and Cartesian. + */ +template +void delete_wires(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"); + if (!lyt.is_clocking_scheme(clock_name::TWODDWAVE)) + { + std::cout << "Clocking scheme is not 2DDWave" << std::endl; + return; + } + + std::vector rows_to_delete{}; + rows_to_delete.reserve(lyt.y()); + for (uint64_t y = 0; y <= lyt.y(); ++y) + { + bool found_row = true; + for (uint64_t x = 0; x <= lyt.x(); ++x) + { + const bool is_vertical_wire = + (lyt.is_wire_tile({x, y}) && lyt.fanin_size(lyt.get_node({x, y})) == 1 && + lyt.fanout_size(lyt.get_node({x, y})) == 1 && lyt.has_northern_incoming_signal({x, y}) && + lyt.has_southern_outgoing_signal({x, y})); + + if (!(is_vertical_wire || lyt.is_empty_tile({x, y}))) + { + found_row = false; + } + } + if (found_row) + { + rows_to_delete.push_back(y); + } + } + + std::vector columns_to_delete{}; + columns_to_delete.reserve(lyt.x()); + for (uint64_t x = 0; x <= lyt.x(); ++x) + { + bool found_column = true; + for (uint64_t y = 0; y <= lyt.y(); ++y) + { + // Check if the column can be deleted based on certain conditions + const bool is_horizontal_wire = + (lyt.is_wire_tile({x, y}) && lyt.fanin_size(lyt.get_node({x, y})) == 1 && + lyt.fanout_size(lyt.get_node({x, y})) == 1 && lyt.has_western_incoming_signal({x, y}) && + lyt.has_eastern_outgoing_signal({x, y})); + + if (!(is_horizontal_wire || lyt.is_empty_tile({x, y}))) + { + found_column = false; + } + } + if (found_column) + { + columns_to_delete.push_back(x); + } + } + + delete_rows_and_columns(lyt, rows_to_delete, columns_to_delete); +} + +/** + * This struct is used to hold information about an update to the layout, where a PO tile + * is moved from its old coordinate to a new coordinate and connected to its new child node. + * + * @tparam Lyt Gate-level layout. + */ +template +struct output_update +{ + /** + * The old coordinate of the PO. + */ + tile old_coordinate; + + /** + * The new coordinate of the PO. + */ + tile new_coordinate; + + /** + * The coordinate of the child node (after the update). + * + * This is the coordinate of the new child (i.e. incoming signal) after the update. + */ + tile child_coordinate; + + /** + * Construct a new Update object. + * + * @param old_coord The old coordinate of the PO. + * @param new_coord The new coordinate of the PO. + * @param child_coord The coordinate of the child node (after the update). + */ + output_update(const tile& old_coord, const tile& new_coord, const tile& child_coord) : + old_coordinate(old_coord), + new_coordinate(new_coord), + child_coordinate(child_coord) + {} +}; + +/** + * Utility function that traces back all output nodes and calculate optimal positions. + * + * @param lyt Gate-level layout. + * + * @note This function requires the gate-level layout to be 2DDWave-clocked and Cartesian. + */ +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"); + if (!lyt.is_clocking_scheme(clock_name::TWODDWAVE)) + { + std::cout << "Clocking scheme is not 2DDWave" << std::endl; + return; + } + + // get path from output to preceding gate + std::vector> paths{}; + paths.reserve(lyt.num_pos()); + + lyt.foreach_po( + [&lyt, &paths](const auto& po) + { + tile po_tile = lyt.get_tile(lyt.get_node(po)); + layout_coordinate_path route{}; + + // trace back outputs + lyt.foreach_fanin(lyt.get_node(po_tile), + [&lyt, &route, &po_tile](const auto& fin) + { + auto fanin = static_cast>(fin); + + route.insert(route.begin(), po_tile); + route.insert(route.begin(), fanin); + + while (lyt.is_wire_tile(fanin) && + lyt.fanout_size(lyt.get_node(fanin)) == 1 // -> Wire with single fanout + && !lyt.is_pi_tile(fanin)) // Can't go further back than until a PI + { + fanin = lyt.incoming_data_flow(fanin).front(); + route.insert(route.begin(), fanin); + } + }); + + paths.push_back(route); + + if (route.size() != 2) // Output is directly connected to a gate already + { + lyt.move_node(lyt.get_node(po), po_tile, {}); + } + }); + + // calculate bounding box around gates without outputs + const auto min_x = + std::max_element(paths.begin(), paths.end(), [](const auto& a, const auto& b) { return a[1].x < b[1].x; }) + ->at(1) + .x; + const auto min_y = + std::max_element(paths.begin(), paths.end(), [](const auto& a, const auto& b) { return a[1].y < b[1].y; }) + ->at(1) + .y; + + std::vector> output_updates{}; + std::vector> cleared{}; + + // move output along its wiring until it lies on the bounding box + for (const auto& route : paths) + { + auto dangling = tile{}; + auto new_pos = tile{}; + bool moved = false; + + for (const auto& tile : route) + { + if ((tile.x < min_x && tile.y < min_y)) + { + dangling = tile; + } + else if (!lyt.is_po_tile(tile) && lyt.is_wire_tile(tile) && lyt.fanout_size(lyt.get_node(tile)) != 2) + { + lyt.clear_tile(tile); + cleared.emplace_back(tile); + if (new_pos.is_dead()) + { + new_pos = tile; + } + } + else if (!dangling.is_dead()) + { + if (!new_pos.is_dead()) + { + new_pos = {new_pos.x, new_pos.y, 0}; + output_update new_update(tile, new_pos, dangling); + output_updates.emplace_back(new_update); + } + moved = true; + } + } + if (!moved) + { + new_pos = {route[1].x, route[1].y, 0}; + output_update new_update(route.back(), new_pos, route.front()); + output_updates.emplace_back(new_update); + } + } + + fix_wires(lyt, cleared); + for (const auto& [tile, new_pos, dangling] : output_updates) + { + if (lyt.is_empty_tile(new_pos) || (tile == new_pos)) + { + lyt.move_node(lyt.get_node(tile), new_pos, {lyt.make_signal(lyt.get_node(dangling))}); + } + else + { + lyt.create_buf(lyt.make_signal(lyt.get_node(dangling)), {new_pos.x, new_pos.y, 1}); + if (new_pos.x == min_x) + { + lyt.move_node(lyt.get_node(tile), {new_pos.x + 1, new_pos.y, 0}, + {lyt.make_signal(lyt.get_node({new_pos.x, new_pos.y, 1}))}); + } + else + { + lyt.move_node(lyt.get_node(tile), {new_pos.x, new_pos.y + 1, 0}, + {lyt.make_signal(lyt.get_node({new_pos.x, new_pos.y, 1}))}); + } + } + } +} + +/** + * This function fixes dead nodes in the layout by moving them to available empty coordinates and back. + * A dead node is a node in the layout that is placed, but not alive, which can happen during the optimization process. + * The function finds an empty coordinate in the layout and moves the dead gates to that coordinate, before moving it + * back to its old location and connecting any fanin signals again. + * + * @param lyt Gate-level layout. + * @param gt Vector containing the coordinates of all gates in the layout. + * + * @note This function requires the gate-level layout to be Cartesian. + */ +template +void fix_dead_nodes(Lyt& lyt, std::vector>& gt) 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"); + + // Find an empty coordinate to move the gate to. + auto empty_pos = tile{}; + + lyt.foreach_coordinate( + [&lyt, &empty_pos](const auto& coord) + { + if (lyt.is_empty_tile(coord)) + { + empty_pos = coord; + return false; + } + return true; + }); + + // If an empty coordinate is found, proceed with moving the dead gates. + if (!empty_pos.is_dead()) + { + for (const auto& gate : gt) + { + if (lyt.is_dead(lyt.get_node(gate))) + { + // Collect fanin signals of the dead gate. + std::vector> signals{}; + signals.reserve(lyt.fanin_size(lyt.get_node(gate))); + lyt.foreach_fanin(lyt.get_node(gate), [&signals](const auto& fanin) { signals.push_back(fanin); }); + + // Move the dead gate to the empty coordinate. + lyt.move_node(lyt.get_node(gate), empty_pos); + lyt.clear_tile(gate); + + // Move the fanin signals to the newly moved gate. + lyt.move_node(lyt.get_node(empty_pos), gate, signals); + } + } + } +} + +/** + * Custom comparison function that sorts gates based on the sum of its coordinates and breaks ties based on the + * x-coordinate. + * + * @param a first gate + * @param b second gate + * @return result of the comparison + */ +template +bool compare_gates(const tile& a, const tile& b) +{ + return static_cast(std::make_pair(a.x + a.y, a.x) < std::make_pair(b.x + b.y, b.x)); +} + +} // namespace detail + +/** + * Optimization algorithm that can be used to reduce the layout area of sub-optimal physical design created by + * heuristics or machine learning. This optimization utilizes the distinct characteristics of the 2DDWave clocking + * scheme, which only allows information flow from top to bottom and left to right, therefore only aforementioned + * clocking scheme is supported. + * + * To reduce the layout area, first, gates are moved up and to the left as far as possible, including rerouting. This + * creates more compact layouts by freeing up space to the right and bottom, as all gates were moved to the top left + * corner. + * + * After moving all gates, this algorithm also checks if excess wiring exists on the layout, i.e., rows that only + * contain vertical wires or columns that only contain horizontal wires and removes them. + * + * As outputs have to lay on the border of a layout for better accessibility, they are also moved to new borders + * determined based on the location of all other gates. + * + * @param lyt Gate-level layout. + * + * @note This function requires the gate-level layout to be 2DDWave-clocked and Cartesian. + */ +template +void post_layout_optimization(const Lyt& lyt, post_layout_optimization_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"); + + if (!lyt.is_clocking_scheme(clock_name::TWODDWAVE)) + { + std::cout << "Clocking scheme is not 2DDWave" << std::endl; + return; + } + + // measure run time + const mockturtle::stopwatch stop{pst->time_total}; + + // Optimization + auto layout = obstruction_layout(lyt); + + 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_inv(node) || layout.is_and(node) || + layout.is_xor(node) || layout.is_fanout(node) || + layout.is_or(node) || layout.is_pi_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_gates); + + // sort gates based on diagonal line + std::sort(gate_tiles.begin(), gate_tiles.end(), [](const auto& a, const auto& b) { return a.x + a.y < b.x + b.y; }); + + bool moved_at_least_one_gate = false; + + do { + moved_at_least_one_gate = false; + for (auto& gate_tile : gate_tiles) + { + const auto try_gate_relocation = detail::improve_gate_location(layout, gate_tile); + + if (std::get<0>(try_gate_relocation)) + { + moved_at_least_one_gate = true; + gate_tile = std::get<1>(try_gate_relocation); // update gate location + } + } + + std::sort(gate_tiles.begin(), gate_tiles.end(), detail::compare_gates); + } while (moved_at_least_one_gate); + + detail::delete_wires(layout); + detail::optimize_output_positions(layout); + detail::fix_dead_nodes(layout, gate_tiles); + + // calculate bounding box + const auto bounding_box = bounding_box_2d(layout); + const auto optimized_layout_width = bounding_box.get_x_size(); + const auto optimized_layout_height = bounding_box.get_y_size(); + + layout.resize({optimized_layout_width, optimized_layout_height, layout.z()}); +} + +} // namespace fiction + +#endif // FICTION_POST_LAYOUT_OPTIMIZATION_HPP diff --git a/include/fiction/layouts/cartesian_layout.hpp b/include/fiction/layouts/cartesian_layout.hpp index c05b86aa2..3e5c119eb 100644 --- a/include/fiction/layouts/cartesian_layout.hpp +++ b/include/fiction/layouts/cartesian_layout.hpp @@ -81,6 +81,15 @@ class cartesian_layout * @param s Storage of another cartesian_layout. */ explicit cartesian_layout(std::shared_ptr s) : strg{std::move(s)} {} + /** + * Clones the layout returning a deep copy. + * + * @return Deep copy of the layout. + */ + [[nodiscard]] cartesian_layout clone() const noexcept + { + return cartesian_layout(std::make_shared(*strg)); + } #pragma endregion diff --git a/include/fiction/layouts/cell_level_layout.hpp b/include/fiction/layouts/cell_level_layout.hpp index e5be8d81c..aea701153 100644 --- a/include/fiction/layouts/cell_level_layout.hpp +++ b/include/fiction/layouts/cell_level_layout.hpp @@ -116,19 +116,34 @@ class cell_level_layout : public ClockedLayout { static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); } - + /** + * Copy constructor from another layout's storage. + * + * @param s Storage of another cell_level_layout. + */ explicit cell_level_layout(std::shared_ptr> s) : strg{std::move(s)} {} - + /** + * Copy constructor from another `ClockedLayout`. + * + * @param lyt Clocked layout. + */ + explicit cell_level_layout(const ClockedLayout& lyt) : + ClockedLayout(lyt), + strg{std::make_shared>("", 1, 1)} + { + static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); + } /** * Clones the layout returning a deep copy. * * @return Deep copy of the layout. */ - cell_level_layout clone() const + [[nodiscard]] cell_level_layout clone() const noexcept { - auto cl = cell_level_layout{strg}; - cl.strg = std::make_shared>(*strg); - return cl; + cell_level_layout copy{ClockedLayout::clone()}; + copy.strg = std::make_shared>(*strg); + + return copy; } #pragma endregion diff --git a/include/fiction/layouts/clocked_layout.hpp b/include/fiction/layouts/clocked_layout.hpp index 6ed3c9a12..e643ab1af 100644 --- a/include/fiction/layouts/clocked_layout.hpp +++ b/include/fiction/layouts/clocked_layout.hpp @@ -93,6 +93,30 @@ class clocked_layout : public CoordinateLayout { static_assert(is_coordinate_layout_v, "CoordinateLayout is not a coordinate layout type"); } + /** + * Copy constructor from another `CoordinateLayout`. + * + * @param lyt Coordinate layout. + */ + explicit clocked_layout(const CoordinateLayout& lyt) : + CoordinateLayout(lyt), + strg{std::make_shared( + open_clocking>(num_clks::FOUR))} + { + static_assert(is_coordinate_layout_v, "CoordinateLayout is not a coordinate layout type"); + } + /** + * Clones the layout returning a deep copy. + * + * @return Deep copy of the layout. + */ + [[nodiscard]] clocked_layout clone() const noexcept + { + auto copy = clocked_layout(CoordinateLayout::clone()); + copy.strg = std::make_shared(*strg); + + return copy; + } #pragma endregion diff --git a/include/fiction/layouts/gate_level_layout.hpp b/include/fiction/layouts/gate_level_layout.hpp index b0dc0cb6b..e6604c791 100644 --- a/include/fiction/layouts/gate_level_layout.hpp +++ b/include/fiction/layouts/gate_level_layout.hpp @@ -181,12 +181,47 @@ class gate_level_layout : public ClockedLayout * * @param s Storage of another gate_level_layout. */ - explicit gate_level_layout(std::shared_ptr s) : + explicit gate_level_layout(storage s) : strg{std::move(s)}, evnts{std::make_shared()} { static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); } + /** + * Copy constructor from another layout's storage. + * + * @param s Storage of another gate_level_layout. + * @param e Event storage of another gate_level_layout. + */ + gate_level_layout(storage s, event_storage e) : strg{std::move(s)}, evnts{std::move(e)} + { + static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); + } + /** + * Copy constructor from another `ClockedLayout`. + * + * @param lyt Clocked layout. + */ + explicit gate_level_layout(const ClockedLayout& lyt) : + ClockedLayout(lyt), + strg{std::make_shared()}, + evnts{std::make_shared()} + { + static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); + } + /** + * Clones the layout returning a deep copy. + * + * @return Deep copy of the layout. + */ + [[nodiscard]] gate_level_layout clone() const noexcept + { + gate_level_layout copy{ClockedLayout::clone()}; + copy.strg = std::make_shared(*strg); + copy.evnts = std::make_shared>(*evnts); + + return copy; + } #pragma endregion diff --git a/include/fiction/layouts/hexagonal_layout.hpp b/include/fiction/layouts/hexagonal_layout.hpp index 8d4849d9d..7316e3d84 100644 --- a/include/fiction/layouts/hexagonal_layout.hpp +++ b/include/fiction/layouts/hexagonal_layout.hpp @@ -200,6 +200,15 @@ class hexagonal_layout "HexagonalCoordinateSystem has to be one of the following: odd_row_hex, even_row_hex, " "odd_column_hex, even_column_hex"); } + /** + * Clones the layout returning a deep copy. + * + * @return Deep copy of the layout. + */ + [[nodiscard]] hexagonal_layout clone() const noexcept + { + return hexagonal_layout(std::make_shared(*strg)); + } #pragma endregion diff --git a/include/fiction/layouts/shifted_cartesian_layout.hpp b/include/fiction/layouts/shifted_cartesian_layout.hpp index fd2ace231..77dbd3090 100644 --- a/include/fiction/layouts/shifted_cartesian_layout.hpp +++ b/include/fiction/layouts/shifted_cartesian_layout.hpp @@ -182,6 +182,9 @@ class shifted_cartesian_layout "odd_column_cartesian, even_column_cartesian"); } + // NOLINTNEXTLINE(*-explicit-constructor, *-explicit-conversions) + shifted_cartesian_layout(const HexagonalLayout& lyt) : HexagonalLayout(lyt) {} + private: // intentionally hide members of HexagonalLayout using HexagonalLayout::to_cube_coordinate; diff --git a/include/fiction/layouts/synchronization_element_layout.hpp b/include/fiction/layouts/synchronization_element_layout.hpp index 8fbddaa7d..709da15ab 100644 --- a/include/fiction/layouts/synchronization_element_layout.hpp +++ b/include/fiction/layouts/synchronization_element_layout.hpp @@ -91,6 +91,29 @@ class synchronization_element_layout : public ClockedLayout { static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); } + /** + * Copy constructor from another `ClockedLayout`. + * + * @param lyt Clocked layout. + */ + explicit synchronization_element_layout(const ClockedLayout& lyt) : + ClockedLayout(lyt), + strg{std::make_shared()} + { + static_assert(is_clocked_layout_v, "ClockedLayout is not a clocked layout type"); + } + /** + * Clones the layout returning a deep copy. + * + * @return Deep copy of the layout. + */ + [[nodiscard]] synchronization_element_layout clone() const noexcept + { + auto copy = synchronization_element_layout(ClockedLayout::clone()); + copy.strg = std::make_shared(*strg); + + return copy; + } #pragma endregion diff --git a/include/fiction/layouts/tile_based_layout.hpp b/include/fiction/layouts/tile_based_layout.hpp index 63382c3eb..f82f4ffd4 100644 --- a/include/fiction/layouts/tile_based_layout.hpp +++ b/include/fiction/layouts/tile_based_layout.hpp @@ -33,12 +33,25 @@ class tile_based_layout : public CoordinateLayout explicit tile_based_layout(const typename CoordinateLayout::aspect_ratio& ar = {}) : CoordinateLayout(ar) { static_assert(is_coordinate_layout_v, "CoordinateLayout is not a coordinate layout type"); + static_assert(!is_clocked_layout_v, "CoordinateLayout cannot be a clocked layout type"); } template explicit tile_based_layout(std::shared_ptr s) : CoordinateLayout(s) { static_assert(is_coordinate_layout_v, "CoordinateLayout is not a coordinate layout type"); + static_assert(!is_clocked_layout_v, "CoordinateLayout cannot be a clocked layout type"); + } + + explicit tile_based_layout(const CoordinateLayout& lyt) : CoordinateLayout(lyt) + { + static_assert(is_coordinate_layout_v, "CoordinateLayout is not a coordinate layout type"); + static_assert(!is_clocked_layout_v, "CoordinateLayout cannot be a clocked layout type"); + } + + [[nodiscard]] tile_based_layout clone() const noexcept + { + return tile_based_layout{CoordinateLayout::clone()}; } #pragma endregion @@ -53,7 +66,7 @@ class tile_based_layout : public CoordinateLayout template void foreach_tile(Fn&& fn, const tile& start = {}, const tile& stop = {}) const { - CoordinateLayout::foreach_coordinate(fn, start, stop); + CoordinateLayout::foreach_coordinate(std::forward(fn), start, stop); } [[nodiscard]] auto ground_tiles(const tile& start = {}, const tile& stop = {}) const @@ -64,7 +77,7 @@ class tile_based_layout : public CoordinateLayout template void foreach_ground_tile(Fn&& fn, const tile& start = {}, const tile& stop = {}) const { - CoordinateLayout::foreach_ground_coordinate(fn, start, stop); + CoordinateLayout::foreach_ground_coordinate(std::forward(fn), start, stop); } std::vector adjacent_tiles(const tile& t) const noexcept diff --git a/test/algorithms/physical_design/post_layout_optimization.cpp b/test/algorithms/physical_design/post_layout_optimization.cpp new file mode 100644 index 000000000..8fb5a96f6 --- /dev/null +++ b/test/algorithms/physical_design/post_layout_optimization.cpp @@ -0,0 +1,256 @@ +// +// Created by Simon Hofmann on 31.07.23. +// + +#include + +#include "utils/blueprints/layout_blueprints.hpp" +#include "utils/blueprints/network_blueprints.hpp" +#include "utils/equivalence_checking_utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace fiction; + +template +void check_mapping_equiv(const Ntk& ntk) +{ + const auto layout = orthogonal(ntk, {}); + + post_layout_optimization_stats stats{}; + post_layout_optimization(layout, &stats); + + check_eq(ntk, layout); +} + +template +void check_mapping_equiv_all() +{ + check_mapping_equiv(blueprints::maj1_network()); + check_mapping_equiv(blueprints::maj4_network()); + check_mapping_equiv(blueprints::unbalanced_and_inv_network()); + check_mapping_equiv(blueprints::and_or_network()); + check_mapping_equiv(blueprints::nary_operation_network()); + check_mapping_equiv(blueprints::constant_gate_input_maj_network()); + check_mapping_equiv(blueprints::half_adder_network()); + check_mapping_equiv(blueprints::full_adder_network()); + check_mapping_equiv(blueprints::mux21_network()); + check_mapping_equiv(blueprints::se_coloring_corner_case_network()); + check_mapping_equiv(blueprints::fanout_substitution_corner_case_network()); + check_mapping_equiv(blueprints::inverter_network()); + check_mapping_equiv(blueprints::clpl()); + check_mapping_equiv(blueprints::one_to_five_path_difference_network()); + check_mapping_equiv(blueprints::nand_xnor_network()); +} + +TEST_CASE("Layout equivalence", "[post_layout_optimization]") +{ + SECTION("Cartesian layouts") + { + using gate_layout = gate_level_layout>>>; + + check_mapping_equiv_all(); + } + + SECTION("Corner Cases") + { + using gate_layout = gate_level_layout>>>; + + const auto layout_corner_case_1 = blueprints::optimization_layout_corner_case_outputs_1(); + post_layout_optimization_stats stats_corner_case_1{}; + post_layout_optimization(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(); + post_layout_optimization_stats stats_corner_case_2{}; + post_layout_optimization(layout_corner_case_2, &stats_corner_case_2); + check_eq(blueprints::optimization_layout_corner_case_outputs_2(), layout_corner_case_2); + } +} + +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}}); + } + + SECTION("Get fanin and fanouts 2") + { + 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); + + CHECK(fanins == std::vector>{{0, 2}, {2, 0}}); + CHECK(fanouts == std::vector>{{2, 3}}); + CHECK(to_clear == std::vector>{{1, 2}, {2, 1}}); + + 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()); + } + + 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); + // I I→= + // ↓ ↓ + // = ▢ = + // ↓ ↓ + // F→=→& + // ↓ ↓ + // O ▢ O + SECTION("Move gates 1") + { + CHECK(std::get<0>(moved_gate_1) == true); + CHECK(std::get<1>(moved_gate_1) == new_pos_1); + CHECK(obstr_lyt.is_pi_tile(new_pos_1) == true); + CHECK(obstr_lyt.is_pi_tile(old_pos_1) == false); + } + + 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); + // I I→= + // ↓ ↓ + // F→= = + // ↓ ↓ ↓ + // = =→& + // ↓ ↓ + // O ▢ O + SECTION("Move gates 2") + { + CHECK(std::get<0>(moved_gate_2) == true); + CHECK(std::get<1>(moved_gate_2) == new_pos_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); + } + + 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); + // I I ▢ + // ↓ ↓ + // F→&→= + // ↓ ↓ + // = ▢ = + // ↓ ↓ + // O ▢ O + SECTION("Move gates 3") + { + CHECK(std::get<0>(moved_gate_3) == true); + CHECK(std::get<1>(moved_gate_3) == new_pos_3); + CHECK(obstr_lyt.is_and(obstr_lyt.get_node(new_pos_3)) == true); + CHECK(obstr_lyt.is_and(obstr_lyt.get_node(old_pos_3)) == false); + } + + detail::delete_wires(obstr_lyt); + // I I ▢ + // ↓ ↓ + // F→&→= + // ↓ ↓ + // O ▢ O + // + // ▢ ▢ ▢ + SECTION("Delete wires") + { + CHECK(obstr_lyt.is_po_tile(coordinate{0, 2}) == true); + CHECK(obstr_lyt.is_po_tile(coordinate{0, 3}) == false); + CHECK(obstr_lyt.is_po_tile(coordinate{2, 2}) == true); + CHECK(obstr_lyt.is_po_tile(coordinate{2, 3}) == false); + } + + detail::optimize_output_positions(obstr_lyt); + // I I ▢ + // ↓ ↓ + // F→&→O + // ↓ + // O ▢ ▢ + // + // ▢ ▢ ▢ + SECTION("Optimize outputs") + { + CHECK(obstr_lyt.is_po_tile(coordinate{0, 2}) == true); + CHECK(obstr_lyt.is_po_tile(coordinate{1, 2}) == false); + CHECK(obstr_lyt.is_po_tile(coordinate{2, 2}) == false); + CHECK(obstr_lyt.is_po_tile(coordinate{2, 1}) == true); + } + + SECTION("Optimized layout") + { + CHECK(obstr_lyt.is_pi_tile(coordinate{0, 0}) == true); + CHECK(obstr_lyt.is_pi_tile(coordinate{1, 0}) == true); + CHECK(obstr_lyt.is_buf(obstr_lyt.get_node(coordinate{0, 1})) == true); + CHECK(obstr_lyt.is_and(obstr_lyt.get_node(coordinate{1, 1})) == true); + CHECK(obstr_lyt.is_po_tile(coordinate{0, 2}) == true); + CHECK(obstr_lyt.is_po_tile(coordinate{2, 1}) == true); + } +} + +TEST_CASE("Wrong clocking scheme", "[post_layout_optimization]") +{ + using gate_layout = gate_level_layout>>>; + const auto layout = blueprints::use_and_gate_layout(); + auto obstr_lyt = obstruction_layout(layout); + + SECTION("Call functions") + { + const coordinate old_pos_1 = {2, 0}; + + const auto moved_gate_1 = detail::improve_gate_location(obstr_lyt, old_pos_1); + + CHECK_FALSE(std::get<0>(moved_gate_1)); + CHECK(std::get<1>(moved_gate_1) == old_pos_1); + CHECK_NOTHROW(detail::delete_wires(obstr_lyt)); + + std::vector rows_to_delete{}; + std::vector columns_to_delete{}; + + CHECK_NOTHROW(detail::delete_rows_and_columns(obstr_lyt, rows_to_delete, columns_to_delete)); + CHECK_NOTHROW(detail::optimize_output_positions(obstr_lyt)); + + post_layout_optimization_stats stats_wrong_clocking_scheme{}; + + CHECK_NOTHROW(post_layout_optimization(obstr_lyt, &stats_wrong_clocking_scheme)); + } +} diff --git a/test/layouts/cartesian_layout.cpp b/test/layouts/cartesian_layout.cpp index f8a7b3c37..617fd238d 100644 --- a/test/layouts/cartesian_layout.cpp +++ b/test/layouts/cartesian_layout.cpp @@ -38,6 +38,23 @@ TEST_CASE("Cartesian layout traits", "[cartesian-layout]") CHECK(has_foreach_adjacent_opposite_coordinates_v); } +TEST_CASE("Deep copy Cartesian layout", "[cartesian-layout]") +{ + const cartesian_layout original{{5, 5, 0}}; + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); +} + TEST_CASE("Cartesian coordinate iteration", "[cartesian-layout]") { cartesian_layout::aspect_ratio ar{9, 9, 1}; diff --git a/test/layouts/cell_level_layout.cpp b/test/layouts/cell_level_layout.cpp index 2df3ab24e..5bde5852b 100644 --- a/test/layouts/cell_level_layout.cpp +++ b/test/layouts/cell_level_layout.cpp @@ -27,10 +27,41 @@ TEMPLATE_TEST_CASE("Cell-level layout traits", "[cell-level-layout]", qca_cell_c CHECK(has_set_layout_name_v); } -TEST_CASE("Cell technology", "[cell-level-layout]") +TEST_CASE("Deep copy cell-level layout", "[cell-level-layout]") { - std::stringstream s{}; + using cell_layout = cell_level_layout>>; + + cell_layout original{{5, 5, 0}, twoddwave_clocking(), "Original", 2, 2}; + original.assign_cell_type({0, 2}, qca_technology::cell_type::NORMAL); + original.assign_cell_type({2, 4}, qca_technology::cell_type::NORMAL); + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + copy.replace_clocking_scheme(use_clocking()); + copy.set_layout_name("Copy"); + copy.assign_cell_type({0, 2}, qca_technology::cell_type::INPUT); + copy.assign_cell_type({2, 4}, qca_technology::cell_type::INPUT); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + CHECK(original.is_clocking_scheme(clock_name::TWODDWAVE)); + CHECK(original.get_layout_name() == "Original"); + CHECK(original.get_cell_type({0, 2}) == qca_technology::cell_type::NORMAL); + CHECK(original.get_cell_type({2, 4}) == qca_technology::cell_type::NORMAL); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); + CHECK(copy.is_clocking_scheme(clock_name::USE)); + CHECK(copy.get_layout_name() == "Copy"); + CHECK(copy.get_cell_type({0, 2}) == qca_technology::cell_type::INPUT); + CHECK(copy.get_cell_type({2, 4}) == qca_technology::cell_type::INPUT); +} +TEST_CASE("Cell technology", "[cell-level-layout]") +{ SECTION("QCA") { CHECK(qca_technology::is_empty_cell(qca_technology::cell_type::EMPTY)); diff --git a/test/layouts/clocked_layout.cpp b/test/layouts/clocked_layout.cpp index 45f3eceba..d87eb7a4b 100644 --- a/test/layouts/clocked_layout.cpp +++ b/test/layouts/clocked_layout.cpp @@ -22,6 +22,28 @@ TEST_CASE("Clocked layout traits", "[clocked-layout]") CHECK(has_foreach_outgoing_clocked_zone_v); } +TEST_CASE("Deep copy clocked layout", "[clocked-layout]") +{ + using clk_lyt = clocked_layout>; + + const clk_lyt original{{5, 5, 0}, twoddwave_clocking()}; + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + copy.replace_clocking_scheme(use_clocking()); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + CHECK(original.is_clocking_scheme(clock_name::TWODDWAVE)); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); + CHECK(copy.is_clocking_scheme(clock_name::USE)); +} + TEST_CASE("Clock zone assignment", "[clocked-layout]") { using clk_lyt = clocked_layout>; @@ -119,7 +141,7 @@ TEST_CASE("Iteration over clocking zones", "[clocked-layout]") { using clk_lyt = clocked_layout>; - clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, twoddwave_clocking()}; + const clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, twoddwave_clocking()}; CHECK(layout.incoming_clocked_zones({0, 0}).empty()); CHECK(layout.outgoing_clocked_zones({2, 2}).empty()); @@ -149,7 +171,7 @@ TEST_CASE("Clocked layout properties", "[clocked-layout]") SECTION("2DDWave Clocking") { - clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, twoddwave_clocking()}; + const clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, twoddwave_clocking()}; CHECK(layout.in_degree({0, 0}) == static_cast(0)); CHECK(layout.in_degree({1, 0}) == static_cast(1)); @@ -171,7 +193,7 @@ TEST_CASE("Clocked layout properties", "[clocked-layout]") } SECTION("USE Clocking") { - clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, use_clocking()}; + const clk_lyt layout{clk_lyt::aspect_ratio{2, 2, 0}, use_clocking()}; CHECK(layout.in_degree({0, 0}) == static_cast(1)); CHECK(layout.in_degree({1, 0}) == static_cast(1)); diff --git a/test/layouts/gate_level_layout.cpp b/test/layouts/gate_level_layout.cpp index 58c366f76..de0bb72db 100644 --- a/test/layouts/gate_level_layout.cpp +++ b/test/layouts/gate_level_layout.cpp @@ -32,6 +32,39 @@ TEST_CASE("Gate-level layout traits", "[gate-level-layout]") CHECK(fiction::has_is_empty_v); } +TEST_CASE("Deep copy gate-level layout", "[gate-level-layout]") +{ + using gate_layout = gate_level_layout>>>; + + gate_layout original{gate_layout::aspect_ratio{5, 5, 0}, twoddwave_clocking(), "Original"}; + original.create_pi("x1", {0, 2}); + original.create_pi("x2", {2, 4}); + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + copy.replace_clocking_scheme(use_clocking()); + copy.set_layout_name("Copy"); + copy.move_node(copy.get_node({0, 2}), {0, 0}); + copy.move_node(copy.get_node({2, 4}), {2, 0}); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + CHECK(original.is_clocking_scheme(clock_name::TWODDWAVE)); + CHECK(original.get_layout_name() == "Original"); + CHECK(original.is_pi_tile({0, 2})); + CHECK(original.is_pi_tile({2, 4})); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); + CHECK(copy.is_clocking_scheme(clock_name::USE)); + CHECK(copy.get_layout_name() == "Copy"); + CHECK(copy.is_pi_tile({0, 0})); + CHECK(copy.is_pi_tile({2, 0})); +} + TEST_CASE("Creation and usage of constants", "[gate-level-layout]") { // adapted from mockturtle/test/networks/klut.cpp @@ -45,7 +78,7 @@ TEST_CASE("Creation and usage of constants", "[gate-level-layout]") REQUIRE(mockturtle::has_get_node_v); REQUIRE(mockturtle::has_is_complemented_v); - gate_layout layout{gate_layout::aspect_ratio{2, 2, 1}}; + const gate_layout layout{gate_layout::aspect_ratio{2, 2, 1}}; CHECK(layout.size() == 2); diff --git a/test/layouts/hexagonal_layout.cpp b/test/layouts/hexagonal_layout.cpp index d5624546e..e08d1b1b8 100644 --- a/test/layouts/hexagonal_layout.cpp +++ b/test/layouts/hexagonal_layout.cpp @@ -95,6 +95,23 @@ TEST_CASE("Hexagonal layout traits", "[hexagonal-layout]") } } +TEST_CASE("Deep copy hexagonal layout", "[hexagonal-layout]") +{ + const hexagonal_layout original{{5, 5, 0}}; + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); +} + template void check_identity_conversion() { diff --git a/test/layouts/shifted_cartesian_layout.cpp b/test/layouts/shifted_cartesian_layout.cpp index d28c81a73..1ce5a881b 100644 --- a/test/layouts/shifted_cartesian_layout.cpp +++ b/test/layouts/shifted_cartesian_layout.cpp @@ -95,3 +95,20 @@ TEST_CASE("Shifted Cartesian layout traits", "[shifted-cartesian-layout]") check_common_traits(); } } + +TEST_CASE("Deep copy shifted Cartesian layout", "[shifted-cartesian-layout]") +{ + const shifted_cartesian_layout original{{5, 5, 0}}; + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); +} diff --git a/test/layouts/synchronization_element_layout.cpp b/test/layouts/synchronization_element_layout.cpp index 82f9bcd96..f8441e69c 100644 --- a/test/layouts/synchronization_element_layout.cpp +++ b/test/layouts/synchronization_element_layout.cpp @@ -20,6 +20,36 @@ TEST_CASE("Synchronization element layout traits", "[synchronization-element-lay CHECK(has_synchronization_elements_v); } +TEST_CASE("Deep copy synchronization element layout", "[synchronization-element-layout]") +{ + using se_layout = synchronization_element_layout>>; + + se_layout original{{5, 5, 0}, twoddwave_clocking()}; + original.assign_synchronization_element({0, 0}, 1); + original.assign_synchronization_element({1, 0}, 2); + + auto copy = original.clone(); + + copy.resize({10, 10, 1}); + copy.replace_clocking_scheme(use_clocking()); + copy.assign_synchronization_element({0, 0}, 2); + copy.assign_synchronization_element({1, 0}, 3); + + CHECK(original.x() == 5); + CHECK(original.y() == 5); + CHECK(original.z() == 0); + CHECK(original.is_clocking_scheme(clock_name::TWODDWAVE)); + CHECK(original.get_synchronization_element({0, 0}) == 1); + CHECK(original.get_synchronization_element({1, 0}) == 2); + + CHECK(copy.x() == 10); + CHECK(copy.y() == 10); + CHECK(copy.z() == 1); + CHECK(copy.is_clocking_scheme(clock_name::USE)); + CHECK(copy.get_synchronization_element({0, 0}) == 2); + CHECK(copy.get_synchronization_element({1, 0}) == 3); +} + TEST_CASE("Shifted clocking with synchronization elements", "[synchronization-element-layout]") { using se_layout = synchronization_element_layout>>; diff --git a/test/utils/blueprints/layout_blueprints.hpp b/test/utils/blueprints/layout_blueprints.hpp index ffc6c331e..61abe322b 100644 --- a/test/utils/blueprints/layout_blueprints.hpp +++ b/test/utils/blueprints/layout_blueprints.hpp @@ -375,6 +375,65 @@ GateLyt row_clocked_and_xor_gate_layout() noexcept return layout; } +template +GateLyt optimization_layout() noexcept +{ + GateLyt layout{{2, 3, 1}, fiction::twoddwave_clocking()}; + + const auto x1 = layout.create_pi("x1", {0, 0}); + const auto x2 = layout.create_pi("x2", {2, 0}); + + const auto w1 = layout.create_buf(x1, {0, 1}); + const auto w2 = layout.create_buf(w1, {0, 2}); + const auto w3 = layout.create_buf(w2, {1, 2}); + const auto w4 = layout.create_buf(x2, {2, 1}); + + const auto and1 = layout.create_and(w3, w4, {2, 2}); + + layout.create_po(w2, "f1", {0, 3}); + layout.create_po(and1, "f2", {2, 3}); + + return layout; +} + +template +GateLyt optimization_layout_corner_case_outputs_1() noexcept +{ + GateLyt layout{{2, 3, 1}, fiction::twoddwave_clocking()}; + + const auto x1 = layout.create_pi("x1", {0, 0}); + const auto x2 = layout.create_pi("x2", {0, 1}); + + const auto not1 = layout.create_not(x1, {1, 0}); + const auto w1 = layout.create_buf(x2, {1, 1}); + const auto w2 = layout.create_buf(not1, {1, 1, 1}); + const auto not2 = layout.create_not(w2, {1, 2}); + + layout.create_po(not2, "f1", {1, 3}); + layout.create_po(w1, "f2", {2, 1}); + + return layout; +} + +template +GateLyt optimization_layout_corner_case_outputs_2() noexcept +{ + GateLyt layout{{3, 2, 1}, fiction::twoddwave_clocking()}; + + const auto x1 = layout.create_pi("x1", {0, 0}); + const auto x2 = layout.create_pi("x2", {1, 0}); + + const auto not1 = layout.create_not(x1, {0, 1}); + const auto w1 = layout.create_buf(not1, {1, 1}); + const auto not2 = layout.create_not(w1, {2, 1}); + const auto w2 = layout.create_buf(x2, {1, 1, 1}); + + layout.create_po(not2, "f1", {3, 1}); + layout.create_po(w2, "f2", {1, 2}); + + return layout; +} + template CellLyt single_layer_qca_and_gate() noexcept {