diff --git a/docs/utils/utils.rst b/docs/utils/utils.rst index 29001f724..f2f408cbe 100644 --- a/docs/utils/utils.rst +++ b/docs/utils/utils.rst @@ -54,6 +54,10 @@ Layout Utils .. doxygenfunction:: fiction::num_adjacent_coordinates .. doxygenfunction:: fiction::relative_to_absolute_cell_position .. doxygenfunction:: fiction::port_direction_to_coordinate +.. doxygenfunction:: fiction::normalize_layout_coordinates +.. doxygenfunction:: fiction::convert_to_siqad_coordinates +.. doxygenfunction:: fiction::convert_to_fiction_coordinates + Placement Utils diff --git a/include/fiction/traits.hpp b/include/fiction/traits.hpp index c6e5cc97e..6e80f54cb 100644 --- a/include/fiction/traits.hpp +++ b/include/fiction/traits.hpp @@ -574,6 +574,10 @@ inline constexpr const bool has_inml_technology_v = std::is_same_v inline constexpr const bool has_sidb_technology_v = std::is_same_v, sidb_technology>; template +inline constexpr const bool has_offset_ucoord_v = std::is_same_v, offset::ucoord_t>; +template +inline constexpr const bool has_cube_coord_v = std::is_same_v, cube::coord_t>; +template inline constexpr const bool has_siqad_coord_v = std::is_same_v, siqad::coord_t>; #pragma region is_cell_level_layout diff --git a/include/fiction/utils/layout_utils.hpp b/include/fiction/utils/layout_utils.hpp index 7fd7518ea..9661857d8 100644 --- a/include/fiction/utils/layout_utils.hpp +++ b/include/fiction/utils/layout_utils.hpp @@ -5,11 +5,15 @@ #ifndef FICTION_LAYOUT_UTILS_HPP #define FICTION_LAYOUT_UTILS_HPP +#include "fiction/layouts/cell_level_layout.hpp" +#include "fiction/layouts/coordinates.hpp" #include "fiction/technology/cell_ports.hpp" #include "fiction/traits.hpp" +#include "fiction/types.hpp" #include #include +#include namespace fiction { @@ -30,6 +34,7 @@ template return static_cast(lyt.adjacent_coordinates(c).size()); } + /** * Converts a relative cell position within a tile to an absolute cell position within a layout. To compute the absolute * position, the layout topology is taken into account. @@ -163,6 +168,7 @@ template return {}; } +/** + * A new layout is constructed and returned that is equivalent to the given cell-level layout. However, its coordinates + * are normalized, i.e., start at `(0, 0)` and are all positive. To this end, all existing coordinates are shifted by an + * x and y offset. + * + * @tparam Lyt Cell-level layout type. + * @param lyt The layout which is to be normalized. + * @return New normalized equivalent layout. + */ +template +Lyt normalize_layout_coordinates(const Lyt& lyt) noexcept +{ + static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + + auto x_offset = std::numeric_limits::max(); + auto y_offset = std::numeric_limits::max(); + + lyt.foreach_cell( + [&x_offset, &y_offset](const auto& c) + { + if (c.y <= y_offset) + { + y_offset = c.y; + } + if (c.x <= x_offset) + { + x_offset = c.x; + } + }); + + Lyt lyt_new{{lyt.x() - x_offset, lyt.y() - y_offset, lyt.z()}, + lyt.get_layout_name(), + lyt.get_tile_size_x(), + lyt.get_tile_size_y()}; + + lyt.foreach_cell( + [&lyt_new, &lyt, &x_offset, &y_offset](const auto& c) + { + lyt_new.assign_cell_type({c.x - x_offset, c.y - y_offset}, lyt.get_cell_type(c)); + lyt_new.assign_cell_mode({c.x - x_offset, c.y - y_offset}, lyt.get_cell_mode(c)); + lyt_new.assign_cell_name({c.x - x_offset, c.y - y_offset}, lyt.get_cell_name(c)); + }); + + return lyt_new; +} + +/** + * Converts the coordinates of a given cell-level layout to SiQAD coordinates. A new equivalent layout based on SiQAD + * coordinates is returned. + * + * @tparam Lyt Cell-level layout type based on fiction coordinates, e.g., `offset::ucoord_t` or `cube::coord_t`. + * @param lyt The layout that is to be converted to a new layout based on SiQAD coordinates. + * @return A new equivalent layout based on SiQAD coordinates. + */ +template +sidb_cell_clk_lyt_siqad convert_to_siqad_coordinates(const Lyt& lyt) noexcept +{ + static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); + + sidb_cell_clk_lyt_siqad lyt_new{{lyt.x(), lyt.y(), lyt.z()}, + lyt.get_layout_name(), + lyt.get_tile_size_x(), + lyt.get_tile_size_y()}; + + lyt.foreach_cell( + [&lyt_new, &lyt](const auto& c) + { + lyt_new.assign_cell_type(siqad::to_siqad_coord>(c), lyt.get_cell_type(c)); + lyt_new.assign_cell_mode(siqad::to_siqad_coord>(c), lyt.get_cell_mode(c)); + lyt_new.assign_cell_name(siqad::to_siqad_coord>(c), lyt.get_cell_name(c)); + }); + + return lyt_new; +} + +/** + * Converts the coordinates of a given cell-level layout to fiction coordinates, e.g., `offset::ucoord_t` or + * `cube::coord_t`. A new equivalent layout based on fiction coordinates is returned. + * + * @tparam Lyt Cell-level layout type based on fiction coordinates. + * @param lyt The layout that is to be converted to a new layout based on fiction coordinates. + * @return A new equivalent layout based on fiction coordinates. + */ +template +Lyt convert_to_fiction_coordinates(const sidb_cell_clk_lyt_siqad& lyt) noexcept +{ + static_assert(is_cartesian_layout_v, "Lyt is not a Cartesian layout"); + static_assert(is_cell_level_layout_v, "Lyt is not a cell-level layout"); + static_assert(has_sidb_technology_v, "Lyt is not an SiDB layout"); + + Lyt lyt_new{{lyt.x(), lyt.y(), lyt.z()}, lyt.get_layout_name(), lyt.get_tile_size_x(), lyt.get_tile_size_y()}; + + const auto assign_coordinates = [&lyt_new](const auto& base_lyt) noexcept + { + base_lyt.foreach_cell( + [&lyt_new, &base_lyt](const auto& c) + { + lyt_new.assign_cell_type(siqad::to_fiction_coord>(c), base_lyt.get_cell_type(c)); + lyt_new.assign_cell_mode(siqad::to_fiction_coord>(c), base_lyt.get_cell_mode(c)); + lyt_new.assign_cell_name(siqad::to_fiction_coord>(c), base_lyt.get_cell_name(c)); + }); + }; + + if (has_offset_ucoord_v && !lyt.is_empty()) + { + auto lyt_normalized = normalize_layout_coordinates(lyt); + assign_coordinates(lyt_normalized); + lyt_new.resize({lyt_normalized.x(), lyt_normalized.y(), lyt_normalized.z()}); + } + else + { + assign_coordinates(lyt); + } + + return lyt_new; +} + } // namespace fiction #endif // FICTION_LAYOUT_UTILS_HPP diff --git a/test/utils/layout_utils.cpp b/test/utils/layout_utils.cpp index 8f740902a..b484a01e0 100644 --- a/test/utils/layout_utils.cpp +++ b/test/utils/layout_utils.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -38,3 +39,182 @@ TEMPLATE_TEST_CASE("Port directions to coordinates", "[layout-utils]", (cartesia lyt.north_west(c)); }); } + +TEMPLATE_TEST_CASE("siqad layout is normalized, shifted to positive coordinates", "[layout-utils]", + sidb_cell_clk_lyt_siqad) +{ + SECTION("empty layout") + { + TestType lyt{{}, "layout based on siqad coordinates"}; + + lyt.assign_cell_type({-5, -1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 1}, TestType::cell_type::NORMAL); + + auto lyt_transformed = normalize_layout_coordinates(lyt); + + CHECK(lyt_transformed.get_layout_name() == "layout based on siqad coordinates"); + CHECK(lyt_transformed.get_cell_type({0, 0}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({10, 2}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({-5, -1}) == TestType::cell_type::EMPTY); + } +} + +TEMPLATE_TEST_CASE("Convert offset::ucoord_t layout to SiQAD coordinate layout", "[layout-utils]", sidb_cell_clk_lyt) +{ + SECTION("empty layout") + { + TestType lyt{{10, 10}, "test"}; + + auto lyt_transformed = convert_to_siqad_coordinates(lyt); + + CHECK(lyt_transformed.is_empty()); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_layout_name() == lyt.get_layout_name()); + } + + SECTION("layout with one normal and one input cell") + { + TestType lyt{{5, 3}}; + + lyt.assign_cell_type({5, 3}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 1}, TestType::cell_type::INPUT); + + auto lyt_transformed = convert_to_siqad_coordinates(lyt); + + CHECK(lyt_transformed.num_cells() == 2); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_cell_type({5, 1, 1}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({5, 0, 1}) == TestType::cell_type::INPUT); + } + + SECTION("layout with three cells") + { + TestType lyt{{5, 3}}; + + lyt.assign_cell_type({0, 0}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 3}, TestType::cell_type::INPUT); + lyt.assign_cell_type({5, 1}, TestType::cell_type::OUTPUT); + lyt.assign_cell_name({0, 0}, "normal cell"); + lyt.assign_cell_name({5, 3}, "input cell"); + lyt.assign_cell_name({5, 1}, "output cell"); + + auto lyt_transformed = convert_to_siqad_coordinates(lyt); + + CHECK(lyt_transformed.num_cells() == 3); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_cell_type({0, 0, 0}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({5, 1, 1}) == TestType::cell_type::INPUT); + CHECK(lyt_transformed.get_cell_type({5, 0, 1}) == TestType::cell_type::OUTPUT); + CHECK(lyt_transformed.get_cell_name({0, 0, 0}) == "normal cell"); + CHECK(lyt_transformed.get_cell_name({5, 1, 1}) == "input cell"); + CHECK(lyt_transformed.get_cell_name({5, 0, 1}) == "output cell"); + } +} + +TEMPLATE_TEST_CASE("Convert SiQAD layout to offset::ucoord_t coordinate layout", "[layout-utils]", sidb_cell_clk_lyt) +{ + SECTION("empty layout") + { + const sidb_cell_clk_lyt_siqad lyt{{}, "layout based on siqad coordinates"}; + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.is_empty()); + CHECK(static_cast(lyt_transformed.area()) == lyt.area()); + CHECK(lyt_transformed.get_layout_name() == lyt.get_layout_name()); + } + + SECTION("layout with one normal and one input cell") + { + sidb_cell_clk_lyt_siqad lyt{{5, 3}}; + + lyt.assign_cell_type({5, 3}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({-5, -1}, TestType::cell_type::INPUT); + CHECK(lyt.x() == 5); + CHECK(lyt.y() == 3); + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.x() == 10); + CHECK(lyt_transformed.y() == 4); + CHECK(lyt_transformed.num_cells() == 2); + CHECK(lyt_transformed.get_cell_type({10, 8}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({0, 0}) == TestType::cell_type::INPUT); + } + + SECTION("layout with three cells") + { + sidb_cell_clk_lyt_siqad lyt{{5, 3}}; + + lyt.assign_cell_type({5, 3}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 0}, TestType::cell_type::INPUT); + lyt.assign_cell_type({5, 1}, TestType::cell_type::OUTPUT); + lyt.assign_cell_name({5, 3}, "normal cell"); + lyt.assign_cell_name({0, 0}, "input cell"); + lyt.assign_cell_name({5, 1}, "output cell"); + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.num_cells() == 3); + CHECK(static_cast(lyt_transformed.area()) == lyt.area()); + CHECK(lyt_transformed.get_cell_type({5, 6}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({0, 0}) == TestType::cell_type::INPUT); + CHECK(lyt_transformed.get_cell_type({5, 2}) == TestType::cell_type::OUTPUT); + CHECK(lyt_transformed.get_cell_name({5, 6}) == "normal cell"); + CHECK(lyt_transformed.get_cell_name({0, 0}) == "input cell"); + CHECK(lyt_transformed.get_cell_name({5, 2}) == "output cell"); + } +} + +TEMPLATE_TEST_CASE("Convert SiQAD layout to cube::coord_t coordinate layout", "[layout-utils]", + (cell_level_layout>>)) +{ + SECTION("empty layout") + { + const sidb_cell_clk_lyt_siqad lyt{{}, "layout based on siqad coordinates"}; + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.is_empty()); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_layout_name() == lyt.get_layout_name()); + } + + SECTION("layout with one normal and one input cell") + { + sidb_cell_clk_lyt_siqad lyt{{5, 1, 1}}; + + lyt.assign_cell_type({5, -1, 1}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({5, 1, 0}, TestType::cell_type::INPUT); + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.num_cells() == 2); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_cell_type({5, -1}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({5, 2, 0}) == TestType::cell_type::INPUT); + } + + SECTION("layout with three cells") + { + sidb_cell_clk_lyt_siqad lyt{{5, 3}}; + + lyt.assign_cell_type({5, -3}, TestType::cell_type::NORMAL); + lyt.assign_cell_type({0, 0}, TestType::cell_type::INPUT); + lyt.assign_cell_type({5, 3}, TestType::cell_type::OUTPUT); + lyt.assign_cell_name({5, -3}, "normal cell"); + lyt.assign_cell_name({0, 0}, "input cell"); + lyt.assign_cell_name({5, 3}, "output cell"); + + auto lyt_transformed = convert_to_fiction_coordinates(lyt); + + CHECK(lyt_transformed.num_cells() == 3); + CHECK(lyt_transformed.area() == static_cast(lyt.area())); + CHECK(lyt_transformed.get_cell_type({5, -6}) == TestType::cell_type::NORMAL); + CHECK(lyt_transformed.get_cell_type({0, 0}) == TestType::cell_type::INPUT); + CHECK(lyt_transformed.get_cell_type({5, 6}) == TestType::cell_type::OUTPUT); + CHECK(lyt_transformed.get_cell_name({5, -6}) == "normal cell"); + CHECK(lyt_transformed.get_cell_name({0, 0}) == "input cell"); + CHECK(lyt_transformed.get_cell_name({5, 6}) == "output cell"); + } +}