| /* | |
| * yosys -- Yosys Open SYnthesis Suite | |
| * | |
| * Copyright (C) 2018 whitequark <whitequark@whitequark.org> | |
| * | |
| * Permission to use, copy, modify, and/or distribute this software for any | |
| * purpose with or without fee is hereby granted, provided that the above | |
| * copyright notice and this permission notice appear in all copies. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| * | |
| */ | |
| // [[CITE]] FlowMap algorithm | |
| // Jason Cong; Yuzheng Ding, "An Optimal Technology Mapping Algorithm for Delay Optimization in Lookup-Table Based FPGA Designs," | |
| // Computer-Aided Design of Integrated Circuits and Systems, IEEE Transactions on, Vol. 13, pp. 1-12, Jan. 1994. | |
| // doi: 10.1109/43.273754 | |
| // [[CITE]] FlowMap-r algorithm | |
| // Jason Cong; Yuzheng Ding, "On Area/Depth Tradeoff in LUT-Based FPGA Technology Mapping," | |
| // Very Large Scale Integration Systems, IEEE Transactions on, Vol. 2, June 1994. | |
| // doi: 10.1109/92.28574 | |
| // Required reading material: | |
| // | |
| // Min-cut max-flow theorem: | |
| // https://www.coursera.org/lecture/algorithms-part2/maxflow-mincut-theorem-beb9G | |
| // FlowMap paper: | |
| // http://cadlab.cs.ucla.edu/~cong/papers/iccad92.pdf (short version) | |
| // https://limsk.ece.gatech.edu/book/papers/flowmap.pdf (long version) | |
| // FlowMap-r paper: | |
| // http://cadlab.cs.ucla.edu/~cong/papers/dac93.pdf (short version) | |
| // https://sci-hub.tw/10.1109/92.285741 (long version) | |
| // Notes on correspondence between paper and implementation: | |
| // | |
| // 1. In the FlowMap paper, the nodes are logic elements (analogous to Yosys cells) and edges are wires. However, in our implementation, | |
| // we use an inverted approach: the nodes are Yosys wire bits, and the edges are derived from (but aren't represented by) Yosys cells. | |
| // This may seem counterintuitive. Three observations may help understanding this. First, for a cell with a 1-bit Y output that is | |
| // the sole driver of its output net (which is the typical case), these representations are equivalent, because there is an exact | |
| // correspondence between cells and output wires. Second, in the paper, primary inputs (analogous to Yosys cell or module ports) are | |
| // nodes, and in Yosys, inputs are wires; our approach allows a direct mapping from both primary inputs and 1-output logic elements to | |
| // flow graph nodes. Third, Yosys cells may have multiple outputs or multi-bit outputs, and by using Yosys wire bits as flow graph nodes, | |
| // such cells are supported without any additional effort; any Yosys cell with n output wire bits ends up being split into n flow graph | |
| // nodes. | |
| // | |
| // 2. The FlowMap paper introduces three networks: Nt, Nt', and Nt''. The network Nt is directly represented by a subgraph of RTLIL graph, | |
| // which is parsed into an equivalent but easier to traverse representation in FlowmapWorker. The network Nt' is built explicitly | |
| // from a subgraph of Nt, and uses a similar representation in FlowGraph. The network Nt'' is implicit in FlowGraph, which is possible | |
| // because of the following observation: each Nt' node corresponds to an Nt'' edge of capacity 1, and each Nt' edge corresponds to | |
| // an Nt'' edge of capacity ∞. Therefore, we only need to explicitly record flow for Nt' edges and through Nt' nodes. | |
| // | |
| // 3. The FlowMap paper ambiguously states: "Moreover, we can find such a cut (X′′, X̅′′) by performing a depth first search starting at | |
| // the source s, and including in X′′ all the nodes which are reachable from s." This actually refers to a specific kind of search, | |
| // min-cut computation. Min-cut computation involves computing the set of nodes reachable from s by an undirected path with no full | |
| // (i.e. zero capacity) forward edges or empty (i.e. no flow) backward edges. In addition, the depth first search is required to compute | |
| // a max-volume max-flow min-cut specifically, because a max-flow min-cut is not, in general, unique. | |
| // Notes on implementation: | |
| // | |
| // 1. To compute depth optimal packing, an intermediate representation is used, where each cell with n output bits is split into n graph | |
| // nodes. Each such graph node is represented directly with the wire bit (RTLIL::SigBit instance) that corresponds to the output bit | |
| // it is created from. Fan-in and fan-out are represented explicitly by edge lists derived from the RTLIL graph. This IR never changes | |
| // after it has been computed. | |
| // | |
| // In terms of data, this IR is comprised of `inputs`, `outputs`, `nodes`, `edges_fw` and `edges_bw` fields. | |
| // | |
| // We call this IR "gate IR". | |
| // | |
| // 2. To compute area optimal packing, another intermediate representation is used, which consists of some K-feasible cone for every node | |
| // that exists in the gate IR. Immediately after depth optimal packing with FlowMap, each such cone occupies the lowest possible depth, | |
| // but this is not true in general, and transformations of this IR may change the cones, although each transformation has to keep each | |
| // cone K-feasible. In this IR, LUT fan-in and fan-out are represented explicitly by edge lists; if a K-feasible cone chosen for node A | |
| // includes nodes B and C, there are edges between all predecessors of A, B and C in the gate IR and node A in this IR. Moreover, in | |
| // this IR, cones may be *realized* or *derealized*. Only realized cones will end up mapped to actual LUTs in the output of this pass. | |
| // | |
| // Intuitively, this IR contains (some, ideally but not necessarily optimal) LUT representation for each input cell. By starting at outputs | |
| // and traversing the graph of this IR backwards, each K-feasible cone is converted to an actual LUT at the end of the pass. This is | |
| // the same as iterating through each realized LUT. | |
| // | |
| // The following are the invariants of this IR: | |
| // a) Each gate IR node corresponds to a K-feasible cut. | |
| // b) Each realized LUT is reachable through backward edges from some output. | |
| // c) The LUT fan-in is exactly the fan-in of its constituent gates minus the fan-out of its constituent gates. | |
| // The invariants are kept even for derealized LUTs, since the whole point of this IR is ease of packing, unpacking, and repacking LUTs. | |
| // | |
| // In terms of data, this IR is comprised of `lut_nodes` (the set of all realized LUTs), `lut_gates` (the map from a LUT to its | |
| // constituent gates), `lut_edges_fw` and `lut_edges_bw` fields. The `inputs` and `outputs` fields are shared with the gate IR. | |
| // | |
| // We call this IR "LUT IR". | |
| #include "kernel/yosys.h" | |
| #include "kernel/sigtools.h" | |
| #include "kernel/modtools.h" | |
| #include "kernel/consteval.h" | |
| USING_YOSYS_NAMESPACE | |
| PRIVATE_NAMESPACE_BEGIN | |
| struct GraphStyle | |
| { | |
| string label; | |
| string color, fillcolor; | |
| GraphStyle(string label = "", string color = "black", string fillcolor = "") : | |
| label(label), color(color), fillcolor(fillcolor) {} | |
| }; | |
| static string dot_escape(string value) | |
| { | |
| std::string escaped; | |
| for (char c : value) { | |
| if (c == '\n') | |
| { | |
| escaped += "\\n"; | |
| continue; | |
| } | |
| if (c == '\\' || c == '"') | |
| escaped += "\\"; | |
| escaped += c; | |
| } | |
| return escaped; | |
| } | |
| static void dump_dot_graph(string filename, | |
| pool<RTLIL::SigBit> nodes, dict<RTLIL::SigBit, pool<RTLIL::SigBit>> edges, | |
| pool<RTLIL::SigBit> inputs, pool<RTLIL::SigBit> outputs, | |
| std::function<GraphStyle(RTLIL::SigBit)> node_style = | |
| [](RTLIL::SigBit) { return GraphStyle{}; }, | |
| std::function<GraphStyle(RTLIL::SigBit, RTLIL::SigBit)> edge_style = | |
| [](RTLIL::SigBit, RTLIL::SigBit) { return GraphStyle{}; }, | |
| string name = "") | |
| { | |
| FILE *f = fopen(filename.c_str(), "w"); | |
| fprintf(f, "digraph \"%s\" {\n", name.c_str()); | |
| fprintf(f, " rankdir=\"TB\";\n"); | |
| dict<RTLIL::SigBit, int> ids; | |
| for (auto node : nodes) | |
| { | |
| ids[node] = ids.size(); | |
| string shape = "ellipse"; | |
| if (inputs[node]) | |
| shape = "box"; | |
| if (outputs[node]) | |
| shape = "octagon"; | |
| auto prop = node_style(node); | |
| string style = ""; | |
| if (!prop.fillcolor.empty()) | |
| style = "filled"; | |
| fprintf(f, " n%d [ shape=%s, fontname=\"Monospace\", label=\"%s\", color=\"%s\", fillcolor=\"%s\", style=\"%s\" ];\n", | |
| ids[node], shape.c_str(), dot_escape(prop.label.c_str()).c_str(), prop.color.c_str(), prop.fillcolor.c_str(), style.c_str()); | |
| } | |
| fprintf(f, " { rank=\"source\"; "); | |
| for (auto input : inputs) | |
| if (nodes[input]) | |
| fprintf(f, "n%d; ", ids[input]); | |
| fprintf(f, "}\n"); | |
| fprintf(f, " { rank=\"sink\"; "); | |
| for (auto output : outputs) | |
| if (nodes[output]) | |
| fprintf(f, "n%d; ", ids[output]); | |
| fprintf(f, "}\n"); | |
| for (auto edge : edges) | |
| { | |
| auto source = edge.first; | |
| for (auto sink : edge.second) { | |
| if (nodes[source] && nodes[sink]) | |
| { | |
| auto prop = edge_style(source, sink); | |
| fprintf(f, " n%d -> n%d [ label=\"%s\", color=\"%s\", fillcolor=\"%s\" ];\n", | |
| ids[source], ids[sink], dot_escape(prop.label.c_str()).c_str(), prop.color.c_str(), prop.fillcolor.c_str()); | |
| } | |
| } | |
| } | |
| fprintf(f, "}\n"); | |
| fclose(f); | |
| } | |
| struct FlowGraph | |
| { | |
| const RTLIL::SigBit source; | |
| RTLIL::SigBit sink; | |
| pool<RTLIL::SigBit> nodes = {source}; | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> edges_fw, edges_bw; | |
| const int MAX_NODE_FLOW = 1; | |
| dict<RTLIL::SigBit, int> node_flow; | |
| dict<pair<RTLIL::SigBit, RTLIL::SigBit>, int> edge_flow; | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> collapsed; | |
| void dump_dot_graph(string filename) | |
| { | |
| auto node_style = [&](RTLIL::SigBit node) { | |
| string label = (node == source) ? "(source)" : log_signal(node); | |
| for (auto collapsed_node : collapsed[node]) | |
| label += stringf(" %s", log_signal(collapsed_node)); | |
| int flow = node_flow[node]; | |
| if (node != source && node != sink) | |
| label += stringf("\n%d/%d", flow, MAX_NODE_FLOW); | |
| else | |
| label += stringf("\n%d/∞", flow); | |
| return GraphStyle{label, flow < MAX_NODE_FLOW ? "green" : "black"}; | |
| }; | |
| auto edge_style = [&](RTLIL::SigBit source, RTLIL::SigBit sink) { | |
| int flow = edge_flow[{source, sink}]; | |
| return GraphStyle{stringf("%d/∞", flow), flow > 0 ? "blue" : "black"}; | |
| }; | |
| ::dump_dot_graph(filename, nodes, edges_fw, {source}, {sink}, node_style, edge_style); | |
| } | |
| // Here, we are working on the Nt'' network, but our representation is the Nt' network. | |
| // The difference between these is that where in Nt' we have a subgraph: | |
| // | |
| // v1 -> v2 -> v3 | |
| // | |
| // in Nt'' we have a corresponding subgraph: | |
| // | |
| // v'1b -∞-> v'2t -f-> v'2b -∞-> v'3t | |
| // | |
| // To address this, we split each node v into two nodes, v't and v'b. This representation is virtual, | |
| // in the sense that nodes v't and v'b are overlaid on top of the original node v, and only exist | |
| // in paths and worklists. | |
| struct NodePrime | |
| { | |
| RTLIL::SigBit node; | |
| bool is_bottom; | |
| NodePrime(RTLIL::SigBit node, bool is_bottom) : | |
| node(node), is_bottom(is_bottom) {} | |
| bool operator==(const NodePrime &other) const | |
| { | |
| return node == other.node && is_bottom == other.is_bottom; | |
| } | |
| bool operator!=(const NodePrime &other) const | |
| { | |
| return !(*this == other); | |
| } | |
| unsigned int hash() const | |
| { | |
| return hash_ops<pair<RTLIL::SigBit, int>>::hash({node, is_bottom}); | |
| } | |
| static NodePrime top(RTLIL::SigBit node) | |
| { | |
| return NodePrime(node, /*is_bottom=*/false); | |
| } | |
| static NodePrime bottom(RTLIL::SigBit node) | |
| { | |
| return NodePrime(node, /*is_bottom=*/true); | |
| } | |
| NodePrime as_top() const | |
| { | |
| log_assert(is_bottom); | |
| return top(node); | |
| } | |
| NodePrime as_bottom() const | |
| { | |
| log_assert(!is_bottom); | |
| return bottom(node); | |
| } | |
| }; | |
| bool find_augmenting_path(bool commit) | |
| { | |
| NodePrime source_prime = {source, true}; | |
| NodePrime sink_prime = {sink, false}; | |
| vector<NodePrime> path = {source_prime}; | |
| pool<NodePrime> visited = {}; | |
| bool found; | |
| do { | |
| found = false; | |
| auto node_prime = path.back(); | |
| visited.insert(node_prime); | |
| if (!node_prime.is_bottom) // vt | |
| { | |
| if (!visited[node_prime.as_bottom()] && node_flow[node_prime.node] < MAX_NODE_FLOW) | |
| { | |
| path.push_back(node_prime.as_bottom()); | |
| found = true; | |
| } | |
| else | |
| { | |
| for (auto node_pred : edges_bw[node_prime.node]) | |
| { | |
| if (!visited[NodePrime::bottom(node_pred)] && edge_flow[{node_pred, node_prime.node}] > 0) | |
| { | |
| path.push_back(NodePrime::bottom(node_pred)); | |
| found = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| else // vb | |
| { | |
| if (!visited[node_prime.as_top()] && node_flow[node_prime.node] > 0) | |
| { | |
| path.push_back(node_prime.as_top()); | |
| found = true; | |
| } | |
| else | |
| { | |
| for (auto node_succ : edges_fw[node_prime.node]) | |
| { | |
| if (!visited[NodePrime::top(node_succ)] /* && edge_flow[...] < ∞ */) | |
| { | |
| path.push_back(NodePrime::top(node_succ)); | |
| found = true; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (!found && path.size() > 1) | |
| { | |
| path.pop_back(); | |
| found = true; | |
| } | |
| } while(path.back() != sink_prime && found); | |
| if (commit && path.back() == sink_prime) | |
| { | |
| auto prev_prime = path.front(); | |
| for (auto node_prime : path) | |
| { | |
| if (node_prime == source_prime) | |
| continue; | |
| log_assert(prev_prime.is_bottom ^ node_prime.is_bottom); | |
| if (prev_prime.node == node_prime.node) | |
| { | |
| auto node = node_prime.node; | |
| if (!prev_prime.is_bottom && node_prime.is_bottom) | |
| { | |
| log_assert(node_flow[node] == 0); | |
| node_flow[node]++; | |
| } | |
| else | |
| { | |
| log_assert(node_flow[node] != 0); | |
| node_flow[node]--; | |
| } | |
| } | |
| else | |
| { | |
| if (prev_prime.is_bottom && !node_prime.is_bottom) | |
| { | |
| log_assert(true /* edge_flow[...] < ∞ */); | |
| edge_flow[{prev_prime.node, node_prime.node}]++; | |
| } | |
| else | |
| { | |
| log_assert((edge_flow[{node_prime.node, prev_prime.node}] > 0)); | |
| edge_flow[{node_prime.node, prev_prime.node}]--; | |
| } | |
| } | |
| prev_prime = node_prime; | |
| } | |
| node_flow[source]++; | |
| node_flow[sink]++; | |
| } | |
| return path.back() == sink_prime; | |
| } | |
| int maximum_flow(int order) | |
| { | |
| int flow = 0; | |
| while (flow < order && find_augmenting_path(/*commit=*/true)) | |
| flow++; | |
| return flow + find_augmenting_path(/*commit=*/false); | |
| } | |
| pair<pool<RTLIL::SigBit>, pool<RTLIL::SigBit>> edge_cut() | |
| { | |
| pool<RTLIL::SigBit> x, xi; | |
| NodePrime source_prime = {source, true}; | |
| pool<NodePrime> visited; | |
| vector<NodePrime> worklist = {source_prime}; | |
| while (!worklist.empty()) | |
| { | |
| auto node_prime = worklist.back(); | |
| worklist.pop_back(); | |
| if (visited[node_prime]) | |
| continue; | |
| visited.insert(node_prime); | |
| if (!node_prime.is_bottom) | |
| x.insert(node_prime.node); | |
| // Mincut is constructed by traversing a graph in an undirected way along forward edges that aren't full, or backward edges | |
| // that aren't empty. | |
| if (!node_prime.is_bottom) // top | |
| { | |
| if (node_flow[node_prime.node] < MAX_NODE_FLOW) | |
| worklist.push_back(node_prime.as_bottom()); | |
| for (auto node_pred : edges_bw[node_prime.node]) | |
| if (edge_flow[{node_pred, node_prime.node}] > 0) | |
| worklist.push_back(NodePrime::bottom(node_pred)); | |
| } | |
| else // bottom | |
| { | |
| if (node_flow[node_prime.node] > 0) | |
| worklist.push_back(node_prime.as_top()); | |
| for (auto node_succ : edges_fw[node_prime.node]) | |
| if (true /* edge_flow[...] < ∞ */) | |
| worklist.push_back(NodePrime::top(node_succ)); | |
| } | |
| } | |
| for (auto node : nodes) | |
| if (!x[node]) | |
| xi.insert(node); | |
| for (auto collapsed_node : collapsed[sink]) | |
| xi.insert(collapsed_node); | |
| log_assert(!x[sink] && xi[sink]); | |
| return {x, xi}; | |
| } | |
| }; | |
| struct FlowmapWorker | |
| { | |
| int order; | |
| int r_alpha, r_beta, r_gamma; | |
| bool debug, debug_relax; | |
| RTLIL::Module *module; | |
| SigMap sigmap; | |
| ModIndex index; | |
| dict<RTLIL::SigBit, ModIndex::PortInfo> node_origins; | |
| // Gate IR | |
| pool<RTLIL::SigBit> nodes, inputs, outputs; | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> edges_fw, edges_bw; | |
| dict<RTLIL::SigBit, int> labels; | |
| // LUT IR | |
| pool<RTLIL::SigBit> lut_nodes; | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> lut_gates; | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> lut_edges_fw, lut_edges_bw; | |
| dict<RTLIL::SigBit, int> lut_depths, lut_altitudes, lut_slacks; | |
| int gate_count = 0, lut_count = 0, packed_count = 0; | |
| int gate_area = 0, lut_area = 0; | |
| enum class GraphMode { | |
| Label, | |
| Cut, | |
| Slack, | |
| }; | |
| void dump_dot_graph(string filename, GraphMode mode, | |
| pool<RTLIL::SigBit> subgraph_nodes = {}, dict<RTLIL::SigBit, pool<RTLIL::SigBit>> subgraph_edges = {}, | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> collapsed = {}, | |
| pair<pool<RTLIL::SigBit>, pool<RTLIL::SigBit>> cut = {}) | |
| { | |
| if (subgraph_nodes.empty()) | |
| subgraph_nodes = nodes; | |
| if (subgraph_edges.empty()) | |
| subgraph_edges = edges_fw; | |
| auto node_style = [&](RTLIL::SigBit node) { | |
| string label = log_signal(node); | |
| for (auto collapsed_node : collapsed[node]) | |
| if (collapsed_node != node) | |
| label += stringf(" %s", log_signal(collapsed_node)); | |
| switch (mode) | |
| { | |
| case GraphMode::Label: | |
| if (labels[node] == -1) | |
| { | |
| label += "\nl=?"; | |
| return GraphStyle{label}; | |
| } | |
| else | |
| { | |
| label += stringf("\nl=%d", labels[node]); | |
| string fillcolor = stringf("/set311/%d", 1 + labels[node] % 11); | |
| return GraphStyle{label, "", fillcolor}; | |
| } | |
| case GraphMode::Cut: | |
| if (cut.first[node]) | |
| return GraphStyle{label, "blue"}; | |
| if (cut.second[node]) | |
| return GraphStyle{label, "red"}; | |
| return GraphStyle{label}; | |
| case GraphMode::Slack: | |
| label += stringf("\nd=%d a=%d\ns=%d", lut_depths[node], lut_altitudes[node], lut_slacks[node]); | |
| return GraphStyle{label, lut_slacks[node] == 0 ? "red" : "black"}; | |
| } | |
| return GraphStyle{label}; | |
| }; | |
| auto edge_style = [&](RTLIL::SigBit, RTLIL::SigBit) { | |
| return GraphStyle{}; | |
| }; | |
| ::dump_dot_graph(filename, subgraph_nodes, subgraph_edges, inputs, outputs, node_style, edge_style, module->name.str()); | |
| } | |
| void dump_dot_lut_graph(string filename, GraphMode mode) | |
| { | |
| pool<RTLIL::SigBit> lut_and_input_nodes; | |
| lut_and_input_nodes.insert(lut_nodes.begin(), lut_nodes.end()); | |
| lut_and_input_nodes.insert(inputs.begin(), inputs.end()); | |
| dump_dot_graph(filename, mode, lut_and_input_nodes, lut_edges_fw, lut_gates); | |
| } | |
| pool<RTLIL::SigBit> find_subgraph(RTLIL::SigBit sink) | |
| { | |
| pool<RTLIL::SigBit> subgraph; | |
| pool<RTLIL::SigBit> worklist = {sink}; | |
| while (!worklist.empty()) | |
| { | |
| auto node = worklist.pop(); | |
| subgraph.insert(node); | |
| for (auto source : edges_bw[node]) | |
| { | |
| if (!subgraph[source]) | |
| worklist.insert(source); | |
| } | |
| } | |
| return subgraph; | |
| } | |
| FlowGraph build_flow_graph(RTLIL::SigBit sink, int p) | |
| { | |
| FlowGraph flow_graph; | |
| flow_graph.sink = sink; | |
| pool<RTLIL::SigBit> worklist = {sink}, visited; | |
| while (!worklist.empty()) | |
| { | |
| auto node = worklist.pop(); | |
| visited.insert(node); | |
| auto collapsed_node = labels[node] == p ? sink : node; | |
| if (node != collapsed_node) | |
| flow_graph.collapsed[collapsed_node].insert(node); | |
| flow_graph.nodes.insert(collapsed_node); | |
| for (auto node_pred : edges_bw[node]) | |
| { | |
| auto collapsed_node_pred = labels[node_pred] == p ? sink : node_pred; | |
| if (node_pred != collapsed_node_pred) | |
| flow_graph.collapsed[collapsed_node_pred].insert(node_pred); | |
| if (collapsed_node != collapsed_node_pred) | |
| { | |
| flow_graph.edges_bw[collapsed_node].insert(collapsed_node_pred); | |
| flow_graph.edges_fw[collapsed_node_pred].insert(collapsed_node); | |
| } | |
| if (inputs[node_pred]) | |
| { | |
| flow_graph.edges_bw[collapsed_node_pred].insert(flow_graph.source); | |
| flow_graph.edges_fw[flow_graph.source].insert(collapsed_node_pred); | |
| } | |
| if (!visited[node_pred]) | |
| worklist.insert(node_pred); | |
| } | |
| } | |
| return flow_graph; | |
| } | |
| void discover_nodes(pool<IdString> cell_types) | |
| { | |
| for (auto cell : module->selected_cells()) | |
| { | |
| if (!cell_types[cell->type]) | |
| continue; | |
| if (!cell->known()) | |
| log_error("Cell %s (%s.%s) is unknown.\n", cell->type.c_str(), log_id(module), log_id(cell)); | |
| pool<RTLIL::SigBit> fanout; | |
| for (auto conn : cell->connections()) | |
| { | |
| if (!cell->output(conn.first)) continue; | |
| int offset = -1; | |
| for (auto bit : conn.second) | |
| { | |
| offset++; | |
| if (!bit.wire) continue; | |
| auto mapped_bit = sigmap(bit); | |
| if (nodes[mapped_bit]) | |
| log_error("Multiple drivers found for wire %s.\n", log_signal(mapped_bit)); | |
| nodes.insert(mapped_bit); | |
| node_origins[mapped_bit] = ModIndex::PortInfo(cell, conn.first, offset); | |
| fanout.insert(mapped_bit); | |
| } | |
| } | |
| int fanin = 0; | |
| for (auto conn : cell->connections()) | |
| { | |
| if (!cell->input(conn.first)) continue; | |
| for (auto bit : sigmap(conn.second)) | |
| { | |
| if (!bit.wire) continue; | |
| for (auto fanout_bit : fanout) | |
| { | |
| edges_fw[bit].insert(fanout_bit); | |
| edges_bw[fanout_bit].insert(bit); | |
| } | |
| fanin++; | |
| } | |
| } | |
| if (fanin > order) | |
| log_error("Cell %s (%s.%s) with fan-in %d cannot be mapped to a %d-LUT.\n", | |
| cell->type.c_str(), log_id(module), log_id(cell), fanin, order); | |
| gate_count++; | |
| gate_area += 1 << fanin; | |
| } | |
| for (auto edge : edges_fw) | |
| { | |
| if (!nodes[edge.first]) | |
| { | |
| inputs.insert(edge.first); | |
| nodes.insert(edge.first); | |
| } | |
| } | |
| for (auto node : nodes) | |
| { | |
| auto node_info = index.query(node); | |
| if (node_info->is_output && !inputs[node]) | |
| outputs.insert(node); | |
| for (auto port : node_info->ports) | |
| if (!cell_types[port.cell->type] && !inputs[node]) | |
| outputs.insert(node); | |
| } | |
| if (debug) | |
| { | |
| dump_dot_graph("flowmap-initial.dot", GraphMode::Label); | |
| log("Dumped initial graph to `flowmap-initial.dot`.\n"); | |
| } | |
| } | |
| void label_nodes() | |
| { | |
| for (auto node : nodes) | |
| labels[node] = -1; | |
| for (auto input : inputs) | |
| { | |
| if (input.wire->attributes.count(ID($flowmap_level))) | |
| labels[input] = input.wire->attributes[ID($flowmap_level)].as_int(); | |
| else | |
| labels[input] = 0; | |
| } | |
| pool<RTLIL::SigBit> worklist = nodes; | |
| int debug_num = 0; | |
| while (!worklist.empty()) | |
| { | |
| auto sink = worklist.pop(); | |
| if (labels[sink] != -1) | |
| continue; | |
| bool inputs_have_labels = true; | |
| for (auto sink_input : edges_bw[sink]) | |
| { | |
| if (labels[sink_input] == -1) | |
| { | |
| inputs_have_labels = false; | |
| break; | |
| } | |
| } | |
| if (!inputs_have_labels) | |
| continue; | |
| if (debug) | |
| { | |
| debug_num++; | |
| log("Examining subgraph %d rooted in %s.\n", debug_num, log_signal(sink)); | |
| } | |
| pool<RTLIL::SigBit> subgraph = find_subgraph(sink); | |
| int p = 1; | |
| for (auto subgraph_node : subgraph) | |
| p = max(p, labels[subgraph_node]); | |
| FlowGraph flow_graph = build_flow_graph(sink, p); | |
| int flow = flow_graph.maximum_flow(order); | |
| pool<RTLIL::SigBit> x, xi; | |
| if (flow <= order) | |
| { | |
| labels[sink] = p; | |
| auto cut = flow_graph.edge_cut(); | |
| x = cut.first; | |
| xi = cut.second; | |
| } | |
| else | |
| { | |
| labels[sink] = p + 1; | |
| x = subgraph; | |
| x.erase(sink); | |
| xi.insert(sink); | |
| } | |
| lut_gates[sink] = xi; | |
| pool<RTLIL::SigBit> k; | |
| for (auto xi_node : xi) | |
| { | |
| for (auto xi_node_pred : edges_bw[xi_node]) | |
| if (x[xi_node_pred]) | |
| k.insert(xi_node_pred); | |
| } | |
| log_assert((int)k.size() <= order); | |
| lut_edges_bw[sink] = k; | |
| for (auto k_node : k) | |
| lut_edges_fw[k_node].insert(sink); | |
| if (debug) | |
| { | |
| log(" Maximum flow: %d. Assigned label %d.\n", flow, labels[sink]); | |
| dump_dot_graph(stringf("flowmap-%d-sub.dot", debug_num), GraphMode::Cut, subgraph, {}, {}, {x, xi}); | |
| log(" Dumped subgraph to `flowmap-%d-sub.dot`.\n", debug_num); | |
| flow_graph.dump_dot_graph(stringf("flowmap-%d-flow.dot", debug_num)); | |
| log(" Dumped flow graph to `flowmap-%d-flow.dot`.\n", debug_num); | |
| log(" LUT inputs:"); | |
| for (auto k_node : k) | |
| log(" %s", log_signal(k_node)); | |
| log(".\n"); | |
| log(" LUT packed gates:"); | |
| for (auto xi_node : xi) | |
| log(" %s", log_signal(xi_node)); | |
| log(".\n"); | |
| } | |
| for (auto sink_succ : edges_fw[sink]) | |
| worklist.insert(sink_succ); | |
| } | |
| if (debug) | |
| { | |
| dump_dot_graph("flowmap-labeled.dot", GraphMode::Label); | |
| log("Dumped labeled graph to `flowmap-labeled.dot`.\n"); | |
| } | |
| } | |
| int map_luts() | |
| { | |
| pool<RTLIL::SigBit> worklist = outputs; | |
| while (!worklist.empty()) | |
| { | |
| auto lut_node = worklist.pop(); | |
| lut_nodes.insert(lut_node); | |
| for (auto input_node : lut_edges_bw[lut_node]) | |
| if (!lut_nodes[input_node] && !inputs[input_node]) | |
| worklist.insert(input_node); | |
| } | |
| int depth = 0; | |
| for (auto label : labels) | |
| depth = max(depth, label.second); | |
| log("Mapped to %d LUTs with maximum depth %d.\n", GetSize(lut_nodes), depth); | |
| if (debug) | |
| { | |
| dump_dot_lut_graph("flowmap-mapped.dot", GraphMode::Label); | |
| log("Dumped mapped graph to `flowmap-mapped.dot`.\n"); | |
| } | |
| return depth; | |
| } | |
| void realize_derealize_lut(RTLIL::SigBit lut, pool<RTLIL::SigBit> *changed = nullptr) | |
| { | |
| pool<RTLIL::SigBit> worklist = {lut}; | |
| while (!worklist.empty()) | |
| { | |
| auto lut = worklist.pop(); | |
| if (inputs[lut]) | |
| continue; | |
| bool realized_successors = false; | |
| for (auto lut_succ : lut_edges_fw[lut]) | |
| if (lut_nodes[lut_succ]) | |
| realized_successors = true; | |
| if (realized_successors && !lut_nodes[lut]) | |
| lut_nodes.insert(lut); | |
| else if (!realized_successors && lut_nodes[lut]) | |
| lut_nodes.erase(lut); | |
| else | |
| continue; | |
| for (auto lut_pred : lut_edges_bw[lut]) | |
| worklist.insert(lut_pred); | |
| if (changed) | |
| changed->insert(lut); | |
| } | |
| } | |
| void add_lut_edge(RTLIL::SigBit pred, RTLIL::SigBit succ, pool<RTLIL::SigBit> *changed = nullptr) | |
| { | |
| log_assert(!lut_edges_fw[pred][succ] && !lut_edges_bw[succ][pred]); | |
| log_assert((int)lut_edges_bw[succ].size() < order); | |
| lut_edges_fw[pred].insert(succ); | |
| lut_edges_bw[succ].insert(pred); | |
| realize_derealize_lut(pred, changed); | |
| if (changed) | |
| { | |
| changed->insert(pred); | |
| changed->insert(succ); | |
| } | |
| } | |
| void remove_lut_edge(RTLIL::SigBit pred, RTLIL::SigBit succ, pool<RTLIL::SigBit> *changed = nullptr) | |
| { | |
| log_assert(lut_edges_fw[pred][succ] && lut_edges_bw[succ][pred]); | |
| lut_edges_fw[pred].erase(succ); | |
| lut_edges_bw[succ].erase(pred); | |
| realize_derealize_lut(pred, changed); | |
| if (changed) | |
| { | |
| if (lut_nodes[pred]) | |
| changed->insert(pred); | |
| changed->insert(succ); | |
| } | |
| } | |
| pair<pool<RTLIL::SigBit>, pool<RTLIL::SigBit>> cut_lut_at_gate(RTLIL::SigBit lut, RTLIL::SigBit lut_gate) | |
| { | |
| pool<RTLIL::SigBit> gate_inputs = lut_edges_bw[lut]; | |
| pool<RTLIL::SigBit> other_inputs; | |
| pool<RTLIL::SigBit> worklist = {lut}; | |
| while (!worklist.empty()) | |
| { | |
| auto node = worklist.pop(); | |
| for (auto node_pred : edges_bw[node]) | |
| { | |
| if (node_pred == lut_gate) | |
| continue; | |
| if (lut_gates[lut][node_pred]) | |
| worklist.insert(node_pred); | |
| else | |
| { | |
| gate_inputs.erase(node_pred); | |
| other_inputs.insert(node_pred); | |
| } | |
| } | |
| } | |
| return {gate_inputs, other_inputs}; | |
| } | |
| void compute_lut_distances(dict<RTLIL::SigBit, int> &lut_distances, bool forward, | |
| pool<RTLIL::SigBit> initial = {}, pool<RTLIL::SigBit> *changed = nullptr) | |
| { | |
| pool<RTLIL::SigBit> terminals = forward ? inputs : outputs; | |
| auto &lut_edges_next = forward ? lut_edges_fw : lut_edges_bw; | |
| auto &lut_edges_prev = forward ? lut_edges_bw : lut_edges_fw; | |
| if (initial.empty()) | |
| initial = terminals; | |
| for (auto node : initial) | |
| lut_distances.erase(node); | |
| pool<RTLIL::SigBit> worklist = initial; | |
| while (!worklist.empty()) | |
| { | |
| auto lut = worklist.pop(); | |
| int lut_distance = 0; | |
| if (forward && inputs[lut]) | |
| lut_distance = labels[lut]; // to support (* $flowmap_level=n *) | |
| for (auto lut_prev : lut_edges_prev[lut]) | |
| if ((lut_nodes[lut_prev] || inputs[lut_prev]) && lut_distances.count(lut_prev)) | |
| lut_distance = max(lut_distance, lut_distances[lut_prev] + 1); | |
| if (!lut_distances.count(lut) || lut_distances[lut] != lut_distance) | |
| { | |
| lut_distances[lut] = lut_distance; | |
| if (changed != nullptr && !inputs[lut]) | |
| changed->insert(lut); | |
| for (auto lut_next : lut_edges_next[lut]) | |
| if (lut_nodes[lut_next] || inputs[lut_next]) | |
| worklist.insert(lut_next); | |
| } | |
| } | |
| } | |
| void check_lut_distances(const dict<RTLIL::SigBit, int> &lut_distances, bool forward) | |
| { | |
| dict<RTLIL::SigBit, int> gold_lut_distances; | |
| compute_lut_distances(gold_lut_distances, forward); | |
| for (auto lut_distance : lut_distances) | |
| if (lut_nodes[lut_distance.first]) | |
| log_assert(lut_distance.second == gold_lut_distances[lut_distance.first]); | |
| } | |
| // LUT depth is the length of the longest path from any input in LUT fan-in to LUT. | |
| // LUT altitude (for lack of a better term) is the length of the longest path from LUT to any output in LUT fan-out. | |
| void update_lut_depths_altitudes(pool<RTLIL::SigBit> worklist = {}, pool<RTLIL::SigBit> *changed = nullptr) | |
| { | |
| compute_lut_distances(lut_depths, /*forward=*/true, worklist, changed); | |
| compute_lut_distances(lut_altitudes, /*forward=*/false, worklist, changed); | |
| if (debug_relax && !worklist.empty()) { | |
| check_lut_distances(lut_depths, /*forward=*/true); | |
| check_lut_distances(lut_altitudes, /*forward=*/false); | |
| } | |
| } | |
| // LUT critical output set is the set of outputs whose depth will increase (equivalently, slack will decrease) if the depth of | |
| // the LUT increases. (This is referred to as RPOv for LUTv in the paper.) | |
| void compute_lut_critical_outputs(dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs, | |
| pool<RTLIL::SigBit> worklist = {}) | |
| { | |
| if (worklist.empty()) | |
| worklist = lut_nodes; | |
| while (!worklist.empty()) | |
| { | |
| bool updated_some = false; | |
| for (auto lut : worklist) | |
| { | |
| if (outputs[lut]) | |
| lut_critical_outputs[lut] = {lut}; | |
| else | |
| { | |
| bool all_succ_computed = true; | |
| lut_critical_outputs[lut] = {}; | |
| for (auto lut_succ : lut_edges_fw[lut]) | |
| { | |
| if (lut_nodes[lut_succ] && lut_depths[lut_succ] == lut_depths[lut] + 1) | |
| { | |
| if (lut_critical_outputs.count(lut_succ)) | |
| lut_critical_outputs[lut].insert(lut_critical_outputs[lut_succ].begin(), lut_critical_outputs[lut_succ].end()); | |
| else | |
| { | |
| all_succ_computed = false; | |
| break; | |
| } | |
| } | |
| } | |
| if (!all_succ_computed) | |
| { | |
| lut_critical_outputs.erase(lut); | |
| continue; | |
| } | |
| } | |
| worklist.erase(lut); | |
| updated_some = true; | |
| } | |
| log_assert(updated_some); | |
| } | |
| } | |
| // Invalidating LUT critical output sets is tricky, because increasing the depth of a LUT may take other, adjacent LUTs off the critical | |
| // path to the output. Conservatively, if we increase depth of some LUT, every LUT in its input cone needs to have its critical output | |
| // set invalidated, too. | |
| pool<RTLIL::SigBit> invalidate_lut_critical_outputs(dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs, | |
| pool<RTLIL::SigBit> worklist) | |
| { | |
| pool<RTLIL::SigBit> changed; | |
| while (!worklist.empty()) | |
| { | |
| auto lut = worklist.pop(); | |
| changed.insert(lut); | |
| lut_critical_outputs.erase(lut); | |
| for (auto lut_pred : lut_edges_bw[lut]) | |
| { | |
| if (lut_nodes[lut_pred] && !changed[lut_pred]) | |
| { | |
| changed.insert(lut_pred); | |
| worklist.insert(lut_pred); | |
| } | |
| } | |
| } | |
| return changed; | |
| } | |
| void check_lut_critical_outputs(const dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs) | |
| { | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> gold_lut_critical_outputs; | |
| compute_lut_critical_outputs(gold_lut_critical_outputs); | |
| for (auto lut_critical_output : lut_critical_outputs) | |
| if (lut_nodes[lut_critical_output.first]) | |
| log_assert(lut_critical_output.second == gold_lut_critical_outputs[lut_critical_output.first]); | |
| } | |
| void update_lut_critical_outputs(dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs, | |
| pool<RTLIL::SigBit> worklist = {}) | |
| { | |
| if (!worklist.empty()) | |
| { | |
| pool<RTLIL::SigBit> invalidated = invalidate_lut_critical_outputs(lut_critical_outputs, worklist); | |
| compute_lut_critical_outputs(lut_critical_outputs, invalidated); | |
| check_lut_critical_outputs(lut_critical_outputs); | |
| } | |
| else | |
| compute_lut_critical_outputs(lut_critical_outputs); | |
| } | |
| void update_breaking_node_potentials(dict<RTLIL::SigBit, dict<RTLIL::SigBit, int>> &potentials, | |
| const dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs) | |
| { | |
| for (auto lut : lut_nodes) | |
| { | |
| if (potentials.count(lut)) | |
| continue; | |
| if (lut_gates[lut].size() == 1 || lut_slacks[lut] == 0) | |
| continue; | |
| if (debug_relax) | |
| log(" Computing potentials for LUT %s.\n", log_signal(lut)); | |
| for (auto lut_gate : lut_gates[lut]) | |
| { | |
| if (lut == lut_gate) | |
| continue; | |
| if (debug_relax) | |
| log(" Considering breaking node %s.\n", log_signal(lut_gate)); | |
| int r_ex, r_im, r_slk; | |
| auto cut_inputs = cut_lut_at_gate(lut, lut_gate); | |
| pool<RTLIL::SigBit> gate_inputs = cut_inputs.first, other_inputs = cut_inputs.second; | |
| if (gate_inputs.empty() && (int)other_inputs.size() == order) | |
| { | |
| if (debug_relax) | |
| log(" Breaking would result in a (k+1)-LUT.\n"); | |
| continue; | |
| } | |
| pool<RTLIL::SigBit> elim_fanin_luts; | |
| for (auto gate_input : gate_inputs) | |
| { | |
| if (lut_edges_fw[gate_input].size() == 1) | |
| { | |
| log_assert(lut_edges_fw[gate_input][lut]); | |
| elim_fanin_luts.insert(gate_input); | |
| } | |
| } | |
| if (debug_relax) | |
| { | |
| if (!lut_nodes[lut_gate]) | |
| log(" Breaking requires a new LUT.\n"); | |
| if (!gate_inputs.empty()) | |
| { | |
| log(" Breaking eliminates LUT inputs"); | |
| for (auto gate_input : gate_inputs) | |
| log(" %s", log_signal(gate_input)); | |
| log(".\n"); | |
| } | |
| if (!elim_fanin_luts.empty()) | |
| { | |
| log(" Breaking eliminates fan-in LUTs"); | |
| for (auto elim_fanin_lut : elim_fanin_luts) | |
| log(" %s", log_signal(elim_fanin_lut)); | |
| log(".\n"); | |
| } | |
| } | |
| r_ex = (lut_nodes[lut_gate] ? 0 : -1) + elim_fanin_luts.size(); | |
| pool<pair<RTLIL::SigBit, RTLIL::SigBit>> maybe_mergeable_luts; | |
| // Try to merge LUTv with one of its successors. | |
| RTLIL::SigBit last_lut_succ; | |
| int fanout = 0; | |
| for (auto lut_succ : lut_edges_fw[lut]) | |
| { | |
| if (lut_nodes[lut_succ]) | |
| { | |
| fanout++; | |
| last_lut_succ = lut_succ; | |
| } | |
| } | |
| if (fanout == 1) | |
| maybe_mergeable_luts.insert({lut, last_lut_succ}); | |
| // Try to merge LUTv with one of its predecessors. | |
| for (auto lut_pred : other_inputs) | |
| { | |
| int fanout = 0; | |
| for (auto lut_pred_succ : lut_edges_fw[lut_pred]) | |
| if (lut_nodes[lut_pred_succ] || lut_pred_succ == lut_gate) | |
| fanout++; | |
| if (fanout == 1) | |
| maybe_mergeable_luts.insert({lut_pred, lut}); | |
| } | |
| // Try to merge LUTw with one of its predecessors. | |
| for (auto lut_gate_pred : lut_edges_bw[lut_gate]) | |
| { | |
| int fanout = 0; | |
| for (auto lut_gate_pred_succ : lut_edges_fw[lut_gate_pred]) | |
| if (lut_nodes[lut_gate_pred_succ] || lut_gate_pred_succ == lut_gate) | |
| fanout++; | |
| if (fanout == 1) | |
| maybe_mergeable_luts.insert({lut_gate_pred, lut_gate}); | |
| } | |
| r_im = 0; | |
| for (auto maybe_mergeable_pair : maybe_mergeable_luts) | |
| { | |
| log_assert(lut_edges_fw[maybe_mergeable_pair.first][maybe_mergeable_pair.second]); | |
| pool<RTLIL::SigBit> unique_inputs; | |
| for (auto fst_lut_pred : lut_edges_bw[maybe_mergeable_pair.first]) | |
| if (lut_nodes[fst_lut_pred]) | |
| unique_inputs.insert(fst_lut_pred); | |
| for (auto snd_lut_pred : lut_edges_bw[maybe_mergeable_pair.second]) | |
| if (lut_nodes[snd_lut_pred]) | |
| unique_inputs.insert(snd_lut_pred); | |
| unique_inputs.erase(maybe_mergeable_pair.first); | |
| if ((int)unique_inputs.size() <= order) | |
| { | |
| if (debug_relax) | |
| log(" Breaking may allow merging %s and %s.\n", | |
| log_signal(maybe_mergeable_pair.first), log_signal(maybe_mergeable_pair.second)); | |
| r_im++; | |
| } | |
| } | |
| int lut_gate_depth; | |
| if (lut_nodes[lut_gate]) | |
| lut_gate_depth = lut_depths[lut_gate]; | |
| else | |
| { | |
| lut_gate_depth = 0; | |
| for (auto lut_gate_pred : lut_edges_bw[lut_gate]) | |
| lut_gate_depth = max(lut_gate_depth, lut_depths[lut_gate_pred] + 1); | |
| } | |
| if (lut_depths[lut] >= lut_gate_depth + 1) | |
| r_slk = 0; | |
| else | |
| { | |
| int depth_delta = lut_gate_depth + 1 - lut_depths[lut]; | |
| if (depth_delta > lut_slacks[lut]) | |
| { | |
| if (debug_relax) | |
| log(" Breaking would increase depth by %d, which is more than available slack.\n", depth_delta); | |
| continue; | |
| } | |
| if (debug_relax) | |
| { | |
| log(" Breaking increases depth of LUT by %d.\n", depth_delta); | |
| if (lut_critical_outputs.at(lut).size()) | |
| { | |
| log(" Breaking decreases slack of outputs"); | |
| for (auto lut_critical_output : lut_critical_outputs.at(lut)) | |
| { | |
| log(" %s", log_signal(lut_critical_output)); | |
| log_assert(lut_slacks[lut_critical_output] > 0); | |
| } | |
| log(".\n"); | |
| } | |
| } | |
| r_slk = lut_critical_outputs.at(lut).size() * depth_delta; | |
| } | |
| int p = 100 * (r_alpha * r_ex + r_beta * r_im + r_gamma) / (r_slk + 1); | |
| if (debug_relax) | |
| log(" Potential for breaking node %s: %d (Rex=%d, Rim=%d, Rslk=%d).\n", | |
| log_signal(lut_gate), p, r_ex, r_im, r_slk); | |
| potentials[lut][lut_gate] = p; | |
| } | |
| } | |
| } | |
| bool relax_depth_for_bound(bool first, int depth_bound, dict<RTLIL::SigBit, pool<RTLIL::SigBit>> &lut_critical_outputs) | |
| { | |
| int initial_count = GetSize(lut_nodes); | |
| for (auto node : lut_nodes) | |
| { | |
| lut_slacks[node] = depth_bound - (lut_depths[node] + lut_altitudes[node]); | |
| log_assert(lut_slacks[node] >= 0); | |
| } | |
| if (debug) | |
| { | |
| dump_dot_lut_graph(stringf("flowmap-relax-%d-initial.dot", depth_bound), GraphMode::Slack); | |
| log(" Dumped initial slack graph to `flowmap-relax-%d-initial.dot`.\n", depth_bound); | |
| } | |
| dict<RTLIL::SigBit, dict<RTLIL::SigBit, int>> potentials; | |
| for (int break_num = 1; ; break_num++) | |
| { | |
| update_breaking_node_potentials(potentials, lut_critical_outputs); | |
| if (potentials.empty()) | |
| { | |
| log(" Relaxed to %d (+%d) LUTs.\n", GetSize(lut_nodes), GetSize(lut_nodes) - initial_count); | |
| if (!first && break_num == 1) | |
| { | |
| log(" Design fully relaxed.\n"); | |
| return true; | |
| } | |
| else | |
| { | |
| log(" Slack exhausted.\n"); | |
| break; | |
| } | |
| } | |
| RTLIL::SigBit breaking_lut, breaking_gate; | |
| int best_potential = INT_MIN; | |
| for (auto lut_gate_potentials : potentials) | |
| { | |
| for (auto gate_potential : lut_gate_potentials.second) | |
| { | |
| if (gate_potential.second > best_potential) | |
| { | |
| breaking_lut = lut_gate_potentials.first; | |
| breaking_gate = gate_potential.first; | |
| best_potential = gate_potential.second; | |
| } | |
| } | |
| } | |
| log(" Breaking LUT %s to %s LUT %s (potential %d).\n", | |
| log_signal(breaking_lut), lut_nodes[breaking_gate] ? "reuse" : "extract", log_signal(breaking_gate), best_potential); | |
| if (debug_relax) | |
| log(" Removing breaking gate %s from LUT.\n", log_signal(breaking_gate)); | |
| lut_gates[breaking_lut].erase(breaking_gate); | |
| auto cut_inputs = cut_lut_at_gate(breaking_lut, breaking_gate); | |
| pool<RTLIL::SigBit> gate_inputs = cut_inputs.first, other_inputs = cut_inputs.second; | |
| pool<RTLIL::SigBit> worklist = lut_gates[breaking_lut]; | |
| pool<RTLIL::SigBit> elim_gates = gate_inputs; | |
| while (!worklist.empty()) | |
| { | |
| auto lut_gate = worklist.pop(); | |
| bool all_gate_preds_elim = true; | |
| for (auto lut_gate_pred : edges_bw[lut_gate]) | |
| if (!elim_gates[lut_gate_pred]) | |
| all_gate_preds_elim = false; | |
| if (all_gate_preds_elim) | |
| { | |
| if (debug_relax) | |
| log(" Removing gate %s from LUT.\n", log_signal(lut_gate)); | |
| lut_gates[breaking_lut].erase(lut_gate); | |
| for (auto lut_gate_succ : edges_fw[lut_gate]) | |
| worklist.insert(lut_gate_succ); | |
| } | |
| } | |
| log_assert(!lut_gates[breaking_lut].empty()); | |
| pool<RTLIL::SigBit> directly_affected_nodes = {breaking_lut}; | |
| for (auto gate_input : gate_inputs) | |
| { | |
| if (debug_relax) | |
| log(" Removing LUT edge %s -> %s.\n", log_signal(gate_input), log_signal(breaking_lut)); | |
| remove_lut_edge(gate_input, breaking_lut, &directly_affected_nodes); | |
| } | |
| if (debug_relax) | |
| log(" Adding LUT edge %s -> %s.\n", log_signal(breaking_gate), log_signal(breaking_lut)); | |
| add_lut_edge(breaking_gate, breaking_lut, &directly_affected_nodes); | |
| if (debug_relax) | |
| log(" Updating slack and potentials.\n"); | |
| pool<RTLIL::SigBit> indirectly_affected_nodes = {}; | |
| update_lut_depths_altitudes(directly_affected_nodes, &indirectly_affected_nodes); | |
| update_lut_critical_outputs(lut_critical_outputs, indirectly_affected_nodes); | |
| for (auto node : indirectly_affected_nodes) | |
| { | |
| lut_slacks[node] = depth_bound - (lut_depths[node] + lut_altitudes[node]); | |
| log_assert(lut_slacks[node] >= 0); | |
| if (debug_relax) | |
| log(" LUT %s now has depth %d and slack %d.\n", log_signal(node), lut_depths[node], lut_slacks[node]); | |
| } | |
| worklist = indirectly_affected_nodes; | |
| pool<RTLIL::SigBit> visited; | |
| while (!worklist.empty()) | |
| { | |
| auto node = worklist.pop(); | |
| visited.insert(node); | |
| potentials.erase(node); | |
| // We are invalidating the entire output cone of the gate IR node, not just of the LUT IR node. This is done to also invalidate | |
| // all LUTs that could contain one of the indirectly affected nodes as a *part* of them, as they may not be in the output cone | |
| // of any of the LUT IR nodes, e.g. if we have a LUT IR node A and node B as predecessors of node C, where node B includes all | |
| // gates from node A. | |
| for (auto node_succ : edges_fw[node]) | |
| if (!visited[node_succ]) | |
| worklist.insert(node_succ); | |
| } | |
| if (debug) | |
| { | |
| dump_dot_lut_graph(stringf("flowmap-relax-%d-break-%d.dot", depth_bound, break_num), GraphMode::Slack); | |
| log(" Dumped slack graph after break %d to `flowmap-relax-%d-break-%d.dot`.\n", break_num, depth_bound, break_num); | |
| } | |
| } | |
| return false; | |
| } | |
| void optimize_area(int depth, int optarea) | |
| { | |
| dict<RTLIL::SigBit, pool<RTLIL::SigBit>> lut_critical_outputs; | |
| update_lut_depths_altitudes(); | |
| update_lut_critical_outputs(lut_critical_outputs); | |
| for (int depth_bound = depth; depth_bound <= depth + optarea; depth_bound++) | |
| { | |
| log("Relaxing with depth bound %d.\n", depth_bound); | |
| bool fully_relaxed = relax_depth_for_bound(depth_bound == depth, depth_bound, lut_critical_outputs); | |
| if (fully_relaxed) | |
| break; | |
| } | |
| } | |
| void pack_cells(int minlut) | |
| { | |
| ConstEval ce(module); | |
| for (auto input_node : inputs) | |
| ce.stop(input_node); | |
| pool<RTLIL::SigBit> mapped_nodes; | |
| for (auto node : lut_nodes) | |
| { | |
| if (node_origins.count(node)) | |
| { | |
| auto origin = node_origins[node]; | |
| if (origin.cell->getPort(origin.port).size() == 1) | |
| log("Packing %s.%s.%s (%s).\n", | |
| log_id(module), log_id(origin.cell), origin.port.c_str(), log_signal(node)); | |
| else | |
| log("Packing %s.%s.%s [%d] (%s).\n", | |
| log_id(module), log_id(origin.cell), origin.port.c_str(), origin.offset, log_signal(node)); | |
| } | |
| else | |
| { | |
| log("Packing %s.%s.\n", log_id(module), log_signal(node)); | |
| } | |
| for (auto gate_node : lut_gates[node]) | |
| { | |
| log_assert(node_origins.count(gate_node)); | |
| if (gate_node == node) | |
| continue; | |
| auto gate_origin = node_origins[gate_node]; | |
| if (gate_origin.cell->getPort(gate_origin.port).size() == 1) | |
| log(" Packing %s.%s.%s (%s).\n", | |
| log_id(module), log_id(gate_origin.cell), gate_origin.port.c_str(), log_signal(gate_node)); | |
| else | |
| log(" Packing %s.%s.%s [%d] (%s).\n", | |
| log_id(module), log_id(gate_origin.cell), gate_origin.port.c_str(), gate_origin.offset, log_signal(gate_node)); | |
| } | |
| vector<RTLIL::SigBit> input_nodes(lut_edges_bw[node].begin(), lut_edges_bw[node].end()); | |
| RTLIL::Const lut_table(State::Sx, max(1 << input_nodes.size(), 1 << minlut)); | |
| unsigned const mask = 1 << input_nodes.size(); | |
| for (unsigned i = 0; i < mask; i++) | |
| { | |
| ce.push(); | |
| for (size_t n = 0; n < input_nodes.size(); n++) | |
| ce.set(input_nodes[n], ((i >> n) & 1) ? State::S1 : State::S0); | |
| RTLIL::SigSpec value = node, undef; | |
| if (!ce.eval(value, undef)) | |
| { | |
| string env; | |
| for (auto input_node : input_nodes) | |
| env += stringf(" %s = %s\n", log_signal(input_node), log_signal(ce.values_map(input_node))); | |
| log_error("Cannot evaluate %s because %s is not defined.\nEvaluation environment:\n%s", | |
| log_signal(node), log_signal(undef), env.c_str()); | |
| } | |
| lut_table[i] = value.as_bool() ? State::S1 : State::S0; | |
| ce.pop(); | |
| } | |
| RTLIL::SigSpec lut_a, lut_y = node; | |
| for (auto input_node : input_nodes) | |
| lut_a.append_bit(input_node); | |
| lut_a.append(RTLIL::Const(State::Sx, minlut - input_nodes.size())); | |
| RTLIL::Cell *lut = module->addLut(NEW_ID, lut_a, lut_y, lut_table); | |
| mapped_nodes.insert(node); | |
| for (auto gate_node : lut_gates[node]) | |
| { | |
| auto gate_origin = node_origins[gate_node]; | |
| lut->add_strpool_attribute(ID(src), gate_origin.cell->get_strpool_attribute(ID(src))); | |
| packed_count++; | |
| } | |
| lut_count++; | |
| lut_area += lut_table.size(); | |
| if ((int)input_nodes.size() >= minlut) | |
| log(" Packed into a %d-LUT %s.%s.\n", GetSize(input_nodes), log_id(module), log_id(lut)); | |
| else | |
| log(" Packed into a %d-LUT %s.%s (implemented as %d-LUT).\n", GetSize(input_nodes), log_id(module), log_id(lut), minlut); | |
| } | |
| for (auto node : mapped_nodes) | |
| { | |
| auto origin = node_origins[node]; | |
| RTLIL::SigSpec driver = origin.cell->getPort(origin.port); | |
| driver[origin.offset] = module->addWire(NEW_ID); | |
| origin.cell->setPort(origin.port, driver); | |
| } | |
| } | |
| FlowmapWorker(int order, int minlut, pool<IdString> cell_types, int r_alpha, int r_beta, int r_gamma, | |
| bool relax, int optarea, bool debug, bool debug_relax, | |
| RTLIL::Module *module) : | |
| order(order), r_alpha(r_alpha), r_beta(r_beta), r_gamma(r_gamma), debug(debug), debug_relax(debug_relax), | |
| module(module), sigmap(module), index(module) | |
| { | |
| log("Labeling cells.\n"); | |
| discover_nodes(cell_types); | |
| label_nodes(); | |
| int depth = map_luts(); | |
| if (relax) | |
| { | |
| log("\n"); | |
| log("Optimizing area.\n"); | |
| optimize_area(depth, optarea); | |
| } | |
| log("\n"); | |
| log("Packing cells.\n"); | |
| pack_cells(minlut); | |
| } | |
| }; | |
| static void split(std::vector<std::string> &tokens, const std::string &text, char sep) | |
| { | |
| size_t start = 0, end = 0; | |
| while ((end = text.find(sep, start)) != std::string::npos) { | |
| tokens.push_back(text.substr(start, end - start)); | |
| start = end + 1; | |
| } | |
| tokens.push_back(text.substr(start)); | |
| } | |
| struct FlowmapPass : public Pass { | |
| FlowmapPass() : Pass("flowmap", "pack LUTs with FlowMap") { } | |
| void help() YS_OVERRIDE | |
| { | |
| // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| | |
| log("\n"); | |
| log(" flowmap [options] [selection]\n"); | |
| log("\n"); | |
| log("This pass uses the FlowMap technology mapping algorithm to pack logic gates\n"); | |
| log("into k-LUTs with optimal depth. It allows mapping any circuit elements that can\n"); | |
| log("be evaluated with the `eval` pass, including cells with multiple output ports\n"); | |
| log("and multi-bit input and output ports.\n"); | |
| log("\n"); | |
| log(" -maxlut k\n"); | |
| log(" perform technology mapping for a k-LUT architecture. if not specified,\n"); | |
| log(" defaults to 3.\n"); | |
| log("\n"); | |
| log(" -minlut n\n"); | |
| log(" only produce n-input or larger LUTs. if not specified, defaults to 1.\n"); | |
| log("\n"); | |
| log(" -cells <cell>[,<cell>,...]\n"); | |
| log(" map specified cells. if not specified, maps $_NOT_, $_AND_, $_OR_,\n"); | |
| log(" $_XOR_ and $_MUX_, which are the outputs of the `simplemap` pass.\n"); | |
| log("\n"); | |
| log(" -relax\n"); | |
| log(" perform depth relaxation and area minimization.\n"); | |
| log("\n"); | |
| log(" -r-alpha n, -r-beta n, -r-gamma n\n"); | |
| log(" parameters of depth relaxation heuristic potential function.\n"); | |
| log(" if not specified, alpha=8, beta=2, gamma=1.\n"); | |
| log("\n"); | |
| log(" -optarea n\n"); | |
| log(" optimize for area by trading off at most n logic levels for fewer LUTs.\n"); | |
| log(" n may be zero, to optimize for area without increasing depth.\n"); | |
| log(" implies -relax.\n"); | |
| log("\n"); | |
| log(" -debug\n"); | |
| log(" dump intermediate graphs.\n"); | |
| log("\n"); | |
| log(" -debug-relax\n"); | |
| log(" explain decisions performed during depth relaxation.\n"); | |
| log("\n"); | |
| } | |
| void execute(std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE | |
| { | |
| int order = 3; | |
| int minlut = 1; | |
| vector<string> cells; | |
| bool relax = false; | |
| int r_alpha = 8, r_beta = 2, r_gamma = 1; | |
| int optarea = 0; | |
| bool debug = false, debug_relax = false; | |
| size_t argidx; | |
| for (argidx = 1; argidx < args.size(); argidx++) | |
| { | |
| if (args[argidx] == "-maxlut" && argidx + 1 < args.size()) | |
| { | |
| order = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-minlut" && argidx + 1 < args.size()) | |
| { | |
| minlut = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-cells" && argidx + 1 < args.size()) | |
| { | |
| split(cells, args[++argidx], ','); | |
| continue; | |
| } | |
| if (args[argidx] == "-relax") | |
| { | |
| relax = true; | |
| continue; | |
| } | |
| if (args[argidx] == "-r-alpha" && argidx + 1 < args.size()) | |
| { | |
| r_alpha = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-r-beta" && argidx + 1 < args.size()) | |
| { | |
| r_beta = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-r-gamma" && argidx + 1 < args.size()) | |
| { | |
| r_gamma = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-optarea" && argidx + 1 < args.size()) | |
| { | |
| relax = true; | |
| optarea = atoi(args[++argidx].c_str()); | |
| continue; | |
| } | |
| if (args[argidx] == "-debug") | |
| { | |
| debug = true; | |
| continue; | |
| } | |
| if (args[argidx] == "-debug-relax") | |
| { | |
| debug = debug_relax = true; | |
| continue; | |
| } | |
| break; | |
| } | |
| extra_args(args, argidx, design); | |
| pool<IdString> cell_types; | |
| if (!cells.empty()) | |
| { | |
| for (auto &cell : cells) | |
| cell_types.insert(cell); | |
| } | |
| else | |
| { | |
| cell_types = {ID($_NOT_), ID($_AND_), ID($_OR_), ID($_XOR_), ID($_MUX_)}; | |
| } | |
| const char *algo_r = relax ? "-r" : ""; | |
| log_header(design, "Executing FLOWMAP pass (pack LUTs with FlowMap%s).\n", algo_r); | |
| int gate_count = 0, lut_count = 0, packed_count = 0; | |
| int gate_area = 0, lut_area = 0; | |
| for (auto module : design->selected_modules()) | |
| { | |
| FlowmapWorker worker(order, minlut, cell_types, r_alpha, r_beta, r_gamma, relax, optarea, debug, debug_relax, module); | |
| gate_count += worker.gate_count; | |
| lut_count += worker.lut_count; | |
| packed_count += worker.packed_count; | |
| gate_area += worker.gate_area; | |
| lut_area += worker.lut_area; | |
| } | |
| log("\n"); | |
| log("Packed %d cells (%d of them duplicated) into %d LUTs.\n", packed_count, packed_count - gate_count, lut_count); | |
| log("Solution takes %.1f%% of original gate area.\n", lut_area * 100.0 / gate_area); | |
| } | |
| } FlowmapPass; | |
| PRIVATE_NAMESPACE_END |