diff --git a/bindings/mnt/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp b/bindings/mnt/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp index 95bf93cccf..bc02b4c21b 100644 --- a/bindings/mnt/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp +++ b/bindings/mnt/pyfiction/include/pyfiction/pybind11_mkdoc_docstrings.hpp @@ -5032,6 +5032,26 @@ Template parameter ``WiringReductionLyt``: Parameter ``lyt``: The wiring_reduction_layout to which obstructions will be added.)doc"; +static const char *__doc_fiction_detail_adjust_final_values = +R"doc(Balances the final x and y wiring coordinates across all nodes in a +level. + +The function identifies the minimal routing demand, which means the +closest diagonal, where all nodes can be placed and can be routed +without conflicts. + +Parameter ``x``: + Vector of x-coordinates to be adjusted. + +Parameter ``y``: + Vector of y-coordinates to be adjusted. + +Parameter ``two_input_indices``: + Indices of two-input nodes within the level. + +Parameter ``two_input_new_lines``: + Number of new routing lines associated with each two-input node.)doc"; + static const char *__doc_fiction_detail_adjust_tile = R"doc(This function adjusts the tile and gates in the layout after deleting wires. It shifts gates to fill the empty coordinates and adjusts the @@ -5217,6 +5237,66 @@ Parameter ``defect_lyt``: Returns: A `CellLyt` object representing the generated cell layout.)doc"; +static const char *__doc_fiction_detail_calculate_allowed_orientation = +R"doc(Determines the allowed orientation of a node based on its predecessor. +For nodes driven by a fan-out, the orientation is defined by their +relative rank position among the fan-out's successors. + +Returns: - 0: Node is the first (east) successor of its predecessor. - +1: Node is the second (west) successor of its predecessor. - 2: Node +is not driven by a fan-out. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Logic network containing the node. + +Parameter ``n``: + Node for which to determine the allowed orientation. + +Returns: + Orientation code (0–2) describing the node's relative position.)doc"; + +static const char *__doc_fiction_detail_calculate_buffer_connection_type = +R"doc(Computes the buffer connection type for a given node. Determines +whether the node serves as the second (rightmost) fan-in of its +successor node. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Logic network containing the node. + +Parameter ``n``: + Node to analyze. + +Returns: + 1 if the node is the rightmost fan-in of its successor, otherwise + 0.)doc"; + +static const char *__doc_fiction_detail_calculate_fanout_connection_type = +R"doc(Computes the fan-out connection type of a node based on its +successors' fan-in structures. Assumes exactly two fan-outs, which are +ordered by rank position. + +Returns: - 0: Both fan-outs have a single fan-in. - 1: First fan-out +has one fan-in, second has multiple. - 2: Second fan-out has one fan- +in, first has multiple. - 3: Both fan-outs have multiple fan-ins. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Logic network containing the node. + +Parameter ``n``: + Node for which to determine the fan-out connection type. + +Returns: + Integer code (0–3) indicating the fan-out connection pattern.)doc"; + static const char *__doc_fiction_detail_calculate_offset_matrix = R"doc(Calculate an offset matrix based on a to-delete list in a `wiring_reduction_layout`. @@ -5243,6 +5323,94 @@ Parameter ``to_delete``: Returns: A 2D vector representing the calculated offset matrix.)doc"; +static const char *__doc_fiction_detail_calculate_pairs = +R"doc(Calculates pairs of nodes from a given vector of nodes. + +This function takes a vector of nodes and returns a vector of node +pairs. Each node pair consists of two nodes from the input vector and +an optional vector of middle nodes. The delay of each node pair is +initialized to infinity. + +Template parameter ``Ntk``: + The network type. + +Parameter ``nodes``: + The vector of nodes. + +Returns: + The vector of node pairs.)doc"; + +static const char *__doc_fiction_detail_calculate_predecessor_gap = +R"doc(Computes the gap between the fan-in node and its preceding node, i.e., +the node with a rank position one less than the current node. This +value indicates the available spacing for placement. + +Template parameter ``Ntk``: + Logic network type. + +Template parameter ``Lyt``: + Layout type. + +Parameter ``ntk``: + Logic network containing the node. + +Parameter ``node2pos``: + Mapping from network nodes to layout tile positions. + +Parameter ``lvl``: + Current level index of the node. + +Parameter ``n``: + Node for which to compute the predecessor gap. + +Returns: + Gap size (clamped to a maximum of 2).)doc"; + +static const char *__doc_fiction_detail_calculate_start_orientation = +R"doc(Determines the initial orientation for a given network level. The +orientation is inferred from the structural pattern of nodes within +the specified level, considering their fan-in and fan-out +relationships. + +Returns: - 0: Default orientation (no specific structure found). - 1: +Level starts with a fan-out or buffer structure. - 3: Level starts +with a two-input (binary) gate. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Logic network to analyze. + +Parameter ``lvl``: + Level index for which to compute the starting orientation. + +Returns: + Orientation code (0, 1, or 3) defining the level's initial + direction.)doc"; + +static const char *__doc_fiction_detail_calculate_two_input_new_lines = +R"doc(Computes the number of new routing lines required. + +Template parameter ``Ntk``: + Logic network type. + +Template parameter ``Lyt``: + Layout type. + +Parameter ``ntk``: + Logic network to analyze. + +Parameter ``node2pos``: + Mapping from network nodes to layout tile positions. + +Parameter ``lvl``: + Level index to inspect. + +Returns: + Vector of gap sizes (new line counts) for all two-input nodes in + the level.)doc"; + static const char *__doc_fiction_detail_check_and_optimize_po_positions = R"doc(Utility function that checks and optimizes PO positions after each gate relocation iteration. This function moves POs that are not @@ -5889,6 +6057,21 @@ Parameter ``lyt``: The number of primary outputs that are placed to the right of the middle primary output.)doc"; +static const char *__doc_fiction_detail_compute_two_input_indices = +R"doc(Collects the indices of all two-input nodes in a given level. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Logic network to analyze. + +Parameter ``lvl``: + Level index to inspect. + +Returns: + Vector of node indices with two fan-ins in the specified level.)doc"; + static const char *__doc_fiction_detail_connect_and_place = R"doc()doc"; static const char *__doc_fiction_detail_connect_and_place_2 = R"doc()doc"; @@ -5923,6 +6106,44 @@ static const char *__doc_fiction_detail_create_array = R"doc(From https://stackoverflow.com/questions/57756557/initializing-a- stdarray-with-a-constant-value)doc"; +static const char *__doc_fiction_detail_create_virtual_pi_ntk_from_duplicated_nodes = +R"doc(Constructs a planar `virtual_pi_network` based on duplicated nodes +derived from the source network. + +The input `ntk_lvls` contains per-level vectors of original node ranks +in the source network. For each level, this function creates +corresponding nodes (including duplicates) in a new +`virtual_pi_network` and restores their fanin relations using the +`gather_fanin_signals` helper function. + +For duplicated PIs (Primary Inputs), virtual PIs are created, and the +original PI is stored in a mapping structure. The auxiliary function +`gather_fanin_signals` collects fanin data for each node and matches +it to its corresponding nodes in the `virtual_pi_network`. + +Example: For a level {2, 3, 2, 4, 2}, new nodes are created for each +duplicated occurrence (e.g., node 2) and stored in the `old2new_v` +node map. This map is then used by `gather_fanin_signals` to correctly +establish fanin relationships between newly created nodes. + +Template parameter ``Ntk``: + Network type. + +Parameter ``ntk``: + Source network used to construct the `virtual_pi_network`. + +Parameter ``ntk_lvls``: + Per-level vectors of original node ranks in the source network + used to derive node duplications. + +Parameter ``ntk_lvls_new``: + Per-level vectors of newly created nodes' ranks in the constructed + `virtual_pi_network`. + +Returns: + The constructed planar `virtual_pi_network` containing duplicated + nodes with restored fanin and fanout relations.)doc"; + static const char *__doc_fiction_detail_create_wiring_reduction_layout = R"doc(Create a wiring_reduction_layout suitable for finding excess wiring based on a Cartesian layout. @@ -8680,6 +8901,35 @@ static const char *__doc_fiction_detail_hexagonalization_impl_pst = R"doc(Hexago static const char *__doc_fiction_detail_hexagonalization_impl_run = R"doc()doc"; +static const char *__doc_fiction_detail_hgraph_node = +R"doc(Represents one node in the H-graph used for crossing minimization. + +For a node in level l of the input network, all possible orderings of +its fanins from layer l−1 are enumerated. Each such ordering is +represented by an H-graph node. The first and last fanins of the +ordering are stored, since these determine the delay in the H-graph. +The remaining fanins are placed in middle. Their mutual order is +irrelevant for this algorithm. + +Template parameter ``Ntk``: + Network type from which node types are drawn.)doc"; + +static const char *__doc_fiction_detail_hgraph_node_delay = R"doc(Specifies the delay value for the hgraph_node.)doc"; + +static const char *__doc_fiction_detail_hgraph_node_fanin_it = R"doc(Index of the predecessor H-graph node.)doc"; + +static const char *__doc_fiction_detail_hgraph_node_hgraph_node = +R"doc(Constructs an H-graph node with given first and last fanins and delay. + +Parameter ``first``: + The first (leftmost) fanin in the ordering. + +Parameter ``last``: + The last (rightmost) fanin in the ordering. + +Parameter ``delay_value``: + The delay value for the node.)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"; @@ -9257,6 +9507,88 @@ static const char *__doc_fiction_detail_new_gate_location_NONE = R"doc(Do not ch static const char *__doc_fiction_detail_new_gate_location_SRC = R"doc(Check if the source tile is empty.)doc"; +static const char *__doc_fiction_detail_node_duplication_planarization_impl = R"doc()doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_compute_slice_delays = +R"doc(A "slice" describes one vertical layer in the H-graph. It is created +by adding all possible combinations of a `node_pair` to the H-graph of +the level. These combinations are formed by selecting pairs of nodes +from the fan-ins of the input node: - If the input node has only one +fan-in, it is treated as a single combination. - If the input node has +two fan-ins, there are two possible combinations. + +Each `node_pair` consists of a first and second element. The objective +is to find an ordering of node pairs that maximizes the instances +where the first element of a node_pair matches the second element of +the preceding node_pair. This ordering is given as a linked list. + +This function computes the optimal ordering by calculating delays as +follows: - All combinations of node pairs are iteratively added to a +linked list. - For each combination, the first element of the current +node_pair is compared with the last element of the preceding +node_pairs. - If a connection exists between two node_pairs, the delay +increases by 1; otherwise, it increases by 2. The default delay for +the first node is 1. - If a node_pair lacks a connection, and its +updated delay (increased by 2) is less than the existing delay, the +node_pair's delay is updated accordingly. + +Processed node_pairs are stored in the `lvl_pairs` member for +subsequent delay calculations. + +Parameter ``nd``: + Node in the H-graph.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_fanout_state = +R"doc(Represents the current state of fanout saturation during node +insertion. A fanout is saturated if it has reached its maximum fanout +limit.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_fanout_state_NORMAL = R"doc(No fanout saturation is active.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_fanout_state_SATURATED = R"doc(Fanout saturation is active.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_insert_if_not_first = +R"doc(Inserts a node into a vector if it is unique. + +This function inserts a node into a vector only if the vector is empty +or the node is not equal to the first element of the vector. If the +vector is not empty and the node is equal to the first element, +insertion depends on the `saturated_fanout_flag` and the node's +`position`: when `position == 0`, a repeated insertion attempt will +succeed only if the node was previously skipped (indicated by +`saturated_fanout_flag == 1`); otherwise, the flag is set to 1 and the +node is skipped for this call. No exception is thrown during this +process. + +Parameter ``node``: + The node to be inserted. + +Parameter ``vec``: + The vector to insert the node into. + +Parameter ``saturated_fanout_flag``: + A state flag toggled when consecutive duplicate insertions occur. + Set to 1 when a node is skipped and reset to 0 when a node is + successfully inserted. + +Parameter ``position``: + The position of the node (0 indicates a terminal node; controls + duplicate insertion behavior).)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_insertion_position = R"doc(Specifies the position of a node during insertion.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_insertion_position_INNER = R"doc(The node is an inner (non-terminal) fanin node.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_insertion_position_TERMINAL = R"doc(The node is a terminal node of a fanin relation.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_lvl_pairs = R"doc(The currently node_pairs used in the current level.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_node_duplication_planarization_impl = R"doc()doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_ntk_lvls = R"doc(The network stored as levels.)doc"; + +static const char *__doc_fiction_detail_node_duplication_planarization_impl_ps = R"doc(The stats of the node_duplication class.)doc"; + static const char *__doc_fiction_detail_non_operationality_reason = R"doc(Reason why a layout is non-operational.)doc"; static const char *__doc_fiction_detail_non_operationality_reason_KINKS = R"doc(Kinks induced the layout to become non-operational.)doc"; @@ -9851,6 +10183,44 @@ static const char *__doc_fiction_detail_placement_info_current_po = R"doc(The in static const char *__doc_fiction_detail_placement_info_node2pos = R"doc(Mapping of nodes to their positions in the layout.)doc"; +static const char *__doc_fiction_detail_plane_impl = +R"doc(Implements the general planar layout generation algorithm. + +The algorithm performs placement and routing level by level, starting +from the primary inputs and proceeding toward the outputs. For each +level, node placement depends on the orientation and excess wiring +computed in auxiliary routines such as `compute_pr_variables` and +`compute_wiring`. Nodes are positioned according to their fan-in +structure and routing constraints to ensure planarity of the resulting +layout. + +Template parameter ``Lyt``: + Gate-level layout type. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``src``: + Source network to be placed and routed. + +Parameter ``p``: + Parameters controlling layout generation and clocking. + +Parameter ``st``: + Statistics object used to collect runtime and layout information.)doc"; + +static const char *__doc_fiction_detail_plane_impl_ntk = R"doc(The input network wrapped in a fanout view.)doc"; + +static const char *__doc_fiction_detail_plane_impl_plane_impl = R"doc()doc"; + +static const char *__doc_fiction_detail_plane_impl_po_counter = R"doc(Primary output counter.)doc"; + +static const char *__doc_fiction_detail_plane_impl_ps = R"doc(The parameters controlling the planar layout from a network embedding.)doc"; + +static const char *__doc_fiction_detail_plane_impl_pst = R"doc(Reference to the statistics collected during planar layout generation.)doc"; + +static const char *__doc_fiction_detail_plane_impl_run = R"doc()doc"; + 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 = @@ -17087,6 +17457,46 @@ Parameter ``file``: Parameter ``rfun``: The actual parsing function.)doc"; +static const char *__doc_fiction_node_duplication_planarization = +R"doc(Implements a planarization mechanism for networks from the paper +\"Fabricatable Interconnect and Molecular QCA Circuits\" by Amitabh +Chaudhary, Danny Ziyi Chen, Xiaobo Sharon Hu, Michael T. Niemier, +Ramprasad Ravichandran and Kevin Whitton in IEEE Transactions on +Computer-Aided Design of Integrated Circuits and Systems, Volume 26, +2007. + +The planarization achieved by this function solves the Node +Duplication Crossing Minimization (NDCE) problem by finding the +shortest x-y path in the H-graph for every level in the network. An +H-graph describes edge relations between two levels in a network, with +one level assumed as fixed, starting at the Primary Outputs (POs). By +finding the shortest path from the source (x) to the sink (y) in this +H-graph, an optimal solution for the NDCE problem for each level is +found. The function traverses from the Primary Outputs (POs) towards +the Primary Inputs (PIs). + +Template parameter ``Ntk``: + Source network type. + +Parameter ``ntk``: + Source network to be utilized for the planarization. + +Parameter ``ps``: + Node duplication parameters used in the computation. + +Returns: + A planarized virtual_pi_network.)doc"; + +static const char *__doc_fiction_node_duplication_planarization_params = R"doc(Parameters for the node duplication algorithm.)doc"; + +static const char *__doc_fiction_node_duplication_planarization_params_output_order = R"doc(Controls how output nodes are ordered before starting the algorithm.)doc"; + +static const char *__doc_fiction_node_duplication_planarization_params_output_order_KEEP_PO_ORDER = R"doc(Keep the PO order from the input network.)doc"; + +static const char *__doc_fiction_node_duplication_planarization_params_output_order_RANDOM_PO_ORDER = R"doc(Randomize the PO order.)doc"; + +static const char *__doc_fiction_node_duplication_planarization_params_po_order = R"doc(The output order used. Defaults to KEEP_PO_ORDER.)doc"; + static const char *__doc_fiction_normalize_layout_coordinates = R"doc(A new layout is constructed and returned that is equivalent to the given cell-level layout. However, its coordinates are normalized, @@ -18416,6 +18826,65 @@ Parameter ``node2pos``: Returns: Signal to the newly placed gate in `lyt`.)doc"; +static const char *__doc_fiction_planar_layout_from_network_embedding_params = R"doc(Parameters for the planar layout from network embedding algorithm.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_params_number_of_clock_phases = R"doc(Number of clock phases to use. 3 and 4 are supported.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_params_verbose = R"doc(Verbosity.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats = R"doc(This struct stores statistics about the planar layout design process.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_duration = R"doc(Runtime of the planar layout design process.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_num_crossings = R"doc(Number of crossings.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_num_gates = R"doc(Number of gates.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_num_wires = R"doc(Number of wires.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_report = +R"doc(Reports the statistics to the given output stream. + +Parameter ``out``: + Output stream.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_x_size = R"doc(Layout width.)doc"; + +static const char *__doc_fiction_planar_layout_from_network_embedding_stats_y_size = R"doc(Layout height.)doc"; + +static const char *__doc_fiction_plane = +R"doc(This algorithm constructs a planar layout from the planar embedding of +a logic network, forming the Planar Layout from Network Embedding +(PLANE) methodology. It provides a fully planar physical design flow +for Field-Coupled Nanocomputing (FCN) circuits. The algorithm operates +on a logic network with an existing planar embedding, represented as a +`mutable_rank_view`, and preserves this embedding during placement and +routing. + +In this approach, each logic level of the network is mapped to a +diagonal in the layout, while nodes within the same level are placed +according to their rank positions in the planar embedding. This +ensures a crossing-free, scalable, and layout-consistent mapping from +logic to physical design. + +Template parameter ``Lyt``: + Gate-level layout type. + +Template parameter ``Ntk``: + Logic network type. + +Parameter ``ntk``: + Planar logic network to be placed and routed. + +Parameter ``ps``: + Configuration parameters for the physical design process. + +Parameter ``pst``: + Optional statistics object to collect runtime and layout metrics. + +Returns: + A fully planar gate-level layout of type `Lyt`.)doc"; + static const char *__doc_fiction_pointy_top_hex = R"doc(\verbatim / \ / \ | | | | \ / \ / \endverbatim)doc"; static const char *__doc_fiction_population_stability_information = diff --git a/cli/cmd/physical_design/cmd_physical_design.hpp b/cli/cmd/physical_design/cmd_physical_design.hpp index 8449d39661..f6422598cd 100644 --- a/cli/cmd/physical_design/cmd_physical_design.hpp +++ b/cli/cmd/physical_design/cmd_physical_design.hpp @@ -18,6 +18,7 @@ #endif #include "include/optimize.hpp" #include "include/ortho.hpp" +#include "include/plane.hpp" // NOLINTEND(misc-include-cleaner) namespace alice @@ -36,6 +37,7 @@ ALICE_ADD_COMMAND(onepass, FICTION_CLI_CATEGORY_PHYSICAL_DESIGN) #endif ALICE_ADD_COMMAND(optimize, FICTION_CLI_CATEGORY_PHYSICAL_DESIGN) ALICE_ADD_COMMAND(ortho, FICTION_CLI_CATEGORY_PHYSICAL_DESIGN) +ALICE_ADD_COMMAND(plane, FICTION_CLI_CATEGORY_PHYSICAL_DESIGN) } // namespace alice diff --git a/cli/cmd/physical_design/include/plane.hpp b/cli/cmd/physical_design/include/plane.hpp new file mode 100644 index 0000000000..219165a704 --- /dev/null +++ b/cli/cmd/physical_design/include/plane.hpp @@ -0,0 +1,82 @@ +// +// Created by benjamin on 21.11.25. +// + +#ifndef FICTION_CMD_PLANE_HPP +#define FICTION_CMD_PLANE_HPP + +#include +#include +#include +#include + +#include +#include + +#include + +namespace alice +{ +/** + * Executes the physical design approach Planar Layout from Network Embedding (PLANE), which finds a valid placement and + * routing without crossings given a planar network embedding. + * + * See include/fiction/algorithms/physical_design/planar_layout_from_network_embedding.hpp for more details. + */ +class plane_command final : public command +{ + public: + /** + * Standard constructor. Adds descriptive information, options, and flags. + * + * @param e alice::environment that specifies stores etc. + */ + explicit plane_command(const environment::ptr& e); + + protected: + /** + * Function to perform the physical design call. Generates a placed and routed FCN gate layout. + */ + void execute() override; + + /** + * Logs the resulting information in a log file. + * + * @return JSON object containing information about the physical design process. + */ + nlohmann::json log() const override; + + private: + /** + * Parameters for fanout substitution. + */ + fiction::fanout_substitution_params fan_ps{}; + /** + * Parameters for network path balancing. + */ + fiction::network_balancing_params bal_ps{}; + /** + * Parameters for planarization via node duplication. + */ + fiction::node_duplication_planarization_params dup_ps{}; + /** + * Parameters for planar layout generation from the network embedding. + */ + fiction::planar_layout_from_network_embedding_params ps{}; + /** + * Statistics collected during planar layout generation. + */ + fiction::planar_layout_from_network_embedding_stats st{}; + /** + * Number of clock phases used in the layout. + */ + uint8_t num_clock_phases = 4u; + /** + * Determines whether primary output order is kept (0) or randomized (1). + */ + uint32_t po_order{0u}; +}; + +} // namespace alice + +#endif // FICTION_CMD_PLANE_HPP diff --git a/cli/cmd/physical_design/src/plane.cpp b/cli/cmd/physical_design/src/plane.cpp new file mode 100644 index 0000000000..968f5cdcac --- /dev/null +++ b/cli/cmd/physical_design/src/plane.cpp @@ -0,0 +1,133 @@ +// +// Created by benjamin on 21.11.25. +// + +#include "cmd/physical_design/include/plane.hpp" + +#include "stores.hpp" // NOLINT(misc-include-cleaner) + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace alice +{ + +plane_command::plane_command(const environment::ptr& e) : + command(e, "First, a planar embedding is obtained by performing fanout substitution, path balancing, and " + "planarization (node duplication). Then, scalable placement and routing are carried out on the " + "planar layout of the current logic network in store. Compared to the gold algorithm, the " + "resulting layout may occupy more area, but it is generated in reasonable runtime.") +{ + // fanout substitution options + fan_ps.degree = 2; + // balancing option + bal_ps.unify_outputs = true; + // planarization option + add_option("--po-order, -p", po_order, "PO order: keep=0, random=1")->set_type_name("{0,1}"); + // plane options + add_option("--clock_numbers,-n", num_clock_phases, "Number of clock phases to be used {3 or 4}"); + add_flag("--verbose,-v", ps.verbose, "Be verbose"); +} + +void plane_command::execute() +{ + auto& lns = store(); + auto& gls = store(); + + if (lns.empty()) + { + env->out() << "[w] no logic network in store\n"; + return; + } + // error case: phases out of range + if (num_clock_phases != 3u && num_clock_phases != 4u) + { + env->out() << "[e] only 3- and 4-phase clocking schemes are supported\n"; + ps = {}; + return; + } + + ps.number_of_clock_phases = num_clock_phases == 3 ? fiction::num_clks::THREE : fiction::num_clks::FOUR; + + switch (po_order) + { + case 0u: + { + dup_ps.po_order = fiction::node_duplication_planarization_params::output_order::KEEP_PO_ORDER; + break; + } + case 1u: + { + dup_ps.po_order = fiction::node_duplication_planarization_params::output_order::RANDOM_PO_ORDER; + break; + } + default: + { + env->out() << "[w] invalid --po-order, defaulting to keep\n"; + dup_ps.po_order = fiction::node_duplication_planarization_params::output_order::KEEP_PO_ORDER; + break; + } + } + + const auto perform_fanouts_and_balance = [this](auto&& ntk_ptr) + { + auto tec_f = fiction::fanout_substitution(*ntk_ptr, fan_ps); + return std::make_shared(fiction::network_balancing(tec_f, bal_ps)); + }; + + auto tec_b = std::visit(perform_fanouts_and_balance, lns.current()); + + const fiction::mutable_rank_view vpi_r(*tec_b); + + auto planarized_ntk = fiction::node_duplication_planarization(vpi_r, dup_ps); + + try + { + gls.extend() = std::make_shared( + fiction::plane(planarized_ntk, ps, &st)); + } + catch (const fiction::high_degree_fanin_exception& e) + { + env->out() << fmt::format("[e] {}\n", e.what()); + } + catch (const std::invalid_argument& e) + { + env->out() << fmt::format("[e] {}\n", e.what()); + } + catch (const std::exception& e) + { + env->out() << fmt::format("[e] unexpected error: {}\n", e.what()); + } + + fan_ps = {}; + bal_ps = {}; + dup_ps = {}; +} + +nlohmann::json plane_command::log() const +{ + return nlohmann::json{{"runtime in seconds", mockturtle::to_seconds(st.time_total)}, + {"number of gates", st.num_gates}, + {"number of wires", st.num_wires}, + {"number of crossings", st.num_crossings}, + {"layout", {{"x-size", st.x_size}, {"y-size", st.y_size}, {"area", st.x_size * st.y_size}}}}; +} + +} // namespace alice diff --git a/docs/changelog.rst b/docs/changelog.rst index eddb1debbb..ba2a918b50 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,9 @@ Unreleased Added ##### +- Algorithms: + - Node Duplication Planarization, reimplemented from "Fabricatable Interconnect and Molecular QCA Circuits" by Amitabh Chaudhary, Danny Ziyi Chen, Xiaobo Sharon Hu, Michael T. Niemier, Ramprasad Ravichandran, and Kevin Whitton. + - Planar Layout from Network Embedding (PLANE) for generating planar 2DDWave-clocked Cartesian gate-level layouts in a fast and scalable fashion. - Documentation: - Added ``AGENTS.md`` to guide AI agents in the repository diff --git a/include/fiction/algorithms/network_transformation/node_duplication_planarization.hpp b/include/fiction/algorithms/network_transformation/node_duplication_planarization.hpp new file mode 100644 index 0000000000..526234e60d --- /dev/null +++ b/include/fiction/algorithms/network_transformation/node_duplication_planarization.hpp @@ -0,0 +1,873 @@ +// +// Created by benjamin on 11.06.24. +// + +#ifndef FICTION_NODE_DUPLICATION_PLANARIZATION_HPP +#define FICTION_NODE_DUPLICATION_PLANARIZATION_HPP + +#include "fiction/algorithms/graph/mincross.hpp" +#include "fiction/algorithms/network_transformation/network_balancing.hpp" +#include "fiction/networks/virtual_pi_network.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fiction +{ + +/** + * Parameters for the node duplication algorithm. + */ +struct node_duplication_planarization_params +{ + /** + * Controls how output nodes are ordered before starting the algorithm. + */ + enum class output_order : uint8_t + { + /** + * Keep the PO order from the input network. + */ + KEEP_PO_ORDER, + /** + * Randomize the PO order. + */ + RANDOM_PO_ORDER + }; + /** + * The output order used. Defaults to KEEP_PO_ORDER. + */ + output_order po_order = output_order::KEEP_PO_ORDER; +}; + +namespace detail +{ + +template +using levelized_node_order = std::vector>>; + +/** + * Represents one node in the H-graph used for crossing minimization. + * + * For a node in level l of the input network, all possible orderings of its fanins from layer l−1 are enumerated. + * Each such ordering is represented by an H-graph node. The first and last fanins of the ordering are stored, since + * these determine the delay in the H-graph. The remaining fanins are placed in middle. Their mutual order is irrelevant + * for this algorithm. + * + * @tparam Ntk Network type from which node types are drawn. + */ +template +struct hgraph_node +{ + /** + * First and last fanin. + */ + std::pair, mockturtle::node> outer_fanins; + /** + * All remaining fanins. + */ + std::vector> middle_fanins; + /** + * Specifies the delay value for the hgraph_node. + */ + uint64_t delay; + /** + * Index of the predecessor H-graph node. + */ + std::size_t fanin_it{}; + /** + * Constructs an H-graph node with given first and last fanins and delay. + * + * @param first The first (leftmost) fanin in the ordering. + * @param last The last (rightmost) fanin in the ordering. + * @param delay_value The delay value for the node. + */ + hgraph_node(const mockturtle::node& first, const mockturtle::node& last, const uint64_t delay_value) : + outer_fanins(first, last), + delay(delay_value) + {} +}; + +/** + * Variant of the `mockturtle::initialize_copy_network`. This function helps with creating new networks from old + * networks. In the mockturtle/original version `old2new` is used to map nodes from the old network to nodes in the new + * network in a one-to-one relation. This variant allows old nodes to map to multiple nodes in order to represent + * relations to dulicated nodes. + + * A map (old2new) is created where old nodes from source network are mapped to new nodes in destination network. + * A destination network is created as a virtual_pi_network. + * + * @tparam Ntk Type of the network. + * @param src The source network. + * + * @return A pair of the destination network and a node map from the source to the destination network. + */ +template +std::pair, mockturtle::node_map>>, Ntk>> +initialize_copy_network_duplicates(Ntk const& src) +{ + static_assert(mockturtle::is_network_type_v, "Ntk is not a network type"); + static_assert(mockturtle::has_get_constant_v, "Ntk does not implement the get_constant method"); + static_assert(mockturtle::has_get_constant_v>, + "virtual_pi_network does not implement get_constant"); + static_assert(mockturtle::has_get_node_v, "Ntk does not implement the get_node method"); + static_assert(mockturtle::has_foreach_pi_v, "Ntk does not implement the foreach_pi method"); + static_assert(mockturtle::has_create_pi_v>, + "virtual_pi_network does not implement create_pi"); + static_assert(mockturtle::has_rank_position_v && "Ntk does not implement the rank_position function"); + + mockturtle::node_map>>, Ntk> old2new(src); + virtual_pi_network dest; + + old2new[src.get_constant(false)].push_back(dest.get_constant(false)); + if (src.get_node(src.get_constant(true)) != src.get_node(src.get_constant(false))) + { + old2new[src.get_constant(true)].push_back(dest.get_constant(true)); + } + if constexpr (has_is_real_pi_v) + { + src.foreach_pi_unranked( + [&](auto const& n) + { + if (src.is_real_pi(n)) + { + old2new[n].push_back(dest.create_pi()); + } + }); + } + else + { + src.foreach_pi_unranked([&](auto const& n) { old2new[n].push_back(dest.create_pi()); }); + } + + return {dest, old2new}; +} + +/** + * The function gather_fanin_signals collects the fanin data for node n from the original ntk. + * For each node n there are the possible fanin candidates old2new_v[fn], which are the original node and all + * the nodes which are duplicates of this node. + * + * lvl[node_index] gives the current iterator at where the edge can be connected. To get the right signal, + * all nodes at old2new[n] need to be viewed. Match lvl[node_index] against all entries in old2new[n], + * then try lvl[node_index+1] then try lvl[node_index+2]. + * + * @tparam Ntk Source network type. + * @tparam NtkDest Destination network type. + * @param ntk Source network from which fanins are collected. + * @param ntk_dest_v Destination network in which the corresponding signals are created. + * @param n Node in the source network whose fanins are processed. + * @param old2new_v Mapping from source nodes to their corresponding signals (including duplicates) + * in the destination network. + * @param lvl Vector of nodes at the current level in the destination network. + * @param node_index Reference to the current index into `lvl`, used to determine valid fanin connections + * and potentially updated during processing. + * @return Vector of fanin signals in the destination network corresponding to the fanins of node `n`. + */ +template +std::vector> +gather_fanin_signals(const Ntk& ntk, NtkDest& ntk_dest_v, const mockturtle::node n, + const mockturtle::node_map>, Ntk>& old2new_v, + const std::vector>& lvl, std::size_t& node_index) +{ + // Initialize variables + std::vector> children{}; + children.reserve(ntk.fanin_size(n)); + std::size_t local_node_index = 0; + + ntk.foreach_fanin( + n, + [&n, &ntk, &ntk_dest_v, &lvl, &old2new_v, &children, &node_index, &local_node_index](const auto& f, + const auto i) + { + // Get the vector of duplicated nodes of the original fan-in node fn. + const auto fn = ntk.get_node(f); + const auto& tgt_signal_v = old2new_v[fn]; + + assert(node_index < lvl.size() && "The fanin iterator is out of scope"); + + // The range indicates the number of candidate fan-ins. + const std::size_t max_candidates = ntk.fanin_size(n) + 1; + + // Iterate through the candidate fan-ins. If a candidate fan-in matches the original fan-in or is a + // duplicate of it, add it to the children of the node n. + const std::size_t end_index = std::min(node_index + max_candidates, lvl.size()); + for (auto j = node_index; j < end_index; ++j) + { + // get the node from the newly generated network. + const auto node_at_index = lvl[j]; + const auto candidate_sig = ntk_dest_v.make_signal(node_at_index); + + // Check if the candidate matches the original fan-in or a duplicate. + // Also, verify if the candidate has already reached its fan-out limit. + // THis adjusts for virtual PIS + const auto fanout_limit = ntk.is_pi(fn) ? 1 : ntk.fanout_size(fn); + if ((std::find(tgt_signal_v.cbegin(), tgt_signal_v.cend(), candidate_sig) != tgt_signal_v.cend()) && + (ntk_dest_v.fanout_size(node_at_index) < fanout_limit)) + { + // Set the local node_index. + if (i == 0) + { + local_node_index = j; + } + else + { + local_node_index = std::max(local_node_index, j); + } + + // Add the matched candidate fan-in to the children. + children.emplace_back(ntk.is_complemented(f) ? ntk_dest_v.create_not(candidate_sig) : + candidate_sig); + break; + } + } + }); + + // Set the node_index. + node_index = local_node_index; + + // Return the children of the node. + return children; +} + +/** + * Constructs a planar `virtual_pi_network` based on duplicated nodes derived from the source network. + * + * The input `ntk_lvls` contains per-level vectors of original node ranks in the source network. For each level, this + * function creates corresponding nodes (including duplicates) in a new `virtual_pi_network` and restores their fanin + * relations using the `gather_fanin_signals` helper function. + * + * For duplicated PIs (Primary Inputs), virtual PIs are created, and the original PI is stored in a mapping structure. + * The auxiliary function `gather_fanin_signals` collects fanin data for each node and matches it to its corresponding + * nodes in the `virtual_pi_network`. + * + * Example: For a level {2, 3, 2, 4, 2}, new nodes are created for each duplicated occurrence (e.g., node 2) and stored + * in the `old2new_v` node map. This map is then used by `gather_fanin_signals` to correctly establish fanin + * relationships between newly created nodes. + * + * @tparam Ntk Network type. + * @param ntk Source network used to construct the `virtual_pi_network`. + * @param ntk_lvls Per-level vectors of original node ranks in the source network used to derive node duplications. + * @param ntk_lvls_new Per-level vectors of newly created nodes' ranks in the constructed `virtual_pi_network`. + * @return The constructed planar `virtual_pi_network` containing duplicated nodes with restored fanin and fanout + * relations. + */ +template +virtual_pi_network +create_virtual_pi_ntk_from_duplicated_nodes(const Ntk& ntk, const levelized_node_order& ntk_lvls, + levelized_node_order>& ntk_lvls_new) +{ + static_assert(mockturtle::has_create_node_v>, "virtual_pi_network lacks create_node"); + static_assert(mockturtle::has_get_node_v>, "virtual_pi_network lacks get_node"); + static_assert(mockturtle::has_fanout_size_v>, "virtual_pi_network lacks fanout_size"); + static_assert(mockturtle::has_create_po_v>, "virtual_pi_network lacks create_po"); + static_assert(mockturtle::has_create_not_v>, "virtual_pi_network lacks create_not"); + + std::unordered_map, bool> node_status; + ntk_lvls_new.resize(ntk_lvls.size()); + + auto init_v = initialize_copy_network_duplicates(ntk); + auto& ntk_dest_v = init_v.first; + auto& old2new_v = init_v.second; + + for (auto i = ntk_lvls.size(); i-- > 0;) + { + // The index of the node in the current node level. + std::size_t node_index = 0; + + // The current node level with duplicated nodes. + // Example vector: {3, 2, 3} + const auto& lvl = ntk_lvls[i]; + + // The current node level in the new network, where duplicated nodes are created as new nodes. + // Example vector {3, 2, 4} + auto& lvl_new = ntk_lvls_new[i]; + + // Create a node in the new network for each node contained in 'lvl'. + for (const auto& nd : lvl) + { + // If the node is a PI create virtual PIs for duplicates. + if (ntk.is_pi(nd)) + { + mockturtle::node pi = nd; + if constexpr (has_is_virtual_pi_v) + { + if (ntk.is_virtual_pi(pi)) + { + pi = ntk.get_real_pi(pi); + node_status[pi] = true; + } + } + if (node_status[pi]) + { + const auto new_sig = ntk_dest_v.create_virtual_pi(pi); + lvl_new.push_back(ntk_dest_v.get_node(new_sig)); + old2new_v[nd].push_back(new_sig); + } + else + { + const auto& sigs = old2new_v[pi]; + assert(!sigs.empty()); + lvl_new.push_back(ntk_dest_v.get_node(sigs.front())); + node_status[pi] = true; + } + } + else + { + const auto children = + gather_fanin_signals(ntk, ntk_dest_v, nd, old2new_v, ntk_lvls_new[i + 1], node_index); + + // Ensure child count matches function arity (including 0-fanin constants) + assert(children.size() == ntk.fanin_size(nd) && + "Mismatch between gathered children and node fanin count"); + + const auto new_sig = ntk_dest_v.create_node(children, ntk.node_function(nd)); + lvl_new.push_back(ntk_dest_v.get_node(new_sig)); + old2new_v[nd].push_back(new_sig); + } + } + } + + ntk.foreach_po( + [&ntk, &ntk_dest_v, &old2new_v](const auto& po) + { + const auto tgt_signal_v = old2new_v[ntk.get_node(po)]; + + // POs are not duplicated as the algorithm starts at POs and duplicates other nodes based on their order + assert(tgt_signal_v.size() == 1 && "Multiple nodes mapped to PO"); + + const auto tgt_signal = tgt_signal_v[0]; + + const auto tgt_po = ntk.is_complemented(po) ? ntk_dest_v.create_not(tgt_signal) : tgt_signal; + + ntk_dest_v.create_po(tgt_po); + }); + + return ntk_dest_v; +} + +/** + * Calculates pairs of nodes from a given vector of nodes. + * + * This function takes a vector of nodes and returns a vector of node pairs. Each node pair consists of two nodes from + * the input vector and an optional vector of middle nodes. The delay of each node pair is initialized to infinity. + * + * @tparam Ntk The network type. + * @param nodes The vector of nodes. + * @return The vector of node pairs. + */ +template +[[nodiscard]] std::vector> calculate_pairs(const std::vector>& nodes) noexcept +{ + std::vector> pairwise_combinations{}; + pairwise_combinations.reserve(nodes.size() * (nodes.size() - 1)); + + if (nodes.size() == 1) + { + const hgraph_node pair = {nodes[0], nodes[0], + std::numeric_limits::max()}; // Initialize delay to inf + pairwise_combinations.push_back(pair); + return pairwise_combinations; + } + + for (auto it1 = nodes.cbegin(); it1 != nodes.cend(); ++it1) + { + for (auto it2 = it1 + 1; it2 != nodes.cend(); ++it2) + { + std::vector> middle_fanins{}; + middle_fanins.reserve(nodes.size() - 2); + + // fill middle_fanins with non-pair members + for (auto it = nodes.cbegin(); it != nodes.cend(); ++it) + { + if (it != it1 && it != it2) + { + middle_fanins.push_back(*it); + } + } + + hgraph_node pair1 = {*it1, *it2, std::numeric_limits::max()}; // Initialize delay to inf + hgraph_node pair2 = {*it2, *it1, std::numeric_limits::max()}; // Initialize delay to inf + + // Add middle_fanins to pairs + pair1.middle_fanins = middle_fanins; + pair2.middle_fanins = middle_fanins; + + pairwise_combinations.push_back(pair1); + pairwise_combinations.push_back(pair2); + } + } + + return pairwise_combinations; +} + +template +class node_duplication_planarization_impl +{ + public: + [[maybe_unused]] node_duplication_planarization_impl(const Ntk& src, + const node_duplication_planarization_params& p) : + ntk(src), + ps{p} + {} + + /** + * A "slice" describes one vertical layer in the H-graph. It is created by adding all possible combinations of a + * `node_pair` to the H-graph of the level. These combinations are formed by selecting pairs of nodes from the + * fan-ins of the input node: + * - If the input node has only one fan-in, it is treated as a single combination. + * - If the input node has two fan-ins, there are two possible combinations. + * + * Each `node_pair` consists of a first and second element. The objective is to find an ordering of node pairs that + * maximizes the instances where the first element of a node_pair matches the second element of the preceding + * node_pair. This ordering is given as a linked list. + * + * This function computes the optimal ordering by calculating delays as follows: + * - All combinations of node pairs are iteratively added to a linked list. + * - For each combination, the first element of the current node_pair is compared with the last element of the + * preceding node_pairs. + * - If a connection exists between two node_pairs, the delay increases by 1; otherwise, it increases by 2. The + * default delay for the first node is 1. + * - If a node_pair lacks a connection, and its updated delay (increased by 2) is less than the existing delay, the + * node_pair's delay is updated accordingly. + * + * Processed node_pairs are stored in the `lvl_pairs` member for subsequent delay calculations. + * + * @param nd Node in the H-graph. + */ + void compute_slice_delays(const mockturtle::node& n) + { + // Pis need to be propagated into the next level, since they have to be connected without crossings + if (ntk.is_pi(n)) + { + fis.push_back(n); + } + + // Respect the rank order. If two combinations have the same delay and have no seen advantage then the one from + // the original ranking is used, since it is inserted and not overwritten afterward. + ntk.foreach_fanin(n, + [&](auto fi) + { + if (!ntk.is_constant(fi)) + { + auto n = ntk.get_node(fi); + + auto it = + std::lower_bound(fis.begin(), fis.end(), n, [&](auto const& a, auto const& b) + { return ntk.rank_position(a) < ntk.rank_position(b); }); + + fis.insert(it, n); + } + }); + + assert(!fis.empty() && "There has to be at least one node in this level"); + + // Compute the combinations in one slice + auto combinations = calculate_pairs(fis); + assert(!combinations.empty() && "Combinations are empty. There might be a dangling node"); + + if (!lvl_pairs.empty()) + { + std::vector>* combinations_last = &lvl_pairs.back(); + + for (std::size_t cur_idx = 0; cur_idx < combinations.size(); ++cur_idx) + { + auto& node_pair_cur = combinations[cur_idx]; + + for (std::size_t last_idx = 0; last_idx < combinations_last->size(); ++last_idx) + { + auto& node_pair_last = (*combinations_last)[last_idx]; + + // If there is a connection between the two node pairs the delay is calculated like this + if ((node_pair_cur.outer_fanins.first == node_pair_last.outer_fanins.second && + node_pair_last.delay + 1 < node_pair_cur.delay)) + { + node_pair_cur.fanin_it = last_idx; + node_pair_cur.delay = node_pair_last.delay + 1; + } + // If there is no connection between the two node pairs the delay is calculated like this + else if (node_pair_last.delay + 2 < node_pair_cur.delay) + { + node_pair_cur.fanin_it = last_idx; + node_pair_cur.delay = node_pair_last.delay + 2; + } + else if (node_pair_last.delay + 2 == node_pair_cur.delay) + { + // This solves equal path delays, if they are connected in the next layer via a fanout + const auto fc0 = fanins(ntk, node_pair_cur.outer_fanins.first); + if (node_pair_last.fanin_it < combinations_last->size()) + { + const auto fc1 = fanins(ntk, node_pair_last.outer_fanins.second); + + for (const auto f0 : fc0.fanin_nodes) + { + for (const auto f1 : fc1.fanin_nodes) + { + if (f0 == f1) + { + node_pair_cur.fanin_it = last_idx; + goto next_combination; + } + } + } + } + } + } + next_combination:; + } + } + else + { + // The delay for the first node in the level is set to 1 + for (auto& node_pair : combinations) + { + node_pair.delay = 1; + } + } + + lvl_pairs.push_back(combinations); + } + + /** + * Represents the current state of fanout saturation during node insertion. + * A fanout is saturated if it has reached its maximum fanout limit. + */ + enum class fanout_state : uint8_t + { + /** + * No fanout saturation is active. + */ + NORMAL, + + /** + * Fanout saturation is active. + */ + SATURATED + }; + + /** + * Specifies the position of a node during insertion. + */ + enum class insertion_position : uint8_t + { + /** + * The node is a terminal node of a fanin relation. + */ + TERMINAL, + + /** + * The node is an inner (non-terminal) fanin node. + */ + INNER + }; + + /** + * Inserts a node into a vector if it is unique. + * + * This function inserts a node into a vector only if the vector is empty or the node is not equal to the first + * element of the vector. If the vector is not empty and the node is equal to the first element, insertion depends + * on the `saturated_fanout_flag` and the node's `position`: when `position == 0`, a repeated insertion attempt will + * succeed only if the node was previously skipped (indicated by `saturated_fanout_flag == 1`); otherwise, the flag + * is set to 1 and the node is skipped for this call. No exception is thrown during this process. + * + * @param node The node to be inserted. + * @param vec The vector to insert the node into. + * @param saturated_fanout_flag A state flag toggled when consecutive duplicate insertions occur. Set to 1 when a + * node is skipped and reset to 0 when a node is successfully inserted. + * @param position The position of the node (0 indicates a terminal node; controls duplicate insertion behavior). + */ + void insert_if_not_first(const mockturtle::node& node, std::vector>& vec, + fanout_state& fo_st, const insertion_position position) + { + if (vec.empty() || vec.front() != node) + { + vec.insert(vec.begin(), node); + fo_st = fanout_state::NORMAL; + } + else if (position == insertion_position::TERMINAL) + { + if (fo_st == fanout_state::SATURATED) + { + vec.insert(vec.begin(), node); + fo_st = fanout_state::NORMAL; + } + else + { + if (ntk.fanout_size(node) == 1) + { + vec.insert(vec.begin(), node); + } + fo_st = fanout_state::SATURATED; + } + } + } + + /** + * This function computes the order of nodes in the next level based on their delay in the H-graph of the level. It + * selects the path with the least delay from the current level pairs and follows it via fanin relations. The nodes + * are inserted into the next level vector in the order they are encountered. + * + * @return The order of nodes in `next_level` + */ + std::vector> compute_node_order() + { + std::vector> next_level; + fanout_state fo_st = fanout_state::NORMAL; + + const auto& combinations = lvl_pairs.back(); + + // Select the path with the least delay and follow it via fanin relations + const auto minimum_it = + std::min_element(combinations.cbegin(), combinations.cend(), + [](const hgraph_node& a, const hgraph_node& b) { return a.delay < b.delay; }); + + if (minimum_it != combinations.cend()) + { + const auto& min_combination = *minimum_it; + + // Insert the terminal node + insert_if_not_first(min_combination.outer_fanins.second, next_level, fo_st, insertion_position::TERMINAL); + + // Insert middle_fanins + for (const auto& node : min_combination.middle_fanins) + { + insert_if_not_first(node, next_level, fo_st, insertion_position::INNER); + } + + // Insert the first node + insert_if_not_first(min_combination.outer_fanins.first, next_level, fo_st, insertion_position::INNER); + + // Start with index instead of pointer + std::size_t level = lvl_pairs.size() - 1; + std::size_t fanin_it = minimum_it->fanin_it; + + // Follow chain while index is valid + while (level > 0 && fanin_it < lvl_pairs[level - 1].size()) + { + const auto& fanin_combination = lvl_pairs[level - 1][fanin_it]; + + // Insert the terminal node + if (ntk.is_pi(fanin_combination.outer_fanins.second)) + { + fo_st = fanout_state::SATURATED; + } + insert_if_not_first(fanin_combination.outer_fanins.second, next_level, fo_st, + insertion_position::TERMINAL); + + // Insert middle_fanins + for (const auto& node : fanin_combination.middle_fanins) + { + insert_if_not_first(node, next_level, fo_st, insertion_position::INNER); + } + + // Insert the first node + insert_if_not_first(fanin_combination.outer_fanins.first, next_level, fo_st, insertion_position::INNER); + + // Move one level up + --level; + fanin_it = fanin_combination.fanin_it; + } + } + + return next_level; + } + + /** + * Checks if the given vector of nodes contains any non-primary inputs. + * + * @param v_next_level The vector of nodes to be checked. + */ + [[nodiscard]] bool check_final_level(const std::vector>& v_next_level) + { + for (const auto& nd : v_next_level) + { + if (!ntk.is_pi(nd)) + { + return false; + } + } + return true; + } + + [[nodiscard]] virtual_pi_network run() + { + // Initialize the POs with foreach_node to retain the rank_view order + std::vector> pos{}; + pos.reserve(ntk.num_pos()); + ntk.foreach_node( + [this, &pos](const auto n) + { + if (ntk.is_po(n)) + { + const auto po = ntk.get_node(n); + if (std::find(pos.begin(), pos.end(), po) == pos.end()) + { + pos.push_back(po); + } + } + }); + + // Randomize the PO order + if (ps.po_order == node_duplication_planarization_params::output_order::RANDOM_PO_ORDER) + { + // Generate a random engine + static std::mt19937_64 generator(std::random_device{}()); + // Shuffle the pos vector + std::shuffle(pos.begin(), pos.end(), generator); + } + + // save the nodes of the next level + std::vector> next_level{}; + next_level.reserve(pos.size()); + + // Process the first level + for (const auto& po : pos) + { + fis.clear(); + compute_slice_delays(po); + next_level.push_back(po); + } + + ntk_lvls.push_back(next_level); + next_level.clear(); + + next_level = compute_node_order(); + + // check if the final/PI level is reached + bool f_final_level = check_final_level(next_level); + + // Process all other levels + while (!next_level.empty() && !f_final_level) + { + // Push the level to the node array + ntk_lvls.push_back(next_level); + lvl_pairs.clear(); + + // Store the nodes of the next level + for (const auto& cur_node : next_level) + { + fis.clear(); + + // There is one slice in the H-Graph for each node in the level + compute_slice_delays(cur_node); + } + // Clear before starting computations on the next level + next_level.clear(); + // Compute the next level + next_level = compute_node_order(); + // Check if we are at the final level + f_final_level = check_final_level(next_level); + } + // Push the final level (PIs) + if (f_final_level) + { + ntk_lvls.push_back(next_level); + } + + levelized_node_order> ntk_lvls_new{}; + + // create virtual pi network + auto virtual_ntk = create_virtual_pi_ntk_from_duplicated_nodes(ntk, ntk_lvls, ntk_lvls_new); + + // the ntk_levels were created in reverse order + std::reverse(ntk_lvls_new.begin(), ntk_lvls_new.end()); + + // assign the ranks in the virtual network based on ntk_lvls_new + virtual_ntk.update_ranks(); + virtual_ntk.set_all_ranks(ntk_lvls_new); + + // restore possibly set signal names + restore_network_name(ntk, virtual_ntk); + restore_output_names(ntk, virtual_ntk); + + return virtual_ntk; + } + + private: + /** + * The input network. + */ + Ntk ntk{}; + /** + * The currently node_pairs used in the current level. + */ + std::vector>> lvl_pairs{}; + /** + * The fanin nodes. + */ + std::vector> fis{}; + /** + * The network stored as levels. + */ + levelized_node_order ntk_lvls{}; + /** + * The stats of the node_duplication class. + */ + node_duplication_planarization_params ps{}; +}; + +} // namespace detail + +/** + * Implements a planarization mechanism for networks from the paper \"Fabricatable Interconnect and Molecular QCA + * Circuits\" by Amitabh Chaudhary, Danny Ziyi Chen, Xiaobo Sharon Hu, Michael T. Niemier, Ramprasad Ravichandran and + * Kevin Whitton in IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems, Volume 26, 2007. + * + * The planarization achieved by this function solves the Node Duplication Crossing Minimization (NDCE) problem by + * finding the shortest x-y path in the H-graph for every level in the network. An H-graph describes edge relations + * between two levels in a network, with one level assumed as fixed, starting at the Primary Outputs (POs). By finding + * the shortest path from the source (x) to the sink (y) in this H-graph, an optimal solution for the NDCE problem for + * each level is found. The function traverses from the Primary Outputs (POs) towards the Primary Inputs (PIs). + * + * @tparam Ntk Source network type. + * @param ntk Source network to be utilized for the planarization. + * @param ps Node duplication parameters used in the computation. + * + * @return A planarized virtual_pi_network. + */ +template +[[nodiscard]] virtual_pi_network node_duplication_planarization(const Ntk& ntk, + node_duplication_planarization_params ps = {}) +{ + static_assert(mockturtle::is_network_type_v, "NtkSrc is not a network type"); + static_assert(mockturtle::has_create_node_v, "NtkSrc does not implement the create_node function"); + static_assert(mockturtle::has_rank_position_v, "NtkSrc does not implement the rank_position function"); + + if (!is_balanced(ntk)) + { + throw std::invalid_argument("Networks have to be balanced for this duplication"); + } + + detail::node_duplication_planarization_impl p{ntk, ps}; + + auto result = p.run(); + + // check for planarity + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + mincross(result, p_min, &st_min); // counts crossings + if (st_min.num_crossings != 0) + { + throw std::runtime_error("Planarization failed: resulting network is not planar"); + } + + return result; +} + +} // namespace fiction + +#endif // FICTION_NODE_DUPLICATION_PLANARIZATION_HPP diff --git a/include/fiction/algorithms/physical_design/planar_layout_from_network_embedding.hpp b/include/fiction/algorithms/physical_design/planar_layout_from_network_embedding.hpp new file mode 100644 index 0000000000..0bc648459b --- /dev/null +++ b/include/fiction/algorithms/physical_design/planar_layout_from_network_embedding.hpp @@ -0,0 +1,1184 @@ +// +// Created by benjamin on 21.01.25. +// + +#ifndef FICTION_PLANAR_LAYOUT_FROM_NETWORK_EMBEDDING_HPP +#define FICTION_PLANAR_LAYOUT_FROM_NETWORK_EMBEDDING_HPP + +#include "fiction/algorithms/graph/mincross.hpp" +#include "fiction/algorithms/physical_design/orthogonal.hpp" +#include "fiction/layouts/clocking_scheme.hpp" +#include "fiction/traits.hpp" +#include "fiction/utils/network_utils.hpp" +#include "fiction/utils/placement_utils.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (PROGRESS_BARS) +#include +#endif + +namespace fiction +{ + +/** + * Parameters for the planar layout from network embedding algorithm. + */ +struct planar_layout_from_network_embedding_params +{ + /** + * Number of clock phases to use. 3 and 4 are supported. + */ + num_clks number_of_clock_phases = num_clks::FOUR; + /** + * Verbosity. + */ + bool verbose = false; +}; +/** + * This struct stores statistics about the planar layout design process. + */ +struct planar_layout_from_network_embedding_stats +{ + /** + * Runtime of the planar layout design process. + */ + mockturtle::stopwatch<>::duration time_total{}; + /** + * Layout width. + */ + uint64_t x_size{0ull}; + /** + * Layout height. + */ + uint64_t y_size{0ull}; + /** + * Number of gates. + */ + uint64_t num_gates{0ull}; + /** + * Number of wires. + */ + uint64_t num_wires{0ull}; + /** + * Number of crossings. + */ + uint64_t num_crossings{0ull}; + /** + * 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)); + out << fmt::format("[i] layout size = {} × {}\n", x_size, y_size); + out << fmt::format("[i] num. gates = {}\n", num_gates); + out << fmt::format("[i] num. wires = {}\n", num_wires); + out << fmt::format("[i] num. crossings = {}\n", num_crossings); + } +}; + +namespace detail +{ + +/** + * Defines a 3D lookup table using `std::array` and encapsulates it within a function. This table encodes all possible + * combinations of the previous level, connection type, orientations, and surrounding spacing (gaps). Based on these + * inputs, it returns the corresponding orientation and spacing configuration for the current buffer. + */ +inline constexpr std::array, 4>, 3>, 3>, 2> + BUFFER_LOOKUP = {{ // Array + {{// Unconnected + {{ + // East + {{{0, 0}, {1, 0}, {1, 1}, {0, 1}}}, // gap 0 + {{{0, 0}, {0, 0}, {1, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 and higher + }}, + {{ + // South + {{{3, 0}, {2, 0}, {0, 0}, {0, 0}}}, // gap 0; only first two entries used + {{{3, 0}, {2, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{3, 0}, {2, 0}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // Free + {{{0, 0}, {1, 0}, {2, 0}, {3, 0}}}, // gap 0 + {{{0, 0}, {0, 0}, {1, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 and higher + }}}}, + {{// Connected + {{ + // East + {{{0, 0}, {0, 0}, {0, 1}, {0, 1}}}, // gap 0 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 and higher + }}, + {{ + // South + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}}, // gap 0 + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}} // gap 2 and higher + }}, + {{ + // Free + {{{0, 0}, {0, 0}, {3, 0}, {3, 0}}}, // gap 0 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 + }}}}}}; + +/** + * Defines a 3D lookup table using `std::array` and encapsulates it within a function. This table encodes all possible + * combinations of the previous level, connection type, orientations, and surrounding spacing (gaps). Based on these + * inputs, it returns the corresponding orientation and spacing configuration for the current fanout. + */ +inline constexpr std::array, 4>, 3>, 3>, 4> + FANOUT_LOOKUP = {{ // Array + {{// Type Fo 1+2 + {{ + // East + {{{1, 0}, {1, 1}, {1, 2}, {1, 1}}}, // gap 0 + {{{1, 0}, {1, 0}, {1, 1}, {1, 0}}}, // gap 1 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}} // gap 2 + }}, + {{ + // South + {{{2, 0}, {2, 1}, {0, 0}, {0, 0}}}, // gap 0 + {{{2, 0}, {2, 1}, {0, 0}, {0, 0}}}, // gap 1 + {{{2, 0}, {2, 1}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // Free + {{{1, 0}, {2, 0}, {2, 1}, {2, 0}}}, // gap 0 + {{{1, 0}, {1, 0}, {2, 0}, {1, 0}}}, // gap 1 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}} // gap 2 + }}}}, + {{// Type F1 + {{ + // East + {{{0, 0}, {0, 1}, {0, 2}, {0, 1}}}, // gap 0 + {{{0, 0}, {0, 0}, {0, 1}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // South + {{{3, 0}, {3, 1}, {0, 0}, {0, 0}}}, // gap 0 + {{{3, 0}, {3, 1}, {0, 0}, {0, 0}}}, // gap 1 + {{{3, 0}, {3, 1}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // Free + {{{0, 0}, {3, 0}, {3, 1}, {3, 0}}}, // gap 0 + {{{0, 0}, {0, 0}, {3, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 + }}}}, + {{// Type F2 + {{ + // East + {{{1, 0}, {1, 0}, {1, 1}, {1, 1}}}, // gap 0 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}}, // gap 1 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}} // gap 2 + }}, + {{ + // South + {{{2, 0}, {2, 0}, {0, 0}, {0, 0}}}, // gap 0 + {{{2, 0}, {2, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{2, 0}, {2, 0}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // Free + {{{1, 0}, {1, 0}, {2, 0}, {2, 0}}}, // gap 0 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}}, // gap 1 + {{{1, 0}, {1, 0}, {1, 0}, {1, 0}}} // gap 2 + }}}}, + {{// Type F3 + {{ + // East + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}}, // gap 0 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // South + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}}, // gap 0 + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{3, 0}, {3, 0}, {0, 0}, {0, 0}}} // gap 2 + }}, + {{ + // Free + {{{0, 0}, {0, 0}, {3, 0}, {3, 0}}}, // gap 0 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}}, // gap 1 + {{{0, 0}, {0, 0}, {0, 0}, {0, 0}}} // gap 2 + }}}}}}; + +/** + * Computes the fan-out connection type of a node based on its successors' fan-in structures. + * Assumes exactly two fan-outs, which are ordered by rank position. + * + * Returns: + * - 0: Both fan-outs have a single fan-in. + * - 1: First fan-out has one fan-in, second has multiple. + * - 2: Second fan-out has one fan-in, first has multiple. + * - 3: Both fan-outs have multiple fan-ins. + * + * @tparam Ntk Logic network type. + * @param ntk Logic network containing the node. + * @param n Node for which to determine the fan-out connection type. + * @return Integer code (0–3) indicating the fan-out connection pattern. + */ +template +uint8_t calculate_fanout_connection_type(const Ntk& ntk, const mockturtle::node& n) +{ + // order the POs + if (ntk.is_po(n)) + { + return 0; + } + auto fo = ntk.fanout(n); + assert(fo.size() == 2); + std::sort(fo.begin(), fo.end(), [&ntk](int a, int b) { return ntk.rank_position(a) < ntk.rank_position(b); }); + if (ntk.fanin_size(fo[0]) == 1 && ntk.fanin_size(fo[1]) == 1) + { + return 0; + } + if (ntk.fanin_size(fo[0]) == 1) + { + return 1; + } + if (ntk.fanin_size(fo[1]) == 1) + { + return 2; + } + // both fan-outs are connected with a neighbor + return 3; +} + +/** + * Computes the gap between the fan-in node and its preceding node, i.e., the node with a rank position one less than + * the current node. This value indicates the available spacing for placement. + * + * @tparam Ntk Logic network type. + * @tparam Lyt Layout type. + * @param ntk Logic network containing the node. + * @param node2pos Mapping from network nodes to layout tile positions. + * @param lvl Current level index of the node. + * @param n Node for which to compute the predecessor gap. + * @return Gap size (clamped to a maximum of 2). + */ +template +uint8_t calculate_predecessor_gap(const Ntk& ntk, const mockturtle::node_map, Ntk>& node2pos, + const uint64_t lvl, const mockturtle::node& n) +{ + // return if in the PI level + if (lvl == 0) + { + return 0; + } + + // calculate the rank of the predecessor node + auto fc = fanins(ntk, n); + + if (fc.fanin_nodes.size() == 2) + { + std::sort(fc.fanin_nodes.begin(), fc.fanin_nodes.end(), + [&ntk](int a, int b) { return ntk.rank_position(a) < ntk.rank_position(b); }); + } + auto pre = fc.fanin_nodes[0]; + const auto r = ntk.rank_position(pre); + + // return if no neighbor + if (r == 0) + { + return 0; + } + + // calculate the level of the predecessor node + const auto l = lvl - 1; + + // get the neighbor with lower rank of the predecessor + const auto pre_neighbor = ntk.at_rank_position(l, r - 1); + + // calculate the gap size + const auto pre1_t = static_cast>(node2pos[pre]); + const auto pre2_t = static_cast>(node2pos[pre_neighbor]); + + assert(pre1_t.y > pre2_t.y); + return std::min(pre1_t.y - pre2_t.y - 1, 2); +} + +/** + * Computes the buffer connection type for a given node. + * Determines whether the node serves as the second (rightmost) fan-in of its successor node. + * + * @tparam Ntk Logic network type. + * @param ntk Logic network containing the node. + * @param n Node to analyze. + * @return 1 if the node is the rightmost fan-in of its successor, otherwise 0. + */ +template +uint64_t calculate_buffer_connection_type(const Ntk& ntk, const mockturtle::node& n) +{ + if (ntk.is_po(n)) + { + return 0; + } + auto fo = ntk.fanout(n); + assert(fo.size() == 1); + if (ntk.fanin_size(fo[0]) == 2) + { + auto fc = fanins(ntk, fo[0]); + auto pre = fc.fanin_nodes; + assert(pre.size() == 2); + std::sort(pre.begin(), pre.end(), [&ntk](int a, int b) { return ntk.rank_position(a) < ntk.rank_position(b); }); + if (pre[1] == n) + { + return 1; + } + } + return 0; +} + +/** + * Determines the allowed orientation of a node based on its predecessor. + * For nodes driven by a fan-out, the orientation is defined by their relative rank position among the fan-out's + * successors. + * + * Returns: + * - 0: Node is the first (east) successor of its predecessor. + * - 1: Node is the second (west) successor of its predecessor. + * - 2: Node is not driven by a fan-out. + * + * @tparam Ntk Logic network type. + * @param ntk Logic network containing the node. + * @param n Node for which to determine the allowed orientation. + * @return Orientation code (0–2) describing the node's relative position. + */ +template +uint8_t calculate_allowed_orientation(const Ntk& ntk, const mockturtle::node& n) +{ + auto fc = fanins(ntk, n); + assert(fc.fanin_nodes.size() == 1); + auto pre = fc.fanin_nodes[0]; + if (ntk.is_fanout(pre)) + { + auto fo = ntk.fanout(pre); + assert(fo.size() == 2); + std::sort(fo.begin(), fo.end(), [&ntk](int a, int b) { return ntk.rank_position(a) < ntk.rank_position(b); }); + if (n == fo[0]) // east + { + return 0; + } + assert(n == fo[1]); + return 1; + } + return 2; +} + +/** + * Determines the initial orientation for a given network level. + * The orientation is inferred from the structural pattern of nodes within the specified level, considering their fan-in + * and fan-out relationships. + * + * Returns: + * - 0: Default orientation (no specific structure found). + * - 1: Level starts with a fan-out or buffer structure. + * - 3: Level starts with a two-input (binary) gate. + * + * @tparam Ntk Logic network type. + * @param ntk Logic network to analyze. + * @param lvl Level index for which to compute the starting orientation. + * @return Orientation code (0, 1, or 3) defining the level's initial direction. + */ +template +int calculate_start_orientation(const Ntk& ntk, const uint32_t lvl) +{ + int orientation = 0; + if (lvl == 0) + { + return orientation; + } + ntk.foreach_node_in_rank(lvl, + [&ntk, &orientation](const auto& n) + { + if (orientation > 0) + { + return; + } + if (ntk.fanin_size(n) == 2) + { + orientation = 4; + return; + } + if (const auto fc = fanins(ntk, n); fc.fanin_nodes.size() == 1) + { + const auto& pre = fc.fanin_nodes[0]; + if (ntk.is_fanout(pre) && ntk.fanout_size(pre) == 2) + { + orientation = 1; + return; + } + } + if (ntk.fanout_size(n) == 2) + { + orientation = 1; + return; + } + }); + return (orientation == 0) ? 0 : (orientation - 1); + // instead of just returning 0, here the buffers could be wired in a way they are closer together and hence overhead + // produced by two input nodes is reduced +} + +/** + * Computes orientation and routing line variables for a given network level. + * + * For each node in the specified level, the function determines: + * - The node's orientation, based on its fan-in structure, gap spacing, and connection type. + * - Whether new routing lines must be introduced, derived from lookup tables for buffer and fan-out configurations. + * + * @tparam Ntk Logic network type. + * @tparam Lyt Layout type. + * @param ntk Logic network to analyze. + * @param node2pos Mapping from network nodes to layout tile positions. + * @param lvl Level index for which to compute the variables. + * @return Tuple containing: + * - `orientation`: orientation values for each node in the level. + * - `new_lines`: flags indicating newly added routing lines. + */ +template +std::tuple, std::vector> +compute_pr_variables(const Ntk& ntk, const mockturtle::node_map, Ntk>& node2pos, + const uint32_t lvl) +{ + std::vector orientation(ntk.rank_width(lvl)); + std::vector new_lines(ntk.rank_width(lvl)); + // get the lookup tables for the gate types + const auto& buffer_lu = BUFFER_LOOKUP; + const auto& fanout_lu = FANOUT_LOOKUP; + + ntk.foreach_node_in_rank( + lvl, + [&ntk, &node2pos, &lvl, &orientation, &new_lines, &buffer_lu, &fanout_lu](const auto& n, const auto i) + { + // calculate the gap between the predecessors + const auto gap = calculate_predecessor_gap(ntk, node2pos, lvl, n); + // calculate the orientation and new_lines depending on the type of nodes and available space + if (ntk.fanin_size(n) == 2) + { + if (i != 0) + { + if (orientation[i - 1] == 2 && gap == 0) + { + orientation[i] = 1; + } + } + } + else if (ntk.fanin_size(n) == 1) + { + // allowed orientation Flag e = 0, s = 1, free = 2 + const auto allowed_orientation = calculate_allowed_orientation(ntk, n); + assert(allowed_orientation < 3); + + // needs the type of connection (F1+2, F1, F2), allowed_orientation, gap, + // orientation, new_line as input + if (ntk.is_fanout(n)) + { + // calculate the type of connection F1+2 = 0, F1 = 1, F2 = 2, F0 = 3; + const auto fanout_connection_type = calculate_fanout_connection_type(ntk, n); + assert(fanout_connection_type < 4); + if (i != 0) + { + const auto& pair = + fanout_lu.at(fanout_connection_type).at(allowed_orientation).at(gap).at(orientation[i - 1]); + orientation[i] = pair.first; + new_lines[i] = pair.second; + } + else if (fanout_connection_type == 0 || fanout_connection_type == 2) + { + orientation[i] = 1; + } + } + // needs the type of connection (connected, unconnected), allowed_orientation, gap, + // orientation, new_line as input + else + { + if (i != 0) + { + // Connected Flag + const auto buffer_connection_type = calculate_buffer_connection_type(ntk, n); + const auto& pair = + buffer_lu.at(buffer_connection_type).at(allowed_orientation).at(gap).at(orientation[i - 1]); + + orientation[i] = pair.first; + new_lines[i] = pair.second; + } + else + { + orientation[i] = calculate_start_orientation(ntk, lvl); + } + } + } + }); + + return std::make_tuple(orientation, new_lines); +} + +/** + * Collects the indices of all two-input nodes in a given level. + * + * @tparam Ntk Logic network type. + * @param ntk Logic network to analyze. + * @param lvl Level index to inspect. + * @return Vector of node indices with two fan-ins in the specified level. + */ +template +std::vector compute_two_input_indices(const Ntk& ntk, const uint64_t lvl) +{ + std::vector two_input_indices{}; + two_input_indices.reserve(ntk.rank_width(lvl)); + ntk.foreach_node_in_rank(lvl, + [&ntk, &two_input_indices, &lvl](const auto& n, const auto& i) + { + if (ntk.fanin_size(n) == 2) + { + two_input_indices.emplace_back(i); + } + }); + return two_input_indices; +} + +/** + * Computes the number of new routing lines required. + * + * @tparam Ntk Logic network type. + * @tparam Lyt Layout type. + * @param ntk Logic network to analyze. + * @param node2pos Mapping from network nodes to layout tile positions. + * @param lvl Level index to inspect. + * @return Vector of gap sizes (new line counts) for all two-input nodes in the level. + */ +template +std::vector calculate_two_input_new_lines(const Ntk& ntk, + const mockturtle::node_map, Ntk>& node2pos, + const uint32_t lvl) +{ + std::vector cluster_new_lines{}; + cluster_new_lines.reserve(ntk.rank_width(lvl)); + ntk.foreach_node_in_rank(lvl, + [&ntk, &node2pos, &cluster_new_lines](const auto& n) + { + auto fc = fanins(ntk, n); + // calculate gaps due to AND gates + if (fc.fanin_nodes.size() == 2) + { + std::sort(fc.fanin_nodes.begin(), fc.fanin_nodes.end(), [&ntk](int a, int b) + { return ntk.rank_position(a) < ntk.rank_position(b); }); + // compute the max_gap for two fan-ins of anode + const auto &pre1 = fc.fanin_nodes[0], &pre2 = fc.fanin_nodes[1]; + + const auto pre1_t = static_cast>(node2pos[pre1]); + const auto pre2_t = static_cast>(node2pos[pre2]); + + cluster_new_lines.emplace_back(static_cast(pre2_t.y - pre1_t.y - 1)); + } + }); + return cluster_new_lines; +} + +/** + * Balances the final x and y wiring coordinates across all nodes in a level. + * + * The function identifies the minimal routing demand, which means the closest diagonal, where all nodes can be placed + * and can be routed without conflicts. + * + * @param x Vector of x-coordinates to be adjusted. + * @param y Vector of y-coordinates to be adjusted. + * @param two_input_indices Indices of two-input nodes within the level. + * @param two_input_new_lines Number of new routing lines associated with each two-input node. + */ +inline void adjust_final_values(std::vector& x, std::vector& y, + const std::vector& two_input_indices, + const std::vector& two_input_new_lines) +{ + // Max element in two_input_new_lines and its index + const auto max_it = std::max_element(two_input_new_lines.begin(), two_input_new_lines.end()); + const uint64_t max_new_lines_two_inputs = (max_it != two_input_new_lines.end()) ? *max_it : 0; + const std::size_t max_two_inputs_idx = + (max_it != two_input_new_lines.end()) ? + static_cast(std::distance(two_input_new_lines.begin(), max_it)) : + 0; + // Find index with max x+y + std::size_t max_xy_idx = 0; + uint64_t max_xy_sum = 0; + for (std::size_t i = 0; i < x.size(); ++i) + { + const uint64_t s = x[i] + y[i]; + if (i == 0 || s > max_xy_sum) + { + max_xy_sum = s; + max_xy_idx = i; + } + } + // Determine the center index + std::size_t center = 0; + uint64_t max = 0; + if (max_xy_sum > max_new_lines_two_inputs) + { + center = max_xy_idx; + max = max_xy_sum; + } + else + { + center = two_input_indices.empty() ? 0 : two_input_indices[max_two_inputs_idx]; + max = max_new_lines_two_inputs; + } + // Create a lookup map for O(1) access instead of O(n) std::find() + std::unordered_map two_input_map; + for (std::size_t i = 0; i < two_input_indices.size(); ++i) + { + two_input_map[two_input_indices[i]] = i; + } + // Iterate through x and y to update values efficiently + for (std::size_t i = 0; i < x.size(); ++i) + { + auto it = two_input_map.find(i); + if (it != two_input_map.end()) + { // Two fan-in node + const auto idx = it->second; + const auto diff = max - x[i] - y[i] - two_input_new_lines[idx]; + if (i < center) + { + y[i] += diff; + } + else if (i > center) + { + x[i] += diff; + } + else + { + assert(x[i] + y[i] + two_input_new_lines[idx] == max); + } + } + else + { // One fan-in node + const auto diff = max - x[i] - y[i]; + if (i < center) + { + y[i] += diff; + } + else if (i > center) + { + x[i] += diff; + } + else + { + assert(x[i] + y[i] == max); + } + } + } +} + +/** + * Computes the x and y wiring coordinates for nodes in a given level. + * + * The function divides the level into clusters separated by two-input gates and computes the relative wire offsets + * within and between these clusters. It accounts for new routing lines, right and left propagation, and spacing + * adjustments between connected clusters. + * + * @tparam Ntk Logic network type. + * @tparam Lyt Layout type. + * @param ntk Logic network to analyze. + * @param node2pos Mapping from network nodes to layout tile positions. + * @param new_lines Vector of newly introduced routing lines for each node. + * @param lvl Level index to process. + * @return Pair of vectors representing x and y wiring coordinates for each node in the level. + */ +template +std::pair, std::vector> +compute_wiring(const Ntk& ntk, const mockturtle::node_map, Ntk>& node2pos, + const std::vector& new_lines, const uint64_t lvl) +{ + // Initialize 2-input indices + const auto two_input_indices = compute_two_input_indices(ntk, lvl); + const auto two_input_new_lines = calculate_two_input_new_lines(ntk, node2pos, lvl); + + // Initialize cluster indices + std::size_t cluster_index_start = 0; + + // Initialize the x and y vectors + std::vector x(ntk.rank_width(lvl)); + std::vector y(ntk.rank_width(lvl)); + + // Initialize the x and y vectors + std::vector cluster_new_lines(two_input_indices.size() + 1); + + // if there is no two input gate, then there is only one cluster + if (two_input_indices.empty()) + { + for (std::size_t j = 0; j < ntk.rank_width(lvl); ++j) + { + x[j] = std::accumulate(new_lines.begin() + static_cast(j + 1), + new_lines.begin() + ntk.rank_width(lvl), 0UL); + y[j] = (j == 0) ? new_lines[j] : y[j - 1] + new_lines[j]; + } + return std::make_pair(x, y); + } + + // Iterate over all cluster indices + uint64_t propagate_right = 0; + for (std::size_t i = 0; i < two_input_indices.size() + 1; ++i) + { + std::size_t cluster_index_end = 0; + if (i == two_input_indices.size()) + { + cluster_index_end = new_lines.size(); + } + else + { + cluster_index_end = two_input_indices[i]; + } + + // Compute x and y for the current cluster + for (auto j = cluster_index_start; j < cluster_index_end; ++j) + { + // adjust x values + x[j] = std::accumulate(new_lines.begin() + static_cast(j + 1), + new_lines.begin() + static_cast(cluster_index_end), 0UL); + + // adjust y values with propagation to the right (direction based on a 1D vector) + y[j] = (j == 0) ? new_lines[j] : y[j - 1] + new_lines[j]; + y[j] += (j == cluster_index_start) ? propagate_right : 0; + } + + // Also set the new lines for two input nodes + if (cluster_index_start != 0) + { + y[cluster_index_start - 1] += propagate_right; + } + + // Save the number of new lines in a cluster + if (cluster_index_start != cluster_index_end) + { + cluster_new_lines[i] = x[cluster_index_start]; + } + + // Save the right propagated new_lines + const uint64_t til = (i < two_input_new_lines.size()) ? two_input_new_lines[i] : 0; + propagate_right = + (til > cluster_new_lines[i] + propagate_right) ? 0 : propagate_right + cluster_new_lines[i] - til; + + // Move to the next cluster + cluster_index_start = cluster_index_end + 1; + } + + uint64_t propagate_left = 0; + // propagate left (direction based on a 1D vector) + for (auto i = two_input_indices.size(); i > 0; --i) + { + std::size_t cluster_index_end = two_input_indices[i - 1]; + + if (i == 1) + { + cluster_index_start = 0; + } + else + { + cluster_index_start = two_input_indices[i - 2] + 1; + } + + // Save the left propagated new_lines + propagate_left = (two_input_new_lines[i - 1] > cluster_new_lines[i] + propagate_left) ? + 0 : + propagate_left + cluster_new_lines[i] - two_input_new_lines[i - 1]; + + // ALso set the new lines for two input nodes + if (cluster_index_end != new_lines.size() - 1) + { + x[cluster_index_end] += propagate_left; + } + + for (auto j = cluster_index_start; j < cluster_index_end; ++j) + { + x[j] += propagate_left; + } + } + + adjust_final_values(x, y, two_input_indices, two_input_new_lines); + + return std::make_pair(x, y); +} + +/** + * Implements the general planar layout generation algorithm. + * + * The algorithm performs placement and routing level by level, starting from the primary inputs and proceeding toward + * the outputs. For each level, node placement depends on the orientation and excess wiring computed in auxiliary + * routines such as `compute_pr_variables` and `compute_wiring`. Nodes are positioned according to their fan-in + * structure and routing constraints to ensure planarity of the resulting layout. + * + * @tparam Lyt Gate-level layout type. + * @tparam Ntk Logic network type. + * @param src Source network to be placed and routed. + * @param p Parameters controlling layout generation and clocking. + * @param st Statistics object used to collect runtime and layout information. + */ +template +class plane_impl +{ + public: + plane_impl(const Ntk& src, const planar_layout_from_network_embedding_params& p, + planar_layout_from_network_embedding_stats& st) : + ntk{mockturtle::fanout_view{src}}, + ps{p}, + pst{st} + {} + + Lyt run() + { + // measure run time + mockturtle::stopwatch stop{pst.time_total}; + // initialize mapping from nodes to positions + mockturtle::node_map, mockturtle::fanout_view> node2pos{ntk}; + // initialize the aspect ratio + aspect_ratio aspect_ratio = {0, 0}; + // instantiate the layout + Lyt layout{aspect_ratio, twoddwave_clocking(ps.number_of_clock_phases)}; + // reserve PI nodes without positions + auto pi2node = reserve_input_nodes(layout, ntk); + // first x-pos to use for gates is 1 because PIs take up the 0th column + tile latest_pos{1, 0}; + +#if (PROGRESS_BARS) + // initialize a progress bar + mockturtle::progress_bar bar{ntk.depth() + 1, "[i] arranging layout: |{0}|"}; +#endif + + tile place_t{0, 0}; + assert(ntk.num_pis() > 0); + tile first_pos = {ntk.num_pis() - 1, 0}; + // place and route the nodes in ascending level order + for (uint32_t lvl = 0; lvl < ntk.depth() + 1; lvl++) + { + const auto variable_tuple = compute_pr_variables, Lyt>(ntk, node2pos, lvl); + const auto orientation = std::get<0>(variable_tuple); + const auto new_lines = std::get<1>(variable_tuple); + + const auto wiring = compute_wiring(ntk, node2pos, new_lines, lvl); + const auto& x = wiring.first; + const auto& y = wiring.second; + // place and route the nodes in ascending rank order + ntk.foreach_node_in_rank( + lvl, + [this, &layout, &pi2node, &node2pos, &orientation, &first_pos, &place_t, &x, &y](const auto& n, + const auto& i) + { + if (!ntk.is_constant(n)) + { + // if node is a PI, move it to its correct position + if (ntk.is_pi(n)) + { + if (ntk.rank_position(n) == 0) + { + node2pos[n] = layout.move_node(pi2node[n], first_pos); + place_t = first_pos; + } + else + { + place_t = {place_t.x - 1, place_t.y + 1}; + if (place_t.x == 0) + { + node2pos[n] = layout.move_node(pi2node[n], place_t); + } + else if (place_t.x < (first_pos.x / 2)) + { + node2pos[n] = layout.move_node(pi2node[n], {0, place_t.y}); + + node2pos[n] = + layout.create_buf(wire_east(layout, {0, place_t.y}, place_t), place_t); + } + else + { + node2pos[n] = layout.move_node(pi2node[n], {place_t.x, 0}); + + node2pos[n] = + layout.create_buf(wire_south(layout, {place_t.x, 0}, place_t), place_t); + } + } + } + // if n has only one fanin + else if (const auto fc = fanins(ntk, n); fc.fanin_nodes.size() == 1) + { + const auto& pre = fc.fanin_nodes[0]; + auto pre_t = static_cast>(node2pos[pre]); + + // Resolve new lines. Special case for the second fan-out of a fan-out node + if (!(ntk.is_fanout(pre) && (orientation[i] == 2 || orientation[i] == 3))) + { + if (x[i] != 0) + { + wire_east(layout, pre_t, {pre_t.x + x[i] + 1, pre_t.y}); + pre_t.x += x[i]; + } + if (y[i] != 0) + { + wire_south(layout, pre_t, {pre_t.x, pre_t.y + y[i] + 1}); + pre_t.y += y[i]; + } + } + // horizontal (corresponding to colored east) + if (orientation[i] == 0 || orientation[i] == 1) + { + place_t.y = pre_t.y; + place_t.x = pre_t.x + 1; + } + else + { + assert(orientation[i] == 2 || orientation[i] == 3); + + // Resolve new lines. Special case for the second fan-out of a fan-out node + if (ntk.is_fanout(pre) && (orientation[i] == 2 || orientation[i] == 3)) + { + // n the special case the + if (x[i] != 0) + { + pre_t.x += x[i]; + } + if (y[i] != 0) + { + wire_south(layout, pre_t, {pre_t.x, pre_t.y + y[i] + 1}); + pre_t.y += y[i]; + } + } + place_t.y = pre_t.y + 1; + place_t.x = pre_t.x; + } + + node2pos[n] = connect_and_place(layout, place_t, ntk, n, pre_t); + + if (ntk.rank_position(n) == 0) + { + first_pos = place_t; + } + } + // if node has two fanins (or three fanins with one of them being constant) + else + { + const auto& pre1 = fc.fanin_nodes[0]; + const auto& pre2 = fc.fanin_nodes[1]; + + auto pre1_t = static_cast>(node2pos[pre1]); + auto pre2_t = static_cast>(node2pos[pre2]); + + // Resolve new lines + if (x[i] != 0) + { + wire_east(layout, pre1_t, {pre1_t.x + x[i] + 1, pre1_t.y}); + wire_east(layout, pre2_t, {pre2_t.x + x[i] + 1, pre2_t.y}); + pre1_t.x += x[i]; + pre2_t.x += x[i]; + } + if (y[i] != 0) + { + wire_south(layout, pre1_t, {pre1_t.x, pre1_t.y + y[i] + 1}); + wire_south(layout, pre2_t, {pre2_t.x, pre2_t.y + y[i] + 1}); + pre1_t.y += y[i]; + pre2_t.y += y[i]; + } + + // pre1_t is the northwards/eastern tile. + if (pre2_t.y < pre1_t.y) + { + std::swap(pre1_t, pre2_t); + } + + place_t = {pre1_t.x, pre2_t.y}; + + node2pos[n] = connect_and_place(layout, place_t, ntk, n, pre1_t, pre2_t, fc.constant_fanin); + + if (ntk.rank_position(n) == 0) + { + first_pos = place_t; + } + } + } + }); +#if (PROGRESS_BARS) + // update progress + bar(lvl); +#endif + } + + // place and route POs + mockturtle::node_map count_map{ntk, 0}; + int add_line = 0; + // the number of outputs on a node is limited to 2, due to fanout substitution + ntk.foreach_po( + [this, &layout, &first_pos, &place_t, &node2pos, &count_map, &add_line](const auto& po) + { + if (!ntk.is_constant(po)) + { + const auto drv = ntk.get_node(po); + auto po_tile = static_cast>(node2pos[drv]); + auto& cnt = count_map[drv]; + if (cnt < 2) // Check if the count is less than 2 + { + // Adjust the position based on whether it's the first or second occurrence + if (cnt == 1) + { + if (po_tile.y == place_t.y) + { + add_line = 1; + } + po_tile = static_cast>(wire_south(layout, po_tile, {po_tile.x, po_tile.y + 2})); + } + const tile anker{po_tile}; + po_tile.x = first_pos.x + 1; + + // Create PO and increment the count + if constexpr (mockturtle::has_has_output_name_v) + { + layout.create_po(wire_east(layout, anker, po_tile), + ntk.has_output_name(po_counter) ? ntk.get_output_name(po_counter++) : + fmt::format("po{}", po_counter++), + po_tile); + } + else + { + layout.create_po(wire_east(layout, anker, po_tile), fmt::format("po{}", po_counter++), + po_tile); + } + + ++cnt; + } + else + { + throw std::logic_error("Node has more than 2 primary outputs after fanout substitution"); + } + } + }); + + layout.resize({first_pos.x + 1, place_t.y + add_line, 0}); + + // restore possibly set signal names + restore_names(ntk, layout, node2pos); + + // statistical information + pst.x_size = layout.x() + 1; + pst.y_size = layout.y() + 1; + pst.num_gates = layout.num_gates(); + pst.num_wires = layout.num_wires(); + + if (ps.verbose) + { + std::cout << "\n[i] Layout generated:\n"; + + std::cout << fmt::format("[i] Layout dimension: {} × {} = {}\n", pst.x_size, pst.y_size, + pst.x_size * pst.y_size); + + std::cout << fmt::format("[i] #Gates: {}\n", pst.num_gates); + std::cout << fmt::format("[i] #Wires: {}\n", pst.num_wires); + std::cout << fmt::format("[i] #Crossings: {}\n", pst.num_crossings); + } + + return layout; + } + + private: + /** + * The input network wrapped in a fanout view. + */ + mockturtle::fanout_view ntk; + /** + * The parameters controlling the planar layout from a network embedding. + */ + planar_layout_from_network_embedding_params ps; + /** + * Reference to the statistics collected during planar layout generation. + */ + planar_layout_from_network_embedding_stats& pst; + /** + * Primary output counter. + */ + uint32_t po_counter{0}; +}; + +} // namespace detail + +/** + * This algorithm constructs a planar layout from the planar embedding of a logic network, forming the Planar Layout + * from Network Embedding (PLANE) methodology. It provides a fully planar physical design flow for Field-Coupled + * Nanocomputing (FCN) circuits. The algorithm operates on a logic network with an existing planar embedding, + * represented as a `mutable_rank_view`, and preserves this embedding during placement and routing. + * + * In this approach, each logic level of the network is mapped to a diagonal in the layout, while nodes within the same + * level are placed according to their rank positions in the planar embedding. This ensures a crossing-free, scalable, + * and layout-consistent mapping from logic to physical design. + * + * @tparam Lyt Gate-level layout type. + * @tparam Ntk Logic network type. + * @param ntk Planar logic network to be placed and routed. + * @param ps Configuration parameters for the physical design process. + * @param pst Optional statistics object to collect runtime and layout metrics. + * @return A fully planar gate-level layout of type `Lyt`. + */ +template +Lyt plane(const Ntk& ntk, planar_layout_from_network_embedding_params ps = {}, + planar_layout_from_network_embedding_stats* pst = nullptr) +{ + static_assert(is_gate_level_layout_v, "Lyt is not a gate-level layout"); + static_assert(mockturtle::is_network_type_v, + "Ntk is not a network type"); // Ntk is being converted to a topology_network anyway, therefore, + // this is the only relevant check here + + // check for input degree + if (has_high_degree_fanin_nodes(ntk, 2)) + { + throw high_degree_fanin_exception(); + } + + // check for planarity + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + mincross(ntk, p_min, &st_min); // counts crossings + if (st_min.num_crossings != 0) + { + throw std::invalid_argument("Input network has to be planar"); + } + + planar_layout_from_network_embedding_stats st{}; + detail::plane_impl p{ntk, ps, st}; + + auto result = p.run(); + + if (pst) + { + *pst = st; + } + + return result; +} + +} // namespace fiction + +#endif // FICTION_PLANAR_LAYOUT_FROM_NETWORK_EMBEDDING_HPP diff --git a/test/algorithms/network_transformation/node_duplication_planarization.cpp b/test/algorithms/network_transformation/node_duplication_planarization.cpp new file mode 100644 index 0000000000..c23f229de3 --- /dev/null +++ b/test/algorithms/network_transformation/node_duplication_planarization.cpp @@ -0,0 +1,207 @@ +// +// Created by benjamin on 11.06.24. +// + +#include + +#include "catch2/matchers/catch_matchers.hpp" +#include "fiction/algorithms/graph/mincross.hpp" +#include "fiction/algorithms/network_transformation/fanout_substitution.hpp" +#include "fiction/algorithms/network_transformation/network_balancing.hpp" +#include "fiction/networks/views/mutable_rank_view.hpp" + +#include +#include +#include + +#include +#include + +using namespace fiction; + +TEST_CASE("Check exceptions", "[node-duplication-planarization]") +{ + technology_network tec{}; + + const auto x1 = tec.create_pi(); + const auto x2 = tec.create_pi(); + const auto b1 = tec.create_buf(x2); + const auto f1 = tec.create_and(x1, b1); + + tec.create_po(f1); + + const auto vpi_r = fiction::mutable_rank_view(tec); + + CHECK_THROWS_WITH(node_duplication_planarization(vpi_r), "Networks have to be balanced for this duplication"); +} + +TEST_CASE("Planarize technology ntk", "[node-duplication-planarization]") +{ + technology_network tec{}; + + const auto x1 = tec.create_pi(); + const auto x2 = tec.create_pi(); + const auto x3 = tec.create_pi(); + const auto x4 = tec.create_pi(); + const auto x5 = tec.create_pi(); + const auto f1 = tec.create_not(x2); + const auto f2 = tec.create_nary_and({x1, x2, x3, x4}); + const auto f3 = tec.create_nary_or({x3, x4, x5}); + tec.create_po(f1); + tec.create_po(f2); + tec.create_po(f3); + + network_balancing_params ps; + ps.unify_outputs = true; + + const auto tec_b = fiction::network_balancing( + fiction::fanout_substitution(tec), ps); + + const auto vpi_r = fiction::mutable_rank_view(tec_b); + + auto planarized_ntk = node_duplication_planarization(vpi_r); + + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + auto ntk = mincross(planarized_ntk, p_min, &st_min); // counts crossings + CHECK(st_min.num_crossings == 0); + + // clang-tidy bugprone-unchecked-optional-access false positive in tests + // NOLINTBEGIN(bugprone-unchecked-optional-access) + mockturtle::equivalence_checking_stats st; + const auto cec_m = + mockturtle::equivalence_checking(*fiction::virtual_miter(tec, planarized_ntk), {}, &st); + REQUIRE(cec_m.has_value()); + const auto result = *cec_m; + CHECK(result == 1); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST_CASE("Buffer AIG and planarize technology_network", "[node-duplication-planarization]") +{ + mockturtle::aig_network aig{}; + + const auto x1 = aig.create_pi(); + const auto x2 = aig.create_pi(); + const auto x3 = aig.create_pi(); + const auto x4 = aig.create_pi(); + const auto x5 = aig.create_pi(); + const auto f1 = aig.create_not(x2); + const auto f2 = aig.create_nary_and({x1, x2, x3, x4}); + const auto f3 = aig.create_nary_or({x3, x4, x5}); + const auto f4 = aig.create_maj(x1, x2, f3); + aig.create_po(f1); + aig.create_po(f2); + aig.create_po(f3); + aig.create_po(f4); + + network_balancing_params ps; + ps.unify_outputs = true; + + const auto aig_b = fiction::network_balancing( + fiction::fanout_substitution(aig), ps); + + const auto vpi_r = fiction::mutable_rank_view(aig_b); + + auto planarized_ntk = node_duplication_planarization(vpi_r); + + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + auto ntk = mincross(planarized_ntk, p_min, &st_min); // counts crossings + CHECK(st_min.num_crossings == 0); + + // clang-tidy bugprone-unchecked-optional-access false positive in tests + // NOLINTBEGIN(bugprone-unchecked-optional-access) + mockturtle::equivalence_checking_stats st; + const auto cec_m = + mockturtle::equivalence_checking(*fiction::virtual_miter(aig, planarized_ntk), {}, &st); + REQUIRE(cec_m.has_value()); + const auto result = *cec_m; + CHECK(result == 1); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST_CASE("Buffer AIG and planarize technology_network 2", "[node-duplication-planarization]") +{ + mockturtle::aig_network aig{}; + + const auto x1 = aig.create_pi(); + const auto x2 = aig.create_pi(); + const auto f1 = aig.create_and(x1, x2); + const auto f2 = aig.create_or(x1, x2); + aig.create_po(f1); + aig.create_po(f2); + + network_balancing_params ps; + ps.unify_outputs = true; + + const auto aig_b = fiction::network_balancing( + fiction::fanout_substitution(aig), ps); + + const auto vpi_r = fiction::mutable_rank_view(aig_b); + + auto planarized_ntk = node_duplication_planarization(vpi_r); + + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + auto ntk = mincross(planarized_ntk, p_min, &st_min); // counts crossings + CHECK(st_min.num_crossings == 0); + + // clang-tidy bugprone-unchecked-optional-access false positive in tests + // NOLINTBEGIN(bugprone-unchecked-optional-access) + mockturtle::equivalence_checking_stats st; + const auto cec_m = + mockturtle::equivalence_checking(*fiction::virtual_miter(aig, planarized_ntk), {}, &st); + REQUIRE(cec_m.has_value()); + const auto result = *cec_m; + CHECK(result == 1); + // NOLINTEND(bugprone-unchecked-optional-access) +} + +TEST_CASE("Planarize multi output network", "[node-duplication-planarization]") +{ + mockturtle::aig_network aig{}; + + const auto x1 = aig.create_pi(); + const auto x2 = aig.create_pi(); + + const auto a1 = aig.create_and(x1, x2); + + aig.create_po(a1); + aig.create_po(a1); + aig.create_po(a1); + aig.create_po(a1); + + network_balancing_params ps; + ps.unify_outputs = true; + + const auto aig_b = fiction::network_balancing( + fiction::fanout_substitution(aig), ps); + + const auto vpi_r = fiction::mutable_rank_view(aig_b); + + auto planarized_ntk = node_duplication_planarization(vpi_r); + + mincross_stats st_min{}; + mincross_params p_min{}; + p_min.optimize = false; + + auto ntk = mincross(planarized_ntk, p_min, &st_min); // counts crossings + CHECK(st_min.num_crossings == 0); + + // clang-tidy bugprone-unchecked-optional-access false positive in tests + // NOLINTBEGIN(bugprone-unchecked-optional-access) + mockturtle::equivalence_checking_stats st; + const auto cec_m = + mockturtle::equivalence_checking(*fiction::virtual_miter(aig, planarized_ntk), {}, &st); + REQUIRE(cec_m.has_value()); + const auto result = *cec_m; + CHECK(result == 1); + // NOLINTEND(bugprone-unchecked-optional-access) +} diff --git a/test/algorithms/physical_design/planar_layout_from_network_embedding.cpp b/test/algorithms/physical_design/planar_layout_from_network_embedding.cpp new file mode 100644 index 0000000000..439d0f713e --- /dev/null +++ b/test/algorithms/physical_design/planar_layout_from_network_embedding.cpp @@ -0,0 +1,140 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace fiction; + +static void check_stats(const planar_layout_from_network_embedding_stats& st) noexcept +{ + CHECK(st.x_size > 0); + CHECK(st.y_size > 0); + CHECK(st.num_gates > 0); + CHECK(st.num_wires > 0); +} + +template +static void check_plane(const Ntk& ntk) +{ + using gate_lyt = + fiction::gate_level_layout>>>; + using cell_layout = cell_level_layout>>; + + fiction::network_balancing_params b_ps; + b_ps.unify_outputs = true; + + const auto tec_balanced = fiction::network_balancing(fiction::fanout_substitution(ntk), b_ps); + + auto tec_ranked = fiction::mutable_rank_view(tec_balanced); + auto planarized_b = fiction::node_duplication_planarization(tec_ranked); + + // clang-tidy bugprone-unchecked-optional-access false positive in tests + // NOLINTBEGIN(bugprone-unchecked-optional-access) + mockturtle::equivalence_checking_stats eq_st; + const auto cec_m = mockturtle::equivalence_checking( + *fiction::virtual_miter(ntk, planarized_b), {}, &eq_st); + REQUIRE(cec_m.has_value()); + const auto result = *cec_m; + CHECK(result == 1); + // NOLINTEND(bugprone-unchecked-optional-access) + + planar_layout_from_network_embedding_stats planar_layout_from_network_embedding_stats{}; + + const auto gate_level_layout = + fiction::plane(planarized_b, {}, &planar_layout_from_network_embedding_stats); + + CHECK(gate_level_layout.num_crossings() == 0); + check_stats(planar_layout_from_network_embedding_stats); + CHECK_NOTHROW(apply_gate_library(gate_level_layout)); +} + +TEST_CASE("Check exceptions", "[planar-layout-from-network-embedding]") +{ + using gate_lyt = + fiction::gate_level_layout>>>; + fiction::network_balancing_params b_ps; + b_ps.unify_outputs = true; + + auto maj = blueprints::maj1_network(); + auto maj_ranked = fiction::mutable_rank_view(maj); + planar_layout_from_network_embedding_stats planar_layout_from_network_embedding_stats{}; + CHECK_THROWS_WITH(fiction::plane(maj_ranked, {}, &planar_layout_from_network_embedding_stats), + "network contains nodes that exceed the supported fanin size"); + + auto ao = blueprints::and_or_network(); + auto ao_ranked = fiction::mutable_rank_view(ao); + CHECK_THROWS_WITH(fiction::plane(ao_ranked, {}, &planar_layout_from_network_embedding_stats), + "Input network has to be planar"); +} + +TEST_CASE("Planar layout tests", "[planar-layout-from-network-embedding]") +{ + // Simple correctness and corner-case networks + check_plane(blueprints::se_coloring_corner_case_network()); + check_plane(blueprints::fanout_substitution_corner_case_network()); + check_plane(blueprints::clpl()); + + // Network with constant inputs + check_plane(blueprints::unbalanced_and_inv_network()); + + // Multi-output network + check_plane(blueprints::multi_output_network()); + + // Parity network + check_plane(blueprints::parity_network()); +} + +TEST_CASE("Name conservation after planar physical design", "[planar-layout-from-network-embedding]") +{ + using gate_lyt = gate_level_layout>>>; + + auto topolinano = blueprints::topolinano_network>(); + topolinano.set_network_name("topolinano"); + + fiction::network_balancing_params b_ps; + b_ps.unify_outputs = true; + + const auto topolinano_balanced = fiction::network_balancing>( + fiction::fanout_substitution>(topolinano), b_ps); + + auto topolinano_ranked = fiction::mutable_rank_view(topolinano_balanced); + auto planarized_b = fiction::node_duplication_planarization(topolinano_ranked); + + CHECK(planarized_b.get_network_name() == "topolinano"); + CHECK(planarized_b.get_output_name(0) == "f1"); + CHECK(planarized_b.get_output_name(1) == "f2"); + CHECK(planarized_b.get_output_name(2) == "f3"); + + planar_layout_from_network_embedding_stats planar_layout_from_network_embedding_stats{}; + planar_layout_from_network_embedding_params ps{}; + ps.verbose = true; + + const auto layout = fiction::plane(planarized_b, ps, &planar_layout_from_network_embedding_stats); + + // network name + CHECK(layout.get_layout_name() == "topolinano"); + // PO names + CHECK(layout.get_output_name(0) == "f1"); + CHECK(layout.get_output_name(1) == "f2"); + CHECK(layout.get_output_name(2) == "f3"); +}