diff --git a/passes/opt/opt_balance_tree.cc b/passes/opt/opt_balance_tree.cc index 915a6fb39..aa1a44de2 100644 --- a/passes/opt/opt_balance_tree.cc +++ b/passes/opt/opt_balance_tree.cc @@ -21,7 +21,7 @@ #include "kernel/yosys.h" #include "kernel/sigtools.h" -#include +#include "kernel/utils.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -35,6 +35,11 @@ struct OptBalanceTreeWorker { // Counts of each cell type that are getting balanced dict cell_count; + // Per cell type netlist indexes, rebuilt for each balanced cell type + dict sig_to_driver; + pool input_port_sigs; + pool consumed_cells; + // Check if cell is of the right type and has matching input/output widths // Only allow cells with "natural" output widths (no truncation) to prevent // equivalence issues when rebalancing (see YosysHQ/yosys#5605) @@ -64,6 +69,18 @@ struct OptBalanceTreeWorker { return y_width >= natural_width; } + // Get the driver of a cell input port if it continues the chain, else nullptr + Cell *chain_driver(Cell *cell, IdString port, IdString cell_type) { + auto sig = sigmap(cell->getPort(port)); + Cell *drv = sig_to_driver[sig]; + if (!drv || !is_right_type(drv, cell_type)) + return nullptr; + for (auto bit : sig) + if (input_port_sigs.count(bit) && !consumed_cells.count(drv)) + return nullptr; + return drv; + } + // Create a balanced binary tree from a vector of source signals SigSpec create_balanced_tree(vector &sources, IdString cell_type, Cell* cell) { // Base case: if we have no sources, return an empty signal @@ -142,7 +159,7 @@ struct OptBalanceTreeWorker { // Do for each cell type for (auto cell_type : cell_types) { // Index all of the nets in the module - dict sig_to_driver; + sig_to_driver.clear(); dict> sig_to_sink; for (auto cell : module->selected_cells()) { @@ -162,7 +179,7 @@ struct OptBalanceTreeWorker { } // Need to check if any wires connect to module ports - pool input_port_sigs; + input_port_sigs.clear(); pool output_port_sigs; for (auto wire : module->selected_wires()) if (wire->port_input || wire->port_output) { @@ -176,7 +193,7 @@ struct OptBalanceTreeWorker { } // Actual logic starts here - pool consumed_cells; + consumed_cells.clear(); for (auto cell : module->selected_cells()) { // If consumed or not the correct type, skip @@ -189,11 +206,15 @@ struct OptBalanceTreeWorker { pool sinks; pool current_loads = sig_to_sink[y]; pool next_loads; + pool visited_loads; while (!current_loads.empty()) { // Find each sink and see what they are for (auto x : current_loads) { + if (!visited_loads.insert(x).second) + continue; + // If not the correct type, don't follow any further // (but add the originating cell to the list of sinks) if (!is_right_type(x, cell_type)) @@ -246,31 +267,45 @@ struct OptBalanceTreeWorker { if (consumed_cells.count(head_cell)) continue; - // Get sources of the chain + // Collect the chain cone into a topological sort + TopoSort> toposort; + toposort.analyze_loops = false; + toposort.node(head_cell); + vector queue = {head_cell}; + while (!queue.empty()) + { + Cell *x = queue.back(); + queue.pop_back(); + for (IdString port: {ID::A, ID::B}) + if (Cell *drv = chain_driver(x, port, cell_type)) { + if (!toposort.has_node(drv)) + queue.push_back(drv); + toposort.edge(drv, x); + } + } + + // Abandon chains containing combinational loops, since + // rebalancing them is not sound (and would not terminate) + if (!toposort.sort()) + continue; + + // Get sources of the chain: process cells from head to + // drivers, counting the paths leading back to the head so + // reconvergent sources are counted with multiplicity dict sources; dict signeds; - int inner_cells = 0; - std::deque bfs_queue = {head_cell}; - while (bfs_queue.size()) + int inner_cells = GetSize(toposort.sorted) - 1; + dict reach; + reach[head_cell] = 1; + for (int i = GetSize(toposort.sorted); i-- > 0; ) { - Cell* x = bfs_queue.front(); - bfs_queue.pop_front(); - + Cell* x = toposort.sorted[i]; for (IdString port: {ID::A, ID::B}) { - auto sig = sigmap(x->getPort(port)); - Cell* drv = sig_to_driver[sig]; - bool drv_ok = drv && is_right_type(drv, cell_type); - for (auto bit : sig) { - if (input_port_sigs.count(bit) && !consumed_cells.count(drv)) { - drv_ok = false; - break; - } - } - if (drv_ok) { - inner_cells++; - bfs_queue.push_back(drv); + if (Cell *drv = chain_driver(x, port, cell_type)) { + reach[drv] += reach[x]; } else { - sources[sig]++; + auto sig = sigmap(x->getPort(port)); + sources[sig] += reach[x]; signeds[sig] = x->getParam(port == ID::A ? ID::A_SIGNED : ID::B_SIGNED).as_bool(); } } diff --git a/tests/opt/opt_balance_tree.ys b/tests/opt/opt_balance_tree.ys index 030449731..dd95576c3 100644 --- a/tests/opt/opt_balance_tree.ys +++ b/tests/opt/opt_balance_tree.ys @@ -1244,3 +1244,96 @@ design -load postopt design -reset log -pop + + +# Test 31 +log -header "Combinational loop is left untouched" +log -push +design -reset +read_verilog <